LLD
Machine Coding
Interview Course
Java โ€ข Interview Prep
Lesson 6 of 6
Prerequisites

Command Pattern


What is it? (The Analogy)

Imagine you're at a restaurant. You don't walk into the kitchen and yell at the chef: "Make me a burger! Now add fries! Now a milkshake!" Instead, you write your order on a slip of paper, hand it to the waiter, and the waiter delivers it to the kitchen. The chef reads the order and executes it.

That slip of paper is a command. It captures WHAT you want done without you needing to know WHO will do it or HOW they'll do it. The waiter can hold multiple order slips, reorder them, save them for later, or even cancel one before it reaches the kitchen.

Here's what makes it powerful:

  • The slip can be stored (saved for later).
  • The slip can be undone (cross out the burger, the chef removes it).
  • Multiple slips can be queued (process them one by one).
  • The slip decouples the person ordering from the person cooking.

The Command Pattern turns a request into an object, letting you parameterize, queue, log, and undo operations.


Why do we need it?

Without the Command Pattern, the code that triggers an action must know exactly who performs it and how:

java
1// BAD EXAMPLE -- Direct coupling with no undo capability
2public class TextEditor {
3    private StringBuilder content = new StringBuilder();
4
5    public void type(String text) {
6        content.append(text);
7    }
8
9    public void deleteLast(int chars) {
10        int start = content.length() - chars;
11        content.delete(start, content.length());
12    }
13
14    // How do you undo? You'd need to remember every single action
15    // and its inverse. With 20 different operations, this becomes
16    // a nightmare of if-else tracking.
17
18    // How do you redo after undoing?
19    // How do you record a macro (sequence of commands)?
20    // How do you support keyboard shortcuts that map to commands?
21    // This approach falls apart FAST.
22}

The pain: Actions are directly executed and immediately forgotten. There's no history, no undo, no redo, no queuing, no macro recording. The caller is tightly coupled to the receiver. Adding undo support would mean rewriting everything.


How it works -- Step by Step

  1. Define the Command interface -- Every command must have an execute() method and (optionally) an undo() method.
  1. Create concrete Command classes -- Each one encapsulates a specific action: what to do and how to undo it.
  1. Identify the Receiver -- The object that actually does the work (the text document, the light, the robot).
  1. Create an Invoker -- The object that triggers commands (a button, a remote control, a menu). It doesn't know what the commands do -- it just calls execute().
  1. Wire them together -- Assign commands to the invoker. The invoker stores and executes commands. A history stack enables undo/redo.

Let's Build It Together

Let's build a Drawing Application with undo/redo -- think of a simplified MS Paint!

