Design Patterns: Behavioral · Behavioral Patterns

Command Pattern

Turning a request into an object you can queue, log, or undo.

  • 5 min read
  • 10 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The pattern
  3. How it works, step by step
  4. Where you'll see it
  5. Choosing among the behavioral patterns
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Command is the behavioral pattern for turning a request into an object: instead of calling editor.deleteLast(5) right away, you create a DeleteCommand that knows what to do and to whom. Because the request is now a thing, it can be stored, queued, logged, undone, redone or replayed. Undo/redo, job queues, macros and CQRS all rest on this idea. This skill covers the pattern, an undo/redo history, and what changes when commands travel through queues.

A text editor has Type, Delete and Paste buttons. Each button calls the editor directly:

Java

typeButton.onClick(() -> editor.insert(text));
deleteButton.onClick(() -> editor.deleteLast(5));

Python

type_button.on_click(lambda: editor.insert(text))
delete_button.on_click(lambda: editor.delete_last(5))

Now users want Undo and Redo, a keyboard shortcut for every action, and recordable macros. None of that is possible, because the actions happen immediately and leave nothing behind to undo, replay or queue.

We'll go from simple to more technical:

  1. The pattern: commands, receivers and invokers.
  2. Undo and redo with two stacks, step by step.
  3. Where you'll see commands: executors, UI actions, job queues, CQRS.
  4. What undo needs to remember, and the rules for commands that are queued or retried.

The pattern

Command turns a request into an object. The object knows what to do and to whom (the receiver), so it can be executed later, stored in a history, undone, or replayed.

Java

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

class DeleteCommand implements Command {
    private final Editor editor;
    private final int count;
    private String deleted;                  // remembered so undo can restore it

    DeleteCommand(Editor editor, int count) { this.editor = editor; this.count = count; }

    public void execute() { deleted = editor.deleteLast(count); }
    public void undo()    { editor.insert(deleted); }
}

class History {
    private final Deque<Command> undoStack = new ArrayDeque<>();
    private final Deque<Command> redoStack = new ArrayDeque<>();

    void run(Command c) { c.execute(); undoStack.push(c); redoStack.clear(); }
    void undo() { if (!undoStack.isEmpty()) { Command c = undoStack.pop(); c.undo(); redoStack.push(c); } }
    void redo() { if (!redoStack.isEmpty()) { Command c = redoStack.pop(); c.execute(); undoStack.push(c); } }
}

Python

from abc import ABC, abstractmethod


class Command(ABC):
    @abstractmethod
    def execute(self) -> None: ...

    @abstractmethod
    def undo(self) -> None: ...


class DeleteCommand(Command):
    def __init__(self, editor: Editor, count: int):
        self._editor = editor
        self._count = count
        self._deleted = ""                  # remembered so undo can restore it

    def execute(self) -> None:
        self._deleted = self._editor.delete_last(self._count)

    def undo(self) -> None:
        self._editor.insert(self._deleted)


class History:
    def __init__(self):
        self._undo: list[Command] = []      # a list is Python's stack: append / pop
        self._redo: list[Command] = []

    def run(self, c: Command) -> None:
        c.execute()
        self._undo.append(c)
        self._redo.clear()

    def undo(self) -> None:
        if self._undo:
            c = self._undo.pop()
            c.undo()
            self._redo.append(c)

    def redo(self) -> None:
        if self._redo:
            c = self._redo.pop()
            c.execute()
            self._undo.append(c)

How it works, step by step

  1. Define a Command interface: execute() and, if you need it, undo().
  2. Each concrete command stores its receiver and parameters, plus anything needed to undo.
  3. An invoker (a button, a history, a job queue) calls execute() without knowing what the command does.
  4. For undo/redo, keep two stacks: executing a new command pushes it on the undo stack and clears the redo stack.
  5. A macro is just a command that holds a list of commands.

Where you'll see it

  • Runnable and Callable handed to an ExecutorService are commands waiting to be run - in Python, functions and functools.partial objects handed to concurrent.futures, or tasks queued in Celery.
  • UI frameworks' action objects (Swing Action) bind one command to a button, a menu item and a shortcut.
  • Job queues and schedulers store commands (as serialized messages) to run later or retry.
  • CQRS architectures model every write as a command object (PlaceOrder, CancelOrder).

Choosing among the behavioral patterns

If you need to...Reach forIt's about
Let many objects react when something happens, without the source knowing themObserverWho gets told about a change.
Swap one algorithm for another at runtimeStrategyHow a task is done - chosen from outside.
Turn a request into an object you can queue, log, undo or redoCommand (this lesson)What to do, packaged for later.
Make an object behave differently depending on its current stateStateBehavior that changes itself as the object moves between states.
Fix the steps of an algorithm but let subclasses fill in some of themTemplate MethodThe skeleton stays, the details vary.

Common mistakes

MistakeWhat happensDo this instead
A command that doesn't remember what it changedundo can't restore itstore the old value or deleted text in execute()
Not clearing the redo stack on a new commandredo recreates a history that never happenedclear redo whenever a new command runs
Recording commands that failedundo reverses something that never happenedpush to history only after success
An unbounded historymemory grows forevercap it (drop the oldest)
Queuing commands that aren't idempotenta redelivered command charges twicea unique command id the receiver checks

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. What must a DeleteCommand remember so that undo() works?

Show answer

The exact text it deleted - the count alone can't say which characters were there, and fewer may have been deleted if the text was shorter.

2. You undo twice, then run a new command. What happens to the redo history?

Show answer

It is cleared. Redoing the old commands on top of the new one would recreate a history that never happened.

3. Why must commands sent through a job queue be idempotent?

Show answer

Queues redeliver: after a crash or a timeout the same command may run twice. A unique command id that the receiver records lets it ignore the repeat.

Key takeaways

  • A command is a request turned into an object: it can be executed later, queued, logged, undone or replayed.
  • To undo, a command must remember what it changed.
  • A new command clears the redo stack; failed commands never enter the history.
  • Queued commands should be serializable and idempotent.
  • A command is a request that may be rejected; an event is a fact that already happened.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

If you need to...Reach for
Undo/redo, macros, queued or logged actionsCommand
Undo without an exact inverseCommand + Memento (snapshots)
Run work later on other machinescommands as data (type, id, params) + handlers
Announce something that happenedan event, not a command
Swap algorithmsStrategy

Snippets worth knowing by heart

Java

void run(Command c) {
    c.execute();                 // throws? then it never enters the history
    undoStack.push(c);
    redoStack.clear();           // a new command invalidates redo
    if (undoStack.size() > LIMIT) undoStack.removeLast();
}
void undo() { if (!undoStack.isEmpty()) { Command c = undoStack.pop(); c.undo(); redoStack.push(c); } }

Python

def run(self, c: Command) -> None:
    c.execute()                  # raises? then it never enters the history
    self._undo.append(c)         # a deque(maxlen=LIMIT) drops the oldest
    self._redo.clear()

command = {"type": "ResizeImage", "id": "cmd-42", "image": "a.png", "width": 800}
HANDLERS[command["type"]](command)

The traps

  • Undo without remembered state.
  • Redo not cleared after a new command.
  • Failed commands in the history.
  • Unbounded history.
  • Non-idempotent queued commands.

Staff-level points, one line each

  • Undo by inverse (cheap, needs a correct inverse) or snapshot/Memento (always correct, costs memory).
  • Bound the history: deque(maxlen=3) kept ['cmd2', 'cmd3', 'cmd4'].
  • A failing macro undoes completed steps in reverse (exec big, exec wide, undo wide, undo big).
  • Collaborative undo needs transformation (OT/CRDTs) or compensating commands, not rewinding.
  • Queued commands are data: lambdas failed to pickle / serialize; dispatch by type to handlers; carry ids; version them.
  • submit() hides exceptions in the future until get()/exception() is called.
  • Command = imperative request that may be refused; event = past-tense fact.

Say it in an interview

  • "Every edit is a command, so undo and redo are two stacks."
  • "The delete command remembers the text it removed."
  • "Jobs go on the queue as data with an id; workers dedupe by id."
  • "PlaceOrder is a command; OrderPlaced is the event it produces."

Practice

This skill comes with 10 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Command Pattern free

Undo and Redo With Command Objects

MediumProCoding problemJava · Python

Every edit becomes a Command object with execute() and undo(), and a History keeps track of them so the user can undo and redo.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Bank Operations as Undoable Commands

MediumProCoding problemJava · Python

Every bank operation is a Command. Implement the three commands and the history.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Command Pattern free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime