Undo needs yesterday's state, and the obvious implementation, a history class that reads and rewrites the editor's fields, quietly hands your object's insides to an outsider. Memento is the Gang of Four behavioral pattern that captures an object's state in a snapshot only that object can read back, what the standard reference frames as saving and restoring previous state without revealing the details of its implementation.
A memento is really just a sealed envelope: the editor writes its state inside, anyone may hold or stack the envelope, and only the editor can open it. That opacity is the entire pattern. Plenty of code can take snapshots, but a snapshot the whole program can read into is a leak wearing a feature's name, and the envelope is what separates this pattern from a public struct named Backup.
Here's the obvious implementation and its bill. The editor's fields go public so the history can copy them out and write them back, and the closing comment tallies what that costs:
Two couplings just shipped. The history now mirrors the editor's exact field layout, so every rename, added field, or type change in the editor breaks a class whose job was supposed to be stacking. And the door swings both ways: fields opened for the history's reading are open to everything, writable by anything holding a reference. The undo feature made the editor's privacy a casualty.
The memento version seals the state. Editor.save() returns a Snapshot whose fields nothing else can read, restore() accepts one back, and History stores them blind. The example types a, checkpoints, types b and c, then restores. Before reading past the code, predict the two printed lines, text and cursor both:
It prints abc 3 and then a 1: one restore jumped the editor back past two edits, because the snapshot stored the whole state at the checkpoint, not a diff. Cursor and text traveled together, which is why the restored editor is consistent rather than a text of a with a cursor stranded at position 3. Check the Java tab for the pattern's sharpest trick: Snapshot as a nested class with private fields means the compiler itself enforces that only Editor reads inside the envelope.
Three roles with a strict information hierarchy. The Originator (Editor) produces snapshots of its own state and accepts them back, the only role that sees inside. The Memento (Snapshot) is an immutable value, loaded once via its constructor. The Caretaker (History) decides when to save and when to restore, holding mementos it cannot inspect, competent entirely about timing and never about contents.
The pattern works like game save files: the game writes saves in its own format, while the launcher that lists, slots, and deletes them never understands a byte of what's inside. The analogy exposes the pattern's nearest confusion. Save files are serialization, built to outlive the process and be read by any code that learns the format, while a memento is in-memory and deliberately readable by one class only. Serialize your state to JSON and anything can parse it back, which is precisely the open door this pattern exists to close.
Installing the pattern is four moves:
Snapshot takes its data at construction and exposes nothing afterward: no getters, no setters, just a sealed value. Immutability here is what makes a year-old snapshot as trustworthy as a fresh one.save() and restore() live on the editor, the one class that understands its own field layout. Java and C# enforce this with nested classes, Go and Rust with unexported fields, and Python by convention.History pushes and pops snapshots without reading a single field inside one. Its whole competence is when to save and when to restore, which survives any change to what a snapshot contains.Undo has exactly two engineering strategies, and this page is the second one's home. Storing inverse operations is Command's approach: memory-light, replay-friendly, and demanding that every action know how to reverse itself. Storing prior state is Memento's: simple and universal, since nothing needs an inverse when you can overwrite wholesale, and paid for in memory. The restore in the example jumped two edits in one hop, something inverse-based undo would replay step by step.
The memory bill is the pattern's documented critique, with the standard reference warning that apps consume serious RAM when clients snapshot too often. Full copies of a large document per keystroke is the canonical mistake. The same reference points at the practical synthesis: Command and Memento work together, commands carrying the cheap reversible edits and mementos checkpointing around operations too gnarly to invert, which is roughly how every serious editor you've used actually does it.
Go makes snapshots almost suspiciously easy: assigning a struct copies it, so Save() returning a Snapshot by value is a complete implementation, and unexported fields seal the envelope at the package boundary. The honesty tax arrives with reference fields, since slices and maps copy as shared headers, the same shallow trap Prototype documents, and deep copies of those are handwritten.
Rust hands the pattern two gifts. derive(Clone) writes the snapshot copying, deep for owned data, so save() is a clone and restore() is an assignment. And the borrow checker enforces the caretaker's blindness structurally: the history owns its snapshots outright, holds no live references into the editor, and couldn't mutate the originator through a stored memento if it tried. The encapsulation guarantee other languages maintain by discipline arrives in Rust as a compile error.
The pattern is a sealed box and a stack, and these five keep the contents trustworthy:
Two questions before you go. First: the history stored and returned the snapshot that fixed the editor, yet it can't read one field inside it. What does that opacity protect, in both directions? Second: snapshot-based undo and inverse-based undo each pay a different cost and risk a different failure. Name both pairs. Both answers are on this page.
Then go spot it in the wild. Every game save slot is a memento managed by a launcher that can't parse it, database savepoints bookmark transaction state for a rollback that restores wholesale, and VM snapshots do it to entire operating systems. Last, look at whatever undo feature lives in an app you maintain, or the spot where users keep asking for one, and run the strategy question from this page against its data: cheap to invert, or cheap to copy? The answer picks your pattern.
class Editor:
def __init__(self):
self.text = "" # public, because History needs to read it
self.cursor = 0 # public, same reason
def type(self, chars: str) -> None:
self.text += chars
self.cursor += len(chars)
class History:
def __init__(self):
self._states: list[tuple[str, int]] = []
def save(self, editor: Editor) -> None:
# History reaches into the editor's fields directly
self._states.append((editor.text, editor.cursor))
def undo(self, editor: Editor) -> None:
# ...and writes them back directly, too
text, cursor = self._states.pop()
editor.text = text
editor.cursor = cursor
# History now knows the editor's exact field layout. Rename a
# field, add one, or change a type, and History breaks. Worse,
# anything holding a History can rewrite the editor at will.class Snapshot: # Memento: a sealed envelope, constructor-only
def __init__(self, text: str, cursor: int):
self._text = text
self._cursor = cursor
class Editor: # Originator: the only reader and writer of snapshots
def __init__(self):
self.text = ""
self.cursor = 0
def type(self, chars: str) -> None:
self.text += chars
self.cursor += len(chars)
def save(self) -> Snapshot:
return Snapshot(self.text, self.cursor)
def restore(self, snapshot: Snapshot) -> None:
self.text = snapshot._text
self.cursor = snapshot._cursor
class History: # Caretaker: stores envelopes, never opens one
def __init__(self):
self._snapshots: list[Snapshot] = []
def push(self, snapshot: Snapshot) -> None:
self._snapshots.append(snapshot)
def pop(self) -> Snapshot:
return self._snapshots.pop()
# Example usage
editor = Editor()
history = History()
editor.type("a")
history.push(editor.save()) # checkpoint at "a"
editor.type("b")
editor.type("c")
print(editor.text, editor.cursor) # abc 3
editor.restore(history.pop())
print(editor.text, editor.cursor) # a 1