java
1// ========================================
2// Step 1: The Command interface
3// Every drawing action must be executable and undoable
4// ========================================
5
6public interface DrawCommand {
7    void execute();
8    void undo();
9    String getDescription();
10}
java
1// ========================================
2// Step 2: The Receiver -- the actual Canvas that gets drawn on
3// ========================================
4
5public class Canvas {
6    private List<String> shapes;
7
8    public Canvas() {
9        this.shapes = new ArrayList<>();
10    }
11
12    public void addShape(String shape) {
13        shapes.add(shape);
14        System.out.println("  [Canvas] Added: " + shape);
15    }
16
17    public void removeShape(String shape) {
18        shapes.remove(shape);
19        System.out.println("  [Canvas] Removed: " + shape);
20    }
21
22    public void clearAll() {
23        shapes.clear();
24        System.out.println("  [Canvas] Cleared all shapes!");
25    }
26
27    public void changeColor(int index, String newColor) {
28        if (index >= 0 && index < shapes.size()) {
29            String old = shapes.get(index);
30            shapes.set(index, old + " [" + newColor + "]");
31            System.out.println("  [Canvas] Colored shape #" + index + " -> " + newColor);
32        }
33    }
34
35    public void display() {
36        System.out.println("\n  ========== CANVAS ==========");
37        if (shapes.isEmpty()) {
38            System.out.println("  (empty canvas)");
39        }
40        for (int i = 0; i < shapes.size(); i++) {
41            System.out.println("  " + i + ": " + shapes.get(i));
42        }
43        System.out.println("  ============================\n");
44    }
45
46    public List<String> getShapes() {
47        return new ArrayList<>(shapes);  // Return a copy for safety
48    }
49
50    public int getShapeCount() { return shapes.size(); }
51}
java
1// ========================================
2// Step 3: Concrete Command classes
3// Each one knows how to execute AND undo itself
4// ========================================
5
6// Command to add a shape
7public class AddShapeCommand implements DrawCommand {
8    private Canvas canvas;
9    private String shape;
10
11    public AddShapeCommand(Canvas canvas, String shape) {
12        this.canvas = canvas;
13        this.shape = shape;
14    }
15
16    @Override
17    public void execute() {
18        canvas.addShape(shape);
19    }
20
21    @Override
22    public void undo() {
23        canvas.removeShape(shape);
24        System.out.println("  (Undid: Add " + shape + ")");
25    }
26
27    @Override
28    public String getDescription() {
29        return "Add " + shape;
30    }
31}
32
33// Command to clear the entire canvas
34public class ClearCanvasCommand implements DrawCommand {
35    private Canvas canvas;
36    private List<String> previousShapes;  // Save state for undo!
37
38    public ClearCanvasCommand(Canvas canvas) {
39        this.canvas = canvas;
40    }
41
42    @Override
43    public void execute() {
44        // Save current state BEFORE clearing (so we can undo!)
45        this.previousShapes = canvas.getShapes();
46        canvas.clearAll();
47    }
48
49    @Override
50    public void undo() {
51        // Restore all the shapes that were there before
52        for (String shape : previousShapes) {
53            canvas.addShape(shape);
54        }
55        System.out.println("  (Undid: Clear canvas -- restored " + previousShapes.size() + " shapes)");
56    }
57
58    @Override
59    public String getDescription() {
60        return "Clear canvas";
61    }
62}
63
64// Command to add multiple shapes at once (a "group" command)
65public class DrawCompositeCommand implements DrawCommand {
66    private Canvas canvas;
67    private List<String> shapes;
68
69    public DrawCompositeCommand(Canvas canvas, List<String> shapes) {
70        this.canvas = canvas;
71        this.shapes = shapes;
72    }
73
74    @Override
75    public void execute() {
76        System.out.println("  Drawing group of " + shapes.size() + " shapes:");
77        for (String shape : shapes) {
78            canvas.addShape(shape);
79        }
80    }
81
82    @Override
83    public void undo() {
84        System.out.println("  (Undoing group of " + shapes.size() + " shapes)");
85        // Remove in reverse order
86        for (int i = shapes.size() - 1; i >= 0; i--) {
87            canvas.removeShape(shapes.get(i));
88        }
89    }
90
91    @Override
92    public String getDescription() {
93        return "Draw group of " + shapes.size() + " shapes";
94    }
95}
java
1// ========================================
2// Step 4: The Invoker -- the Drawing App with undo/redo
3// ========================================
4
5public class DrawingApp {
6    private Deque<DrawCommand> undoStack;  // Commands that can be undone
7    private Deque<DrawCommand> redoStack;  // Commands that can be redone
8
9    public DrawingApp() {
10        this.undoStack = new ArrayDeque<>();
11        this.redoStack = new ArrayDeque<>();
12    }
13
14    // Execute a command and add it to the undo stack
15    public void executeCommand(DrawCommand command) {
16        System.out.println(">> Executing: " + command.getDescription());
17        command.execute();
18        undoStack.push(command);
19        redoStack.clear();  // New action invalidates redo history
20    }
21
22    // Undo the last command
23    public void undo() {
24        if (undoStack.isEmpty()) {
25            System.out.println(">> Nothing to undo!");
26            return;
27        }
28        DrawCommand command = undoStack.pop();
29        System.out.println(">> Undoing: " + command.getDescription());
30        command.undo();
31        redoStack.push(command);  // Save for potential redo
32    }
33
34    // Redo the last undone command
35    public void redo() {
36        if (redoStack.isEmpty()) {
37            System.out.println(">> Nothing to redo!");
38            return;
39        }
40        DrawCommand command = redoStack.pop();
41        System.out.println(">> Redoing: " + command.getDescription());
42        command.execute();
43        undoStack.push(command);
44    }
45
46    // Show command history
47    public void showHistory() {
48        System.out.println("\n--- Command History ---");
49        int i = 1;
50        // Copy the stack to iterate without modifying
51        for (DrawCommand cmd : undoStack) {
52            System.out.println("  " + i + ". " + cmd.getDescription());
53            i++;
54        }
55        System.out.println("--- (oldest at bottom) ---\n");
56    }
57}
java
1// ========================================
2// Step 5: Put it all together!
3// ========================================
4
5public class PaintApp {
6    public static void main(String[] args) {
7        // Create the canvas (Receiver) and app (Invoker)
8        Canvas canvas = new Canvas();
9        DrawingApp app = new DrawingApp();
10
11        // Draw some shapes
12        app.executeCommand(new AddShapeCommand(canvas, "Red Circle (50, 50)"));
13        app.executeCommand(new AddShapeCommand(canvas, "Blue Rectangle (100, 200)"));
14        app.executeCommand(new AddShapeCommand(canvas, "Green Triangle (150, 100)"));
15
16        canvas.display();
17
18        // Oops, didn't want that triangle!
19        app.undo();
20        canvas.display();
21
22        // Actually, bring it back!
23        app.redo();
24        canvas.display();
25
26        // Draw a group of shapes (house = square + triangle + door)
27        app.executeCommand(new DrawCompositeCommand(canvas,
28            List.of("Brown Square (200, 200)", "Red Roof Triangle (200, 150)", "Yellow Door (215, 230)")
29        ));
30        canvas.display();
31
32        // Undo the entire house in ONE step!
33        app.undo();
34        canvas.display();
35
36        // Clear everything
37        app.executeCommand(new ClearCanvasCommand(canvas));
38        canvas.display();
39
40        // Undo the clear -- everything comes back!
41        app.undo();
42        canvas.display();
43
44        // Show command history
45        app.showHistory();
46    }
47}

