Design Patterns, Explained Simply · Behavioral Patterns: How Objects Share Work
Memento
Save and restore an object's state as snapshots, without exposing its private fields to the code that stores the snapshots.
The problem
A drawing app needs undo for complex operations ("auto-arrange shapes") where writing a precise reverse operation is hard. The simplest idea is to save the canvas state before each operation and restore it on undo.
But the history code would then need to read and write the canvas's private fields (shape list, selection, zoom), breaking encapsulation. Every new field on the canvas means changing the history code too.
Real-world analogy
Saving a game. The game writes a save file you can't read or edit; you just keep it and later say "load this one". Only the game knows what's inside. You, the player, are the caretaker of save files, not their author.
Without the pattern
history.append({"shapes": list(canvas._shapes), "zoom": canvas._zoom}) # reaches into private fields
...
canvas._shapes = snapshot["shapes"] # and writes them back
The pattern
- Originator (
Editor): the object whose state is saved. It creates mementos (save()) and restores from them (restore(memento)). - Memento: an immutable snapshot of the originator's state. To everyone else it's an opaque token.
- Caretaker (
History): stores mementos and decides when to save and restore, but never looks inside them.
Command (previous lesson) undoes by reversing actions; Memento undoes by restoring snapshots. Command is cheaper in memory; Memento is simpler when operations are hard to reverse.
The code
class Memento: # immutable snapshot; opaque to the caretaker
def __init__(self, text, cursor):
self._text, self._cursor = text, cursor
class Editor: # originator: the only one that reads mementos
def __init__(self):
self._text, self._cursor = "", 0
def type(self, words):
self._text += words
self._cursor = len(self._text)
def save(self):
return Memento(self._text, self._cursor)
def restore(self, memento):
self._text, self._cursor = memento._text, memento._cursor
def show(self):
return f"'{self._text}' (cursor {self._cursor})"
class History: # caretaker: stores snapshots, never looks inside
def __init__(self):
self.snapshots = []
def push(self, memento):
self.snapshots.append(memento)
def pop(self):
return self.snapshots.pop()
editor, history = Editor(), History()
editor.type("Design")
history.push(editor.save())
editor.type(" patterns")
history.push(editor.save())
editor.type(" are hard!!!")
print("now: ", editor.show())
editor.restore(history.pop())
print("undo: ", editor.show())
editor.restore(history.pop())
print("undo: ", editor.show())
Output
now: 'Design patterns are hard!!!' (cursor 27) undo: 'Design patterns' (cursor 15) undo: 'Design' (cursor 6)
class Memento { // immutable snapshot; opaque to the caretaker
constructor(readonly text: string, readonly cursor: number) {}
}
class Editor { // originator: the only one that reads mementos
private text = "";
private cursor = 0;
type(words: string) {
this.text += words;
this.cursor = this.text.length;
}
save(): Memento { return new Memento(this.text, this.cursor); }
restore(m: Memento) {
this.text = m.text;
this.cursor = m.cursor;
}
show() { return `'${this.text}' (cursor ${this.cursor})`; }
}
class History { // caretaker: stores snapshots, never looks inside
private snapshots: Memento[] = [];
push(m: Memento) { this.snapshots.push(m); }
pop(): Memento { return this.snapshots.pop()!; }
}
const editor = new Editor();
const history = new History();
editor.type("Design");
history.push(editor.save());
editor.type(" patterns");
history.push(editor.save());
editor.type(" are hard!!!");
console.log("now: ", editor.show());
editor.restore(history.pop());
console.log("undo: ", editor.show());
editor.restore(history.pop());
console.log("undo: ", editor.show());
Output
now: 'Design patterns are hard!!!' (cursor 27) undo: 'Design patterns' (cursor 15) undo: 'Design' (cursor 6)
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) {
Editor editor = new Editor();
History history = new History();
editor.type("Design");
history.push(editor.save());
editor.type(" patterns");
history.push(editor.save());
editor.type(" are hard!!!");
System.out.println("now: " + editor.show());
editor.restore(history.pop());
System.out.println("undo: " + editor.show());
editor.restore(history.pop());
System.out.println("undo: " + editor.show());
}
}
class Editor { // originator: the only one that reads mementos
private String text = "";
private int cursor = 0;
// nested record: only Editor can read its fields in practice
record Memento(String text, int cursor) {}
void type(String words) {
text += words;
cursor = text.length();
}
Memento save() { return new Memento(text, cursor); }
void restore(Memento m) {
text = m.text();
cursor = m.cursor();
}
String show() { return "'" + text + "' (cursor " + cursor + ")"; }
}
class History { // caretaker: stores snapshots, never looks inside
private final Deque<Editor.Memento> snapshots = new ArrayDeque<>();
void push(Editor.Memento m) { snapshots.push(m); }
Editor.Memento pop() { return snapshots.pop(); }
}
Output
now: 'Design patterns are hard!!!' (cursor 27) undo: 'Design patterns' (cursor 15) undo: 'Design' (cursor 6)
Run this on your computer — see “Get Started” for setup.
<?php
final class Memento { // immutable snapshot; opaque to the caretaker
public function __construct(public readonly string $text, public readonly int $cursor) {}
}
class Editor { // originator: the only one that reads mementos
private string $text = "";
private int $cursor = 0;
public function type(string $words): void {
$this->text .= $words;
$this->cursor = strlen($this->text);
}
public function save(): Memento { return new Memento($this->text, $this->cursor); }
public function restore(Memento $m): void {
$this->text = $m->text;
$this->cursor = $m->cursor;
}
public function show(): string { return "'{$this->text}' (cursor {$this->cursor})"; }
}
class History { // caretaker: stores snapshots, never looks inside
private array $snapshots = [];
public function push(Memento $m): void { $this->snapshots[] = $m; }
public function pop(): Memento { return array_pop($this->snapshots); }
}
$editor = new Editor();
$history = new History();
$editor->type("Design");
$history->push($editor->save());
$editor->type(" patterns");
$history->push($editor->save());
$editor->type(" are hard!!!");
echo "now: ", $editor->show(), "\n";
$editor->restore($history->pop());
echo "undo: ", $editor->show(), "\n";
$editor->restore($history->pop());
echo "undo: ", $editor->show(), "\n";
Output
now: 'Design patterns are hard!!!' (cursor 27) undo: 'Design patterns' (cursor 15) undo: 'Design' (cursor 6)
The history holds snapshots but has no idea what is inside them. The editor can add fields (selection, font) without the history changing.
When to use it
- You need undo, checkpoints or rollback, and reversing operations precisely is hard.
- You want to keep the originator's state private while still letting other code store snapshots.
- Transactions in memory: take a snapshot, try a change, restore if validation fails.
When not to use it
- The state is large and snapshots are frequent. Memory grows quickly. Store diffs, limit history length, or use Command with reverse operations instead.
- The object holds external resources (open files, connections) that can't be captured in a snapshot.
- The state is already immutable. Then "snapshots" are just references to old versions, with no pattern needed.
You've already used it
- Editors and design tools: undo history and "revert to saved".
- Games: save files and checkpoints.
- Databases: savepoints (
SAVEPOINT/ROLLBACK TO) restore an earlier state inside a transaction. - Redux DevTools / time-travel debugging: each state is a snapshot you can jump back to.
Key points
- Memento captures and restores an object's state without exposing its internals.
- Roles: originator (creates/restores), memento (opaque snapshot), caretaker (stores, never inspects).
- Memento restores snapshots; Command reverses actions.
- Watch memory use: limit history or store diffs.
- Further reading: Refactoring Guru: Memento.
Practice questions
What are the three roles in the Memento pattern?
Answer
The originator creates snapshots of its own state and restores from them; the memento is the immutable snapshot; the caretaker stores mementos and decides when to save or restore, but never looks inside them.
When would you choose Memento over Command for undo?
Answer
When operations are hard to reverse precisely (complex transformations, auto-layout), snapshots are simpler and safer. Command uses less memory because it stores only how to reverse each action, so it suits frequent, small, reversible edits.
What is the main cost of the Memento pattern?
Answer
Memory. Each snapshot copies state, so frequent snapshots of large objects grow quickly. Limit history length, store diffs, or use immutable data structures that share unchanged parts.
Open in the app to mark lessons complete, take notes and track your progress.