Memento Pattern: Undo/Redo State Management
TL;DR
The Memento pattern captures and externalizes an object’s internal state without violating encapsulation, allowing the object to be restored to that state later. It’s the foundation for undo/redo functionality, checkpoints, and state rollback mechanisms. Think of it as taking a snapshot of an object that you can restore later.
Core Concept
What is the Memento Pattern?
The Memento pattern is a behavioral design pattern that lets you save and restore the previous state of an object without revealing the details of its implementation. It involves three key participants:
The Three Roles
Originator: The object whose state needs to be saved. It creates a memento containing a snapshot of its current state and can restore its state from a memento.
Memento: A value object that acts as a snapshot of the Originator’s state. It’s typically immutable and provides no methods to modify its contents after creation. Crucially, only the Originator can access the memento’s internal state.
Caretaker: Manages the mementos (stores, retrieves, and maintains the history). The Caretaker never examines or modifies the memento’s contents—it treats mementos as opaque objects.
Why It Matters
The Memento pattern solves a critical problem: how do you implement undo/redo, checkpoints, or state rollback without breaking encapsulation? If you expose an object’s internal state through getters, you violate encapsulation and create tight coupling. The Memento pattern provides a clean solution by packaging the state in a separate object that only the Originator can unpack.
When to Use It
- Undo/Redo functionality: Text editors, drawing applications, game state management
- Transactional operations: Database transactions, form wizards with “go back” functionality
- Checkpoints: Saving game progress, long-running computations that need rollback capability
- State history: Debugging tools that need to replay object state changes
The pattern is especially valuable when the object’s state is complex, contains private data, or when direct access to state would create maintenance problems.
Visual Guide
Memento Pattern Structure
classDiagram
class Originator {
-state
+createMemento() Memento
+restore(memento: Memento)
}
class Memento {
-state
-Memento(state)
-getState()
}
class Caretaker {
-mementos: List~Memento~
+save()
+undo()
}
Originator --> Memento : creates
Caretaker --> Memento : stores
Caretaker --> Originator : uses
note for Memento "Only Originator can\naccess internal state"
The Originator creates and restores from Mementos. The Caretaker stores Mementos but cannot access their contents.
Memento Pattern Sequence
sequenceDiagram
participant C as Caretaker
participant O as Originator
participant M as Memento
C->>O: Request to save state
O->>O: Capture current state
O->>M: Create memento(state)
M-->>O: Return memento
O-->>C: Return memento
C->>C: Store memento
Note over O: State changes occur
C->>O: Request to restore
C->>O: restore(memento)
O->>M: getState()
M-->>O: Return state
O->>O: Set internal state
Typical flow: Caretaker requests save, stores the memento, then later requests restore by passing the memento back to the Originator.
Examples
Example 1: Text Editor with Undo
This example shows a simple text editor that can undo changes using the Memento pattern.
class EditorMemento:
"""Memento: Stores the state of the editor."""
def __init__(self, content: str, cursor_position: int):
self._content = content
self._cursor_position = cursor_position
def get_content(self) -> str:
return self._content
def get_cursor_position(self) -> int:
return self._cursor_position
class TextEditor:
"""Originator: The object whose state we want to save."""
def __init__(self):
self._content = ""
self._cursor_position = 0
def type(self, text: str):
"""Add text at cursor position."""
self._content = (
self._content[:self._cursor_position] +
text +
self._content[self._cursor_position:]
)
self._cursor_position += len(text)
def delete(self, count: int):
"""Delete characters before cursor."""
start = max(0, self._cursor_position - count)
self._content = (
self._content[:start] +
self._content[self._cursor_position:]
)
self._cursor_position = start
def save(self) -> EditorMemento:
"""Create a memento with current state."""
return EditorMemento(self._content, self._cursor_position)
def restore(self, memento: EditorMemento):
"""Restore state from memento."""
self._content = memento.get_content()
self._cursor_position = memento.get_cursor_position()
def get_content(self) -> str:
return self._content
class EditorHistory:
"""Caretaker: Manages the history of mementos."""
def __init__(self, editor: TextEditor):
self._editor = editor
self._history = []
def backup(self):
"""Save current state to history."""
self._history.append(self._editor.save())
def undo(self):
"""Restore previous state."""
if not self._history:
return
memento = self._history.pop()
self._editor.restore(memento)
# Usage example
editor = TextEditor()
history = EditorHistory(editor)
# Type some text and save states
history.backup() # Save empty state
editor.type("Hello")
print(f"After typing 'Hello': {editor.get_content()}")
# Output: After typing 'Hello': Hello
history.backup() # Save "Hello"
editor.type(" World")
print(f"After typing ' World': {editor.get_content()}")
# Output: After typing ' World': Hello World
history.backup() # Save "Hello World"
editor.type("!")
print(f"After typing '!': {editor.get_content()}")
# Output: After typing '!': Hello World!
# Undo operations
history.undo()
print(f"After first undo: {editor.get_content()}")
# Output: After first undo: Hello World
history.undo()
print(f"After second undo: {editor.get_content()}")
# Output: After second undo: Hello
history.undo()
print(f"After third undo: {editor.get_content()}")
# Output: After third undo: (empty string)
Try it yourself: Extend this example to support redo functionality. You’ll need to maintain a separate redo stack that stores states when undo is called.
Example 2: Game Character State with Checkpoint System
This example demonstrates saving game character state at checkpoints.
from dataclasses import dataclass
from typing import Dict
@dataclass(frozen=True)
class CharacterMemento:
"""Memento: Immutable snapshot of character state."""
health: int
mana: int
position: tuple
inventory: tuple # Use tuple for immutability
level: int
class GameCharacter:
"""Originator: Character with complex state."""
def __init__(self, name: str):
self.name = name
self._health = 100
self._mana = 50
self._position = (0, 0)
self._inventory = []
self._level = 1
def take_damage(self, amount: int):
self._health = max(0, self._health - amount)
def use_mana(self, amount: int):
self._mana = max(0, self._mana - amount)
def move_to(self, x: int, y: int):
self._position = (x, y)
def add_item(self, item: str):
self._inventory.append(item)
def level_up(self):
self._level += 1
self._health = 100
self._mana = 50
def create_checkpoint(self) -> CharacterMemento:
"""Save current state."""
return CharacterMemento(
health=self._health,
mana=self._mana,
position=self._position,
inventory=tuple(self._inventory), # Convert to immutable
level=self._level
)
def restore_from_checkpoint(self, memento: CharacterMemento):
"""Restore from saved state."""
self._health = memento.health
self._mana = memento.mana
self._position = memento.position
self._inventory = list(memento.inventory) # Convert back to list
self._level = memento.level
def __str__(self):
return (f"{self.name} - Level {self._level} | "
f"HP: {self._health} | MP: {self._mana} | "
f"Pos: {self._position} | Items: {self._inventory}")
class CheckpointManager:
"""Caretaker: Manages game checkpoints."""
def __init__(self):
self._checkpoints: Dict[str, CharacterMemento] = {}
def save_checkpoint(self, name: str, character: GameCharacter):
"""Save a named checkpoint."""
self._checkpoints[name] = character.create_checkpoint()
print(f"Checkpoint '{name}' saved!")
def load_checkpoint(self, name: str, character: GameCharacter) -> bool:
"""Load a named checkpoint."""
if name not in self._checkpoints:
print(f"Checkpoint '{name}' not found!")
return False
character.restore_from_checkpoint(self._checkpoints[name])
print(f"Checkpoint '{name}' loaded!")
return True
def list_checkpoints(self):
return list(self._checkpoints.keys())
# Usage example
player = GameCharacter("Hero")
checkpoint_mgr = CheckpointManager()
print("Initial state:")
print(player)
# Output: Hero - Level 1 | HP: 100 | MP: 50 | Pos: (0, 0) | Items: []
# Progress through the game
player.move_to(10, 20)
player.add_item("Sword")
player.add_item("Shield")
checkpoint_mgr.save_checkpoint("village", player)
# Output: Checkpoint 'village' saved!
print("\nAfter reaching village:")
print(player)
# Output: Hero - Level 1 | HP: 100 | MP: 50 | Pos: (10, 20) | Items: ['Sword', 'Shield']
# Continue playing
player.move_to(50, 75)
player.take_damage(60)
player.use_mana(30)
player.add_item("Potion")
player.level_up()
checkpoint_mgr.save_checkpoint("boss_entrance", player)
# Output: Checkpoint 'boss_entrance' saved!
print("\nBefore boss fight:")
print(player)
# Output: Hero - Level 2 | HP: 100 | MP: 50 | Pos: (50, 75) | Items: ['Sword', 'Shield', 'Potion']
# Boss fight goes badly
player.take_damage(95)
player.use_mana(50)
print("\nAfter failed boss fight:")
print(player)
# Output: Hero - Level 2 | HP: 5 | MP: 0 | Pos: (50, 75) | Items: ['Sword', 'Shield', 'Potion']
# Restore to checkpoint
checkpoint_mgr.load_checkpoint("boss_entrance", player)
# Output: Checkpoint 'boss_entrance' loaded!
print("\nAfter loading checkpoint:")
print(player)
# Output: Hero - Level 2 | HP: 100 | MP: 50 | Pos: (50, 75) | Items: ['Sword', 'Shield', 'Potion']
print(f"\nAvailable checkpoints: {checkpoint_mgr.list_checkpoints()}")
# Output: Available checkpoints: ['village', 'boss_entrance']
Try it yourself: Add a feature to automatically save checkpoints every N actions, and limit the total number of checkpoints to prevent memory issues.
Java/C++ Notes
Java: Use inner classes to restrict access to Memento internals. Make the Memento a private static inner class of the Originator, so only the Originator can access its state.
public class TextEditor {
private String content;
public Memento save() {
return new Memento(content);
}
public void restore(Memento m) {
content = m.getState();
}
// Private inner class - only TextEditor can access
private static class Memento {
private final String state;
private Memento(String state) {
this.state = state;
}
private String getState() {
return state;
}
}
}
C++: Use friend classes to control access. Declare the Originator as a friend of the Memento class.
class Memento {
friend class TextEditor;
private:
std::string state;
Memento(const std::string& s) : state(s) {}
std::string getState() const { return state; }
};
Common Mistakes
1. Exposing Memento Internals to Caretaker
Mistake: Providing public getters/setters on the Memento that allow the Caretaker to inspect or modify the saved state.
# WRONG: Memento with public access
class Memento:
def __init__(self, state):
self.state = state # Public attribute!
# Caretaker can now peek and modify
memento = editor.save()
memento.state = "hacked!" # Breaks encapsulation
Why it’s wrong: This defeats the entire purpose of the pattern. The Caretaker should treat mementos as opaque objects. In Python, use name mangling (_state) or rely on convention. In Java/C++, use private access with friend/inner classes.
Correct approach: Make Memento immutable and restrict access to its internals to only the Originator.
2. Storing Too Much State History
Mistake: Keeping unlimited history without considering memory constraints, especially for large objects.
# WRONG: Unbounded history
class History:
def __init__(self):
self._history = [] # Grows forever!
def backup(self, editor):
self._history.append(editor.save()) # Memory leak
Why it’s wrong: For applications with frequent state changes (like a text editor saving after every keystroke), you’ll quickly consume all available memory.
Correct approach: Implement a bounded history with a maximum size, or use a circular buffer. Consider compression for older states.
class BoundedHistory:
def __init__(self, max_size=100):
self._history = []
self._max_size = max_size
def backup(self, editor):
if len(self._history) >= self._max_size:
self._history.pop(0) # Remove oldest
self._history.append(editor.save())
3. Forgetting to Make Mementos Immutable
Mistake: Storing mutable references in the Memento, allowing external code to modify the saved state.
# WRONG: Storing mutable reference
class GameMemento:
def __init__(self, inventory):
self.inventory = inventory # Same list reference!
# Problem:
inventory = ["sword", "shield"]
memento = GameMemento(inventory)
inventory.append("potion") # Modifies saved state!
Why it’s wrong: The memento should be a snapshot, not a live reference. Changes to the original object shouldn’t affect saved states.
Correct approach: Create deep copies of mutable objects or use immutable data structures.
class GameMemento:
def __init__(self, inventory):
self.inventory = tuple(inventory) # Immutable copy
4. Violating Single Responsibility by Combining Caretaker and Originator
Mistake: Making the Originator manage its own history, mixing state management with history management.
# WRONG: Originator managing its own history
class TextEditor:
def __init__(self):
self._content = ""
self._history = [] # Should be in Caretaker!
def undo(self):
if self._history:
self._content = self._history.pop()
Why it’s wrong: The Originator should focus on its core functionality. History management is a separate concern that should be handled by the Caretaker.
Correct approach: Keep the Originator focused on creating and restoring from mementos. Let the Caretaker handle storage and retrieval.
5. Not Considering Performance for Large State Objects
Mistake: Copying entire large objects when only a small portion of the state changes.
# WRONG: Copying huge object for small change
class LargeDocument:
def __init__(self):
self._pages = [Page() for _ in range(1000)] # Large!
def save(self):
return Memento(self._pages[:]) # Copies all 1000 pages!
Why it’s wrong: This is inefficient when only one page changed. You’re wasting memory and CPU.
Correct approach: Consider incremental snapshots, copy-on-write strategies, or the Command pattern for undo/redo of individual operations instead of full state snapshots.
Interview Tips
What Interviewers Look For
1. Understanding the “Why”: Be ready to explain why the Memento pattern exists. Interviewers want to hear that you understand encapsulation and why exposing getters for every field is problematic. Say: “The Memento pattern lets us implement undo without breaking encapsulation by packaging state in an object that only the Originator can unpack.”
2. Distinguish from Similar Patterns: You might be asked how Memento differs from Command or Prototype patterns. Key differences:
- Command: Stores operations to replay, not state snapshots
- Prototype: Creates new objects by cloning, but doesn’t necessarily save for later restoration
- Memento: Specifically for saving and restoring state
3. Discuss Trade-offs: Mention memory concerns upfront. “The main trade-off is memory usage—each memento is a full state copy. For large objects or frequent saves, we’d need strategies like limiting history size, compression, or incremental snapshots.”
Common Interview Questions
Q: “Design an undo system for a drawing application.”
Structure your answer:
- Identify the Originator (Canvas/Drawing)
- Define what state needs saving (shapes, colors, positions)
- Explain how Memento captures this state
- Describe the Caretaker (UndoManager) with bounded history
- Mention optimization: “For efficiency, I’d consider storing only the delta between states rather than full snapshots.”
Q: “How would you implement redo functionality?”
Show you can extend the pattern: “Maintain two stacks—undo and redo. When undo is called, pop from undo stack, restore state, and push the current state to redo stack. When a new action occurs, clear the redo stack since the timeline has changed.”
Q: “What if the object state is very large?”
Demonstrate practical thinking:
- Limit history size (circular buffer)
- Compress older mementos
- Use incremental snapshots (only store changes)
- Consider Command pattern instead for operation-level undo
- Implement lazy copying or copy-on-write
Code Interview Tips
Start simple: In a 45-minute interview, implement the basic three-component structure first. Don’t jump into optimizations immediately.
Show encapsulation awareness: Use Python’s naming conventions (_private) or explicitly mention how you’d use Java inner classes or C++ friend classes to protect Memento internals.
Handle edge cases: Check for empty history before undo, handle null mementos, validate state before restoration.
Discuss testing: Mention how you’d test: “I’d verify that restoring a memento returns the object to the exact previous state, and that the Caretaker can’t modify memento contents.”
Red Flags to Avoid
- Don’t confuse Memento with serialization (serialization is for persistence, Memento is for in-memory state management)
- Don’t suggest using Memento for everything—acknowledge when simpler solutions (like copying a single value) are better
- Don’t ignore the performance implications of deep copying large objects
Key Takeaways
-
The Memento pattern captures object state in a separate object (memento) that only the originator can access, preserving encapsulation while enabling undo/redo and checkpoint functionality.
-
Three roles work together: Originator creates and restores from mementos, Memento stores state immutably, and Caretaker manages memento storage without accessing their contents.
-
Memory management is critical—implement bounded history, consider compression or incremental snapshots for large objects, and be aware that each memento is a full state copy.
-
Encapsulation is the core benefit: The pattern lets you save and restore state without exposing internal object structure through getters/setters, maintaining clean separation of concerns.
-
Use when you need state rollback: Text editors, game checkpoints, transaction systems, and any application requiring undo/redo functionality are ideal candidates for the Memento pattern.