Hot-swappable Components Expert¶
When you'd use this
Change behavior at runtime with feature flags, strategy swaps and dynamic reloading.
Reload or replace parts of a running system without downtime — for long-running services and live-reconfigurable platforms.
What you'll learn¶
- Swap a component's behavior at runtime with no restart
- Toggle code paths with feature flags
- Reload modules dynamically with
importlib.reload - Do zero-downtime deploys (rolling updates)
- Understand the real limits and dangers of live reloading
The idea¶
The idea — a key concept in Hot-swappable Components.
"Hot-swappable" means changing part of a running system without stopping it. Three levels, from safest to riskiest:
- Swap an object's strategy — replace a pluggable behavior via a reference. Trivial and safe.
- Feature flags — toggle which code path runs, at runtime. Safe and widely used.
- Reload code (
importlib.reload) — replace a module's code in a live process. Powerful but full of sharp edges.
Plus the deployment-level answer — rolling updates — which achieves zero downtime by swapping whole instances rather than reloading code in place.
Level 1: swap a strategy at runtime¶
Change behavior by swapping an injected function/object — no restart.
If a component holds its behavior behind an interface, swapping it is just reassigning a reference. Runnable:
class PricingStrategy:
def price(self, base: float) -> float: ...
class StandardPricing(PricingStrategy):
def price(self, base: float) -> float:
return base
class BlackFridayPricing(PricingStrategy):
def price(self, base: float) -> float:
return round(base * 0.7, 2)
class Checkout:
def __init__(self, strategy: PricingStrategy) -> None:
self._strategy = strategy
def set_strategy(self, strategy: PricingStrategy) -> None:
self._strategy = strategy # hot swap — no restart
def total(self, base: float) -> float:
return self._strategy.price(base)
cart = Checkout(StandardPricing())
print("before swap:", cart.total(100.0))
cart.set_strategy(BlackFridayPricing()) # flip behavior live
print("after swap: ", cart.total(100.0))
Output:
The running Checkout object changed its pricing behavior mid-flight. No process restart, no re-import — just a new strategy object. This is the safest form of hot-swapping and the one to prefer: build components around interfaces (see Plugin-based Architectures) and swapping becomes trivial.
Level 2: feature flags¶
Toggle features at runtime for gradual rollout and quick rollback.
A feature flag is a runtime switch that selects which code path executes. It decouples deploying code from releasing it — you ship the new path dark, then flip it on when ready.
flags = {"new_checkout": False}
def checkout_flow() -> str:
if flags["new_checkout"]:
return "NEW checkout flow"
return "OLD checkout flow"
print(checkout_flow())
flags["new_checkout"] = True # flip at runtime
print(checkout_flow())
Output:
In production the flag values come from a config service or a flag platform (LaunchDarkly, Unleash, or a simple database table), refreshed periodically — so you flip a switch in a dashboard and running processes pick it up without deploying.
Why teams love flags:
- Decouple deploy from release — merge and deploy anytime; release when the business is ready.
- Gradual rollout — enable for 1% of users, then 10%, then everyone.
- Instant kill switch — a bad feature is turned off in seconds, no rollback deploy.
- A/B testing — route different users to different paths.
Flags are debt if you don't remove them
Every flag is a live branch in your code. Left forever, they accumulate into a tangle of if flag_a and not flag_b conditions nobody understands. Delete a flag (and the dead path) once the feature is fully rolled out. Track flags with expiry dates.
Level 3: reloading code with importlib.reload¶
Reload changed modules in a running process (with caveats).
Python can re-execute a module's source in a running process, replacing its code:
import importlib
import mymodule
importlib.reload(mymodule) # re-runs mymodule's source, updating it in place
This is what powers dev-server auto-reload and interactive workflows. But for production it's a minefield:
importlib.reload does NOT do what people hope
- Existing objects keep the OLD class. Instances created before the reload still point at the previous class definition — you get a confusing mix of old and new. Only new instances use the reloaded code.
- Other modules keep old references. If module B did
from A import thing, reloading A does not update B'sthing— B still holds the old one. - Module-level state is re-run. Reloading re-executes top-level code, resetting or duplicating globals, registrations, and side effects.
- No rollback. If the new code has a syntax error mid-reload, you can leave the module half-updated and the process broken.
Because of these, importlib.reload is great for development and interactive experimentation, and a poor choice for updating production code. For production, don't reload code in place — replace whole processes with a rolling update (below).
Zero-downtime deploys: rolling updates¶
Replace instances gradually so the service never fully goes down.
The production-grade way to "hot swap" code is at the infrastructure level, not inside the Python process. Instead of reloading a module, you start new instances running the new code and retire the old ones gradually, so the service never fully goes down.
Rolling update (3 instances, one at a time):
[v1] [v1] [v1] ← all serving v1
[v2] [v1] [v1] ← replace one; traffic still served by the others
[v2] [v2] [v1]
[v2] [v2] [v2] ← done, zero downtime
- Kubernetes does this natively (
RollingUpdatestrategy) with readiness probes so traffic only goes to instances that are actually ready. - Blue-green runs two full environments and switches traffic all at once.
- Canary sends a small slice of traffic to the new version first, watches metrics, then proceeds.
The application's job is to make this safe: start fast, expose a health/readiness check, and drain gracefully (finish in-flight requests before shutting down). See the CI/CD and Kubernetes topics for the mechanics.
Choosing the right level¶
Match the swap mechanism to how dynamic you actually need to be.
| Need | Use | Restart? |
|---|---|---|
| Swap a pluggable behavior | Strategy reassignment | No |
| Toggle / gradually release a feature | Feature flag | No |
| Update code during development | importlib.reload | No (dev only) |
| Update code in production | Rolling update (new instances) | Per-instance, zero downtime overall |
Prefer swapping data/objects over reloading code
Reassigning a strategy or flipping a flag changes behavior by changing values, which is predictable. Reloading code in a live process is unpredictable. When you think you need importlib.reload in production, you almost always want a feature flag or a rolling deploy instead.
Practice exercises¶
- Add a
PremiumPricingstrategy and swap between all three at runtime, printing the total after each swap. - Turn the flag dict into a class that reads flags from a JSON file and re-reads on demand, so you can flip a flag by editing the file.
- Demonstrate the
importlib.reloadgotcha: create an instance, reload its module after changing a method, and show the old instance still uses the old behavior. - Write a graceful-shutdown handler (catch SIGTERM) that stops accepting new work but finishes in-flight tasks — the piece that makes rolling updates safe.
- Explain to a teammate why a feature flag is safer than
importlib.reloadfor turning a new feature on in production.
💬 Discussion
Have a question about this topic? Found an error? Share your thoughts below.