Home › Low-Level Design › Command Pattern
C

Command Pattern

Wrap a request as an object so you can pass it around, queue it, log it, and undo it. I reach for this pattern the moment a feature grows an undo button, a job queue, or a remote control. It turns "do this thing right now" into "here is a thing to do, do it whenever you like." That one shift, from a method call to a value you can hold, is the whole pattern.

Low-Level DesignMedium10 min read

If you remember five things

The whole pattern in one glance. Everything below defends these five calls.

  1. An action becomes a value. A command object carries the operation, the receiver, and the arguments, so you can store it, queue it, log it, and reverse it. A plain method call can do none of that.
  2. The invoker stays blind. It only knows the Command interface, never the receiver. That missing dependency edge is the entire payoff.
  3. Reach for it on undo, queue, or replay. If the prompt says undo, redo, schedule, or audit log, this is the pattern the examiner wants.
  4. Capture undo state at execute time. Snapshot the old value inside execute() right before you mutate, never in the constructor, or undo restores a stale value.
  5. It is not Strategy. Strategy swaps an algorithm to compute a result. Command captures a request with a receiver and side effects so it can be stored and undone.

1The problem it solves#

Start with the coupling pain, because that is what the pattern actually cures. Picture a text editor with three ways to bold the selection: a toolbar button, a keyboard shortcut, and a right-click menu.

  • The naive version has all three call editor.bold() directly.
  • Now add undo. Then a macro recorder. Then "repeat last action."
  • Every trigger now knows the editor's internals, and undo becomes a giant switch statement trying to reverse things it never recorded.

The real pain is coupling.

  • The thing that triggers an action (a button) is welded to the thing that performs it (the editor), and to the exact way it performs it.
  • That violates the Single Responsibility Principle: the button now owns both "I was clicked" and "here is how bold works."
  • You cannot store the action for later, replay it, or reverse it, because nobody wrote down what "it" was.

What I actually want is to treat an action as a value.

  • Something I can put in a list, hand to a worker thread, serialize to a log file, and ask to reverse itself.
  • A method call happens and then it is gone, along with the arguments and the context it ran in.
  • A command object keeps all of that around.

2The idea (mental model)#

Take the request and make it an object that holds everything needed to perform the action later: which operation, on which receiver, with what arguments. It exposes one method, usually execute(). If you need undo, it also remembers enough state to offer undo().

The picture I use at the whiteboard: a restaurant order ticket.

  • The waiter (the caller) does not cook. They write the order on a ticket and stick it on the rail.
  • The ticket is the command object. The kitchen (the receiver) picks it up and does the work.
  • The waiter has no idea how the risotto is made. The kitchen has no idea which waiter wrote the ticket.
  • Because the ticket is a physical thing, you can stack tickets, reorder them, and keep them as a record.

Four roles fall out of this:

  • Command: the interface with execute() (and maybe undo()).
  • Concrete Command: a specific ticket that knows its receiver and arguments.
  • Receiver: the object that actually does the work.
  • Invoker: the thing that holds commands and triggers them. It only knows the Command interface.
The one idea that earns the point

The decoupling between Invoker and Receiver is the whole payoff. It is the Dependency Inversion Principle in action: the invoker depends on an abstraction, never on the concrete receiver. If you can state that out loud, you have shown you understand why the pattern exists, not just how to draw it.

3Structure#

Wire it once, then fire blind. The client creates the receiver, builds the concrete commands, and loads them into the invoker. After that wiring step, the invoker fires commands knowing nothing about what they do.

  • The invoker depends only on the Command interface.
  • Each concrete command holds a reference to its receiver and carries any parameters it needs.
  • That wiring step is the point: it is the only place that knows both sides.
classDiagram class Command { <> +execute() void +undo() void } class LightOnCommand { -Light light +execute() void +undo() void } class LightOffCommand { -Light light +execute() void +undo() void } class Light { +on() void +off() void } class RemoteControl { -Deque~Command~ history +press(Command cmd) void +undoLast() void } Command <|.. LightOnCommand Command <|.. LightOffCommand LightOnCommand --> Light LightOffCommand --> Light RemoteControl o-- Command
Notice the one thing that is not there: the RemoteControl has no arrow to Light. The remote only touches the Command interface, while the two concrete commands each hold a Light. That missing edge from invoker to receiver is the pattern working.

4Implementation in Java#

Here is a remote control with undo. I kept the receiver trivial so the pattern stays in focus.

interface Command {
    void execute();
    void undo();
}

// Receiver: the thing that does real work
class Light {
    private final String room;
    private boolean on = false;

    Light(String room) { this.room = room; }

    void on()  { on = true;  System.out.println(room + " light ON"); }
    void off() { on = false; System.out.println(room + " light OFF"); }
    boolean isOn() { return on; }
}

class LightOnCommand implements Command {
    private final Light light;
    LightOnCommand(Light light) { this.light = light; }

    public void execute() { light.on(); }
    public void undo()    { light.off(); }
}

class LightOffCommand implements Command {
    private final Light light;
    LightOffCommand(Light light) { this.light = light; }

    public void execute() { light.off(); }
    public void undo()    { light.on(); }
}

The invoker holds a history stack so undo walks backward through what actually ran:

import java.util.ArrayDeque;
import java.util.Deque;

class RemoteControl {
    private final Deque<Command> history = new ArrayDeque<>();

    void press(Command cmd) {
        cmd.execute();
        history.push(cmd);
    }

    void undoLast() {
        if (history.isEmpty()) return;
        history.pop().undo();
    }
}

Wiring and use:

public class Demo {
    public static void main(String[] args) {
        Light kitchen = new Light("Kitchen");
        RemoteControl remote = new RemoteControl();

        remote.press(new LightOnCommand(kitchen));   // Kitchen light ON
        remote.press(new LightOffCommand(kitchen));  // Kitchen light OFF
        remote.undoLast();                           // Kitchen light ON
        remote.undoLast();                           // Kitchen light OFF
    }
}

Two things worth calling out. First, undo is easy here and harder in real life.

  • Turning a light off is the exact inverse of turning it on, so undo() is trivial in this toy.
  • A command that sets a value should store the old value before it overwrites, so it can restore it.
  • Capture that snapshot inside execute(), not in the constructor, because the receiver's state at construction time may differ from its state when the command finally runs.
  • For full-state undo instead of inverse operations, the Memento pattern pairs in: the command grabs a memento before mutating and restores it on undo.
Pause before you read on: a command stores the old value in its constructor instead of inside execute(). Three commands run between construction and this one running. What does undo restore?
Reveal the answer
A stale value. The constructor snapshot captured the receiver's state at build time, but other commands mutated it before this one ran. Undo now rolls back to a value that was never the state right before this command. That is why you capture the undo state inside execute(), the moment before you mutate. Getting this wrong is the single most common undo bug in the room.

Second, a composite trick interviewers love. You can make a command that runs a list of commands, which gives you macros for free. This is Command married to Composite, a single command that is also a tree of commands:

import java.util.List;
import java.util.Collections;

class MacroCommand implements Command {
    private final List<Command> commands;
    MacroCommand(List<Command> commands) { this.commands = commands; }

    public void execute() {
        for (Command c : commands) c.execute();
    }

    public void undo() {
        // reverse order matters: undo last thing first
        List<Command> reversed = new java.util.ArrayList<>(commands);
        Collections.reverse(reversed);
        for (Command c : reversed) c.undo();
    }
}

5A real-world example#

The clearest place I have shipped this is a job queue. Users kick off exports, report generations, and bulk emails, and none of that should run on the web request thread.

  • A report that takes 8 seconds would hold an HTTP connection open and block the thread pool.
  • So the handler builds a command object, new GenerateReportCommand(reportId, userId), and pushes it onto a queue.
  • A pool of worker threads pulls commands off and calls execute().
  • The web tier knows nothing about report generation. The workers know nothing about HTTP. The queue in the middle is just a list of commands.

This is exactly how task frameworks model work.

  • A Runnable in Java is the Command pattern stripped down to one method, run().
  • Hand a Runnable to an ExecutorService and the executor is your invoker, your lambda is the concrete command.
  • People use this every day without naming it. Callable is the same thing when you want a return value back.

It also shows up anywhere there is transaction logging.

  • If every state change is a command, you can write each one to a log before running it.
  • Crash and restart? Replay the log and you are back where you were. That is write-ahead logging, and databases and message brokers lean on it hard.
  • Editors use the same trick: the undo stack is a list of executed commands, and redo is a second stack of commands you popped off.
From the interviewer's chair

In LLD interviews you will meet this pattern wearing a costume: a text editor, a vending machine, a smart home remote, a drawing app, a bank or wallet transaction system. If the prompt says undo, redo, replay, schedule, or queue, I am handing you the Command pattern on purpose and watching to see if you catch it.

6When to use it, and when not to#

Reach for Command when any of these are true:

  • You need undo or redo.
  • You want to queue, schedule, or delay operations.
  • You want to log operations so you can replay or audit them.
  • The same action is triggered from several places (menu, shortcut, button) and you are tired of duplicating the call.
  • You want to compose small operations into macros.

