Skip to main content

Memento Pattern

Capture and restore object state without exposing internal details

TL;DR

Memento captures an object's internal state as a separate immutable object (memento) without exposing private details. A caretaker manages saved mementos. Use it for snapshots, checkpoints, or implementing undo/redo by restoring previous states without breaking encapsulation.

Learning Objectives

  • You will understand how to capture state without exposing internal structure.
  • You will distinguish between originator, memento, and caretaker roles.
  • You will implement mementos that are immutable and externally opaque.
  • You will design caretakers that manage state history safely.

Motivating Scenario

A game needs save/load functionality. If the player object exposes all internal state (health, inventory, position), external code could read and corrupt it. Memento allows the player to create a Snapshot (memento) containing only relevant state. The game engine (caretaker) stores snapshots. Loading restores the player from a snapshot without the game needing to know the player's private fields.

Core Concepts

Memento captures state at a moment in time as an immutable snapshot. The originator creates mementos and can restore from them. A caretaker stores mementos but cannot inspect their contents.

Key elements:

  • Originator: object whose state is captured; creates and restores mementos
  • Memento: immutable snapshot of originator state
  • Caretaker: stores mementos and retrieves them on demand
Memento structure

Practical Example

Implement save/load for a game character.

memento.py
from dataclasses import dataclass
from typing import List

@dataclass
class CharacterSnapshot:
"""Memento: immutable state snapshot"""
name: str
level: int
health: int
experience: int

class Character:
"""Originator"""
def __init__(self, name: str):
self.name = name
self.level = 1
self.health = 100
self.experience = 0

def take_damage(self, damage: int):
self.health = max(0, self.health - damage)

def gain_experience(self, exp: int):
self.experience += exp
if self.experience >= 100:
self.level += 1
self.experience = 0

def create_snapshot(self) -> CharacterSnapshot:
"""Create memento"""
return CharacterSnapshot(
name=self.name,
level=self.level,
health=self.health,
experience=self.experience
)

def restore_snapshot(self, snapshot: CharacterSnapshot):
"""Restore from memento"""
self.name = snapshot.name
self.level = snapshot.level
self.health = snapshot.health
self.experience = snapshot.experience

class GameSave:
"""Caretaker"""
def __init__(self):
self.saves: List[CharacterSnapshot] = []

def save(self, character: Character):
self.saves.append(character.create_snapshot())

def load(self, index: int, character: Character):
if 0 <= index < len(self.saves):
character.restore_snapshot(self.saves[index])

# Usage
game_save = GameSave()
hero = Character("Aragorn")

game_save.save(hero)
print(f"Saved: Level {hero.level}, Health {hero.health}")

hero.take_damage(30)
hero.gain_experience(150)
print(f"After action: Level {hero.level}, Health {hero.health}")

game_save.load(0, hero)
print(f"Loaded: Level {hero.level}, Health {hero.health}")

When to Use / When Not to Use

Use Memento
  1. You need to save and restore object state
  2. State must not be exposed outside the object
  3. Undo/redo functionality is required
  4. Checkpoint or snapshot functionality needed
  5. Multiple versions of state must coexist
Avoid Memento
  1. State is simple and can be exposed without risk
  2. Creating snapshots is expensive (large state)
  3. No need to restore previous states
  4. Serialization frameworks already solve this
  5. Real-time constraints prevent state copying

Patterns and Pitfalls

Design Review Checklist

  • Are mementos immutable once created?
  • Can caretakers store mementos without understanding their structure?
  • Does restoration correctly populate all necessary state?
  • Are large state objects handled efficiently in memory?
  • Is thread safety ensured for concurrent snapshot/restore?
  • Can partial state be selectively captured if needed?
  • Is the memento format stable for long-term storage?

Advanced Patterns and Strategies

Wide vs Shallow Snapshots

Full Memento (Shallow Copy): Capture all state

@dataclass
class CharacterSnapshot:
name: str
level: int
health: int
inventory: List[Item] # Reference, not deep copy

Selective Memento: Capture only relevant state

@dataclass
class CharacterCheckpoint:
level: int # Core progression
health: int # Combat state
# Intentionally omit: cosmetics, temporary buffs

Deep Copy Memento: For complex objects with nested state

import copy

class Character:
def create_snapshot(self):
return CharacterSnapshot(
name=self.name,
level=self.level,
health=self.health,
inventory=copy.deepcopy(self.inventory) # Deep copy
)

History Management Strategies

Time-based Retention: Keep snapshots for last 24 hours

class GameSave:
def __init__(self, max_age_hours=24):
self.saves = []
self.max_age = max_age_hours

def save(self, character):
self.saves.append({
'snapshot': character.create_snapshot(),
'timestamp': datetime.now()
})
self.cleanup_old()

def cleanup_old(self):
cutoff = datetime.now() - timedelta(hours=self.max_age)
self.saves = [s for s in self.saves if s['timestamp'] > cutoff]

Compression Strategy: Store deltas instead of full snapshots

class DeltaSnapshot:
def __init__(self, base_snapshot, changes):
self.base = base_snapshot
self.changes = changes # Only modified fields

