skip to content

questions

6

What is the Command design pattern, and what are the responsibilities of the invoker, the command object, and the receiver?

level: juniorimportance: must knowfreq 72%

answer

  1. request → object
  2. invoker knows only execute()
  3. receiver holds the real logic
  4. client wires command to receiver
  5. unlocks queue / log / undo / parameterize

basics

~20 s

Command turns a request into an object. The command holds what to do plus the data needed. The invoker (button, queue, scheduler) just calls execute() without knowing the receiver — the object that actually performs the work.

solid answer

~50 s

Command is a behavioral pattern that encapsulates a request as a first-class object with a uniform interface, typically `execute()`. Four roles: the **receiver** holds the real domain logic (e.g. `Document.insertText`); the **concrete command** stores a reference to the receiver plus the bound arguments and calls the receiver inside `execute()`; the **invoker** holds commands and triggers them, knowing only the interface; the **client** wires a concrete command to its receiver and hands it to the invoker. The payoff is decoupling in time and in dependency: the invoker compiles against `Command` only, so new operations are added without touching it (Open/Closed). Because a request is now a value, it can be stored in a list, put on a queue, sent over a network, logged, replayed, or reversed — none of which is possible when the request is just an inline method call on the call stack.

code

typescript · 16 lines
typescript
interface Command { execute(): void }

class InsertText implements Command {          // concrete command
  constructor(private doc: Document,
              private text: string,
              private pos: number) {}
  execute() { this.doc.insert(this.text, this.pos) }  // delegate to receiver
}

class Button {                                  // invoker
  constructor(private cmd: Command) {}
  click() { this.cmd.execute() }               // no domain knowledge at all
}

// client wires receiver + args into a command, hands it to the invoker
const button = new Button(new InsertText(doc, "hello", 0))

go deeper

for a junior

Name the roles (invoker, command, receiver, client), state that a request becomes an object with execute(), and give one concrete example such as a toolbar button.

for a middle

Add why it matters: the invoker depends only on the interface (Open/Closed, dependency inversion), and reification enables queueing, logging, and undo. Mention that execute() should delegate, not contain domain logic.

for a senior

Discuss the cost/benefit boundary, the smart-vs-thin command trade-off, and how the pattern appears in real infrastructure (task submission, job tables, message payloads). Contrast with Strategy by intent.

for a principal

Frame it as the object-oriented root of request reification that scales up to command buses, CQRS write models, transaction logs and replicated state machines, and discuss the systemic consequences: serialization/versioning of the command schema, idempotency, and auditability.

## The problem it solves Normally, invoking behavior means writing a method call: `document.insertText("hi", 5)`. That call exists only while the stack frame is alive. You cannot put it in a list, delay it by ten minutes, write it to a log file, send it to another machine, or reverse it, because it is not a *thing* — it is an event in the runtime. Command's whole idea: **reify the call**. Take the target, the operation, and the arguments, and package them into an object with a uniform interface, usually a single method `execute()`. Once the request is an object, everything you can do with objects becomes available to requests. ## The four roles | Role | Responsibility | Example | |---|---|---| | **Receiver** | Knows how to actually do the work; contains domain logic. Usually has no idea Command exists. | `Document`, `Light`, `OrderService` | | **Concrete Command** | Holds a reference to the receiver + the bound arguments; `execute()` translates the generic call into a specific receiver call. | `InsertTextCommand(doc, "hi", 5)` | | **Command interface** | The uniform contract the invoker depends on — typically `execute()`, sometimes `undo()`. | `interface Command { execute() }` | | **Invoker** | Stores and triggers commands; knows only the interface. Never learns what the command does. | Toolbar button, job queue, scheduler, macro recorder | | **Client** | Wires a concrete command to its receiver and installs it into the invoker. | The composition/wiring code, DI container, controller | The client is sometimes counted as a fifth role. The key line to remember: **the client knows the receiver, the invoker does not.** ## Why the decoupling matters Without Command, a toolbar button would have a `switch` on its own identity, or a direct reference to `Document`. Every new operation edits the button class. With Command, adding "Insert Table" means writing one new class and wiring it — no existing class changes. That is the Open/Closed Principle expressed concretely, and it also inverts the dependency: UI code depends on an abstraction (`Command`), not on the domain layer. The second decoupling is **temporal**. Because the request is a value, it can be executed later, elsewhere, or repeatedly. That single property is what unlocks the pattern's four classic applications: 1. **Parameterization** — the same widget/worker is configured with different commands at runtime. 2. **Queueing / deferred execution** — commands go into a work queue, thread pool, or persistent job table. 3. **Logging / auditing** — serialize each command before running; replay after a crash. 4. **Undo/redo** — keep a history stack; ask commands to reverse themselves. ## Minimal shape (language-neutral pseudocode) ``` interface Command { execute() } class InsertTextCommand implements Command { constructor(doc, text, position) { ...store all three... } execute() { doc.insert(text, position) } // delegate to receiver } // invoker class Button { constructor(command) { this.command = command } onClick() { this.command.execute() } // knows nothing else } ``` Note the invoker's method body: no conditionals, no domain types, no knowledge of what happens. That emptiness is the point. ## Command should not contain the business logic A very common mistake is to move the domain logic *into* `execute()`, so the receiver disappears. Then the command becomes a god object and the domain logic is only reachable through the invocation machinery — you cannot call it directly from a test or another entry point. The canonical form keeps `execute()` as a thin adapter that binds arguments and delegates. (There is a legitimate variant — often called a *smart command* — where trivial logic lives inline, but the default should be delegation.) ## Where you have already seen it - GUI toolkits: menu items, toolbar buttons, keyboard shortcut maps all bound to action objects. - Thread pools and job systems: submitting a runnable/task object is exactly a command handed to an invoker. - HTTP request handlers modeled as request objects dispatched by a router. - Database transaction logs and message queues: each entry is a serialized command. ## Cost One class (or closure) per operation. For a handful of operations with no undo, no queueing, and no dynamic binding, that ceremony buys nothing — a direct call is better. Command earns its keep when at least one of parameterization, deferral, logging, or undo is actually required.

  • If the invoker never knows the receiver, who does?
    The client / wiring code (a factory, controller, or DI container) constructs the concrete command with its receiver and arguments already bound, then installs it in the invoker.
  • When is Command over-engineering?
    When there is no parameterization, no deferral, no logging, and no undo — i.e. the request is executed immediately and exactly once by code that already legitimately depends on the receiver. Then a direct method call is clearer and cheaper.
  • How does Command differ from Strategy, since both wrap behavior in an object?
    Intent differs. Strategy swaps *how* one fixed operation is performed (interchangeable algorithms, usually called immediately by a context that owns them). Command reifies *what* to do, including its arguments and target, so the request can be stored, transported, delayed, and reversed. Strategies are rarely queued or undone.

A restaurant order slip. The waiter (invoker) writes the order on a slip (command object) and puts it on the rail; the cook (receiver) does the actual work. The waiter never cooks, the cook never sees the customer, and because the order is a physical slip it can be stacked, re-sorted, held back, voided, or re-fired.

saying these in an interview costs you the question

  • Saying the invoker calls the receiver directly — that destroys the decoupling the pattern exists for.
  • Putting the business logic inside execute() and deleting the receiver, making the logic unreachable except through the invocation machinery.
  • Claiming Command is a creational pattern; it is behavioral.
  • Assuming every command must support undo — undo() is an optional extension, not part of the core definition.
  • Confusing Command with Strategy or with the Observer/callback mechanism just because all three involve indirection.

context

open as a page

How do you implement undo and redo using the Command pattern, and what state must each command object capture to be reversible?

level: middleimportance: must knowfreq 58%

basics

~20 s

Add an undo() method next to execute(). Each executed command is pushed onto an undo stack. Undo pops it, calls undo(), and pushes it onto a redo stack. The command must remember whatever it needs to restore the previous state.

open as a page

How would you model a single user action that must perform several operations atomically as command objects, and what happens if one of them fails partway through?

level: middleimportance: should knowfreq 30%

basics

~20 s

Wrap the child commands in one macro (composite) command that implements the same interface. Its execute() runs children in order; if one fails, it undoes the already-executed children in reverse order and rethrows, so the history sees one entry that either fully applied or not at all.

open as a page

In a language with first-class functions, a request can be captured as a closure. When is a full Command object still the better choice?

level: middleimportance: should knowfreq 42%

basics

~20 s

A closure captures a call to run later, which covers the simple case. Prefer a command object when you need more than one operation on it (undo, describe, merge), or when the request must be serialized, inspected, logged, or persisted — a closure is an opaque blob.

open as a page

Reifying a request as a command object enables queueing, scheduling, retrying and logging. What properties must those command objects have to be safely queued and replayed?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The command must be self-contained and serializable — all inputs captured, no live references — and its effect must be safe to apply more than once, because queues normally deliver at-least-once. It also needs a version so old stored commands still parse.

open as a page

How does the object-oriented Command pattern relate to a command bus, CQRS, and event sourcing — and what is the difference between a command and an event?

level: principalimportance: nice to knowfreq 32%

basics

~20 s

They are the same idea at different scales: a request reified as an object. A command bus routes command objects to handlers; CQRS separates command handling from reads. A command is an imperative request that may be rejected; an event is an immutable fact that already happened.

open as a page