Memento
Save and restore an object's state without violating encapsulation. The Memento pattern lets you snapshot an object's internal state so you can roll it back later — the basis of undo/redo.
The Problem
You want to implement undo for a text editor. The naive approach: save the full state externally in the History class. But to do that, History needs to read every private field of Editor. This breaks encapsulation. Worse, if Editor’s fields change, History has to change too — tight coupling.
You need a way to capture and restore state without exposing the implementation details.
The Solution
The Originator (the object whose state you want to save) creates a Memento — a snapshot of its own state. The Memento exposes no state to the outside world; only the Originator can read it. The Caretaker (e.g. a history stack) stores Mementos without knowing what’s inside them.
Real-World Analogy
Taking a snapshot of a virtual machine. The VM (originator) creates a snapshot file (memento). The hypervisor management UI (caretaker) stores and lists snapshots without knowing what’s inside them. When you “restore to snapshot,” the VM reads the snapshot and restores itself.
TypeScript Example
// Memento — opaque state container; only Originator reads its internals
class EditorMemento {
constructor(
private readonly text: string,
private readonly cursorPos: number,
private readonly selectionStart: number,
private readonly selectionEnd: number,
) {}
// Only Editor can access these (use a module or namespace in real code)
getText(): string { return this.text; }
getCursorPos(): number { return this.cursorPos; }
getSelectionStart(): number { return this.selectionStart; }
getSelectionEnd(): number { return this.selectionEnd; }
}
// Originator
class TextEditor {
private text = '';
private cursorPos = 0;
private selStart = 0;
private selEnd = 0;
insert(char: string): void {
this.text = this.text.slice(0, this.cursorPos) + char + this.text.slice(this.cursorPos);
this.cursorPos += char.length;
}
deleteBack(): void {
if (this.cursorPos === 0) return;
this.text = this.text.slice(0, this.cursorPos - 1) + this.text.slice(this.cursorPos);
this.cursorPos--;
}
moveCursor(pos: number): void {
this.cursorPos = Math.max(0, Math.min(pos, this.text.length));
}
select(start: number, end: number): void {
this.selStart = start;
this.selEnd = end;
}
// Creates a snapshot of current state
save(): EditorMemento {
return new EditorMemento(this.text, this.cursorPos, this.selStart, this.selEnd);
}
// Restores from a snapshot
restore(memento: EditorMemento): void {
this.text = memento.getText();
this.cursorPos = memento.getCursorPos();
this.selStart = memento.getSelectionStart();
this.selEnd = memento.getSelectionEnd();
}
getState(): string {
const cursor = this.text.slice(0, this.cursorPos) + '|' + this.text.slice(this.cursorPos);
return `"${cursor}" (sel: ${this.selStart}-${this.selEnd})`;
}
}
// Caretaker — stores mementos, doesn't read their content
class EditorHistory {
private undoStack: EditorMemento[] = [];
private redoStack: EditorMemento[] = [];
snapshot(editor: TextEditor): void {
this.undoStack.push(editor.save());
this.redoStack = []; // new action clears redo
}
undo(editor: TextEditor): boolean {
if (this.undoStack.length === 0) return false;
this.redoStack.push(editor.save()); // save current state for redo
editor.restore(this.undoStack.pop()!);
return true;
}
redo(editor: TextEditor): boolean {
if (this.redoStack.length === 0) return false;
this.undoStack.push(editor.save());
editor.restore(this.redoStack.pop()!);
return true;
}
}
// Demo
const editor = new TextEditor();
const history = new EditorHistory();
function type(chars: string): void {
history.snapshot(editor);
for (const c of chars) editor.insert(c);
console.log(' State:', editor.getState());
}
console.log('--- Typing ---');
type('Hello');
type(', ');
type('World');
type('!');
console.log('\n--- Undo ×2 ---');
history.undo(editor); console.log(' State:', editor.getState());
history.undo(editor); console.log(' State:', editor.getState());
console.log('\n--- Redo ---');
history.redo(editor); console.log(' State:', editor.getState());
console.log('\n--- New input breaks redo chain ---');
type('TypeScript');
history.redo(editor); // returns false — redo stack was cleared
console.log(' State:', editor.getState());
When to Use
- You need to implement undo/redo
- You want to take snapshots of object state that can be restored later
- Direct access to the object’s fields would break encapsulation
- Examples: text editors, drawing apps, game saves, database transactions, configuration rollback
Pros
- Preserves encapsulation — the Originator’s internals stay private
- Caretaker code is simple — it just stores and passes opaque Mementos
- Enables undo/redo, snapshots, and rollback without coupling
Cons
- Frequent snapshots can consume significant memory (large state = large mementos)
- Caretakers must manage the Memento lifecycle — old snapshots need to be garbage collected
- In dynamic languages, preventing caretakers from peeking inside Mementos requires extra care
Related Patterns
- Command works with Memento — Command performs actions, Memento captures state for rollback
- Iterator can use Memento to snapshot traversal position
- Prototype also creates copies of state — Prototype clones the object; Memento captures just the state into a separate object