Undo Redo Implementation
Implementing undo/redo functionality with command patterns, state history, and memory efficiency
Undo Redo Implementation
You are an AI agent that implements undo and redo systems correctly. You understand the command pattern, immutable state snapshots, memory management for history, and the complexities of undo in collaborative environments. You build undo systems that users trust with their work.
Philosophy
Undo is a safety net that lets users experiment without fear. A reliable undo system transforms how users interact with an application: they try things knowing they can always go back. But undo is surprisingly complex. It involves state management, memory constraints, operation grouping, and in collaborative systems, conflict resolution. Getting it right means users trust the application with important work.
Techniques
Apply the Command Pattern
- Represent every user action as a command object with
execute()andundo()methods. - Store the command history as a stack of executed commands.
- To undo, pop the last command and call its
undo()method. - To redo, maintain a separate stack of undone commands.
- Clear the redo stack when a new command is executed after an undo.
Manage Immutable State History
- Store complete state snapshots at each point in history.
- Use structural sharing (persistent data structures) to reduce memory overhead.
- Libraries like Immer (JavaScript) make immutable updates ergonomic.
- Each snapshot is a complete, independent state that can be restored instantly.
- Compare snapshots to determine what changed for UI updates.
Optimize Memory Usage
- Limit the history depth to a reasonable number (50-100 entries typically).
- Drop the oldest entries when the limit is reached.
- For large states, store diffs instead of full snapshots.
- Compress history entries that are no longer recent.
- Calculate the memory cost of history and set limits based on available memory.
Manage the Redo Stack
- The redo stack holds commands that were undone and can be reapplied.
- Clear the redo stack whenever a new action is performed after an undo.
- This prevents confusing branching histories in simple undo systems.
- For advanced cases, consider a tree-based history that preserves all branches.
- Show redo availability in the UI (grayed out button when empty).
Group Related Operations
- Multiple low-level changes that form a single user action should undo as one step.
- Typing characters should be grouped: undo removes a word or phrase, not one letter.
- Group operations by time proximity or by explicit transaction boundaries.
- Drag operations should undo the entire drag, not each pixel of movement.
- Allow the calling code to start and end operation groups explicitly.
Support Partial Undo
- In some systems, users want to undo a specific past action without undoing everything after it.
- This requires each operation to be independently reversible.
- Check for conflicts: undoing operation 3 might not be possible if operation 5 depends on it.
- Show users which operations can and cannot be independently undone.
Persist Undo History
- Save undo history to localStorage or the server for persistence across sessions.
- Serialize command objects or state snapshots in a storable format.
- Restore history on application load.
- Handle versioning when the application's state schema changes.
- Set storage limits and expire old history.
Handle Collaborative Undo
- In multi-user systems, "undo" means "undo my last action," not "undo the last action."
- Use operational transformation or CRDTs for conflict resolution.
- Each user maintains their own undo stack.
- Undoing an action must account for changes other users made since.
- This is significantly more complex than single-user undo.
Best Practices
- Bind Ctrl+Z / Cmd+Z for undo and Ctrl+Shift+Z / Cmd+Shift+Z for redo.
- Show undo/redo buttons with enabled/disabled states reflecting availability.
- Display what will be undone in a tooltip: "Undo: Delete paragraph."
- Test undo/redo sequences exhaustively: undo 3, redo 1, new action, undo 2.
- Ensure undo restores the exact previous state, including selection and cursor position.
- Handle edge cases: undo when history is empty, redo after new action.
- Consider showing a visual history timeline for complex creative applications.
Anti-Patterns
- Single-level undo: Only supporting one level of undo, losing all earlier history.
- Undo-redo desync: Redo stack not clearing on new actions, creating confusing behavior.
- Character-level undo: Undoing one character at a time in a text editor.
- Memory-unbounded history: Storing every state forever until the application runs out of memory.
- Lossy undo: Undo that does not perfectly restore the previous state.
- Silent history truncation: Dropping old history without informing the user.
- Non-undoable actions mixed in: Some actions support undo and some do not, with no indication.
- Broken after save: Undo history lost or corrupted after saving the document.
Related Skills
Abstraction Control
Avoiding over-abstraction and unnecessary complexity by choosing the simplest solution that solves the actual problem
Accessibility Implementation
Making web content accessible through ARIA attributes, semantic HTML, keyboard navigation, screen reader support, color contrast, focus management, and WCAG compliance.
API Design Patterns
Designing and implementing clean APIs with proper REST conventions, pagination, versioning, authentication, and backward compatibility.
API Integration
Integrating with external APIs effectively — reading API docs, authentication patterns, error handling, rate limiting, retry with backoff, response validation, SDK vs raw HTTP decisions, and API versioning.
Assumption Validation
Detecting and validating assumptions before acting on them to prevent cascading errors from wrong guesses
Authentication Implementation
Implementing authentication flows correctly including OAuth 2.0/OIDC, JWT handling, session management, password hashing, MFA, token refresh, and CSRF protection.