All questions

Undo / Redo Manager

Premium

Undo / Redo Manager

Every editor you've ever used — a text editor, a Figma canvas, a database GUI — keeps a history of what you've done so you can step backwards (undo) and forwards (redo) through it. You're going to build the data structure that makes that possible: a small class that tracks a sequence of values, remembers a cursor into that sequence, and exposes navigation methods.

Implement UndoRedoManager(initial). It accepts a starting value and exposes five methods. current() returns the value at the cursor. set(value) records value as the new current and advances the cursor — and, importantly, if you're sitting in the middle of the history (you've undone a few steps), set BRANCHES: it drops everything past the cursor before recording the new value. undo() steps the cursor backwards one place and returns the value now under it. redo() steps forwards. canUndo() and canRedo() are booleans reporting whether each direction has somewhere to go.

Signature

class UndoRedoManager {
  constructor(initial)
  current()         // -> the value at the cursor
  set(value)        // record value as new current; drops any redo tail
  undo()            // step cursor back; returns the value now under it
  redo()            // step cursor forward; returns the value now under it
  canUndo()         // -> boolean: is there anywhere to go back to?
  canRedo()         // -> boolean: is there anywhere to go forward to?
}

Examples

// Basic linear edits
const m = new UndoRedoManager(1);
m.current();  // 1
m.set(2);
m.set(3);
m.current();  // 3
m.canUndo();  // true
m.canRedo();  // false
// An undo/redo round trip
const m = new UndoRedoManager('a');
m.set('b');
m.set('c');
m.undo();     // 'b'   (cursor steps back one)
m.undo();     // 'a'   (back to the start)
m.canUndo();  // false
m.redo();     // 'b'   (cursor steps forward)
m.current();  // 'b'
// set() in the middle of history BRANCHES — drops the redo tail
const m = new UndoRedoManager(1);
m.set(2);
m.set(3);
m.undo();     // 2  (cursor is now in the middle)
m.set(4);     // branch! "3" is dropped from history
m.canRedo();  // false — there is no 3 to redo to anymore
m.undo();     // 2
m.redo();     // 4

Notes

  • Cursor at the startcanUndo() is false and undo() is a no-op that returns the current value. Don't throw and don't return undefined.
  • Cursor at the endcanRedo() is false and redo() is a no-op that returns the current value. Same rule.
  • set always branches when there is a redo tail. This is the defining behaviour. After set, canRedo() must be false.
  • Repeated set(v) with the same value still records a new history entry. Equality detection is out of scope.
  • Object values are stored by reference. Mutating an object after passing it to set mutates the history entry too. Treat values as immutable, or clone them yourself.
  • No memory cap. History grows without bound; a maxSize option is discussed in the solution but not part of this spec.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium