🧭 Analogy
Getting to the airport, you pick a strategy: drive, take the train, or hail a cab. The goal is identical — only the algorithm changes. You choose based on time, budget and traffic, and you can switch on the day without changing your destination.
The problem
A type needs to perform a task that has several interchangeable implementations. The naive approach hard-codes them with a conditional:
func (d *Duck) Fly(kind string) {
switch kind {
case "wings":
// flap...
case "rocket":
// ignite...
case "none":
// can't fly
}
}
Every new behavior means editing Fly and growing the switch — the type is closed against extension but you keep opening it. Strategy pulls each algorithm into its own type behind a shared interface, so behavior becomes a value you set, not a branch you add.
Structure
classDiagram
class Duck {
-flyBehavior FlyBehavior
+PerformFly()
+SetFlyBehavior(FlyBehavior)
}
class FlyBehavior {
<<interface>>
+Fly()
}
class FlyWithWings { +Fly() }
class FlyNoWay { +Fly() }
Duck o--> FlyBehavior : delegates to
FlyBehavior <|.. FlyWithWings
FlyBehavior <|.. FlyNoWay
The Duck (the context) holds a FlyBehavior and delegates to it. Swapping the concrete behavior changes what the duck does — at runtime, without touching Duck.
How it works
sequenceDiagram
participant C as Client
participant D as Duck (context)
participant F as FlyBehavior
C->>D: SetFlyBehavior(FlyWithWings)
C->>D: PerformFly()
D->>F: Fly()
F-->>D: "Flying with wings"
C->>D: SetFlyBehavior(FlyNoWay)
C->>D: PerformFly()
D->>F: Fly()
F-->>D: "Can't fly"
Idiomatic Go — the interface form
Each behavior is its own type satisfying a tiny interface, and the duck delegates. Edit and Run it:
package main
import "fmt"
// Strategy interface + two implementations
type FlyBehavior interface{ Fly() }
type FlyWithWings struct{}
func (FlyWithWings) Fly() { fmt.Println("Flying with wings") }
type FlyNoWay struct{}
func (FlyNoWay) Fly() { fmt.Println("Can't fly") }
// Context holds a strategy and delegates to it
type Duck struct{ fly FlyBehavior }
func (d *Duck) SetFly(fb FlyBehavior) { d.fly = fb }
func (d *Duck) PerformFly() { d.fly.Fly() }
func main() {
mallard := &Duck{fly: FlyWithWings{}}
mallard.PerformFly() // Flying with wings
mallard.SetFly(FlyNoWay{}) // swap behavior at runtime
mallard.PerformFly() // Can't fly
}
You can extend this with a QuackBehavior too, plus concrete MallardDuck and RubberDuck types that embed a base Duck — a great look at composition.
Idiomatic Go — the function form
Here’s where Go diverges from Java. When a strategy is a single operation, you don’t need an interface and a struct at all — a function value is the strategy:
type FlyFunc func()
type Duck struct{ fly FlyFunc }
func (d *Duck) PerformFly() { d.fly() }
mallard := &Duck{fly: func() { fmt.Println("Flying with wings") }}
mallard.PerformFly()
mallard.fly = func() { fmt.Println("Can't fly") } // swap in one line
🐹 Go reshapes this pattern
First-class functions collapse single-method Strategies into a func field or argument. Reach for the interface form when a strategy needs multiple methods or its own state; reach for the function form when it’s one operation. Both are Strategy.
In the standard library
sort.Slice(s, less)— you supply the comparison strategy as a function.http.HandlerFunc— an adapter that lets any function be a request-handling strategy.strings.Map(mapping, s)— the per-rune transformation is a pluggable strategy.template.FuncMap— inject formatting strategies into a template.
Pitfalls
⚠️ Don't over-engineer a single algorithm
If there’s truly only one implementation and no prospect of a second, an interface and a context type are ceremony with no payoff. Introduce Strategy when variation actually exists (or is clearly coming) — not speculatively.
When to use it — and when not
✅ Reach for it when
- You have several variants of an algorithm (sorting orders, pricing rules, compression schemes) and want to switch between them at runtime.
- You want to remove a sprawling if/else or switch that picks behavior.
- Callers should be able to plug in their own behavior without you editing the core type.
⛔ Think twice when
- There's only ever one algorithm and no realistic chance of a second — the indirection just adds noise.
- The 'strategies' differ by a single value, not behavior — a parameter is simpler than a type.
Related patterns
Let an object alter its behavior when its internal state changes, as if it changed class.
BEHAVIORAL Template MethodDefine the skeleton of an algorithm once, deferring the steps that vary to per-type implementations.
BEHAVIORAL CommandTurn a request into a standalone object, letting you parameterize, queue, log, and undo operations.
STRUCTURAL DecoratorAttach new behavior to an object by wrapping it in another object of the same interface — without changing the original's type.
Check your understanding
Score: 0 / 31. In Go, what is the most idiomatic form of a simple Strategy?
When a strategy is a single operation, a func value is the cleanest expression — exactly what sort.Slice's less argument is.
2. How does Strategy differ from State?
Both swap behavior behind an interface, but State objects usually decide their own next state, whereas a Strategy is selected from outside and rarely changes itself.
3. Which standard-library API is a Strategy in disguise?
sort.Slice takes the comparison strategy as a function, letting you sort the same data any way you like without changing the sort algorithm.