Skip to main content

Command Palette

Search for a command to run...

Memento Pattern

Updated
•View as Markdown
Memento Pattern

The Memento Pattern is a behavioral design pattern that lets you capture an object's internal state and restore it later—without violating encapsulation. It's the pattern behind every "Ctrl+Z" you've ever pressed.

Think of a video game save point. You play ahead, things go wrong, you reload your save. You don't know exactly how the game stored your state—you just know you can restore it. That's the Memento Pattern.

The Problem

Imagine you're building a text editor with undo functionality. Without the Memento Pattern, implementing undo is messy:

// Without Memento Pattern - broken encapsulation
class TextEditor {
    private String content;
    private int cursorPosition;
    private String fontStyle;

    // To support undo, we expose internal state publicly
    public String getContent() { return content; }
    public int getCursorPosition() { return cursorPosition; }
    public String getFontStyle() { return fontStyle; }

    // Now the undo manager must know ALL internal details!
    public void restore(String content, int cursorPosition, String fontStyle) {
        this.content = content;
        this.cursorPosition = cursorPosition;
        this.fontStyle = fontStyle;
    }
}

class UndoManager {
    // Stores raw fields - tightly coupled to TextEditor internals!
    private List<String> contentHistory = new ArrayList<>();
    private List<Integer> cursorHistory = new ArrayList<>();
    private List<String> fontHistory = new ArrayList<>();
}

Problems:

  • Broken encapsulation: Internal state is publicly exposed just to support undo

  • Tight coupling: Undo manager knows all internal details of the editor

  • Fragile design: Adding a new field to the editor means updating the undo manager too

  • No clean API: Restoring state is a scattered, multi-field operation

The Solution: Memento Pattern

The Memento Pattern introduces a Memento object that captures state privately. Only the originator can read its contents.

The pattern separates:

  • Who owns the state (Originator)

  • Who stores the state (Caretaker)

  • What the state looks like (Memento — opaque to Caretaker)

How It Solves Each Problem

Problem How Memento Pattern Solves It
Broken encapsulation Memento is created and read only by the Originator—Caretaker stores but never inspects it.
Tight coupling Caretaker holds Mementos without knowing their contents. Adding fields doesn't break it.
Fragile design Internal state changes are isolated inside Originator and Memento only.
No clean API save() and restore() are simple, expressive operations.

Key Components

  1. Originator: The object whose state needs to be saved and restored

  2. Memento: A snapshot of the Originator's state at a point in time

  3. Caretaker: Manages the history of Mementos; never reads their contents

How It Solves the Problem

Real-World Implementation

Example 1: Text Editor with Undo/Redo

import java.util.Stack;

// Memento
class EditorMemento {
    private final String content;
    private final int cursorPosition;
    private final String fontStyle;

    public EditorMemento(String content, int cursorPosition, String fontStyle) {
        this.content = content;
        this.cursorPosition = cursorPosition;
        this.fontStyle = fontStyle;
    }

    // Only the Originator should call these
    String getContent() { return content; }
    int getCursorPosition() { return cursorPosition; }
    String getFontStyle() { return fontStyle; }
}

// Originator
class TextEditor {
    private String content = "";
    private int cursorPosition = 0;
    private String fontStyle = "Normal";

    public void type(String text) {
        content += text;
        cursorPosition = content.length();
        System.out.println("Typed: \"" + text + "\" -> Content: \"" + content + "\"");
    }

    public void setFontStyle(String style) {
        this.fontStyle = style;
        System.out.println("Font changed to: " + fontStyle);
    }

    public EditorMemento save() {
        System.out.println("State saved.");
        return new EditorMemento(content, cursorPosition, fontStyle);
    }

    public void restore(EditorMemento memento) {
        this.content = memento.getContent();
        this.cursorPosition = memento.getCursorPosition();
        this.fontStyle = memento.getFontStyle();
        System.out.println("Restored -> Content: \"" + content + "\", Font: " + fontStyle);
    }

    public String getContent() { return content; }
}

// Caretaker
class UndoManager {
    private Stack<EditorMemento> undoStack = new Stack<>();
    private Stack<EditorMemento> redoStack = new Stack<>();
    private TextEditor editor;

