StackPractices
intermediate By Mathias Paulenko

Memento Pattern

Capture and restore an object's internal state without violating encapsulation. A behavioral design pattern for undo/redo.

Topics: design

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

VariantDescriptionUse Case
Full SnapshotStores entire object stateSmall objects, infrequent snapshots
Delta/IncrementalStores only changed fieldsLarge objects, frequent snapshots
Serializable MementoUses serialization for deep copyingComplex object graphs
Command + MementoCommands store mementos for undoTransaction 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.