Behavioral Pattern

Memento

Captures and externalizes an object's state for later restoration.

Memento Pattern: A Complete Guide with Examples in 11 Programming Languages

The Memento Pattern is a behavioral design pattern that captures and externalizes an object's internal state so that the object can be restored to that state later — without violating encapsulation. The object whose state you want to save (the Originator) creates a snapshot of itself. That snapshot (the Memento) is handed to a caretaker for safekeeping. The caretaker holds it but never inspects it. When restoration is needed, the caretaker returns the memento to the originator, which restores itself from the snapshot. The originator's private internals are never exposed.

In this comprehensive guide, we'll explore the Memento Pattern with real-world examples in Python, TypeScript, Java, JavaScript, C#, PHP, Go, Rust, Dart, Swift, and Kotlin — complete with language-specific best practices, common pitfalls, and when to use this powerful pattern.

Table of Contents

What is the Memento Pattern?

The Memento Pattern solves one deceptively hard problem: how do you save an object's state for later restoration without exposing its private internals to the rest of the world?

The naive approach — serializing everything into a public struct or exposing getters for every field — breaks encapsulation. Any code can now inspect or modify the supposedly private state. The Memento Pattern avoids this by making the snapshot opaque. The caretaker holds it but can't read it. Only the originator, which created the memento, can interpret and restore from it.

Think of it like a sealed envelope. You write your current thoughts on a piece of paper, seal it in an envelope, and hand it to a friend. Your friend keeps it safe. Later, you open the envelope and read your notes. Your friend never knew what was inside.

Why Use the Memento Pattern?

The Memento Pattern offers several compelling benefits:

  1. Non-destructive Undo/Redo: Capture state before any operation; restore it if the operation should be undone — the foundation of undo/redo in every text editor, drawing tool, and IDE
  2. Preserved Encapsulation: The originator's private internals never leak to the caretaker or any other class — the memento is opaque to everyone except the originator
  3. Snapshot and Restore: Save the complete state of a complex object at a point in time and restore it exactly — useful for checkpointing long-running computations
  4. Transaction Rollback: Before a risky multi-step operation, save a snapshot; if any step fails, restore the snapshot — lightweight transactional behavior without a database
  5. History Browsing: Maintain a stack of snapshots and traverse history backward and forward — not just single-level undo but full multi-level history
  6. State Versioning: Store named snapshots ("autosave", "before refactor", "v1.0") that can be restored on demand

Memento Pattern Comparison

Let's compare the Memento Pattern with related patterns:

PatternPurposeWho Holds StateEncapsulationUse Case
MementoCapture and restore internal stateCaretaker holds opaque snapshotPreserved — caretaker can't read snapshotUndo/redo, checkpointing, rollback
CommandEncapsulate a request as an objectCommand holds receiver + paramsN/AUndo via inverse command, job queues
PrototypeClone an objectClone is a full live copyNot preserved — clone is accessibleObject copying, object pools
SerializationConvert state to bytes/stringExternal storage holds bytesNot preserved — state is fully exposedPersistence, network transfer
StateChange behavior based on internal stateObject holds current statePreserved within state hierarchyFinite state machines

Key Distinctions:

  • Memento vs. Command for Undo: Command-based undo stores the inverse operation (e.g., delete the character that was typed). Memento-based undo stores a full snapshot of state before the operation. Command undo is memory-efficient for simple operations; Memento undo is simpler to implement for complex objects with deeply interrelated state.
  • Memento vs. Prototype/Clone: A Prototype creates a fully live, accessible copy that any code can inspect and modify. A Memento creates an opaque snapshot that only the originator can read — encapsulation is preserved.
  • Memento vs. Serialization: Serialization exposes every field as raw data (JSON, XML, bytes) for persistence or transmission. Memento is for in-process state preservation where the internal structure should remain private.

Memento Pattern Explained

The Memento Pattern involves three participants:

Core Components:

  1. Originator: The object whose state needs to be saved and restored. It creates a Memento containing a snapshot of its current private state. It can also restore its state from a Memento. The Originator is the ONLY class that knows the Memento's internal structure.

  2. Memento: The snapshot object. Contains a copy of the Originator's internal state at a specific point in time. The Memento's data is opaque to all other classes — typically achieved by making the state fields private, making the Memento class nested inside the Originator, or using a narrow public interface.

  3. Caretaker: Manages the lifecycle of Mementos. Requests a Memento from the Originator when it wants to save state. Returns a Memento to the Originator when it wants to restore state. The Caretaker NEVER reads or modifies the Memento's contents — it treats it as a black box.

Memento Variants:

  • Stack-based History: A List<Memento> (undo stack) and optionally a redo stack — the most common pattern
  • Named Snapshots: A Map<String, Memento> of named checkpoints ("autosave", "before_refactor")
  • Incremental Memento: Only saves the delta from the previous state rather than a full copy — more memory-efficient for large objects
  • Memento with Metadata: Snapshot includes timestamp, description, author, or other audit information alongside the state
  • Serialized Memento: State is serialized to bytes or JSON for persistence across sessions

