The Strategy pattern defines a family of interchangeable algorithms (strategies), encapsulates each one in its own type, and lets the caller swap them at runtime without changing the code that uses them. The object that holds and delegates to a strategy is called the context.
It sits in the Behavioral family because it defines how objects collaborate to carry out behavior — the context delegates what to do; the strategy decides how to do it.
- You have multiple variants of an algorithm and want to switch between them at runtime.
- You want to eliminate large
if/elseorswitchblocks that select behavior based on a type or flag. - Different parts of the system need to compose or extend behavior without inheriting from a common base class.
Avoid it when you only ever have one algorithm and the extra indirection adds no value.
Classical OOP uses an abstract Strategy base class. Go replaces this with an interface — leaner and more composable:
| Mechanism | Purpose |
|---|---|
PaymentMode interface |
Declares the strategy contract (Name, Settle) |
Concrete mode structs (IMPSMode, UPIMode, NEFTMode) |
Each encapsulates one payment algorithm |
PaymentExecutor (context) |
Holds a PaymentMode field and delegates execution to it |
SetPaymentMode |
Swaps the strategy at runtime with no structural change |
strategy/
├── payment_transfer_strategy/
│ └── transfer_modes.go # Interface, context, and all concrete strategies
└── main.go # Usage demo
The strategy interface — payment_transfer_strategy/transfer_modes.go
type PaymentMode interface {
Name() string
Settle(id string, amount float64) error
}Any type that implements Name and Settle is a valid payment strategy — no embedding, no registration required.
PaymentExecutor holds the active strategy in a strategy PaymentMode field. It exposes two methods:
| Method | Behavior |
|---|---|
SetPaymentMode(mode) |
Replaces the current strategy |
ExecuteSettlement(id, amount) |
Guards (nil strategy, non-positive amount), then delegates to strategy.Settle |
The guard logic lives in the context, not in individual strategies — they only contain the algorithm itself.
| Type | Description |
|---|---|
IMPSMode |
Immediate Payment Service — real-time interbank transfer |
UPIMode |
Unified Payments Interface — mobile-first instant transfer |
NEFTMode |
National Electronic Funds Transfer — batch-settled transfer |
All three implement the same PaymentMode interface and are fully interchangeable from the context's perspective.
var executor PaymentExecutor
executor.SetPaymentMode(&NEFTMode{})
executor.ExecuteSettlement("aniket", 9999999)
// → [NEFT:Settle] Amount 9999999.000000 to aniket
executor.SetPaymentMode(&UPIMode{})
executor.ExecuteSettlement("aniket", 500)
// → [UPI:Settle] Amount 500.000000 to aniketThe executor's code never changes — only the injected strategy does.
- Define a new struct (e.g.,
RTGSMode). - Implement
Name() stringandSettle(id string, amount float64) error. - Pass it to
SetPaymentMode.
No existing code changes — the context, the interface, and the other strategies are untouched. This is the Open/Closed Principle in practice.
Q: How does Strategy differ from Chain of Responsibility? Strategy selects one algorithm and executes it. Chain of Responsibility passes a request through multiple handlers in sequence, each of which may or may not handle it. Strategy is about substitution; CoR is about delegation and short-circuiting.
Q: How does Strategy differ from a simple function argument? When the algorithm is a single function with no state, a function argument (first-class function) is simpler and idiomatic Go. Strategy makes sense when the algorithm needs its own state, configuration, or helper methods — the struct form gives you that.
Q: Where does the switching logic live?
In the caller that calls SetPaymentMode. Strategy does not eliminate the selection decision — it moves it to a clean, explicit call site rather than burying it inside if/else branches.
Q: Is this the same as dependency injection?
Structurally yes — injecting a PaymentMode into PaymentExecutor is DI. Strategy is the GoF pattern name for this specific form of DI applied to interchangeable algorithms.
Q: How would you make PaymentExecutor thread-safe?
Protect the strategy field with a sync.RWMutex — a read lock in ExecuteSettlement, a write lock in SetPaymentMode.