Skip to main content

Strategy

complexitypopularity

Define a family of interchangeable algorithms, put each in its own class, and let the client decide which one the context uses.

The problem

You ship a navigation app that builds driving routes. Users love it, so you add walking routes. Then public transport. Cycling is on the roadmap, and somebody in marketing wants routes that pass every tourist attraction in town.

Each new mode doubles the size of the navigator class. A one-line tweak to a street score means editing the file that also contains the timetable logic, which means a chance of breaking something that worked yesterday. Your teammates spend their mornings resolving merge conflicts in the same class, because there is only one class where routing can possibly go.

The solution

Take the class that does one thing in many different ways, and pull each of those ways into a class of its own. Those are the strategies. The original class becomes the context and keeps a reference to one of them.

The context does not choose. The client passes in the strategy it wants, and the context talks to it through a generic interface with a single execution method. So the context has no idea which algorithm it holds and no reason to care: its actual job here is rendering checkpoints on a map, and checkpoints are checkpoints regardless of who computed them.

The payoff compounds. New algorithms arrive as new files. Existing algorithms change without touching the context or each other. And because the context exposes a setter, the user can flip from driving to walking between two taps.

Route buttons (client)Navigator (context)WalkingStrategyPublicTransportStrategysetStrategy(new WalkingStrategy())1buildRoute(home, museum)2buildRoute(home, museum)3returns [checkpoints]4render(checkpoints)5setStrategy(new PublicTransportStrategy())6buildRoute(home, museum)7
  1. 1The user taps the pedestrian icon. The client picks the algorithm, because only the client knows what the user wants.
  2. 2Then the ordinary request. The navigator's API does not change with the algorithm.
  3. 3The context delegates through the interface. It could not name the concrete class if you asked it.
  4. 4Back comes a list of checkpoints in the shared format. Different algorithm, same shape of answer.
  5. 5The navigator does the one job it actually owns: drawing the route on the map.
  6. 6The user changes their mind and taps the bus icon. Swapping the algorithm mid-flight costs one assignment.
  7. 7Same call, entirely different computation, and not a single line of Navigator was edited to make this possible.

Structure

Note what is missing from the diagram: any arrow from Navigator to a concrete strategy. That absence is the pattern. The only class that names WalkingStrategy is the client that constructs it.

«interface»RouteStrategybuildRoute(from, to): Checkpoint[]STRATEGYNavigator- routeStrategy: RouteStrategysetStrategy(strategy)buildRoute(from, to)CONTEXTRoadStrategybuildRoute(from, to): Checkpoint[]CONCRETEWalkingStrategybuildRoute(from, to): Checkpoint[]CONCRETEPublicTransportStrategybuildRoute(from, to): Checkpoint[]CONCRETE
implements

Code

Same example three ways: a navigator that renders routes, and a family of routing algorithms it knows nothing about.

// One class, every algorithm, and a conditional that grows forever.
class Navigator is
field mode: string
 
method buildRoute(from, to) is
if (mode == "road")
// Several hundred lines of road graph search, street scores,
// turn restrictions, toll avoidance...
else if (mode == "walking")
// Several hundred more, sidewalks and crossings, none of it
// sharing anything with the road code above.
else if (mode == "public-transport")
// Timetables, transfers, walking legs between stops...
 
// Every new travel mode doubles this class. Every bug fix in one
// branch risks the other two. Two teammates editing two modes get
// the same merge conflict, every single week.
// The context delegates and gets on with its actual job.
class Navigator is
private field routeStrategy: RouteStrategy
 
method setStrategy(strategy: RouteStrategy) is
this.routeStrategy = strategy
 
method buildRoute(from, to) is
render(routeStrategy.buildRoute(from, to))
 
// Each algorithm lives alone, testable in isolation:
class WalkingStrategy implements RouteStrategy is
method buildRoute(from, to) is
// sidewalks and crossings only
return checkpoints
 
// Cycling routes ship next sprint as one new file.
// Navigator does not get reopened, and neither does the merge conflict.
// The strategy interface is the only thing the context knows about.
interface RouteStrategy is
method buildRoute(from, to): array of Checkpoint
 
// Each concrete strategy implements one variant of the algorithm. Their
// internals share nothing, and that is fine.
class RoadStrategy implements RouteStrategy is
method buildRoute(from, to) is
// Search the road graph, respecting turn restrictions and tolls.
return checkpoints
 
class WalkingStrategy implements RouteStrategy is
method buildRoute(from, to) is
// Search sidewalks and crossings; motorways are off limits.
return checkpoints
 
class PublicTransportStrategy implements RouteStrategy is
method buildRoute(from, to) is
// Combine timetables, transfers, and short walking legs.
return checkpoints
 
// The context. Its job is rendering, not routing.
class Navigator is
// It holds a strategy but never learns the concrete class.
private field routeStrategy: RouteStrategy
 
constructor Navigator(strategy) is
this.routeStrategy = strategy
 
// A setter lets the UI swap algorithms while the app is running.
method setStrategy(strategy: RouteStrategy) is
this.routeStrategy = strategy
 
// Delegation, then the context's own real work.
method buildRoute(from, to) is
checkpoints = routeStrategy.buildRoute(from, to)
render(checkpoints)
return checkpoints
 
// The client knows what the user wants, so the client picks.
class Application is
method main() is
navigator = new Navigator(new RoadStrategy())
 
if (userTapped == "walk") then
navigator.setStrategy(new WalkingStrategy())
 
if (userTapped == "transit") then
navigator.setStrategy(new PublicTransportStrategy())
 
navigator.buildRoute(currentLocation, destination)

When to use it

  • You need several variants of an algorithm inside an object and the ability to switch between them at runtime.
  • You have a pile of near-identical classes that differ only in how they perform one behavior. Extract the behavior, collapse the classes.
  • Business logic is tangled with algorithmic detail that is irrelevant to it. Strategy isolates the algorithm's code, data, and dependencies behind one method.
  • A massive conditional selects between variants of the same computation. Each branch becomes a class and the conditional disappears.

Pitfalls

  • Class explosion for nothing. Two algorithms that have not changed since 2019 do not need an interface and a factory. Add the seam when the churn arrives, not before.
  • Clients must know the menu. Someone has to choose correctly, so the differences between strategies must be documented where the choosing happens.
  • Leaky context. If strategies need context data, pass it as parameters or expose a narrow interface. Handing each strategy the whole context recreates the coupling you removed.
  • Ignoring lambdas. In a language with first-class functions, a one-method strategy is a function. Reaching for a class hierarchy anyway is ceremony, not design.

Don't confuse it with

  • State. The class diagrams are nearly the same, so lean on intent. Strategy hands the client a menu of independent, mutually unaware algorithms. State models a lifecycle in which the current object knows the context, often knows its siblings, and installs its own replacement. State is Strategy with the "no talking to each other" rule removed.
  • Template Method. The inheritance-flavored answer to the same question. Template Method fixes an algorithm's skeleton in a superclass and lets subclasses override steps, so the variation is chosen at the class level and frozen at compile time. Strategy composes the variation into an object, so it can change per instance, mid-run. Prefer Template Method when the variants share substantial structure; prefer Strategy when they need to be swapped or are genuinely unrelated inside.
  • Command. Both hand behavior to an object. Command exists to make an operation into a first-class thing you can queue, log, ship over the wire, or undo. Strategy exists to make one operation replaceable.
  • Decorator. Decorator wraps an object to change its skin; Strategy plugs into an object to change its guts.

Check yourself

Question 1 of 5

Who is responsible for choosing which concrete strategy the context uses?