Class Diagram

Here's the UML class diagram showing the Memento structure:

Beginner-Friendly Example

Let's start with the most intuitive Memento example: a text editor with full undo/redo. Every edit creates a snapshot. Ctrl+Z restores the previous snapshot. Ctrl+Y re-applies a snapshot from the redo stack. The snapshot stores the text content and cursor position — the editor's private state — without exposing it.

from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional, List


# ── Memento ────────────────────────────────────────────────────────
@dataclass(frozen=True)
class EditorMemento:
    """Opaque snapshot — only EditorState can interpret it."""
    _content:  str
    _cursor:   int
    _timestamp: str = field(default_factory=lambda: datetime.now().strftime("%H:%M:%S"))
    _description: str = ""

    # Narrow public interface — caretaker can read metadata but not content
    @property
    def timestamp(self) -> str:
        return self._timestamp

    @property
    def description(self) -> str:
        return self._description

    def __str__(self) -> str:
        preview = self._content[:20] + "…" if len(self._content) > 20 else self._content
        return f"[{self._timestamp}] {self._description!r} | \"{preview}\" (cursor={self._cursor})"


# ── Originator ─────────────────────────────────────────────────────
class TextEditor:
    """Originator — knows how to create and restore from snapshots."""

    def __init__(self) -> None:
        self._content = ""
        self._cursor  = 0

    # ── Editing operations ──
    def type(self, text: str) -> None:
        self._content = (
            self._content[:self._cursor] + text + self._content[self._cursor:]
        )
        self._cursor += len(text)

    def delete(self, count: int = 1) -> None:
        if self._cursor > 0:
            remove = min(count, self._cursor)
            self._content = self._content[:self._cursor - remove] + self._content[self._cursor:]
            self._cursor -= remove

    def move_cursor(self, position: int) -> None:
        self._cursor = max(0, min(position, len(self._content)))

    def select_all_and_replace(self, text: str) -> None:
        self._content = text
        self._cursor  = len(text)

    # ── Snapshot API ──
    def save(self, description: str = "") -> EditorMemento:
        return EditorMemento(
            _content=self._content,
            _cursor=self._cursor,
            _description=description,
        )

    def restore(self, memento: EditorMemento) -> None:
        # Only the originator accesses the memento's private fields
        self._content = memento._content
        self._cursor  = memento._cursor

    # ── Display ──
    def display(self) -> None:
        before = self._content[:self._cursor]
        after  = self._content[self._cursor:]
        print(f'  Content : "{self._content}"')
        print(f'  Cursor  : {self._cursor}  (…"{before}|{after}")')


# ── Caretaker ─────────────────────────────────────────────────────
class EditorHistory:
    """Caretaker — stores snapshots but never reads their content."""

    def __init__(self, editor: TextEditor) -> None:
        self._editor:    TextEditor     = editor
        self._undo_stack: List[EditorMemento] = []
        self._redo_stack: List[EditorMemento] = []

    def save(self, description: str = "") -> None:
        """Take a snapshot of the current editor state."""
        snapshot = self._editor.save(description)
        self._undo_stack.append(snapshot)
        self._redo_stack.clear()   # New action clears redo history
        print(f"  💾 Saved: {snapshot}")

    def undo(self) -> bool:
        if not self._undo_stack:
            print("  ↩  Nothing to undo")
            return False
        # Push current state to redo stack before restoring
        current = self._editor.save("(before undo)")
        self._redo_stack.append(current)
        # Restore previous state
        memento = self._undo_stack.pop()
        self._editor.restore(memento)
        print(f"  ↩  Undo → restored: {memento}")
        return True

    def redo(self) -> bool:
        if not self._redo_stack:
            print("  ↪  Nothing to redo")
            return False
        current = self._editor.save("(before redo)")
        self._undo_stack.append(current)
        memento = self._redo_stack.pop()
        self._editor.restore(memento)
        print(f"  ↪  Redo → restored: {memento}")
        return True

    def history(self) -> None:
        print(f"  Undo stack ({len(self._undo_stack)}):")
        for m in self._undo_stack:
            print(f"    {m}")
        print(f"  Redo stack ({len(self._redo_stack)}):")
        for m in self._redo_stack:
            print(f"    {m}")


# ── Client ─────────────────────────────────────────────────────────
if __name__ == "__main__":
    editor  = TextEditor()
    history = EditorHistory(editor)

    print("=== Text Editor with Undo/Redo ===\n")

    # Initial save (empty state)
    history.save("initial empty state")

    print("\n--- Typing ---")
    editor.type("Hello")
    history.save("typed 'Hello'")
    editor.display()

    editor.type(", World")
    history.save("typed ', World'")
    editor.display()

    editor.type("!")
    history.save("typed '!'")
    editor.display()

    print("\n--- Undo × 2 ---")
    history.undo()
    editor.display()
    history.undo()
    editor.display()

    print("\n--- Redo × 1 ---")
    history.redo()
    editor.display()

    print("\n--- Replace all ---")
    editor.select_all_and_replace("Brand new content")
    history.save("replaced all content")
    editor.display()

    print("\n--- Undo after branch (redo stack cleared) ---")
    history.undo()
    editor.display()

    print()
    history.history()