Aha moment: Each command is a self-contained object that knows how to execute AND undo itself. The DrawingApp (invoker) doesn't know anything about circles, rectangles, or canvases -- it just manages a stack of command objects. This separation is what makes undo/redo, macro recording, and command queuing possible!


Visual Mental Model

java
1  User Action        Invoker              Command              Receiver
2  (clicks button)    (DrawingApp)         (AddShapeCommand)    (Canvas)
3
4  "Draw circle" --> executeCommand() --> execute() ----------> addShape()
5                    |                    |
6                    +--push to           +-- knows HOW to
7                       undo stack           execute AND undo
8
9  "Undo"       --> undo()            --> undo() ------------> removeShape()
10                    |                    |
11                    +--pop from          +-- reverses what
12                       undo stack           execute() did
13                    +--push to
14                       redo stack
15
16  UNDO STACK (LIFO):          REDO STACK (LIFO):
17  +--------------------+      +--------------------+
18  | AddShape("circle") | <--> | (moves here on     |
19  | AddShape("rect")   |      |  undo, back on     |
20  | ClearCanvas        |      |  redo)              |
21  +--------------------+      +--------------------+

Common Mistakes & Gotchas

  • Forgetting to save state for undo -- The ClearCanvasCommand must save what was on the canvas BEFORE clearing. Without this, undo is impossible. Always think about what state you need to restore.
  • Commands that can't be undone -- Some operations (like sending an email or deleting a file permanently) can't truly be undone. Consider marking these commands as non-undoable and preventing undo past them.
  • Stale references -- If your command stores a reference to an object that changes, the undo might not work correctly. Consider storing snapshots (copies) of the data, not references.
  • Redo invalidation -- When the user performs a new action after undoing, the redo stack should be cleared. You can't redo an action that no longer makes sense in the new timeline.
  • Growing history forever -- In real applications, limit the undo stack size to avoid memory issues. Old commands get discarded.
  • Confusing Command with Strategy -- Strategy is about choosing an ALGORITHM. Command is about encapsulating a REQUEST as an object (with execute, undo, queue, log capabilities). If you need undo/redo or command history, it's Command pattern.

Interview Tip

Command Pattern is especially useful in LLD interviews involving: text editors (undo/redo), remote controls (button mapping), task schedulers (queued execution), transaction systems (rollback), and macro recording (replay sequences). When you see "undo," "redo," "queue," "schedule," or "log actions," think Command Pattern. Tell the interviewer: "I'll model each action as a Command object with execute() and undo() methods, and use a stack for undo/redo history." Also mention that Command enables the Macro Pattern -- recording a sequence of commands and replaying them.


Quick Quiz

  1. You're building a smart home remote control. The remote has buttons for lights, fan, and TV. Each button can do an action, and there's an "undo" button. How would you design this using the Command Pattern?
  1. What happens if you undo 3 times and then perform a new action? What should happen to the redo stack and why?
  1. How would you implement a "macro" feature -- where the user records a sequence of actions and replays them all with one click? (Hint: think about a command that contains other commands.)

Summary -- Key Takeaways

  • Command Pattern turns requests into objects, each with execute() and (optionally) undo() methods. This enables storing, queuing, and reversing actions.
  • The Invoker (remote control, app toolbar) manages commands without knowing what they do. The Receiver (canvas, light, document) does the actual work.
  • Undo/redo is the killer feature -- maintain two stacks (undo and redo) and move commands between them.
  • Use it when you need undo/redo, command queuing, macro recording, transaction rollback, or decoupling the object that triggers an action from the one that performs it.