    public UndoManager(TextEditor editor) {
        this.editor = editor;
    }

    public void backup() {
        undoStack.push(editor.save());
        redoStack.clear();
    }

    public void undo() {
        if (undoStack.isEmpty()) {
            System.out.println("Nothing to undo!");
            return;
        }
        redoStack.push(editor.save());
        System.out.println("--- Undo ---");
        editor.restore(undoStack.pop());
    }

    public void redo() {
        if (redoStack.isEmpty()) {
            System.out.println("Nothing to redo!");
            return;
        }
        undoStack.push(editor.save());
        System.out.println("--- Redo ---");
        editor.restore(redoStack.pop());
    }
}

// Client
public class TextEditorDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        UndoManager history = new UndoManager(editor);

        history.backup();
        editor.type("Hello");

        history.backup();
        editor.type(" World");

        history.backup();
        editor.setFontStyle("Bold");

        System.out.println("\nContent: " + editor.getContent());

        history.undo();
        history.undo();
        history.redo();
    }
}

Example 2: Game Save System

import java.util.ArrayList;
import java.util.List;

// Memento
class GameMemento {
    private final int level;
    private final int health;
    private final int score;
    private final String checkpoint;

    public GameMemento(int level, int health, int score, String checkpoint) {
        this.level = level;
        this.health = health;
        this.score = score;
        this.checkpoint = checkpoint;
    }

    int getLevel() { return level; }
    int getHealth() { return health; }
    int getScore() { return score; }
    String getCheckpoint() { return checkpoint; }
}

// Originator
class GameCharacter {
    private int level;
    private int health;
    private int score;
    private String checkpoint;

    public GameCharacter(int level, int health, int score, String checkpoint) {
        this.level = level;
        this.health = health;
        this.score = score;
        this.checkpoint = checkpoint;
    }

    public void takeDamage(int damage) {
        health = Math.max(0, health - damage);
        System.out.println("Took " + damage + " damage. Health: " + health);
    }

    public void advanceLevel() {
        level++;
        score += 1000;
        checkpoint = "Level " + level + " Start";
        System.out.println("Advanced to level " + level + ". Score: " + score);
    }

    public GameMemento save() {
        System.out.println("Game saved at: " + checkpoint);
        return new GameMemento(level, health, score, checkpoint);
    }

    public void restore(GameMemento memento) {
        this.level = memento.getLevel();
        this.health = memento.getHealth();
        this.score = memento.getScore();
        this.checkpoint = memento.getCheckpoint();
        System.out.println("Loaded save: Level " + level
            + ", Health: " + health + ", Score: " + score);
    }

    @Override
    public String toString() {
        return "GameCharacter{level=" + level + ", health="
            + health + ", score=" + score + ", checkpoint='" + checkpoint + "'}";
    }
}

// Caretaker - Save Slots
class SaveManager {
    private List<GameMemento> saveSlots = new ArrayList<>();

    public void save(GameCharacter character) {
        saveSlots.add(character.save());
    }

    public GameMemento getSave(int slot) {
        if (slot >= 0 && slot < saveSlots.size()) {
            return saveSlots.get(slot);
        }
        throw new IllegalArgumentException("No save in slot " + slot);
    }

    public int saveCount() {
        return saveSlots.size();
    }
}

// Client
public class GameDemo {
    public static void main(String[] args) {
        GameCharacter hero = new GameCharacter(1, 100, 0, "Start");
        SaveManager saves = new SaveManager();

        saves.save(hero);
        hero.advanceLevel();

        saves.save(hero);
        hero.takeDamage(80);
        hero.takeDamage(40);

        System.out.println("\nCharacter died! Loading last save...");
        hero.restore(saves.getSave(1));

        System.out.println("\n" + hero);
    }
}

Example 3: Configuration Manager

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

// Memento
class ConfigMemento {
    private final Map<String, String> settings;

    public ConfigMemento(Map<String, String> settings) {
        this.settings = new HashMap<>(settings);
    }

    Map<String, String> getSettings() {
        return new HashMap<>(settings);
    }
}

// Originator
class AppConfig {
    private Map<String, String> settings = new HashMap<>();

    public void set(String key, String value) {
        settings.put(key, value);
        System.out.println("Config set: " + key + " = " + value);
    }

    public String get(String key) {
        return settings.getOrDefault(key, "not set");
    }

    public ConfigMemento save() {
        System.out.println("Config snapshot saved.");
        return new ConfigMemento(settings);
    }

    public void restore(ConfigMemento memento) {
        settings = memento.getSettings();
        System.out.println("Config restored: " + settings);
    }
}

// Caretaker
class ConfigHistory {
    private Stack<ConfigMemento> history = new Stack<>();

    public void push(ConfigMemento memento) {
        history.push(memento);
    }

    public ConfigMemento pop() {
        if (history.isEmpty()) {
            throw new RuntimeException("No config history available!");
        }
        return history.pop();
    }

    public boolean hasHistory() {
        return !history.isEmpty();
    }
}

// Client
public class ConfigDemo {
    public static void main(String[] args) {
        AppConfig config = new AppConfig();
        ConfigHistory history = new ConfigHistory();

        config.set("theme", "light");
        config.set("language", "en");
        history.push(config.save());

        config.set("theme", "dark");
        config.set("fontSize", "14px");
        history.push(config.save());

        config.set("theme", "experimental-neon");

        System.out.println("\nTheme before rollback: " + config.get("theme"));

        config.restore(history.pop());
        System.out.println("Theme after rollback: " + config.get("theme"));
    }
}

Workflow Diagram

Real-World Use Cases

  • Text Editors: Undo/Redo (VS Code, Word, IntelliJ)

  • Game Development: Save points and game state recovery

  • Database Transactions: Rollback to a consistent state

  • Configuration Tools: Revert settings to previous snapshots

  • Version Control: Conceptually similar to commits

  • UI Builders: Undo drag-and-drop or property changes

  • Workflow Engines: Revert completed workflow steps

When to Use the Memento Pattern

✅ Use When

  1. Undo/Redo Needed: You want to restore previous states

  2. Encapsulation is Important: You can't expose internal state publicly

  3. Snapshot History: You need to keep multiple versions of an object

  4. Rollback Support: Transactions or operations that may fail

❌ Avoid When

  1. Memory is Tight: Storing many large snapshots is expensive

  2. State is Huge: Saving full state is impractical

  3. No History Needed: Simple state storage suffices

Benefits

  1. Encapsulation Preserved: Internal state not exposed to Caretaker

  2. Clean Undo/Redo: Simple, expressive save/restore API

  3. Snapshots: Multiple saved states are easy to manage

  4. Originator Independence: Caretaker changes don't affect Originator

Drawbacks

  1. Memory Usage: Many Mementos can consume significant RAM

  2. Serialization Cost: Deep copies of large objects are slow

  3. Caretaker Overhead: Managing Memento lifecycle adds complexity

Memento vs Command Pattern

Aspect Memento Command
Undo Mechanism Restore full snapshot Execute reverse operation
Memory Stores full state Stores action + data
Encapsulation State hidden in Memento Logic hidden in Command
Best For Arbitrary state rollback Discrete reversible actions

Best Practices

  1. Immutable Mementos: Never allow state to change after saving

  2. Limit History Size: Cap undo stack to avoid memory bloat

  3. Shallow vs Deep Copy: Use deep copy for mutable nested objects

  4. Lazy Snapshots: Only save when state actually changes

  5. Compression: For large states, consider delta compression

  6. Interface Segregation: Use a narrow interface for Caretaker access

Conclusion

The Memento Pattern is the backbone of undo/redo everywhere—from your IDE to your favorite game's save system. It captures state cleanly, restores it elegantly, and keeps your internal details where they belong: inside the originator.

Remember: Save early, restore often! 💾


🎯 Key Takeaway

The Memento Pattern is about saving moments without breaking boundaries. When you need undo/redo with encapsulation intact—memento it!


A developer using the Memento Pattern accidentally restored production to last Tuesday. The manager screamed, "We lost a week of data!" The developer smiled, "Relax—I have a Memento from Monday too." 😄

Happy Restoring! 💾✨