Zero-Downtime Deployment & Rollback

A system design interview guide to shipping a major change to a critical core platform without a single dropped request — and being able to undo it in seconds when the new version misbehaves.

The prompt is deceptively simple: “When rolling out a major update to a critical core platform, how do you design the deployment and rollback strategy to guarantee zero downtime?” The naive answer — stop the old version, start the new one — guarantees the opposite. Real zero-downtime deployment is a discipline built from a few interlocking ideas: never run two incompatible things at once, always change one variable at a time, expose the new code to a sliver of traffic before the whole fleet, watch objective health signals decide whether to proceed, and keep the previous good state one traffic-flip away at all times. This guide walks that toolkit — compatibility as the foundation, the expand/contract pattern for schema and API changes, progressive delivery with canaries and blue-green, feature flags that decouple deploy from release, staged rings with an explicit bug bar, automated guardrails, and the rollback strategies that make all of it safe. It is the sibling of Designing a Highly Reliable Service, viewed through the lens of the single riskiest thing you do to a running system: change it.

Contents

  1. Requirements & Scope
  2. Compatibility Is the Foundation
  3. The Expand / Contract Pattern
  4. Progressive Delivery
  5. Feature Flags & Dark Launches
  6. Rings, Dogfooding & the Bug Bar
  7. Rollback Strategy
  8. Automated Guardrails
  9. Stateful vs Stateless & Tradeoffs
  10. Summary

1. Requirements & Scope

Start by stating what “zero downtime” actually means for this platform, because it shapes every later choice. Frame the requirements crisply and confirm them with the interviewer.

Interview tip: Say explicitly that zero-downtime deployment is really a compatibility problem, not a tooling problem. Orchestrators, canaries, and flags are the machinery, but the thing that guarantees no downtime is that at every instant the versions and data shapes in production can coexist. Lead with that and the rest of the answer falls out of it.

2. Compatibility Is the Foundation

During any rollout there is a window where old and new code run side by side and talk to the same data and the same clients. Zero downtime is only possible if both versions can operate correctly during that overlap. That gives one non-negotiable rule: every change must be backward and forward compatible with the versions it will coexist with.

The cardinal sin, and the thing interviewers probe for, is combining a breaking change with a deploy in a single step. If you rename a column and ship the code that depends on the new name at the same moment, there is an instant where half the fleet expects the old name and half expects the new one, and neither the forward nor the backward direction holds. You have manufactured a window with no safe state and no clean rollback. The rule is absolute: never make a breaking change and a deploy in the same step. Split every breaking change into a sequence of individually compatible steps — which is exactly what the expand/contract pattern does. This is the deployment-time face of the same discipline covered in Reviewing API, Schema & Query Compatibility.

3. The Expand / Contract Pattern

The expand/contract pattern — also called parallel change — is how you make a change that looks breaking without ever having a breaking moment. You never mutate a thing in place; instead you add the new thing alongside the old, migrate everything onto it, and only then remove the old. It applies identically to a database column, an API field, a message schema, or an RPC method.

Expand, migrate, and contract phases: add new field, deploy readers, deploy writers dual-writing, backfill, cut over reads, then drop the old field
Expand adds the new shape so old and new coexist. The middle phases migrate readers, then writers (dual-write), then backfill historical rows and cut reads over to the new path. Contract removes the old shape only once nothing reads it. Every arrow between phases is an independently deployable, individually compatible, reversible step.

The three macro-phases, using the example of renaming a column full_name to display_name:

  1. Expand. Add the new display_name column (nullable, no default backfill yet). The schema now carries both. Old code ignores the new column entirely; nothing breaks. This migration is additive, so it is safe to run against a live system and safe to roll back.
  2. Migrate. Deploy code that reads from display_name but falls back to full_name, then deploy code that writes both (dual-write). Backfill the existing rows so display_name is populated everywhere, and verify parity between the two columns. Finally, cut reads fully over to display_name. At every sub-step both versions of the code work against the data as it exists.
  3. Contract. Once you are confident nothing reads or writes full_name — and you have baked long enough that a rollback would not need it — drop the old column. This is the only destructive step, and it happens last, deliberately, after the risk has been retired.
