In React, useReducer and Redux both update state with a pure function reducer(state, action) → newState. Write such a reducer for a to-do list, run it over a list of actions starting from an empty list, and print the final state.
Actions:
add <text>— append a new item with the next id (ids start at 1 and are never reused) anddone = false.toggle <id>— flip that item'sdone.edit <id> <text>— replace that item's text.remove <id>— delete that item.clear-done— delete every item whosedoneis true.
Actions that mention an id not currently in the list do nothing.
Constraints
- 1 ≤ number of actions ≤ 10^4
- Texts are 1–100 printable characters and may contain spaces.
Input and output
- Input
- Line 1: the number of actions Next lines: one action each
- Output
- One line per remaining item, in list order: `<id> [x] <text>` if done or `<id> [ ] <text>` if not. Print `(empty)` if no items remain.
Examples
Input 1
6
add Buy notebooks
add Pay hostel fee
add Revise DBMS
toggle 2
edit 3 Revise DBMS unit 4
remove 1Output
2 [x] Pay hostel fee
3 [ ] Revise DBMS unit 4Ids are given in order of adding: 1, 2, 3. Item 2 is done; item 1 is removed.
Input 2
4
add A
toggle 1
clear-done
toggle 1Output
(empty)clear-done removes finished items; later actions on a removed id are ignored.
Show hints
- Each case returns a new array: map to change one item, filter to remove.
- Keep nextId in the state, not in a global variable.
Show the explanation
Model the state as { nextId, items } and handle each action type by returning a new state, never mutating the old one:
function todos(state, action) {
switch (action.type) {
case 'add': return { nextId: state.nextId + 1, items: [...state.items, { id: state.nextId, text: action.text, done: false }] };
case 'toggle': return { ...state, items: state.items.map(t => (t.id === action.id ? { ...t, done: !t.done } : t)) };
case 'edit': return { ...state, items: state.items.map(t => (t.id === action.id ? { ...t, text: action.text } : t)) };
case 'remove': return { ...state, items: state.items.filter(t => t.id !== action.id) };
case 'clear-done': return { ...state, items: state.items.filter(t => !t.done) };
default: return state;
}
}Unknown ids fall out naturally: map changes nothing and filter removes nothing. Keeping nextId inside the state keeps the reducer pure (no outside counter), so replaying the same actions always gives the same result — the property Redux DevTools' time travel relies on. Immutable updates also let React detect changes with a cheap reference comparison.
Original UniCircle question. Last reviewed 22 Oct 2026.