def restore(self):
snapshot = copy.deepcopy(self.base)
snapshot.__dict__.update(self.changes)
return snapshot

Transaction-Style State Management

class Transaction:
"""Group multiple state changes into atomic operation"""
def __init__(self, character):
self.character = character
self.initial_snapshot = character.create_snapshot()
self.changes = []
self.active = True

def add_change(self, change_description, operation):
if not self.active:
raise RuntimeError("Transaction already committed/rolled back")
self.changes.append((change_description, operation))
operation()

def commit(self):
self.active = False
# Transaction succeeds; initial snapshot becomes fallback
return self.initial_snapshot

def rollback(self):
self.active = False
# Restore to initial snapshot
self.character.restore_snapshot(self.initial_snapshot)
return self.initial_snapshot

# Usage
txn = Transaction(character)
try:
txn.add_change("Take damage", lambda: character.take_damage(30))
txn.add_change("Gain XP", lambda: character.gain_experience(100))
txn.add_change("Level up", lambda: character.level_up())
committed = txn.commit() # All changes applied
except Exception:
rolled_back = txn.rollback() # Revert all changes

Undo/Redo Implementation

class UndoRedoManager:
def __init__(self):
self.undo_stack = []
self.redo_stack = []

def do_action(self, action, character):
# Save before state
self.undo_stack.append(character.create_snapshot())
self.redo_stack = [] # Clear redo on new action

# Execute
action.execute(character)

def undo(self, character):
if self.undo_stack:
self.redo_stack.append(character.create_snapshot())
snapshot = self.undo_stack.pop()
character.restore_snapshot(snapshot)

def redo(self, character):
if self.redo_stack:
self.undo_stack.append(character.create_snapshot())
snapshot = self.redo_stack.pop()
character.restore_snapshot(snapshot)

Persisted Memento (Database Storage)

import json

class CharacterSnapshotRepository:
def save_to_db(self, character_id, snapshot):
"""Persist snapshot to database"""
data = json.dumps({
'character_id': character_id,
'level': snapshot.level,
'health': snapshot.health,
'experience': snapshot.experience,
'timestamp': datetime.now().isoformat()
})
# Store in database
db.insert('snapshots', data)

def load_from_db(self, character_id, timestamp):
"""Restore snapshot from database"""
row = db.query('snapshots',
where={'character_id': character_id, 'timestamp': timestamp})
data = json.loads(row)
return CharacterSnapshot(**data)

Performance Considerations

Memory Impact

Large objects with many snapshots consume significant memory:

class MemoryEfficientSnapshot:
"""Only snapshot changed fields"""
def __init__(self, object_state, changed_fields=None):
self.changed_fields = changed_fields or {}
self.full_state = None
if not changed_fields:
# Full snapshot only if necessary
self.full_state = copy.deepcopy(object_state)

def get_estimated_size_bytes(self):
"""Estimate memory used by snapshot"""
import sys
size = sys.getsizeof(self.changed_fields)
if self.full_state:
size += sys.getsizeof(self.full_state)
return size

Cleanup Strategies

class SnapshotManager:
"""Manage memento lifecycle and cleanup"""
def __init__(self, max_snapshots=100, max_age_hours=24):
self.snapshots = []
self.max_snapshots = max_snapshots
self.max_age = max_age_hours

def add_snapshot(self, snapshot):
self.snapshots.append({
'snapshot': snapshot,
'timestamp': datetime.now()
})
self._cleanup()

def _cleanup(self):
# Remove old snapshots
cutoff = datetime.now() - timedelta(hours=self.max_age)
self.snapshots = [s for s in self.snapshots if s['timestamp'] > cutoff]

# Remove excess snapshots (keep most recent N)
if len(self.snapshots) > self.max_snapshots:
self.snapshots = self.snapshots[-self.max_snapshots:]

Command + Memento: Command pattern stores action; Memento stores state. Together: undo/redo Observer + Memento: Observer notified of state changes; Memento captures the changed state Prototype + Memento: Prototype clones objects; Memento captures specific state points

Self-Check

  1. How does Memento differ from cloning an object? Memento captures specific state intentionally and prevents external inspection; cloning copies the entire object structure.

  2. What prevents external code from modifying a memento? Make mementos immutable—use private fields and expose only necessary accessor methods.

  3. Can mementos be serialized for persistent storage? Yes—design them as plain data objects that serialize to JSON, binary, or other formats.

  4. How do you implement unlimited undo/redo? Keep stacks of snapshots; memory-intensive but simple. For large objects, use delta encoding or compression.

  5. When would you use selective vs. full mementos? Selective when you have large state but only care about certain fields (saves memory). Full when you need complete restoration.

One Takeaway

Memento preserves object encapsulation while enabling state capture and restoration. Use it for save/load, undo/redo, or transaction-like rollback without exposing internals.

Next Steps

References

  • Gang of Four, "Design Patterns: Elements of Reusable Object-Oriented Software"
  • Refactoring Guru's Memento ↗️
  • Martin Fowler on Event Sourcing ↗️