CONCEPT 05 · FOUNDATIONS
State as a Return Value
"Never mutate — return the new state."
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?