CONCEPT 05 · FOUNDATIONS

State as a Return Value

"Never mutate — return the new state."

statereduximmutability
Plain English

Instead of modifying a variable in place, write a function that takes the current state and returns the new state. State becomes just data — a plain value. Transitions become just functions — pure, testable, replayable. You never mutate; you always produce a new version.

Analogy

Git commits. Git doesn't modify files in place — each commit creates a new snapshot of the entire repository. You can go back to any previous state, see exactly what changed and when, and even replay history. Functional state works the same way: every transition is a new "commit" of your state.

You already know this
React useState: you call setState(newValue), you never do state.value = xRedux reducers: (state, action) => newState — literally functional stateReact useReducer: the same pattern, built into ReactImmutable.js / Immer: libraries that enforce functional state updatesEvent sourcing: store all state transitions as events, rebuild state by replaying them
Interactive Demo
Pure state threading — no mutation, no hidden state
Each function takes the current RNG state in and returns (value, new state) out. Press Next to advance.
nextInt(rng0)
RNG → (Int, RNG)
in: rng0 = seed(42)
nextInt(rng1)
RNG → (Int, RNG)
waiting...
nextDouble(rng2)
RNG → (Double, RNG)
waiting...
Press Next Step to watch state thread through each call.
Code Example
JavaScript
// MUTABLE STATE — hard to test, debug, or replay
class Counter {
  count = 0;
  increment() { this.count++; }           // modifies in place
  add(n)      { this.count += n; }        // side effect
  reset()     { this.count = 0; }
}
// To test: create Counter, call methods, check .count
// Can't easily replay, can't snapshot, can't run in parallel

// FUNCTIONAL STATE — state is just a value
const initialState = { count: 0, history: [] };

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'ADD':
      return { ...state, count: state.count + action.n };
    case 'RESET':
      return { ...state, count: 0 };
    default:
      return state;
  }
}

// Pure functions: trivially testable with no setup
const s0 = initialState;                          // {count:0}
const s1 = reducer(s0, { type: 'INCREMENT' });    // {count:1}
const s2 = reducer(s1, { type: 'ADD', n: 5 });   // {count:6}
const s3 = reducer(s2, { type: 'RESET' });        // {count:0}

// This is literally what React's useReducer does:
// const [state, dispatch] = useReducer(reducer, initialState);
Apply when
Any state that changes over time: counters, shopping carts, game state, form state
State machines: model each state as a value and each transition as a pure function
When debugging state bugs: pure state transitions let you log every state and replay exactly
Complex update logic (useReducer pattern): extract state transitions to pure functions, test them in isolation
Undo/redo features: keep a history of state values, each one is a snapshot
Check Your Understanding
In React, why does useState return [value, setter] instead of giving you a mutable object you can modify directly?