# Phase 1 — EXPAND: additive migration, both columns exist
ALTER TABLE users ADD COLUMN display_name TEXT NULL;   # old code ignores it

# Phase 2 — MIGRATE: dual-write + read-with-fallback in app code
function write_user(u):
  row.full_name    = u.name        # keep writing old (rollback safety)
  row.display_name = u.name        # also write new
  save(row)

function read_user(row):
  return row.display_name          # prefer new ...
      if row.display_name != null
      else row.full_name           # ... fall back to old

# backfill historical rows, then verify:  COUNT(display_name IS NULL) == 0
UPDATE users SET display_name = full_name WHERE display_name IS NULL;

# Phase 3 — CONTRACT: only after new code is fully baked & won't roll back
ALTER TABLE users DROP COLUMN full_name;               # irreversible — do it last

Two properties make this safe. First, each step is independently deployable — you deploy readers before writers so that no instance ever writes a shape another instance cannot read. Second, each step is reversible until the contract: if the dual-writing version regresses, you roll back to the read-with-fallback version, and the data it wrote is still readable because you never dropped the old column. The pattern converts one dangerous atomic change into a series of boring, safe ones.

4. Progressive Delivery

Compatibility makes coexistence possible; progressive delivery is how you actually move traffic from old to new while limiting blast radius. There are three canonical strategies, and a strong answer names all three and says when each fits.

Router splits traffic between a stable v1 fleet and a small canary v2 fleet, with a metric gate watching SLO and error rate to promote or abort
A weighted router sends a small slice of live traffic to the canary (v2) while the stable fleet (v1) serves the rest. A metric gate compares the canary’s SLIs against the baseline and either promotes to the next ramp step or auto-aborts by shifting traffic back to v1. The ramp climbs 1% → 10% → 50% → 100%.

The engine that makes canaries safe is the health/metric gate: at each ramp step, an automated analysis compares the canary’s error rate, latency percentiles, and key business metrics against the stable baseline running the same traffic. If the canary is statistically no worse, it is promoted to the next percentage; if it regresses, the rollout aborts and traffic shifts back. A typical ramp is 1% → 10% → 50% → 100%, with a bake time at each step long enough to catch slow-burning problems (memory leaks, cache pollution) that a thirty-second check would miss.

StrategyExtra capacityRollback speedBlast radius during rollout
Rolling updateNone (in-place)Slow (roll forward old)Grows as the fleet flips
Blue-green2× (full duplicate)Instant (flip router)All-or-nothing at the switch
CanarySmall (canary fleet)Fast (shift % back)Bounded to the canary %

5. Feature Flags & Dark Launches

The single most powerful idea in modern rollout is decoupling deploy from release. A deploy puts new code on the servers; a release turns a new behavior on for users. A feature flag is the switch between them: you ship the new code paths dark (present but off), deploy them normally, and then enable the behavior with a config change you can flip in seconds — no rebuild, no redeploy.

This buys several things at once. Rollback of a feature becomes flipping a flag off, which is faster and far lower-risk than rolling back a binary. You can enable a feature for internal users first, then a cohort, independent of the deploy cadence. And you separate the risk of the deploy (does the new binary run?) from the risk of the release (does the new behavior work?), so you never debug both at once.

Interview tip: The line “deploy is not release” signals maturity. It lets you say: the binary rolls out through a canary with health gates, but the feature itself is behind a flag ramped independently — so a bad feature is turned off with a flag flip while the binary stays put, and a bad binary is rolled back while the flag state is preserved. Two independent safety controls, two independent failure modes.

6. Rings, Dogfooding & the Bug Bar

Canary percentages control how much traffic sees the change; deployment rings control who sees it, in order of increasing blast radius and decreasing tolerance for bugs. The change graduates outward through the rings, and each boundary is a gate.

  1. Ring 0 — internal / dogfood. The team and then the whole company use the new version first (“eating your own dog food”). Employees hit real edge cases and report them, and the cost of a bug here is an annoyed colleague, not a customer.
  2. Ring 1 — small external cohort. A small percentage of real users, ideally ones who have opted into early releases or a region you can watch closely.
  3. Ring 2 — general availability. The full population, reached only after the earlier rings held.

