Memento Pattern
Capture and restore an object's internal state without violating encapsulation. A behavioral design pattern for undo/redo.
Overview
The Memento Pattern is a behavioral design pattern that lets you save and restore the previous state of an object without revealing its internal structure. It is the foundation of undo/redo functionality in applications like text editors, drawing programs, and game state management.
When to Use
Use the Memento Pattern when:
- You need to implement undo and redo functionality
- You want to save checkpoints of an object’s state
- You must preserve encapsulation and not expose internal state directly
- State restoration should be possible without the client knowing the object’s internals
Solution
Python
class EditorMemento:
def __init__(self, content: str, cursor: int):
self._content = content
self._cursor = cursor
@property
def content(self) -> str:
return self._content
@property
def cursor(self) -> int:
return self._cursor
class TextEditor:
def __init__(self):
self._content = ""
self._cursor = 0
def type(self, text: str):
self._content = self._content[:self._cursor] + text
self._cursor += len(text)
def save(self) -> EditorMemento:
return EditorMemento(self._content, self._cursor)
def restore(self, memento: EditorMemento):
self._content = memento.content
self._cursor = memento.cursor
@property
def content(self) -> str:
return self._content
# Usage with history
class History:
def __init__(self):
self._history = []
def push(self, memento):
self._history.append(memento)
def pop(self):
if not self._history:
return None
return self._history.pop()
editor = TextEditor()
history = History()
history.push(editor.save())
editor.type("Hello ")
history.push(editor.save())
editor.type("World!")
print(editor.content) # Hello World!
editor.restore(history.pop())
print(editor.content) # Hello
editor.restore(history.pop())
print(editor.content) # (empty)
JavaScript
class EditorMemento {
constructor(content, cursor) {
this.content = content;
this.cursor = cursor;
}
}
class TextEditor {
constructor() {
this._content = "";
this._cursor = 0;
}
type(text) {
this._content = this._content.slice(0, this._cursor) + text;
this._cursor += text.length;
}
save() {
return new EditorMemento(this._content, this._cursor);
}
restore(memento) {
this._content = memento.content;
this._cursor = memento.cursor;
}
get content() {
return this._content;
}
}
// Usage
class History {
constructor() {
this.history = [];
}
push(memento) {
this.history.push(memento);
}
pop() {
return this.history.pop();
}
}
const editor = new TextEditor();
const history = new History();
history.push(editor.save());
editor.type("Hello ");
history.push(editor.save());
editor.type("World!");
console.log(editor.content); // Hello World!
editor.restore(history.pop());
console.log(editor.content); // Hello
Java
public class EditorMemento {
private final String content;
private final int cursor;
public EditorMemento(String content, int cursor) {
this.content = content;
this.cursor = cursor;
}
public String getContent() { return content; }
public int getCursor() { return cursor; }
}
public class TextEditor {
private String content = "";
private int cursor = 0;
public void type(String text) {
content = content.substring(0, cursor) + text;
cursor += text.length();
}
public EditorMemento save() {
return new EditorMemento(content, cursor);
}
public void restore(EditorMemento memento) {
this.content = memento.getContent();
this.cursor = memento.getCursor();
}
public String getContent() { return content; }
}
// Usage with history
import java.util.ArrayDeque;
import java.util.Deque;
public class History {
private final Deque<EditorMemento> stack = new ArrayDeque<>();
public void push(EditorMemento memento) {
stack.push(memento);
}
public EditorMemento pop() {
return stack.isEmpty() ? null : stack.pop();
}
}
// Demo
TextEditor editor = new TextEditor();
History history = new History();
history.push(editor.save());
editor.type("Hello ");
history.push(editor.save());
editor.type("World!");
System.out.println(editor.getContent());
editor.restore(history.pop());
System.out.println(editor.getContent());
Explanation
The Memento Pattern has three roles:
- Originator (
TextEditor): The object whose state needs to be saved - Memento (
EditorMemento): An immutable snapshot of the originator’s state - Caretaker (
History): Manages mementos (when to save, when to restore) without accessing their contents
The key benefit is that the memento’s internal state is opaque to the caretaker, preserving encapsulation.
Variants
| Variant | Description | Use Case |
|---|---|---|
| Full Snapshot | Stores entire object state | Small objects, infrequent snapshots |
| Delta/Incremental | Stores only changed fields | Large objects, frequent snapshots |
| Serializable Memento | Uses serialization for deep copying | Complex object graphs |
| Command + Memento | Commands store mementos for undo | Transaction systems, editors |
What Works
- Keep mementos immutable after creation to prevent accidental tampering
- Limit memento lifetime — large histories consume substantial memory
- Consider serialization for complex object graphs, but be aware of performance costs
- Implement a memento interface that only exposes state restoration methods to the originator
- Use delta mementos for large objects where only a few fields change
Common Mistakes
- Exposing the memento’s internal state to the caretaker, breaking encapsulation
- Storing too many full snapshots, causing excessive memory usage
- Not handling memento versioning when the originator’s structure changes over time
- Forgetting to validate mementos before restoration (corrupted or incompatible snapshots)
- Allowing originators to modify mementos after creation, causing unpredictable undo behavior
Troubleshooting
- Pattern does not fit the problem: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
- Too many abstractions: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
- Tight coupling after refactoring: check that interfaces are stable and dependencies point inward.
- Tests break when the design changes: favor stable contracts over internal structure.
- Performance regression from indirection: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.
Further Reading
- Official documentation: check the current reference for the framework or tool used.
- Related guides: explore the memento and pattern guides for deeper coverage.
- Complementary patterns: review design patterns applicable to your technology stack.
- Public postmortems: study real incidents from teams that faced similar production issues.
Production Notes
- Deploy gradually using canary or blue-green to catch regressions early.
- Configure alerts for error rate, p99 latency, and failure rate before enabling in production.
- Document the rollback in the runbook; test the procedure in staging at least once per quarter.
- Review structured logs with correlation IDs to trace requests end-to-end during incidents.
Key Takeaways
- Apply memento pattern when you need a practical solution for your use case.
- Monitor performance after implementation; measure latency, errors, and resource usage before and after.
- Check the Troubleshooting section for common failures; most have documented root causes with fixes.
- Keep dependencies updated and run tests in CI to prevent production regressions.
Common Production Pitfalls
- Applying the pattern where no abstraction is needed, adding accidental complexity.
- Letting the pattern leak into unrelated modules and blur ownership boundaries.
- Over-engineering the first implementation instead of starting simple and measuring pain.
- Skipping contract tests, so refactors silently break consumers.
- Ignoring failure modes that the pattern does not cover.
- Using the pattern as a default instead of choosing the right tool for the current scale.
- Forgetting to document when to stop using the pattern and what replaces it.
- Missing observability around the pattern’s performance and error propagation.
Frequently Asked Questions
How is Memento different from Prototype?
Prototype creates a new object by copying an existing one. Memento saves an object's state so it can be restored later. Prototype is about duplication; Memento is about time-travel.
Can I use serialization instead of Memento?
Yes, but serialization is often slower and less controlled. Memento gives you fine-grained control over what state is saved and how it is restored.
Related Resources
Command Pattern
Encapsulate a request as an object, letting you parameterize clients with queues, logs, and undoable operations. A behavioral design pattern.
PatternState Pattern
Allow an object to alter its behavior when its internal state changes. A behavioral design pattern for finite state machines.
PatternPrototype Pattern
Create new objects by copying existing ones. A creational design pattern for cloning and object duplication.
PatternChain of Responsibility Pattern
Pass requests along a chain of handlers until one handles it. A behavioral design pattern for decoupling senders and receivers.
PatternInterpreter Pattern
Define a representation for a language's grammar along with an interpreter that uses the representation to interpret sentences. A behavioral design pattern for mini-languages.