Intent
Capture and externalize an object's internal state so it can be restored later — without exposing the object's structure to the world. The state goes into a memento that only the object's own code can read; everyone else just stores it and hands it back.
The Problem
You're adding undo to a text editor. The editor has a complex internal state — the text buffer, the cursor position, the active selection, the scroll offset, the dirty flag, the syntax-highlighting cache. Every meaningful action (typing, cutting, pasting, formatting) should be reversible.
The naive shortcuts:
Make every field public, snapshot them externally. Now every undo-aware caller knows the editor's internal layout. Refactor the editor's state and you break every snapshot site. The encapsulation you spent so much effort building has just been shredded.
Add a
getState()andsetState()to the editor. A bit better, butgetStatehas to return something — and whatever shape that is becomes a public contract. Internals leak through the data shape.Just clone the entire editor on every action. Works for tiny objects; crushes the heap for anything real. And cloning live resources (file handles, timers, observers) is its own pile of pain.
The shape of the smell: you need another object to remember the editor's state — but that object can't know what's in the state. It just stores and returns.
The Solution
Memento says: introduce three roles. The Originator (the editor) creates a memento that captures its state in an opaque form. The Caretaker (the undo manager) holds onto mementos but never inspects them. To undo, the caretaker hands a memento back to the Originator, which restores from it.
final class EditorState
{
// Constructor is package-private — only Editor can create one.
public function __construct(
public readonly string $text,
public readonly int $cursor,
public readonly array $selection,
) {}
}
final class Editor
{
private string $text = '';
private int $cursor = 0;
private array $selection = [];
public function snapshot(): EditorState
{
return new EditorState($this->text, $this->cursor, $this->selection);
}
public function restore(EditorState $state): void
{
$this->text = $state->text;
$this->cursor = $state->cursor;
$this->selection = $state->selection;
}
}
final class UndoManager
{
/** @var EditorState[] */
private array $history = [];
public function record(Editor $e): void { $this->history[] = $e->snapshot(); }
public function undo(Editor $e): void
{
$previous = array_pop($this->history);
if ($previous) $e->restore($previous);
}
}
The UndoManager never reads $state->text or even knows it exists. It just keeps a list of opaque snapshots and hands them back when asked. Add a new field to Editor (say, a viewportScrollY)? Update EditorState and Editor.snapshot/restore. Nothing else moves.
Real-World Analogy
A save game. When you press "Save" in a video game, the game writes its entire state to a file — your inventory, position, quest progress, NPC relationships. The launcher menu shows that file in a list with a timestamp, maybe a screenshot — but the launcher can't read the contents. When you press "Load," the launcher hands the file back to the game, and the game itself knows how to restore its state from the bytes inside.
The launcher is the caretaker; the file is the memento; the game is the originator. The launcher manages the files; only the game understands the state.
Structure
Three roles you'll see in every Memento implementation:
- Originator — the object whose state is being captured. Creates mementos and restores from them. Here:
Editor. - Memento — the opaque snapshot of state. Often immutable. Only the Originator should be able to read its contents — the Caretaker just holds the reference. Here:
EditorState. - Caretaker — the manager that stores and hands back mementos. Knows which memento goes when but not what's in one. Here:
UndoManager.
The defining property: the Memento's contents are visible to the Originator but opaque to the Caretaker. Different languages enforce this with different mechanisms (package access in Java, internal in C#, naming conventions in Python and JS, immutable readonly fields in others).
Code Examples
Here's an editor undo system in five languages. Notice that the UndoManager never inspects what's in an EditorState — it just stores and hands them back.
// EditorState is the memento. Only Editor reads its fields; UndoManager just holds it.
class EditorState {
constructor(
readonly text: string,
readonly cursor: number,
readonly selection: [number, number] | null,
) {}
}
class Editor {
private text = "";
private cursor = 0;
private selection: [number, number] | null = null;
type(s: string): void {
this.text = this.text.slice(0, this.cursor) + s + this.text.slice(this.cursor);
this.cursor += s.length;
}
snapshot(): EditorState {
return new EditorState(this.text, this.cursor, this.selection);
}
restore(state: EditorState): void {
this.text = state.text;
this.cursor = state.cursor;
this.selection = state.selection;
}
}
class UndoManager {
private history: EditorState[] = [];
record(editor: Editor): void { this.history.push(editor.snapshot()); }
undo(editor: Editor): void {
const previous = this.history.pop();
if (previous) editor.restore(previous);
}
}
from dataclasses import dataclass
@dataclass(frozen=True)
class EditorState:
text: str
cursor: int
selection: tuple | None
class Editor:
def __init__(self):
self._text = ""
self._cursor = 0
self._selection = None
def type(self, s):
self._text = self._text[:self._cursor] + s + self._text[self._cursor:]
self._cursor += len(s)
def snapshot(self):
return EditorState(self._text, self._cursor, self._selection)
def restore(self, state):
self._text = state.text
self._cursor = state.cursor
self._selection = state.selection
class UndoManager:
def __init__(self):
self._history = []
def record(self, editor):
self._history.append(editor.snapshot())
def undo(self, editor):
if self._history:
editor.restore(self._history.pop())
public final class Editor {
private String text = "";
private int cursor = 0;
private int[] selection;
public void type(String s) {
text = text.substring(0, cursor) + s + text.substring(cursor);
cursor += s.length();
}
public EditorState snapshot() {
return new EditorState(text, cursor, selection);
}
public void restore(EditorState state) {
this.text = state.text();
this.cursor = state.cursor();
this.selection = state.selection();
}
// Memento: a record holding the state, only Editor reads its fields publicly.
public record EditorState(String text, int cursor, int[] selection) {}
}
public final class UndoManager {
private final Deque<Editor.EditorState> history = new ArrayDeque<>();
public void record(Editor editor) {
history.push(editor.snapshot());
}
public void undo(Editor editor) {
if (!history.isEmpty()) editor.restore(history.pop());
}
}
<?php
namespace App\Editor;
final class EditorState
{
public function __construct(
public readonly string $text,
public readonly int $cursor,
public readonly ?array $selection,
) {}
}
final class Editor
{
private string $text = '';
private int $cursor = 0;
private ?array $selection = null;
public function type(string $s): void
{
$this->text = substr($this->text, 0, $this->cursor) . $s . substr($this->text, $this->cursor);
$this->cursor += strlen($s);
}
public function snapshot(): EditorState
{
return new EditorState($this->text, $this->cursor, $this->selection);
}
public function restore(EditorState $state): void
{
$this->text = $state->text;
$this->cursor = $state->cursor;
$this->selection = $state->selection;
}
}
final class UndoManager
{
/** @var EditorState[] */
private array $history = [];
public function record(Editor $editor): void
{
$this->history[] = $editor->snapshot();
}
public function undo(Editor $editor): void
{
if ($previous = array_pop($this->history)) {
$editor->restore($previous);
}
}
}
package editor
type EditorState struct {
Text string
Cursor int
Selection *[2]int
}
type Editor struct {
text string
cursor int
selection *[2]int
}
func (e *Editor) Type(s string) {
e.text = e.text[:e.cursor] + s + e.text[e.cursor:]
e.cursor += len(s)
}
func (e *Editor) Snapshot() EditorState {
return EditorState{Text: e.text, Cursor: e.cursor, Selection: e.selection}
}
func (e *Editor) Restore(state EditorState) {
e.text = state.Text
e.cursor = state.Cursor
e.selection = state.Selection
}
type UndoManager struct {
history []EditorState
}
func (u *UndoManager) Record(editor *Editor) {
u.history = append(u.history, editor.Snapshot())
}
func (u *UndoManager) Undo(editor *Editor) {
n := len(u.history)
if n == 0 {
return
}
last := u.history[n-1]
u.history = u.history[:n-1]
editor.Restore(last)
}
The UndoManager is dumb — it just keeps a stack of opaque snapshots. The Editor is the only thing that knows what's in a snapshot. Add a viewport scroll position to Editor? Update three lines: Editor.snapshot, Editor.restore, and the EditorState fields. The UndoManager doesn't move.
When to Use It
Reach for Memento when you can answer "yes" to any of these:
- You need undo / redo. This is the canonical use case — text editors, design tools, IDEs.
- You need snapshots for time-travel debugging or replay. Save state at intervals; rewind on demand.
- You need transactional rollback at the application level (where database transactions don't reach). Try an operation; if it fails halfway, restore the prior memento.
- You need point-in-time backups of an in-memory model. Saving a level in a game; checkpointing a long-running computation.
- You need to externalize state without exposing internals. Anywhere a "save state" feature would otherwise tempt you to make all the fields public.
If you don't need to restore the captured state — only inspect it — you don't need Memento. Just expose a read-only view.
Pros and Cons
Pros
- Encapsulation stays intact. The originator's internals don't leak through the snapshot's shape.
- Adding new state fields means updating one class (the originator) plus the memento — never every caller that holds snapshots.
- Caretakers can be generic: an
UndoManager<T>works for any originator type. - Combines naturally with Command — each command can carry the memento it needs to undo itself.
Cons
- Memory cost adds up. A long undo history can pin a lot of memory. For large originators, prefer diffs over full snapshots.
- Mementos must be safe to hold across time. If they reference live resources (open files, mutable objects elsewhere), restoring may produce a half-valid object.
- In some languages, true encapsulation is hard. Java's package-private and C#'s
internalenforce it; Python and JavaScript rely on convention. Mementos in dynamic languages are opaque by agreement, not by enforcement. - Snapshotting can be expensive for large objects — even before the cost of holding them. Capture only what you need to undo.
Pro Tips
- Snapshot diffs, not full state, when objects are large. For a text editor, store what changed (an insertion at position N, a deletion at range M..K) rather than the full text every time. Smaller mementos, longer histories, faster restore in many cases.
- Make mementos immutable. If a caretaker can mutate a memento, restore is no longer deterministic. Frozen objects,
readonlyfields, copy semantics — pick the language's strongest guarantee. - Pair Memento with Command for action-level undo. Each command captures its own undo state in a memento; undoing the command means restoring its memento. Clean separation between what was done and how to take it back.
- Bound the history. A Caretaker that grows forever is a memory leak. Cap it at N entries, or by total memory.
- Don't try to capture outgoing references to mutable objects. A memento that holds a reference to a
Documentwhose own state has changed since the snapshot will restore stale data. Snapshot what you own; treat references to others' state as out of scope.
Relations with Other Patterns
- Command is the natural partner: each Command produces its own Memento before executing, so undo just restores the memento and the Command becomes reversible.
- Prototype has a similar capture-then-create mechanic, but Prototype produces new objects of the same type; Memento restores the same originator's previous state.
- Iterator can use a Memento to capture and restore traversal position — useful for resumable iteration over large collections.
- State can sometimes be implemented by storing successive States as Mementos in a history.
- Event Sourcing is the larger architectural cousin: instead of snapshots, store every state-changing event; reconstruct state by replaying events. The trade-off is more memory used over time vs. always being able to time-travel anywhere.
Final Tips
The cleanest Memento I've ever shipped was on a level editor for a game project. Every action — moving a tile, painting a region, placing an entity — pushed a memento on a history stack. Ctrl+Z popped and restored. Because the mementos were diffs (what changed and what was there before), the history could grow long without bloating memory, and a 30-step undo took the same time as a single one.
Reach for Memento when "save and restore" enters the requirements. The pattern's promise is encapsulation that survives time travel — the rest of the system can manage the list of saved points without ever needing to peek inside one.


