> Capture and restore object state using mementos for undo history and time-travel.
Scanned 9/11/2026
Install to Claude Code
npx -y skills add Intense-Visions/harness-engineering --skill gof-memento-pattern --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Gof Memento Pattern?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/intense-visions-gof-memento-pattern-harness-engineering)More formats (shields.io, HTML) on the badges page.
# GOF Memento Pattern
> Capture and restore object state using mementos for undo history and time-travel.
## When to Use
- You need to implement undo/redo without exposing the object's internals
- You want to save and restore checkpoints of an object's state
- You're building a text editor, drawing app, form wizard, or game save system
- You need time-travel debugging or state snapshots for comparison
## Instructions
**Classic memento with Originator and Caretaker:**
```typescript
// Memento — stores a snapshot of state
class EditorMemento {
constructor(
private readonly content: string,
private readonly cursorPosition: number,
private readonly timestamp: Date
) {}
getContent(): string {
return this.content;
}
getCursorPosition(): number {
return this.cursorPosition;
}
getTimestamp(): Date {
return this.timestamp;
}
describe(): string {
return `[${this.timestamp.toISOString()}] ${this.content.slice(0, 30)}...`;
}
}
// Originator — creates and restores from mementos
class TextEditor {
private content = '';
private cursorPosition = 0;
type(text: string): void {
this.content =
this.content.slice(0, this.cursorPosition) + text + this.content.slice(this.cursorPosition);
this.cursorPosition += text.length;
}
moveCursor(position: number): void {
this.cursorPosition = Math.max(0, Math.min(position, this.content.length));
}
delete(count: number): void {
this.content =
this.content.slice(0, this.cursorPosition - count) + this.content.slice(this.cursorPosition);
this.cursorPosition = Math.max(0, this.cursorPosition - count);
}
// Save state to memento
save(): EditorMemento {
return new EditorMemento(this.content, this.cursorPosition, new Date());
}
// Restore state from memento
restore(memento: EditorMemento): void {
this.content = memento.getContent();
this.cursorPosition = memento.getCursorPosition();
}
getState(): { content: string; cursor: number } {
return { content: this.content, cursor: this.cursorPosition };
}
}
// Caretaker — manages the history of mementos
class EditorHistory {
private history: EditorMemento[] = [];
private future: EditorMemento[] = [];
save(editor: TextEditor): void {
this.history.push(editor.save());
this.future = []; // clear redo history
}
undo(editor: TextEditor): boolean {
if (this.history.length === 0) return false;
this.future.push(editor.save());
editor.restore(this.history.pop()!);
return true;
}
redo(editor: TextEditor): boolean {
if (this.future.length === 0) return false;
this.history.push(editor.save());
editor.restore(this.future.pop()!);
return true;
}
getHistoryDescriptions(): string[] {
return this.history.map((m) => m.describe());
}
}
// Usage
const editor = new TextEditor();
const history = new EditorHistory();
history.save(editor);
editor.type('Hello, world!');
history.save(editor);
editor.type(' How are you?');
history.save(editor);
editor.delete(4);
console.log(editor.getState()); // { content: 'Hello, world! How are', cursor: 21 }
history.undo(editor);
console.log(editor.getState()); // { content: 'Hello, world! How are you?', cursor: 26 }
history.undo(editor);
console.log(editor.getState()); // { content: 'Hello, world!', cursor: 13 }
history.redo(editor);
console.log(editor.getState()); // { content: 'Hello, world! How are you?', cursor: 26 }
```
**Lightweight memento using plain objects (TypeScript idiomatic):**
```typescript
type FormState = {
firstName: string;
lastName: string;
email: string;
step: number;
};
class MultiStepForm {
private state: FormState = { firstName: '', lastName: '', email: '', step: 1 };
private snapshots: FormState[] = [];
updateField<K extends keyof FormState>(field: K, value: FormState[K]): void {
this.state = { ...this.state, [field]: value };
}
checkpoint(): void {
this.snapshots.push({ ...this.state }); // shallow copy sufficient for flat state
}
rollback(): boolean {
const snapshot = this.snapshots.pop();
if (!snapshot) return false;
this.state = snapshot;
return true;
}
getState(): Readonly<FormState> {
return this.state;
}
}
```
## Details
**Encapsulation is key:** The Originator creates and restores mementos. The Caretaker stores them but must NOT access their internal data. In TypeScript, enforce this with private constructors or closures.
**Memory management:** Unlimited undo history can exhaust memory. Implement a fixed-size ring buffer or time-limited history:
```typescript
class BoundedHistory {
private history: Memento[] = [];
constructor(private readonly maxSize: number) {}
push(memento: Memento): void {
this.history.push(memento);
if (this.history.length > this.maxSize) {
this.history.shift(); // drop oldest
}
}
}
```
**Anti-patterns:**
- Memento that exposes mutable references to internal state — snapshots must be copies
- Caretaker that inspects memento contents — violates encapsulation
- Saving mementos too frequently (e.g., on every keystroke without debounce) — throttle or batch snapshots
**Memento vs. Command:** Command stores the operation needed to undo an action. Memento stores a complete state snapshot. Command is more memory-efficient for simple state changes; Memento is simpler to implement when state is complex and hard to invert.
## Source
refactoring.guru/design-patterns/memento
## Process
1. Read the instructions and examples in this document.
2. Apply the patterns to your implementation, adapting to your specific context.
3. Verify your implementation against the details and edge cases listed above.
## Harness Integration
- **Type:** knowledge — this skill is a reference document, not a procedural workflow.
- **No tools or state** — consumed as context by other skills and agents.
## Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!