Skip it when the action is a plain, immediate method call with no undo, no queueing, and one caller.

  • Wrapping light.on() in a class earns you nothing there. It just adds a file.
  • I have seen candidates bury a four-line feature under five command classes to show off, and it reads as cargo-culting, not design sense.
  • The pattern is a tool for decoupling and deferral. If you are neither decoupling callers nor deferring execution, you are paying the ceremony tax for no reason.

If you cannot answer "why can't this just be a method call," you do not need the pattern yet.

7Common mistakes#

Putting business logic in the command.

  • The concrete command should be thin: it holds a receiver and delegates.
  • When I see a TransferMoneyCommand doing balance math, locking, and validation inside execute(), that logic belongs on the receiver (the account service).
  • The command's job is to say "call this, with these arguments," not to be the implementation. Keep it skinny and the receiver stays testable on its own.

Forgetting to capture undo state at the right time.

  • A classic bug: the command stores the receiver's old value in its constructor, but it runs much later after other commands changed that value.
  • Now undo restores a stale value.
  • Capture the state to reverse inside execute(), right before you mutate.

Undoing macros in the wrong order.

  • If a macro ran A then B then C, undo must run C then B then A.
  • Reverse the list. Candidates forget this constantly and it is an easy point to lose.

Making the invoker too smart.

  • The whole value is that the invoker only knows the Command interface.
  • The second it starts type-checking commands with instanceof and branching, you have rebuilt the switch statement you were trying to kill, and broken the Open/Closed Principle you were trying to honor.
Red flag that fails you here

Calling it Command when you built a Strategy, or the reverse. Both wrap behavior in an object, so the shallow answer blurs them. Strategy swaps one interchangeable algorithm for another to compute a result. Command captures a request, usually with a receiver and side effects, so it can be stored, queued, and undone. If you cannot cleanly say which one you built and why, I read it as pattern-name bingo, not design judgment.

8Where it shows up in interviews#

Expect to meet it under a different name. The prompt rarely says "use the Command pattern."

  • It says "design a text editor with undo and redo," or "design a remote control for a smart home," or "build a task scheduler."
  • Your move is to name the four roles out loud: here is my Command interface, here are the concrete commands, this class is the receiver, this one is the invoker holding the history stack.
  • Naming the roles early signals you have seen this before.

If you want a sequence to picture, here is the runtime flow for a press-then-undo:

sequenceDiagram participant Client participant Remote participant Command as LightOnCommand participant Light Client->>Remote: press(cmd) Remote->>Command: execute() Command->>Light: on() Remote->>Remote: history.push(cmd) Client->>Remote: undoLast() Remote->>Command: undo() Command->>Light: off()
Notice the invoker never talks to Light. Remote calls execute() and undo() on the command, and the command is the only thing that reaches the receiver. The history push between the two client calls is what makes undo possible: the remote kept the command so it could ask it to reverse itself later.
Follow-ups the interviewer will ask (and your answers)
"How does redo work?"
Keep a second stack. When you undo a command, push it onto the redo stack. Redo pops it and calls execute() again. Clear the redo stack whenever a fresh command runs, because a new action invalidates the old redo history.
"How do you queue these across threads?"
The invoker becomes a thread-safe queue (a BlockingQueue works) feeding a worker pool. Commands must be self-contained, so pass data by value, not references that can mutate underneath you between enqueue and execute.
"How would you persist and replay?"
Make commands serializable, or log the operation plus its arguments before running it, then rebuild and re-run on restart. That is the write-ahead log idea: every state change is a command written down before it happens, so a crash just means replaying the log.
"What is the difference from Strategy?"
Both wrap behavior in an object, so the difference is intent. Strategy swaps an interchangeable algorithm to compute a result. Command captures a request with a receiver and side effects so it can be stored, queued, and undone. If you care about reversing it or running it later, it is a command.
"When would you not use it?"
When the action is an immediate method call with one caller, no undo, and no queueing. Wrapping a one-line call in a command class adds a file and buys nothing. The pattern pays off only when you are decoupling callers or deferring execution.

What you can now explain in the room

  • Why turning an action into an object is what lets you queue, log, and undo it.
  • The four roles and why the invoker depending only on the interface is the entire payoff.
  • Why you capture undo state inside execute() and not in the constructor.
  • Why macro undo runs the sub-commands in reverse order.
  • How a job queue, a Runnable, and write-ahead logging are all this pattern in disguise.
  • The clean one-liner that separates Command from Strategy under pressure.

Next, look at the Memento pattern (another way to build undo by snapshotting state) and the classic "design a text editor with undo/redo" LLD problem where Command and Memento often team up.

Get a prep plan built around your Command Pattern interview

Tell us the role and your interview date. We will send back a dated, day by day plan. Free.

Thanks. Check your inbox, your Command Pattern prep plan is on the way.