Prompt
Write historyReducer(state, action) over the shape { past: [], present, future: [] }:
{ type: 'SET', value }— pushes current present onto past, sets new present, clears future{ type: 'UNDO' }— moves present to future, pops the last past entry into present{ type: 'REDO' }— inverse of UNDO- UNDO with empty past / REDO with empty future return state unchanged
- Pure function — no mutation, no side effects
Solution
Undo/redo is the interview's favorite reducer because it proves you understand why reducers must be pure: time travel only works if every state was a new object — mutate the past and there's no past to go back to. This is the concrete payoff of immutability, not the abstract lecture version.
Design notes a mentor would flag:
- The three-field shape (
past / present / future) is the whole design. Undo and redo become array moves. Candidates who store an index into one array get lost in off-by-ones; candidates who store snapshots inside the present conflate data with history. - SET clears
future— after you undo twice and type something new, redo must die. Branching history (a tree) is the follow-up question; naming that trade-off ("linear history loses the abandoned branch; Photoshop keeps a tree") is a seniority marker. - The no-op guards (
past.length === 0 → return state) aren't just correctness: returning the same reference means React/Redux bail out of re-rendering.return stateandreturn { ...state }behave identically to your logic and completely differently to React. - The unchanged-value guard on SET keeps a same-value dispatch from polluting history with duplicates.
This is also useReducer interview gold: wrap any value in this reducer and you've added undo to a form, a drawing canvas, a filter panel — with zero library code.
Say it: "History is past/present/future arrays and every transition builds new objects — which is exactly why reducers must be pure: undo is only possible if the past was never mutated."