Production-Ready Example

Now let's tackle a realistic scenario: a game character state system with named checkpoints and auto-save. The character has deeply interrelated state — position, health, inventory, quests, and experience. The system supports: saving named checkpoints ("before boss fight"), bounded auto-save history (last N states), restore-to-checkpoint, undo last action, and a full save browser. This demonstrates a production-grade caretaker with named snapshots, bounded history, and metadata.

🐍 Python (Production Example)

from __future__ import annotations
from dataclasses import dataclass, field, asdict
from datetime import datetime
from typing import Dict, List, Optional, Any
import copy
import json


# ─── Domain Types ─────────────────────────────────────────────────
@dataclass
class Position:
    x: float
    y: float
    zone: str

    def __str__(self) -> str:
        return f"({self.x:.1f}, {self.y:.1f}) @ {self.zone}"


@dataclass
class InventoryItem:
    name:     str
    quantity: int
    rarity:   str = "common"


# ─── Memento ──────────────────────────────────────────────────────
@dataclass(frozen=True)
class GameMemento:
    """
    Opaque snapshot of CharacterState.
    All fields are private by convention (_prefixed).
    Only CharacterState can create and restore from this.
    """
    _position:   Position
    _health:     int
    _max_health: int
    _experience: int
    _level:      int
    _inventory:  tuple          # Immutable copy
    _quests:     tuple          # Immutable copy
    _gold:       int

    # ── Metadata (readable by caretaker) ──
    label:       str
    created_at:  str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    auto_save:   bool = False

    def __str__(self) -> str:
        return (f"[{self.created_at}] {'🔄' if self.auto_save else '💾'} "
                f"\"{self.label}\" | "
                f"HP:{self._health}/{self._max_health} "
                f"XP:{self._experience} "
                f"Lvl:{self._level} "
                f"Gold:{self._gold} "
                f"@ {self._position}")

    def to_json(self) -> str:
        """Serialize to JSON for persistence (e.g., save file)."""
        data = {
            "position":   asdict(self._position),
            "health":     self._health,
            "max_health": self._max_health,
            "experience": self._experience,
            "level":      self._level,
            "inventory":  [asdict(i) for i in self._inventory],
            "quests":     list(self._quests),
            "gold":       self._gold,
            "label":      self.label,
            "created_at": self.created_at,
            "auto_save":  self.auto_save,
        }
        return json.dumps(data, indent=2)


# ─── Originator ───────────────────────────────────────────────────
class CharacterState:
    """The originator — all state is private; snapshot API is the only external interface."""

    def __init__(self, name: str) -> None:
        self.name        = name
        self._position   = Position(0.0, 0.0, "Starting Village")
        self._health     = 100
        self._max_health = 100
        self._experience = 0
        self._level      = 1
        self._inventory: List[InventoryItem] = [InventoryItem("Sword", 1, "common")]
        self._quests:    List[str]           = ["Find the ancient temple"]
        self._gold       = 50

    # ── Actions ───────────────────────────────────────────────────
    def move_to(self, x: float, y: float, zone: str) -> None:
        self._position = Position(x, y, zone)
        print(f"  [{self.name}] Moved to {self._position}")

    def take_damage(self, amount: int) -> bool:
        self._health = max(0, self._health - amount)
        print(f"  [{self.name}] Took {amount} damage → HP: {self._health}/{self._max_health}")
        return self._health > 0  # Returns False if dead

    def heal(self, amount: int) -> None:
        self._health = min(self._max_health, self._health + amount)
        print(f"  [{self.name}] Healed {amount} → HP: {self._health}/{self._max_health}")

    def gain_experience(self, xp: int) -> None:
        self._experience += xp
        level_threshold   = self._level * 100
        if self._experience >= level_threshold:
            self._level      += 1
            self._max_health += 20
            self._health      = self._max_health
            print(f"  [{self.name}] ⭐ LEVEL UP! Now level {self._level} | HP fully restored")
        else:
            print(f"  [{self.name}] Gained {xp} XP → {self._experience}/{level_threshold}")

    def pick_up_item(self, item: InventoryItem) -> None:
        for existing in self._inventory:
            if existing.name == item.name:
                existing.quantity += item.quantity
                print(f"  [{self.name}] Picked up {item.quantity}× {item.name}")
                return
        self._inventory.append(item)
        print(f"  [{self.name}] Picked up {item.quantity}× {item.name} ({item.rarity})")

    def complete_quest(self, quest: str) -> None:
        if quest in self._quests:
            self._quests.remove(quest)
            print(f"  [{self.name}] ✅ Completed quest: \"{quest}\"")

    def add_quest(self, quest: str) -> None:
        self._quests.append(quest)
        print(f"  [{self.name}] 📜 New quest: \"{quest}\"")

    def earn_gold(self, amount: int) -> None:
        self._gold += amount
        print(f"  [{self.name}] 💰 Earned {amount} gold → {self._gold} total")

    # ── Snapshot API ──────────────────────────────────────────────
    def save(self, label: str, auto_save: bool = False) -> GameMemento:
        return GameMemento(
            _position=   copy.deepcopy(self._position),
            _health=     self._health,
            _max_health= self._max_health,
            _experience= self._experience,
            _level=      self._level,
            _inventory=  tuple(copy.deepcopy(i) for i in self._inventory),
            _quests=     tuple(self._quests),
            _gold=       self._gold,
            label=       label,
            auto_save=   auto_save,
        )

    def restore(self, memento: GameMemento) -> None:
        self._position   = copy.deepcopy(memento._position)
        self._health     = memento._health
        self._max_health = memento._max_health
        self._experience = memento._experience
        self._level      = memento._level
        self._inventory  = [copy.deepcopy(i) for i in memento._inventory]
        self._quests     = list(memento._quests)
        self._gold       = memento._gold
        print(f"  [{self.name}] ♻️  State restored from: \"{memento.label}\"")

    def status(self) -> None:
        print(f"\n  ── {self.name} Status ──────────────────────────")
        print(f"    Position  : {self._position}")
        print(f"    Health    : {self._health}/{self._max_health}")
        print(f"    Level     : {self._level} (XP: {self._experience})")
        print(f"    Gold      : {self._gold}")
        print(f"    Inventory : {[(i.name, i.quantity) for i in self._inventory]}")
        print(f"    Quests    : {self._quests}")


