What is the Command design pattern, and what are the responsibilities of the invoker, the command object, and the receiver?
answer
- request → object
- invoker knows only execute()
- receiver holds the real logic
- client wires command to receiver
- unlocks queue / log / undo / parameterize
basics
~20 sCommand 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 sCommand 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 linesinterface 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
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.
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.
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.
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.