What makes rings a discipline rather than a hope is an explicit bug bar: a pre-agreed set of severity thresholds that block promotion to the next ring. For example — any Sev1/Sev2, any regression in a core SLI beyond a set margin, or an error-rate delta above X% halts the rollout automatically; a cosmetic Sev3 does not. Defining the bar before the rollout removes the in-the-moment temptation to wave through a problem because a deadline looms. The bug bar turns “is this good enough to proceed?” from a judgment call under pressure into a rule checked against objective signals.

7. Rollback Strategy

A deployment strategy is only as good as its undo button. There are two fundamentally different ways to recover, and the whole reason the earlier compatibility work matters is that it keeps the fast one available.

Two rollback strategies side by side: instant blue-green traffic shift back to the healthy old version, versus roll-forward by detecting a regression, shipping a fix or flipping a kill switch, and re-deploying
Instant rollback (left) simply flips the router back to the old version, which was kept warm — recovery in seconds, no rebuild, only possible when the change is compatible. Roll-forward (right) is the fallback when a change cannot be cleanly reversed: detect, fix or kill-switch, and re-deploy forward.

The key insight is that irreversible DB migrations are what make rollback hard, and expand/contract is the antidote. Because the destructive contract step happens last and separately, a rollback during the risky middle phases never needs the dropped data — the old shape is still there. A few more data-safety rules make rollback trustworthy:

8. Automated Guardrails

For a critical platform, humans watching a dashboard are too slow and too unreliable to be the safety mechanism. The rollout should defend itself with automated gates wired to objective signals.

Interview tip: When asked “how do you know the canary is bad?”, resist “we watch the dashboards.” The strong answer is an automated gate: define the SLIs and thresholds up front, compare canary vs baseline statistically, and wire failure to an automatic traffic-shift back. The machine should catch and revert the regression before a human is even paged.

9. Stateful vs Stateless & Tradeoffs

Everything above is easiest when the tier being deployed is stateless — any instance can serve any request, so you can freely add, drain, and remove instances and shift traffic at will. Stateful components need extra care, and calling that out separates a real answer from a checklist.

The tradeoffs to name:

10. Summary

Zero-downtime deployment is compatibility plus progressive delivery plus a fast, safe undo. You never run incompatible versions together, you change one variable at a time, you expose the change to production in widening slices gated on objective health, and you keep the previous good state one traffic-flip away.

ConcernMechanism
How can old and new coexist during a rollout?Backward + forward compatibility as a hard rule; never a breaking change and a deploy in one step.
How do we make a “breaking” schema/API change safely?Expand / contract (parallel change): add new, dual-write, backfill, cut over, drop old last.
How do we move traffic without downtime?Rolling, blue-green, or canary — canary with a 1%→10%→50%→100% ramp for a critical platform.
How do we decide to promote or abort?Automated health/metric gates comparing canary vs baseline on SLIs and business KPIs.
How do we separate shipping code from releasing behavior?Feature flags; dark launches and shadow traffic to validate at scale with no user risk.
How do we bound who is exposed and when?Rings (internal → cohort → GA) with an explicit bug bar blocking promotion.
How do we roll back fast and safely?Instant traffic shift to the warm old version; roll-forward only when a migration is irreversible.
How do we make rollback trustworthy?Expand/contract so no dropped data is needed; idempotent, verified, resumable backfills.
How do we handle stateful tiers and version skew?Drain connections, deploy per-replica within quorum, externalize session state, compatible wire formats.
The recurring theme: there must never be a moment in production with no safe state. Compatibility guarantees old and new can coexist, expand/contract guarantees every schema change is reversible until the last step, progressive delivery guarantees a bad change hurts a sliver before the whole, and automated guardrails guarantee the system reverts before a human is paged. Zero downtime is not one trick — it is the sum of always keeping a way back.