Design patternBehavioral Patterns
Memento
Capture an object's internal state in an opaque snapshot so it can be restored later without exposing that state to the code holding the snapshot.
- Complexity
- High
- Seen in the wild
- Low
- Also known as
- Snapshot, Token
Intent
Let an object save its own state into a snapshot object and later restore from it. Other code can store and pass the snapshot around, but can't read or change what is inside. Rollback and undo work without breaking the object's encapsulation.
Forces
- Rolling back vs keeping fields private: the code that decides to roll back should not need to know which fields to reset.
- Trying a change vs predicting its effect: sometimes the only way to know whether a change is valid is to apply it and check.
- Complete snapshots vs memory cost: copying the whole state is simple and correct, but expensive when state is large or snapshots are frequent.
- Shallow copies vs shared mutable parts: a snapshot that shares a mutable list with the live object is not a snapshot.
Therefore
Have the object (the originator) produce and consume its own snapshots, and give everyone else an opaque handle to hold.
The originator's save() copies its internal state into a memento, and restore(m) copies it back. A separate caretaker decides when to save and restore and keeps the mementos, but treats them as tokens. Each language enforces "opaque" differently: nested classes with private fields in Java, unexported fields in Go, a private lookup table or brand type in TypeScript, and naming convention in Python.
Structure
Diagram source (Mermaid)
classDiagram
class RoutePlan {
-Stop[] stops
-int shiftMin
+add(stop) void
+minutes() int
+save() RouteSnapshot
+restore(s) void
}
class RouteSnapshot {
-Stop[] stops
}
class Dispatcher {
+planDay(route, batches) void
}
class Stop {
-String id
-int driveMin
-int serviceMin
}
RoutePlan *-- Stop
RoutePlan ..> RouteSnapshot : creates
Dispatcher ..> RouteSnapshot : holds
Dispatcher --> RoutePlanParticipants
- Originator (
RoutePlan): the object whose state is saved. Only it can create a memento or read one. - Memento (
RouteSnapshot): an immutable copy of the originator's state, opaque to everyone else. - Caretaker (
Dispatcher): decides when to snapshot and when to roll back, and stores mementos without looking inside them.
Use it when
- You apply a change tentatively, check the result, and roll back on failure, and the change touches state the caller can't see.
- You need undo for an object whose internals are private, and writing an inverse for every operation is impractical.
- You need checkpoints in a long-running computation (a solver, an import, a simulation) to resume or backtrack.
- Several fields must be restored together, and resetting them one by one from outside would leak the object's structure.
Do not use it when
- The state is already immutable. Keep a reference to the previous value; persistent data structures and reducer-style state (
state = reduce(state, action)) give you snapshots for free. - You can validate before mutating. Check the change first and skip the snapshot. It is cheaper and simpler.
- The state is a plain public record with no invariants to protect. Copy it (
structuredClone,copy.deepcopy, a copy constructor); encapsulation was the only reason for the pattern. - State is large and changes are small and frequent. Store deltas with Command and
undoinstead of full copies. - The snapshot must survive a restart or cross a process. That is serialization or an event log with a versioned format, not an in-memory memento.
Application grill
Inspect first: the state being snapshotted and its size; existing undo or history features (editors, forms) and their storage; whether snapshots hold personal data that crosses a trust boundary or retention rule.
Missing pieces: in-memory: the language's value copies - nothing to install. Persisted: a table or blob store with a retention and deletion policy; snapshots of personal data must age out on the same schedule as the records they mirror.
Ask before you build:
- Scale: what is snapshot size times retention count - memory or storage bound?
- Performance: snapshot on every change or per action, and is coalescing needed?
- Reliability: under concurrent edits, what does restore mean - is the snapshot stale the moment it restores?
- Budget: who pays the retention storage and runs the deletion pipeline?
- Implementation: when the internal state shape changes, do old snapshots still restore (versioned snapshots), and opaque to whom?
Implementation steps
- List every field that has to come back on restore, including derived fields and caches, or recompute them after restore.
- Create the memento type with those fields, immutable, and make it unreadable outside the originator.
- Add
save()to the originator. Copy mutable collections deeply enough that later changes don't leak into the snapshot. - Add
restore(memento). Copy again on the way in, so restoring twice from the same memento works. - Move the "when to save and roll back" logic into a caretaker that holds mementos as opaque values.
- Test that
save, then a change, thenrestoregives back an object equal to the original, and that the same memento can be restored twice.
Example
A dispatch tool builds a delivery van's route for the day. Stops arrive in batches that must be kept together (several drops for one customer). The dispatcher adds each batch, and if the route no longer fits the shift, rolls the route back to its state before that batch.
interface Stop {
readonly id: string;
readonly driveMin: number;
readonly serviceMin: number;
}
// Opaque to callers: they can hold a snapshot but cannot read or build one.
declare const brand: unique symbol;
type RouteSnapshot = { readonly [brand]: true };
class RoutePlan {
private static readonly saved = new WeakMap<RouteSnapshot, readonly Stop[]>();
private stops: Stop[] = [];
constructor(readonly shiftMin: number) {}
add(stop: Stop): void {
this.stops.push(stop);
}
minutes(): number {
return this.stops.reduce((t, s) => t + s.driveMin + s.serviceMin, 0);
}
ids(): string[] {
return this.stops.map((s) => s.id);
}
save(): RouteSnapshot {
const snapshot = Object.freeze({}) as RouteSnapshot;
RoutePlan.saved.set(snapshot, [...this.stops]); // stops are immutable, so a shallow copy is enough
return snapshot;
}
restore(snapshot: RouteSnapshot): void {
const stops = RoutePlan.saved.get(snapshot);
if (!stops) throw new Error("snapshot was not created by RoutePlan");
this.stops = [...stops];
}
}
// Caretaker: decides when to roll back, never looks inside the snapshot.
function planDay(route: RoutePlan, batches: Stop[][]): void {
for (const batch of batches) {
const checkpoint = route.save();
batch.forEach((s) => route.add(s));
if (route.minutes() > route.shiftMin) {
route.restore(checkpoint);
console.log("rejected:", batch.map((s) => s.id).join(","));
}
}
}
const route = new RoutePlan(480);
planDay(route, [
[{ id: "s1", driveMin: 60, serviceMin: 15 }, { id: "s2", driveMin: 40, serviceMin: 20 }],
[{ id: "s3", driveMin: 90, serviceMin: 30 }, { id: "s4", driveMin: 50, serviceMin: 10 }],
[{ id: "s5", driveMin: 120, serviceMin: 30 }, { id: "s6", driveMin: 45, serviceMin: 15 }],
[{ id: "s7", driveMin: 30, serviceMin: 20 }],
]);
console.log(route.ids(), route.minutes()); // [ 's1', 's2', 's3', 's4', 's7' ] 365In Java, nested classes share private access within their top-level class, so Dispatch could technically read Snapshot.stops. In real code, put RoutePlan in its own file.
Trade-offs
Gains
- Rollback and undo without exposing the originator's fields.
- The caretaker's logic stays simple: save, try, restore.
- One restore resets every related field together, so invariants hold.
- Snapshots are immutable and safe to keep in a history.
Costs
- Each snapshot copies state. Large state or frequent snapshots cost memory and time.
- Deep versus shallow copying is easy to get wrong, and the bug only shows on a later mutation.
- Opaqueness is weak in some languages (Python convention, Go package scope).
- Caretakers that keep every snapshot need a retention limit.
Relations
- Command records the operation and its inverse. Memento records the state. Use a memento inside a command when the operation has no cheap inverse.
- Iterator positions can be saved as mementos so a long traversal can resume.
- Prototype clones the whole object. A memento copies only the state needed to restore it, and keeps it opaque.
- State objects can be stored in a memento when the current state is part of what must roll back.
Agent checklist
- You checked that validating before mutating, or keeping an immutable previous value, doesn't already solve the problem.
- Every field that must come back is in the snapshot, or is recomputed after restore.
- Mutable collections are copied on
saveand onrestore, and a test mutates after saving to prove the snapshot is unaffected. - Caretakers hold mementos as opaque values, and the history has a size or age limit.
- A round-trip test (save, change, restore) asserts the originator equals its original state.
See also
- CommandWrap an action and its arguments in an object so you can queue, log, retry or undo it separately from the code that triggers it.
- IteratorWalk the elements of a collection one at a time through a standard interface, without exposing how the collection stores or fetches them.
- PrototypeCreate new objects by copying a configured existing instance, so callers get a ready-made variant without knowing its class or setup.
- StateMove each mode of an object into its own class so behavior and transitions change with the mode instead of branching on a status field.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.