# ─── Caretaker: Save Manager ──────────────────────────────────────
class SaveManager:
    """
    Production caretaker with:
    - Named checkpoints (persist until manually deleted)
    - Bounded auto-save ring buffer (last N states)
    - Undo/redo stack
    - Full save browser
    """
    MAX_AUTO_SAVES = 5

    def __init__(self, character: CharacterState) -> None:
        self._character:    CharacterState             = character
        self._checkpoints:  Dict[str, GameMemento]     = {}
        self._auto_saves:   List[GameMemento]          = []  # Ring buffer
        self._undo_stack:   List[GameMemento]          = []
        self._redo_stack:   List[GameMemento]          = []

    # ── Save operations ───────────────────────────────────────────
    def checkpoint(self, name: str) -> None:
        """Save a named checkpoint that persists until explicitly deleted."""
        m = self._character.save(name)
        self._checkpoints[name] = m
        print(f"\n  [SaveManager] 📌 Checkpoint saved: \"{name}\"")
        print(f"    {m}")

    def auto_save(self) -> None:
        """Save to the auto-save ring buffer (oldest dropped when full)."""
        label = f"Auto #{len(self._auto_saves) + 1}"
        m = self._character.save(label, auto_save=True)
        self._auto_saves.append(m)
        if len(self._auto_saves) > self.MAX_AUTO_SAVES:
            dropped = self._auto_saves.pop(0)
            print(f"  [SaveManager] 🔄 Auto-save (dropped: \"{dropped.label}\"): {m}")
        else:
            print(f"  [SaveManager] 🔄 Auto-save: {m}")

    def quick_save(self) -> None:
        """Save current state to the undo stack (for quick undo)."""
        m = self._character.save("Quick Save")
        self._undo_stack.append(m)
        self._redo_stack.clear()
        print(f"  [SaveManager] ⚡ Quick save: {m}")

    # ── Restore operations ────────────────────────────────────────
    def restore_checkpoint(self, name: str) -> bool:
        if name not in self._checkpoints:
            print(f"  [SaveManager] ❌ No checkpoint named \"{name}\"")
            return False
        # Push current state to undo stack before restoring
        self._undo_stack.append(self._character.save("(before restore)"))
        self._redo_stack.clear()
        self._character.restore(self._checkpoints[name])
        return True

    def restore_auto_save(self, index: int = -1) -> bool:
        """Restore most recent auto-save (or by index)."""
        if not self._auto_saves:
            print("  [SaveManager] ❌ No auto-saves available")
            return False
        target = self._auto_saves[index]
        self._undo_stack.append(self._character.save("(before auto-restore)"))
        self._redo_stack.clear()
        self._character.restore(target)
        return True

    def undo(self) -> bool:
        if not self._undo_stack:
            print("  [SaveManager] ↩  Nothing to undo")
            return False
        self._redo_stack.append(self._character.save("(before undo)"))
        m = self._undo_stack.pop()
        self._character.restore(m)
        return True

    def redo(self) -> bool:
        if not self._redo_stack:
            print("  [SaveManager] ↪  Nothing to redo")
            return False
        self._undo_stack.append(self._character.save("(before redo)"))
        m = self._redo_stack.pop()
        self._character.restore(m)
        return True

    def delete_checkpoint(self, name: str) -> None:
        if self._checkpoints.pop(name, None):
            print(f"  [SaveManager] 🗑️  Deleted checkpoint: \"{name}\"")
        else:
            print(f"  [SaveManager] ❌ No checkpoint named \"{name}\"")

    # ── Browser ───────────────────────────────────────────────────
    def browse_saves(self) -> None:
        print("\n  [SaveManager] ── SAVE BROWSER ────────────────────────")
        print(f"  Checkpoints ({len(self._checkpoints)}):")
        for name, m in self._checkpoints.items():
            print(f"    📌 \"{name}\" → {m}")
        print(f"\n  Auto-saves ({len(self._auto_saves)}/{self.MAX_AUTO_SAVES}):")
        for i, m in enumerate(self._auto_saves):
            print(f"    {i}: {m}")
        print(f"\n  Undo stack depth : {len(self._undo_stack)}")
        print(f"  Redo stack depth : {len(self._redo_stack)}")


