Memento
Memento is a behavioral pattern that captures an object's internal state into a separate snapshot object, so that state can later be restored without any outside code ever seeing what the snapshot contains.
Problem
A vector-graphics app needs undo: before every move, resize, or color change, it should remember how the shape looked a moment ago. The obvious approach is to have the undo manager reach into the shape and copy out its fields.
That forces the shape to expose everything an outside snapshot-taker might ever need - position, dimensions, fill, stroke, z-order - as public fields or getters. The shape's internals are now part of its public contract, so refactoring its internal representation later means also rewriting the undo manager. And every new property added to the shape has to be remembered by whoever is copying it from outside.
Solution
Memento moves the job of taking a snapshot inside the object that owns the state - the originator. Since the originator already has full access to its own fields, it can package a private copy of them into a memento object and hand that memento back to whoever asked for it.
The memento itself is opaque to everyone except the originator that created it: outside code can hold onto it, store it, or pass it back later to be restored, but it can't peek inside or modify what it holds. The object responsible for keeping a history of these mementos - the caretaker - never needs to know what a shape's position or fill color actually is; it just archives snapshots and returns them on request.
When to Use
- Reach for Memento when you need to capture and later restore an object's state - undo stacks, rollback points, checkpoints.
- Use it when the only way to grab that state from outside the object would mean exposing fields that should stay implementation detail.
Real-World Examples
- **Ctrl+Z in editors** - word processors and design tools like Figma snapshot the document before each edit so changes can be stepped back through.
- **SQL SAVEPOINT** - a database transaction marks a savepoint it can roll back to without discarding the whole transaction.
- **Git stash** - Git packs away the working tree's current state as an opaque commit-like object you can reapply later.
Structure
The Originator is the only party that can read or write the full contents of a Memento - it builds one from its current fields and can later reset itself from one. The Caretaker holds a collection of mementos (say, an undo stack) purely as opaque handles, requesting a save or a restore from the originator without ever touching the state inside.
Participants
Owns the actual state. Builds a memento from its current fields on request, and can reset itself from a memento it's handed back.
In the diagram: Editor.
A sealed container for one copy of the originator's state. Its contents are readable only by the originator class that produced it.
In the diagram: Editor Memento.
Manages a collection of mementos over time - for example, a linear undo stack - without ever inspecting or altering what any single memento contains.
In the diagram: History.
class EditorMemento {
#content;
#cursor;
constructor(content, cursor) {
this.#content = content;
this.#cursor = cursor;
}
getContent() { return this.#content; }
getCursor() { return this.#cursor; }
}
class Editor {
#content = '';
#cursor = 0;
type(text) {
this.#content += text;
this.#cursor = this.#content.length;
}
getContent() { return this.#content; }
save() {
return new EditorMemento(this.#content, this.#cursor);
}
restore(memento) {
this.#content = memento.getContent();
this.#cursor = memento.getCursor();
}
}
class History {
#mementos = [];
push(memento) { this.#mementos.push(memento); }
pop() { return this.#mementos.pop(); }
}
const editor = new Editor();
const history = new History();
editor.type('Hello');
history.push(editor.save());
editor.type(', world');
console.log(editor.getContent());
editor.restore(history.pop());
console.log(editor.getContent());
Step 1 of 6
EditorMemento is an opaque, read-only snapshot
`#content` and `#cursor` are private fields set once in the constructor, exposed only through getters - nothing outside `EditorMemento` can mutate a snapshot once it's created.
Step 2 of 6
Editor is the originator - it creates and restores its own mementos
`save()` packs its own private `#content`/`#cursor` into a new `EditorMemento`; `restore(memento)` reads them back out via the memento's getters - only the originator ever reaches into its own private state.
Step 3 of 6
History is the caretaker - it stores mementos without looking inside them
`push` and `pop` just move `EditorMemento` objects in and out of `#mementos` - `History` never calls `getContent()` or `getCursor()`, it treats each memento as a black box.
Step 4 of 6
A snapshot is taken right after typing 'Hello'
`editor.type('Hello')` mutates the editor's private state, then `editor.save()` freezes that exact state into a memento which `history.push(...)` stores.
Step 5 of 6
Further typing changes state that the saved memento doesn't know about
`editor.type(', world')` appends more text, so `getContent()` now logs `'Hello, world'` - but the memento pushed earlier still holds only `'Hello'`.
Step 6 of 6
restore() rolls the editor back to the pushed snapshot
`history.pop()` returns the earlier memento, and `editor.restore(...)` overwrites `#content`/`#cursor` with its values - the log now prints `'Hello'` again, undoing the second `type` call.
class EditorMemento {
constructor(
private readonly content: string,
private readonly cursor: number,
) {}
getContent(): string { return this.content; }
getCursor(): number { return this.cursor; }
}
class Editor {
private content = '';
private cursor = 0;
type(text: string): void {
this.content += text;
this.cursor = this.content.length;
}
getContent(): string { return this.content; }
save(): EditorMemento {
return new EditorMemento(this.content, this.cursor);
}
restore(memento: EditorMemento): void {
this.content = memento.getContent();
this.cursor = memento.getCursor();
}
}
class History {
private mementos: EditorMemento[] = [];
push(memento: EditorMemento): void { this.mementos.push(memento); }
pop(): EditorMemento | undefined { return this.mementos.pop(); }
}
const editor = new Editor();
const history = new History();
editor.type('Hello');
history.push(editor.save());
editor.type(', world');
console.log(editor.getContent());
const snapshot = history.pop();
if (snapshot) editor.restore(snapshot);
console.log(editor.getContent());
import java.util.ArrayDeque;
import java.util.Deque;
final class EditorMemento {
private final String content;
private final int cursor;
EditorMemento(String content, int cursor) {
this.content = content; this.cursor = cursor;
}
String getContent() { return content; }
int getCursor() { return cursor; }
}
class Editor {
private String content = "";
private int cursor = 0;
public void type(String text) { content += text; cursor = content.length(); }
public String getContent() { return content; }
public EditorMemento save() { return new EditorMemento(content, cursor); }
public void restore(EditorMemento m) { content = m.getContent(); cursor = m.getCursor(); }
}
class History {
private final Deque<EditorMemento> stack = new ArrayDeque<>();
public void push(EditorMemento m) { stack.push(m); }
public EditorMemento pop() { return stack.isEmpty() ? null : stack.pop(); }
}
Editor editor = new Editor();
History history = new History();
editor.type("Hello");
history.push(editor.save());
editor.type(", world");
System.out.println(editor.getContent());
EditorMemento snapshot = history.pop();
if (snapshot != null) editor.restore(snapshot);
System.out.println(editor.getContent());
sealed record EditorMemento(string Content, int Cursor);
class Editor
{
private string _content = "";
private int _cursor = 0;
public void Type(string text) { _content += text; _cursor = _content.Length; }
public string GetContent() => _content;
public EditorMemento Save() => new(_content, _cursor);
public void Restore(EditorMemento m) { _content = m.Content; _cursor = m.Cursor; }
}
class History
{
private readonly Stack<EditorMemento> _stack = new();
public void Push(EditorMemento m) => _stack.Push(m);
public EditorMemento? Pop() => _stack.Count > 0 ? _stack.Pop() : null;
}
var editor = new Editor();
var history = new History();
editor.Type("Hello");
history.Push(editor.Save());
editor.Type(", world");
Console.WriteLine(editor.GetContent());
var snapshot = history.Pop();
if (snapshot is not null) editor.Restore(snapshot);
Console.WriteLine(editor.GetContent());
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class EditorMemento:
content: str
cursor: int
class Editor:
def __init__(self) -> None:
self._content = ''
self._cursor = 0
def type(self, text: str) -> None:
self._content += text
self._cursor = len(self._content)
def get_content(self) -> str:
return self._content
def save(self) -> EditorMemento:
return EditorMemento(self._content, self._cursor)
def restore(self, memento: EditorMemento) -> None:
self._content = memento.content
self._cursor = memento.cursor
class History:
def __init__(self) -> None:
self._mementos: list[EditorMemento] = []
def push(self, memento: EditorMemento) -> None:
self._mementos.append(memento)
def pop(self) -> EditorMemento | None:
return self._mementos.pop() if self._mementos else None
editor = Editor()
history = History()
editor.type('Hello')
history.push(editor.save())
editor.type(', world')
print(editor.get_content())
snapshot = history.pop()
if snapshot:
editor.restore(snapshot)
print(editor.get_content())
Step 1 of 6
EditorMemento is an opaque, read-only snapshot
`@dataclass(frozen=True)` makes `content` and `cursor` immutable after construction - once created, a snapshot can't be modified by anyone, including `History`.
Step 2 of 6
Editor is the originator - it creates and restores its own mementos
`save()` packs its own `_content`/`_cursor` into a new `EditorMemento`; `restore(memento)` reads `memento.content`/`memento.cursor` back into its own fields - only the originator reaches into its own private state.
Step 3 of 6
History is the caretaker - it stores mementos without looking inside them
`push` and `pop` just move `EditorMemento` objects in and out of `_mementos`, returning `None` if empty - `History` never reads `content` or `cursor` itself.
Step 4 of 6
A snapshot is taken right after typing 'Hello'
`editor.type('Hello')` mutates the editor's state, then `editor.save()` freezes that exact state into a memento which `history.push(...)` stores.
Step 5 of 6
Further typing changes state that the saved memento doesn't know about
`editor.type(', world')` appends more text, so `get_content()` now prints `'Hello, world'` - but the memento pushed earlier still holds only `'Hello'`.
Step 6 of 6
restore() rolls the editor back to the popped snapshot
`history.pop()` returns the earlier memento as `snapshot`; the `if snapshot` guard handles the empty-history case, and `editor.restore(snapshot)` overwrites `_content`/`_cursor` - the print now shows `'Hello'` again.
Click "Run" to execute this code in a sandboxed frame and see console output here.
TypeScript runs as plain JavaScript here - type annotations are stripped, not type-checked.
Click "Run" to execute this code in a sandboxed frame and see console output here.
Advantages
- The originator's internal layout stays private - no field, getter, or setter needs to be exposed just so external code can take a snapshot.
- History bookkeeping (how many snapshots to keep, when to discard old ones) is entirely the caretaker's job, keeping that logic out of the originator.
- Because a memento is just a value carrying a copy of state, it's easy to serialize, log, or send across a network for later replay.
Disadvantages
- Keeping a long history of full snapshots can burn through memory fast, especially if the originator's state is large.
- The caretaker has to actively prune snapshots it no longer needs, or the history grows without bound as the originator keeps changing.
- In languages without real private fields, nothing stops other code from reaching into a memento and mutating it despite the intended encapsulation.
Question 1 of 10
What does Memento capture, and where does it store it?
Correct! Memento captures an object's internal state into a separate snapshot object, so that state can later be restored without any outside code ever seeing what the snapshot contains.
Not quite. Memento captures an object's internal state into a separate snapshot object, so that state can later be restored without any outside code ever seeing what the snapshot contains.
Question 2 of 10
In the vector-graphics editor example, when does the app need to remember how a shape looked a moment ago?
Correct! Before every move, resize, or color change, the app should remember how the shape looked a moment ago, so that change can later be undone.
Not quite. Before every move, resize, or color change, the app should remember how the shape looked a moment ago, so that change can later be undone.
Question 3 of 10
What is a stated drawback of keeping a long history of full snapshots?
Correct! Keeping a long history of full snapshots can burn through memory fast, especially if the originator's state is large.
Not quite. Keeping a long history of full snapshots can burn through memory fast, especially if the originator's state is large.
Question 4 of 10
Why is it easy to serialize, log, or send a memento across a network for later replay?
Correct! Because a memento is just a value carrying a copy of state, it's easy to serialize, log, or send across a network for later replay.
Not quite. Because a memento is just a value carrying a copy of state, it's easy to serialize, log, or send across a network for later replay.
Question 5 of 10
According to the pros listed for Memento, what happens to history bookkeeping like how many snapshots to keep?
Correct! History bookkeeping (how many snapshots to keep, when to discard old ones) is entirely the caretaker's job, keeping that logic out of the originator.
Not quite. History bookkeeping (how many snapshots to keep, when to discard old ones) is entirely the caretaker's job, keeping that logic out of the originator.
Question 6 of 10
Once outside code holds a memento, what can it do with it?
Correct! Outside code can hold onto a memento, store it, or pass it back later to be restored, but it can't peek inside or modify what it holds - only the originator that created it can read its contents.
Not quite. Outside code can hold onto a memento, store it, or pass it back later to be restored, but it can't peek inside or modify what it holds - only the originator that created it can read its contents.
Question 7 of 10
What does the caretaker need to know about the state stored in a memento?
Correct! The caretaker never needs to know what a shape's position or fill color actually is; it just archives snapshots and returns them on request.
Not quite. The caretaker never needs to know what a shape's position or fill color actually is; it just archives snapshots and returns them on request.
Question 8 of 10
Who is responsible for taking the snapshot in Memento, and why?
Correct! Memento moves the job of taking a snapshot inside the originator, since it already has full access to its own fields and can package a private copy of them into a memento.
Not quite. Memento moves the job of taking a snapshot inside the originator, since it already has full access to its own fields and can package a private copy of them into a memento.
Question 9 of 10
What goes wrong if an undo manager reaches directly into a shape and copies out its fields?
Correct! This forces the shape to expose everything an outside snapshot-taker might need as public fields or getters, so its internals become part of its public contract and refactoring breaks the undo manager too.
Not quite. This forces the shape to expose everything an outside snapshot-taker might need as public fields or getters, so its internals become part of its public contract and refactoring breaks the undo manager too.
Question 10 of 10
What can go wrong with memento encapsulation in a language without real private fields?
Correct! In languages without real private fields, nothing stops other code from reaching into a memento and mutating it despite the intended encapsulation.
Not quite. In languages without real private fields, nothing stops other code from reaching into a memento and mutating it despite the intended encapsulation.