The Command pattern — turning a request into an object you can store and pass around

A plain method call happens immediately and leaves nothing behind — Command wraps a request as a real object instead, which is what makes undo, queuing, and logging "what happened" possible without the caller and the receiver needing to know anything about each other.

Intermediate

3 min read

The problem: a direct method call can't be queued, logged, or undone

class Light:
    def turn_on(self):
        print("Light is ON")
    def turn_off(self):
        print("Light is OFF")
 
light = Light()
light.turn_on()  # happens immediately — there's no OBJECT representing "the act of turning on the light"

Calling light.turn_on() directly executes the action right away and leaves nothing behind to inspect, store, queue for later, or reverse — the "request" only exists as a single line of code being executed, not as a real value in the program that anything else could hold onto or reason about.

The fix: wrap the request itself as an object

class Command:
    def execute(self):
        raise NotImplementedError
 
class TurnOnCommand(Command):
    def __init__(self, light):
        self._light = light
    def execute(self):
        self._light.turn_on()
 
class TurnOffCommand(Command):
    def __init__(self, light):
        self._light = light
    def execute(self):
        self._light.turn_off()
 
commands = [TurnOnCommand(light), TurnOffCommand(light)]  # a LIST of requests, not yet executed
for command in commands:
    command.execute()  # executed later, in order — or stored, logged, undone, etc.

Each Command object bundles together everything needed to perform an action later — which receiver (light), which operation (turn_on) — as a real, storable value with a uniform execute() interface. This is the entire mechanism: turning "do this now" into "here's an object representing doing this, whenever someone calls .execute() on it" — which unlocks everything a bare method call can't do: queuing commands to run later, logging every command that ran, and undo (below).

Undo: possible because the command object can remember how to reverse itself

class TurnOnCommand(Command):
    def __init__(self, light):
        self._light = light
    def execute(self):
        self._light.turn_on()
    def undo(self):
        self._light.turn_off()  # the command KNOWS its own reverse operation
 
history = []
def run(command):
    command.execute()
    history.append(command)
 
def undo_last():
    if history:
        history.pop().undo()
 
run(TurnOnCommand(light))  # Light is ON
undo_last()                  # Light is OFF — undone without the caller knowing HOW to reverse it

Because each command is a real object, it can carry its own undo() logic alongside execute() — a history list of executed commands is all that's needed to support undo, since reversing the last action just means calling .undo() on whatever command object is on top. This is genuinely difficult to retrofit onto plain method calls after the fact, since a bare light.turn_on() call leaves no object around afterward that could be asked to undo itself.

How this differs from Strategy, since both wrap "a way of doing something"

The Strategy pattern (from its own lesson) wraps an algorithm — interchangeable ways of computing the same kind of result, chosen once and used repeatedly (a sort strategy, a pricing strategy). Command wraps a specific request, bound to specific arguments and a specific receiver, meant to be executed (once, later, or repeatedly) and potentially undone or logged — the object represents "this particular action," not "this general approach." A TurnOnCommand for light is a distinct object from a TurnOnCommand for a different light; two SortStrategy instances implementing the same algorithm are interchangeable.

The concrete signal a Command belongs somewhere

The tell: a request needs to be treated as a first-class value — queued for later execution (a job queue, a task scheduler), logged for auditing ("what actions were taken and by whom"), or made undoable (an editor's undo stack, a transaction that might need to roll back). If a request only ever needs to happen immediately, once, with no need to inspect, delay, or reverse it, a plain method call is simpler and Command is unnecessary ceremony.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What can a Command object do that a direct method call like `light.turn_on()` cannot?

2. How does the Command pattern make undo possible?

3. What's the structural difference between Command and Strategy?