# ─── Client ────────────────────────────────────────────────────────
if __name__ == "__main__":
    hero    = CharacterState("Hero")
    manager = SaveManager(hero)

    print("=" * 60)
    print("SCENARIO 1: Normal progression with checkpoints")
    print("=" * 60)

    manager.checkpoint("game_start")
    hero.status()

    # Progress through the game
    hero.move_to(50.0, 30.0, "Dark Forest")
    hero.pick_up_item(InventoryItem("Health Potion", 3, "uncommon"))
    hero.earn_gold(25)
    manager.auto_save()

    hero.add_quest("Defeat the Forest Troll")
    hero.move_to(75.0, 60.0, "Troll Bridge")
    manager.checkpoint("before_troll_fight")

    print("\n" + "=" * 60)
    print("SCENARIO 2: Boss fight — take damage, die, restore")
    print("=" * 60)

    hero.take_damage(40)
    manager.auto_save()
    hero.take_damage(50)
    hero.take_damage(30)
    hero.status()

    # Oops — character died in a hard fight. Restore to before the boss.
    print("\n  [Player] Character in bad shape — restoring checkpoint...")
    manager.restore_checkpoint("before_troll_fight")
    hero.status()

    print("\n" + "=" * 60)
    print("SCENARIO 3: Successful run after restore")
    print("=" * 60)

    hero.take_damage(30)
    hero.heal(20)
    hero.gain_experience(150)   # Level up!
    hero.complete_quest("Defeat the Forest Troll")
    hero.earn_gold(100)
    manager.auto_save()
    manager.checkpoint("after_troll_victory")
    hero.status()

    print("\n" + "=" * 60)
    print("SCENARIO 4: Undo last action")
    print("=" * 60)
    manager.quick_save()
    hero.move_to(200.0, 100.0, "Dragon Lair")
    hero.take_damage(80)
    hero.status()

    print("\n  [Player] That was a mistake! Undoing...")
    manager.undo()
    hero.status()

    print("\n" + "=" * 60)
    print("SCENARIO 5: Auto-save ring buffer fills up")
    print("=" * 60)
    for i in range(6):
        hero.earn_gold(10)
        manager.auto_save()   # 6th auto-save drops the 1st

    manager.browse_saves()

    # Save a checkpoint to JSON (for disk persistence)
    print("\n[SaveManager] Serializing 'after_troll_victory' to JSON:")
    json_str = manager._checkpoints["after_troll_victory"].to_json()
    print(json_str[:300] + "...")

Real-World Use Cases

1. Text Editors and IDEs

Every text editor with Ctrl+Z uses the Memento Pattern. VS Code, IntelliJ IDEA, Vim, and Emacs all maintain a history of editor states. Some implement it as a Command-based undo (each edit is a reversible command); others use full snapshots. Vim's persistent undo tree stores the entire edit history across sessions.

2. Drawing and Design Tools

Figma, Photoshop, and Sketch maintain a history of canvas states. Each brushstroke, shape addition, or transformation creates a Memento. The user can step backward through history or jump to a named version ("Before color correction"). Photoshop's History panel IS a Memento caretaker.

3. Database Transaction Savepoints

SQL SAVEPOINT creates a named memento of the database's transaction state. ROLLBACK TO SAVEPOINT name restores to that point without rolling back the entire transaction:

BEGIN;
SAVEPOINT before_risky_update;    -- Create Memento
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
-- Something went wrong:
ROLLBACK TO SAVEPOINT before_risky_update;  -- Restore Memento
COMMIT;

4. Game Save Systems

Every video game with a save system uses the Memento Pattern. Quick save, named save slots, and auto-save checkpoints are all Mementos managed by a save manager (caretaker). Cloud save sync serializes the Memento to JSON and stores it remotely.

5. Browser Back/Forward Navigation

The browser's history stack is a series of Mementos of the user's navigation state (URL, scroll position, form state). Back and Forward buttons are undo and redo over this history.

6. Configuration Snapshots

Infrastructure tools (Terraform, Ansible) and application servers snapshot configuration state before applying changes. If the new configuration fails validation or causes errors, the previous snapshot is restored. Kubernetes rolling updates use the same principle.

7. Workflow Step Rollback

Long-running business processes (order fulfillment, loan applications) save a Memento before each workflow step. If a step fails or the user cancels midway, the workflow can roll back to the last successful checkpoint without redoing every preceding step.


Language-Specific Mistakes and Anti-Patterns

