Learn how to implement Undo/Redo and history stacks in JavaScript using the Command Pattern, a must-know tool for building real-world apps.

Introduction
Undo and Redo are two of those features developers underestimate until they have to implement them. Whether it’s a text editor, a whiteboard app, or a drawing tool, the logic can quickly become messy:
- How do you keep track of every user action?
- How do you revert a specific action without breaking others?
- How do you redo something after it’s undone?
This is where the Command Pattern shines.
Instead of directly executing actions in your app, you wrap them in command objects that know how to:
- Do the action
- Undo the action
This makes history stacks, redo, and undo trivial to implement.
In this article, we’ll cover:
- ✅ What the Command Pattern is
- ✅ How it enables Undo/Redo functionality
- ✅ A step-by-step JavaScript implementation
- ✅ Real-world examples (text editors, drawing apps, React apps)
- ✅ Gotchas and pro tips
What is the Command Pattern?
The Command Pattern is a behavioral design pattern that turns requests or operations into standalone objects called “commands.”
Each command object encapsulates:
- execute() → performs the action
- undo() → reverts the action
👉 Analogy: Think of a remote control. Each button is a command. Press it once, and it executes (turns on the TV). Press undo, and it reverts (turns it off). The remote doesn’t know how the TV works; it just triggers commands.
Why Use It for Undo/Redo?
Without a command structure, undo/redo logic often gets messy and tied directly to your app state.
With the Command Pattern, you:
- Store commands in a history stack
- Call
undo()
on the most recent command to revert - Move undone commands into a redo stack.k
- Call
execute()
again to redo
Clean, consistent, and scalable.
Step-by-Step Implementation in JavaScript
Let’s build a simple text editor that supports typing and undo/redo.
Step 1: Define a Command Interface
interface Command {
execute(): void;
undo(): void;
}
Step 2: Create Concrete Commands
class AddTextCommand implements Command {
constructor(private editor: TextEditor, private text: string) {}
execute() {
this.editor.addText(this.text);
}
undo() {
this.editor.removeText(this.text.length);
}
}
Step 3: The Receiver (Text Editor)
class TextEditor {
private content = "";
addText(text: string) {
this.content += text;
}
removeText(length: number) {
this.content = this.content.slice(0, -length);
}
getContent() {
return this.content;
}
}
Step 4: Command Manager (Invoker)
class CommandManager {
private undoStack: Command[] = [];
private redoStack: Command[] = [];
executeCommand(command: Command) {
command.execute();
this.undoStack.push(command);
this.redoStack = []; // clear redo on new action
}
undo() {
const command = this.undoStack.pop();
if (command) {
command.undo();
this.redoStack.push(command);
}
}
redo() {
const command = this.redoStack.pop();
if (command) {
command.execute();
this.undoStack.push(command);
}
}
}
Step 5: Put It All Together
const editor = new TextEditor();
const manager = new CommandManager();
manager.executeCommand(new AddTextCommand(editor, "Hello "));
manager.executeCommand(new AddTextCommand(editor, "World!"));
console.log(editor.getContent()); // Hello World!
manager.undo();
console.log(editor.getContent()); // Hello
manager.redo();
console.log(editor.getContent()); // Hello World!
Boom 💥 a simple, extensible undo/redo system.
Real-World Use Cases
1. Text Editors
Typing, deleting, and formatting each is a command that can be undone.
2. Drawing Apps
Each stroke or shape placement is a command; undo removes it, redo adds it back.
3. Spreadsheet Software
Every cell update is a command with execute
(apply new value) and undo
(restore old value).
4. React/Redux Apps
User actions (like “add todo” or “delete item”) can be wrapped as commands instead of plain reducers, enabling undo/redo easily.
Benefits of the Command Pattern
- Undo/Redo support becomes trivial.
- Extensibility: Add new commands without touching existing code
- Decoupling: Invoker doesn’t care about how actions work
- Macros: Chain multiple commands into one (e.g., “bold + italic + underline”)
Gotchas & Things to Watch Out For
- Memory growth: Long undo history = large memory usage. Consider limiting the stack size.
- Complex state changes: Some operations may need to snapshot the state before/after.
- Overengineering risk: Don’t use it for trivial single actions.
Pro Tip: Macro Commands
You can combine multiple commands into one “macro command”:
class MacroCommand implements Command {
constructor(private commands: Command[]) {}
execute() {
this.commands.forEach((cmd) => cmd.execute());
}
undo() {
[...this.commands].reverse().forEach((cmd) => cmd.undo());
}
}
This is perfect for applying batch actions with a single undo.
Conclusion
The Command Pattern is more than just a fancy design pattern; it’s the secret sauce behind Undo/Redo, macros, and history stacks.
By encapsulating actions as commands with execute
and undo
You make your codebase cleaner, testable, and far easier to extend.
✅ Key takeaway: If your app needs Undo/Redo or action history, don’t hack it together; use the Command Pattern.
Call to Action
Have you ever built Undo/Redo logic before? Did you struggle or hack it together?
Share your experience in the comments 👇
And if your teammate is building a text editor or drawing app, send them this article. It’ll save them hours.
Leave a Reply