I. Command — Behavioral Pattern
The Command pattern encapsulates a request as a standalone object that contains all information needed to perform an action. This decouples the object that invokes the operation (the Invoker) from the one that knows how to perform it (the Receiver).
The key insight is that a command object acts as a bridge: instead of the Invoker calling a method directly on the Receiver, it calls Execute() on a Command object — which internally delegates to the Receiver. This indirection unlocks powerful capabilities: commands can be queued, logged, or undone, without the Invoker ever knowing the details.
This pattern is especially useful when you need to support undo/redo, transactional operations, or deferred execution — scenarios where a raw function call is simply not enough.
II. Real-world Example
Imagine a manufacturing line controlled by a remote controller. The remote can issue commands like "add wheels" or "paint the frame" — and it can also undo the last step if something goes wrong.
- Receiver (
ManufacturingContext) — knows how to assemble parts; maintains the current state of the vehicle being built.
- Command interface (
Command) — declaresExecute()andUndo()methods that every command must implement.
- ConcreteCommands (
AddWheelsCommand,PaintFrameCommand) — each wraps a specific action on theManufacturingContextand implements its own reversal logic.
- Invoker (
ManufacturingRemote) — holds a history of commands; callsRun()to execute andUndoLast()to reverse.
- Client — wires everything together: creates the context, instantiates commands, and hands them to the remote.

III. Implementation
// command.go
package command
type Command interface {
Execute() string
Undo() string
}// manufacturing_commands.go
package command
import "strings"
type ManufacturingContext struct {
parts []string
}
func NewManufacturingContext() *ManufacturingContext {
return &ManufacturingContext{}
}
func (c *ManufacturingContext) Snapshot() string {
return strings.Join(c.parts, ", ")
}
type AddWheelsCommand struct {
ctx *ManufacturingContext
}
func NewAddWheelsCommand(ctx *ManufacturingContext) *AddWheelsCommand {
return &AddWheelsCommand{ctx: ctx}
}
func (c *AddWheelsCommand) Execute() string {
c.ctx.parts = append(c.ctx.parts, "wheels")
return "Added wheels"
}
func (c *AddWheelsCommand) Undo() string {
if len(c.ctx.parts) == 0 {
return "Nothing to undo"
}
c.ctx.parts = c.ctx.parts[:len(c.ctx.parts)-1]
return "Removed wheels"
}
type PaintFrameCommand struct {
ctx *ManufacturingContext
}
func NewPaintFrameCommand(ctx *ManufacturingContext) *PaintFrameCommand {
return &PaintFrameCommand{ctx: ctx}
}
func (c *PaintFrameCommand) Execute() string {
c.ctx.parts = append(c.ctx.parts, "painted frame")
return "Painted frame"
}
func (c *PaintFrameCommand) Undo() string {
if len(c.ctx.parts) == 0 {
return "Nothing to undo"
}
c.ctx.parts = c.ctx.parts[:len(c.ctx.parts)-1]
return "Removed paint"
}// manufacturing_remote.go
package command
type ManufacturingRemote struct {
history []Command
}
func NewManufacturingRemote() *ManufacturingRemote {
return &ManufacturingRemote{}
}
func (r *ManufacturingRemote) Run(command Command) string {
result := command.Execute()
r.history = append(r.history, command)
return result
}
func (r *ManufacturingRemote) UndoLast() string {
if len(r.history) == 0 {
return "No commands to undo"
}
last := r.history[len(r.history)-1]
r.history = r.history[:len(r.history)-1]
return last.Undo()
}IV. Explain the example above
Step 1 — Setup. The client creates a ManufacturingContext (receiver) and a ManufacturingRemote (invoker). It then instantiates two commands, each holding a reference to the same context.
ctx := NewManufacturingContext() // parts: []
remote := NewManufacturingRemote() // history: []Step 2 — Execute AddWheels. The remote calls Run(addWheels). Internally, Execute() appends "wheels" to ctx.parts and the command is pushed onto the history stack.
remote.Run(addWheels) // → "Added wheels"
// ctx.parts = ["wheels"]
// history = [AddWheelsCommand]Step 3 — Execute PaintFrame. Same pattern: Execute() appends "painted frame" and the command is recorded.
remote.Run(paintFrame) // → "Painted frame"
// ctx.parts = ["wheels", "painted frame"]
// history = [AddWheelsCommand, PaintFrameCommand]Step 4 — Undo. The remote pops the last command from history and calls Undo() on it. PaintFrameCommand.Undo() slices the last element off ctx.parts.
remote.UndoLast() // → "Removed paint"
// ctx.parts = ["wheels"]
// history = [AddWheelsCommand]Step 5 — Undo again. Calling UndoLast() once more removes the wheels.
remote.UndoLast() // → "Removed wheels"
// ctx.parts = []
// history = []Notice that ManufacturingRemote never knows what "add wheels" or "paint" actually means — it just calls Execute() and Undo(). Adding a new operation (e.g., AssembleEngineCommand) requires zero changes to the remote.
V. Conclusion
The Command pattern shines when you need decoupling between who triggers an action and who performs it. The history stack makes undo/redo trivially composable, and commands can be serialized, queued, or batched without touching the Invoker.
However, it comes at a cost: every operation becomes a new struct, which can balloon the codebase when you have dozens of commands. In Go, closures often serve as lightweight commands for simple fire-and-forget scenarios — reach for the full pattern only when you genuinely need history, reversibility, or parametrized operations.
No pattern is universally superior.
VI. References
- Go Design Patterns (Mario Castro Contreras)