❌ Mistake #1: Shallow Copy in the Snapshot Creates Shared Mutable State

# BAD: Shallow copy means memento and originator share the same list objects
@dataclass
class BadMemento:
    _inventory: list  # Shared reference!

class CharacterState:
    def save(self) -> BadMemento:
        return BadMemento(_inventory=self._inventory)  # Shallow! Same list object

state   = CharacterState()
memento = state.save()

state._inventory.append(InventoryItem("Sword", 1))
# memento._inventory also now has the Sword! — Memento has been corrupted!

# GOOD: Always deep-copy mutable nested objects in the snapshot
import copy

def save(self) -> GameMemento:
    return GameMemento(
        _inventory=tuple(copy.deepcopy(i) for i in self._inventory),  # Deep copy → immutable tuple
        _position=copy.deepcopy(self._position),
    )

❌ Mistake #2: Exposing the Memento's Internal State via Public Properties

# BAD: Memento exposes all state — caretaker (or anyone) can read and modify it
class LeakyMemento:
    def __init__(self, content, cursor):
        self.content = content  # Public! Breaks encapsulation.
        self.cursor  = cursor

# Now caretaker can do:
m = editor.save()
print(m.content)   # Caretaker reading private editor content — violation!
m.content = "hacked"  # Caretaker modifying the snapshot — corruption!

# GOOD: Keep state fields private; expose only metadata
@dataclass(frozen=True)
class EditorMemento:
    _content: str      # Private by convention — only EditorState reads this
    _cursor:  int
    label:    str      # Metadata — safe for caretaker to read
    timestamp:str

❌ Mistake #3: Unbounded History Without a Memory Limit

# BAD: Every single keystroke saves a full snapshot — memory grows without bound
class Editor:
    def on_key_press(self, key):
        self.history.save(f"pressed {key}")   # A snapshot per character typed!
        self.type(key)

# After typing a 10,000-word document: 50,000+ full content snapshots in memory!

# GOOD: Coalesce rapid changes; save at meaningful points; bound the history
class EditorHistory:
    MAX_HISTORY = 100

    def save(self, description):
        self._stack.append(self._editor.save(description))
        if len(self._stack) > self.MAX_HISTORY:
            self._stack.pop(0)   # Drop oldest — fixed memory footprint

# Or: save on meaningful events (sentence complete, paste, format change)
# rather than on every keystroke

Frequently Asked Questions

Q1: What is the difference between Memento and Command for implementing undo?

Both implement undo, but via opposite strategies:

Command-based undo stores the inverse operation. When you type "A", the command stores "delete the A at this position". Undo runs the inverse. This is efficient for simple operations — no full state copy needed. But implementing the inverse can be complex or even impossible for some operations.

Memento-based undo stores a full state snapshot before the operation. Undo restores the snapshot. This is simpler to implement for complex objects with deeply interrelated state — no need to compute the inverse. The trade-off is memory: every snapshot is a full copy of the object's state.

In practice, many systems use both: Command for simple edits (typing, deleting), Memento for complex operations (paste, format, bulk replace) where computing the inverse would be complicated.

Q2: How do I prevent the Memento from breaking encapsulation?

The key is making the Memento's state accessible ONLY to the Originator, not to any other class. Strategies by language:

  • Java: Static nested class — the outer class can access the inner class's private fields; external classes cannot
  • C#: internal access modifier — fields are accessible only within the same assembly (where the originator lives)
  • Python: Name-mangling convention (_prefixed), frozen=True dataclass, and documentation
  • TypeScript/JavaScript: Private class fields (#field) — truly private, not accessible to any external code
  • Kotlin: internal constructor and properties — accessible within the module
  • Swift: fileprivate or structuring files so caretaker and memento are in different files
  • Go: Unexported fields — accessible within the same package, which typically contains both the originator and memento

Q3: Should the Caretaker know the content of the Memento?

No. The caretaker is a black-box holder. It stores, retrieves, and manages the lifecycle of mementos, but it should never read or interpret the state they contain. The only information the caretaker needs is metadata — timestamp, label, description, index — for organizing the history. If the caretaker needs to read content, the design is wrong: either the content should be moved to metadata, or the caretaker should be replaced by a richer component.

Q4: How do I implement bounded undo history without losing the oldest saves?

Use a deque (double-ended queue) with a maximum size. When the deque is full, remove the oldest entry before adding the new one:

from collections import deque

class EditorHistory:
    def __init__(self, max_size: int = 50):
        self._history = deque(maxlen=max_size)  # Python's deque auto-drops oldest

    def save(self, description: str) -> None:
        self._history.append(self._editor.save(description))
        # deque automatically drops leftmost (oldest) when maxlen is exceeded

Q5: How do I persist Mementos to disk (save files)?

Add serialization to the Memento. The simplest approach is JSON:

@dataclass(frozen=True)
class GameMemento:
    # ... fields ...

    def to_json(self) -> str:
        return json.dumps(asdict(self))

    @classmethod
    def from_json(cls, raw: str) -> "GameMemento":
        data = json.loads(raw)
        return cls(**data)

# Save:
with open("save.json", "w") as f:
    f.write(manager.checkpoint_memento.to_json())

# Load:
with open("save.json") as f:
    memento = GameMemento.from_json(f.read())
character.restore(memento)

For security, validate the loaded data before restoring — never blindly restore from untrusted external input.

Q6: How do I implement "incremental" Mementos to save memory?

Instead of storing the full state every time, store only the delta from the previous state. On restore, apply deltas in sequence from the nearest full snapshot:

@dataclass
class DeltaMemento:
    changes: dict  # Only the fields that changed

class SmartHistory:
    FULL_SNAPSHOT_INTERVAL = 10

    def save(self) -> None:
        if len(self._history) % self.FULL_SNAPSHOT_INTERVAL == 0:
            self._history.append(FullMemento(self._editor.save()))  # Full snapshot every N saves
        else:
            prev  = self._history[-1].state
            delta = {k: v for k, v in self._editor.state.items() if v != prev.get(k)}
            self._history.append(DeltaMemento(delta))

Q7: Is it safe to store Mementos in a database or transmit them over a network?

Yes, but with important security considerations:

  1. Sanitize on restore: Never blindly apply a memento from an external source — validate all values before restoring
  2. Sign or encrypt: If mementos are stored client-side (browser local storage, save files), sign them with an HMAC to detect tampering
  3. Version your schema: When the originator's structure changes, older mementos may not restore correctly — include a schema version in the memento
  4. Avoid executable content: Never store functions, lambdas, or code in mementos — only data

Q8: What is the difference between Memento and a simple getter/setter for all fields?

Getters and setters expose individual fields — any code can read and write each one independently. The object's state can be reconstructed only if the caller knows exactly which fields exist and in what order to set them. Adding a new field requires every caller to be updated.

Memento encapsulates the complete state as an atomic snapshot. The caller gets one opaque object; passing it back to the originator restores the entire state atomically. Internal structure changes don't affect callers — they just get a new kind of opaque token.

Q9: How do I implement redo correctly after a new action is taken?

When a new action is taken (creating a new snapshot), the redo stack must be cleared. Any redo history refers to a "future" that no longer exists — the user has taken a different branch. Applying stale redo entries would result in inconsistent or corrupted state.

def save(self, description: str) -> None:
    self._undo_stack.append(self._editor.save(description))
    self._redo_stack.clear()   # CRITICAL: new action always clears redo

Some advanced editors (Vim's undo tree) preserve all branches as a tree rather than clearing — the user can navigate to the "other" branch through a redo tree UI. This is significantly more complex to implement.

Q10: When should I NOT use the Memento Pattern?

Avoid the Memento Pattern when:

  • State is huge: Snapshotting gigabytes of image data or a large game world is prohibitively expensive. Use incremental snapshots or command-based undo instead.
  • State is not cleanly separable: If the originator's state includes live external resources (database connections, file handles, network sockets), snapshots can't capture them — use command-based rollback instead.
  • Undo is not needed: Don't add snapshot infrastructure for objects that never need to be restored.
  • Command-based undo is simpler: If operations are simple and easily invertible (add/remove an item), a Command with an inverse is more memory-efficient than a full snapshot.
  • The object's lifecycle is very short: For temporary objects that are created and discarded quickly, snapshot overhead is never justified.

Key Takeaways

🎯 Core Concept: The Memento Pattern captures an object's complete internal state in an opaque snapshot (the Memento) without exposing the object's private internals. The Originator creates and restores from Mementos. The Caretaker stores them as black boxes — it manages the lifecycle but never reads the content. The Memento is the only pattern specifically designed to enable state restoration while preserving encapsulation.

🔑 Key Benefits:

  • Encapsulated snapshots: The originator's private state is captured without leaking it to the caretaker or any external class
  • Undo/redo built-in: Maintain a stack of mementos for multi-level undo and redo with no additional infrastructure
  • Atomic restore: Restoring from a memento is always complete — partial state is never a risk
  • Checkpoint branching: Name key snapshots and restore to any of them independently of the linear undo/redo history
  • Rollback without a database: Lightweight transactional behavior — save before risky operations, restore if they fail

🌐 Language-Specific Highlights:

  • Python: Use @dataclass(frozen=True) for immutable mementos; copy.deepcopy() for any mutable nested objects; deque(maxlen=N) for bounded history; prefix private fields with _ by convention
  • TypeScript: Use #private fields (truly private at runtime) rather than private keyword (only TypeScript compile-time); always clear the redo stack on every new save(); return typed state from getState() rather than exposing fields directly
  • Java: Use a static final nested class inside the Originator — Java's access rules let the outer class access the inner class's private fields while preventing external access; use List.copyOf() for defensive immutable copies of collections
  • JavaScript: Use #private class fields for true runtime privacy; structuredClone() for deep copies of complex state; never store live DOM references in mementos — only serializable data
  • C#: Use internal access modifier for Memento fields so only the originator's assembly can read them; sealed class prevents subclassing the memento; Stack<T> for the undo/redo stacks; consider record types only if all state is public metadata
  • PHP: PHP has no nested classes — use naming conventions (_getState()) and readonly properties; document clearly that only the Originator should call internal methods; never use serialize() as the memento
  • Go: Unexported struct fields provide package-level privacy — if cross-package protection is needed, use an opaque interface; always copy() slices before storing in the memento; use time.Now().Format() for timestamps
  • Rust: #[derive(Clone, Debug)] on the Memento struct; String::clone() and explicit Vec::clone() for deep copies; the caretaker can own both the editor and the history or borrow via lifetime annotations
  • Dart: Use ._() private named constructor (library-private) so only code in the same library can construct mementos; List.from() or spread operator for shallow copies; final fields for immutability
  • Swift: fileprivate init on the Memento so only code in the same file can create it; separate the caretaker into its own file so fileprivate actually restricts access; struct Mementos for value semantics (automatic copy-on-assign)
  • Kotlin: internal constructor for module-level privacy on Memento; data class with internal properties for the bridge between originator and memento; ArrayDeque for the undo/redo stacks with a size guard

📋 When to Use Memento:

  • You need multi-level undo/redo in an editor, design tool, or interactive application
  • You want to snapshot an object's state before a risky operation and restore it on failure (transactional rollback)
  • You need named checkpoints ("before boss fight", "v1.0 config") that persist independently of the undo stack
  • The originator has complex, interrelated private state that can't be easily restored by replaying inverse operations
  • You're building a game save system, browser history, or configuration snapshot mechanism
  • You want to preserve encapsulation — the originator's internals should not be exposed to any other class

⚠️ When NOT to Use Memento:

  • State includes live external resources (DB connections, file handles) that can't be meaningfully snapshotted
  • The object's state is huge (large images, game worlds) — snapshots are too expensive; use incremental/delta approaches instead
  • Operations are simple and easily invertible — Command-based undo is more memory-efficient
  • No undo/restore is ever needed — don't add snapshot infrastructure without a use case
  • The object's lifecycle is very short — snapshot overhead is never worth it

🛠️ Best Practices Across Languages:

  1. Always deep-copy mutable nested state: Shallow copies leave the memento and originator sharing mutable objects — the memento becomes corrupted when the originator changes
  2. Keep memento state private: Use nested classes (Java), internal/private modifiers (C#/Kotlin/Swift), #private fields (JS/TS), or module-level privacy (Go/Dart) to ensure only the originator can read the snapshot
  3. Clear the redo stack on every new action: Stale redo entries cause inconsistent state when applied after a branching action
  4. Bound your history: Use deque(maxlen=N), ring buffers, or explicit size guards to prevent unbounded memory growth
  5. Separate the undo stack from named checkpoints: The undo stack is linear and ephemeral; named checkpoints are named and persistent — manage them independently
  6. Include metadata in mementos: Timestamp, description, and label are for the caretaker's benefit — they don't violate encapsulation because they're not internal state
  7. Save before, not after: Create the memento BEFORE making the change so the memento represents the "previous" state that can be restored
  8. Save before restoring: When restoring from a checkpoint, save the current state first so the user can undo the restore itself

💡 Common Pitfalls:

  • Shallow copy corruption: Memento and originator share the same nested mutable objects — originator changes corrupt the memento
  • Exposed memento content: Public getters or public fields on the memento let the caretaker (or anyone) read private originator state
  • Unbounded history growth: Every keystroke saves a full snapshot → thousands of snapshots → out-of-memory error
  • Stale redo after new action: New action taken without clearing redo stack → pressing redo applies an inconsistent old state
  • Live references in mementos: Storing object references (not copies) in the memento — the memento changes when the originator does
  • Not saving before restore: Restoring to a checkpoint without saving current state first → user can't undo the restore

🎁 Advanced Techniques:

  • Memento + Command hybrid: Use Command for simple invertible operations (typing); use Memento for complex operations (paste, bulk format) — hybrid undo systems are common in professional editors
  • Persistent undo tree: Instead of a linear undo stack, store an edit tree where each node is a memento. Users can navigate to any branch — Vim's undo tree implements this
  • Lazy snapshots: Instead of capturing state immediately on save, record a "dirty" flag. Only compute the full snapshot if the user actually asks for an undo — avoids snapshot cost for saves that are never restored
  • Differential mementos: Store only changed fields (the diff from the previous snapshot). Reconstruct full state by walking the chain from the nearest full snapshot. Reduces memory dramatically for large objects with small per-operation changes
  • Compressed mementos: Serialize the state to bytes and compress. For text editors, compression ratios of 10:1 or better are common — a 10KB document snapshot compresses to ~1KB
  • Versioned mementos: Include a schema version in every memento. On restore, check the version and apply migration logic if the schema has changed — essential for long-lived save files

The Memento Pattern is the cleanest solution whenever you need to restore an object to a previous state while keeping its internals private. It's not just about undo — it's about the principle that an object's private state is a covenant between the object and its design, and that covenant should never be broken even when taking snapshots.


Want to dive deeper into design patterns? Check out our comprehensive guides on: