Design Patterns

Momento Design Pattern

I built a text editor with an undo feature. My first approach: store a deep copy of the entire document state before every keystroke. It worked. It also a...

23 Mar 2024

Momento Design Pattern

I built a text editor with an undo feature. My first approach: store a deep copy of the entire document state before every keystroke. It worked. It also ate 500MB of memory in a 10-minute editing session.

The Memento pattern provides a structured way to capture and restore an object's state without exposing its internals. Three roles make it work:

  • Originator — The object whose state you want to save (the editor).
  • Memento — A snapshot of the originator's state at a point in time.
  • Caretaker — Manages the collection of mementos (the undo history).

Think of it like saving a video game. The game (originator) creates a save file (memento). Your save slot list (caretaker) stores them. When you want to go back, you load a save file and the game restores to that state.

Javascript
class EditorMemento {
  constructor(content) {
    this.content = content;
  }

  getContent() {
    return this.content;
  }
}

class Editor {
  constructor() {
    this.content = "";
  }

  type(text) {
    this.content += text;
  }

  save() {
    return new EditorMemento(this.content);
  }

  restore(memento) {
    this.content = memento.getContent();
  }
}

class History {
  constructor() {
    this.states = [];
  }

  push(memento) {
    this.states.push(memento);
  }

  pop() {
    return this.states.pop();
  }
}

const editor = new Editor();
const history = new History();

editor.type("First sentence. ");
history.push(editor.save());

editor.type("Second sentence. ");
history.push(editor.save());

editor.type("Third sentence.");
console.log(editor.content);
// First sentence. Second sentence. Third sentence.

editor.restore(history.pop());
console.log(editor.content);
// First sentence. Second sentence.

editor.restore(history.pop());
console.log(editor.content);
// First sentence.

The Editor creates mementos via save(). The History (caretaker) stores them. When you undo, pop() the last memento and pass it to restore(). The Editor's internal state is never exposed directly — the memento is opaque.

Making It Practical

For real applications, you'd optimize this:

  • Store diffs instead of full snapshots. If the document is 10,000 characters and you typed one letter, store the delta, not a full copy.
  • Limit history depth. Keep the last 50 states, not unlimited. Memory matters.
  • Support redo. Maintain two stacks — undo and redo. When you undo, push the current state onto the redo stack.

The benefit: Clean undo/redo without exposing object internals. The originator controls what gets saved. The caretaker doesn't need to understand the state format.

The cost: Memory. Each memento is a snapshot. For large objects saved frequently, memory usage grows fast. The pattern also adds complexity — for simple undo cases, storing a plain array of previous values might be enough.

I use the Memento pattern for text editors, form wizards (go back to a previous step), and transactional operations where rollback is needed. For simple state changes, it's overkill.