System design sample solution

System design: build a feature flag platform

A step-by-step system design answer you could actually give in 60 minutes.

System design interview · Sample 60-minute answer · Not Stripe's actual design · Independent, not affiliated with Stripe

The system design question

Design a feature flag system for Stripe's engineering organization. Engineers can turn features on or off, roll them out to a percentage of merchants or users, and keep explicit include and exclude lists. It has to work across all of Stripe's backend services.

Agree on the scope (5 minutes)

Say up front what you're covering.

We'll designWe'll skip
Creating flags and their rulesMeasuring experiment results
Checking a flag inside any backend serviceThe admin UI design
Getting flag changes out to every service quicklyFlags in browsers and mobile apps
A history of who changed whatGeneral config that isn't on or off

Then agree on what "good" means:

  • Nearly free to check: a flag check is an in-memory lookup, well under a millisecond, with no network call.
  • Keeps working: if the flag service goes down, every service keeps using the last flags it saw.
  • Quick to change: when someone flips a flag, every service sees it within a few seconds.
  • Stable: a merchant in a 10% rollout gets the same answer on every request and in every service.
  • Safe: every change is recorded, and a bad change can be undone in one click.

Rough numbers (5 minutes)

These are assumptions, but reasonable ones: 5,000 active flags, 500 services running on about 20,000 processes, a million flag checks a second across all of them, and a few hundred flag changes a day.

~5K
active flags
~20K
processes that check flags
~1M
flag checks per second
~10 MB
for every flag and rule
QuestionRough mathWhat it means
Can each check call a flag service?1M calls a second, each adding about 1 msNo. That's a huge service to run, and it'd slow down every request. Check flags in memory
Does every flag fit in memory?5,000 flags × about 2 KBAbout 10 MB. Every process can hold a full copy
What if we resend everything on each change?300 changes × 20,000 processes × 10 MBAbout 60 TB a day. So send only the flags that changed

The overall design (10 minutes)

Admin APIengineer edits a flagFlag storeversioned rulesChange feednew versionsHost agentkeeps a local copyService codechecks in memory
Changes flow out to a copy on every host. Checks never leave the process.
  1. An engineer creates or edits a flag through the admin API. The flag store saves the new rules and bumps a global version number.
  2. The change is published to a change feed, and a fresh full snapshot of all flags is written to file storage now and then.
  3. A small agent on each host keeps an open connection to the flag service. When the version moves, it pulls the changed flags and writes them to local disk.
  4. Each service uses a small library that loads the local copy into memory and answers is_enabled(flag, merchant) with no network call.
  5. The library counts how often each flag is checked and reports it in the background, so we can spot flags nobody uses any more.

What the apps need to do

ActionWho does itWhat's sent
Create or edit a flagEngineerFlag name, on or off, rollout percentage, include and exclude lists, and a reason
Kill switchEngineerFlag name. Turns it off everywhere at once
Get all flagsHost agent, on startupNothing. Gets the latest snapshot and its version
Watch for changesHost agentThe version it has. Gets back any flags changed since
Check a flagService code, in memoryFlag name, merchant ID, and user ID if there is one
See a flag's historyEngineerFlag name. Gets every change, who made it and why

What we store and where

DataWhere it goesWhy
Flags and rulesRelational databaseSmall, changed rarely, and edits need to be all or nothing
Change historyAppend-only table in the same databaseWho changed what and when, and lets us roll back
Full snapshotsFile storage, one per versionNew hosts can start from one file instead of asking for every flag
Local copyEach host's disk, plus memory in each processChecks stay fast, and survive the flag service being down

Step 1: Checking a flag (10 minutes)

The check runs in order, and the first thing that matches wins: if the flag's switched off, return false. If the merchant's on the exclude list, false. If they're on the include list, true. Then the percentage rollout. If nothing matched, return the flag's default.

For the percentage, hash the flag name and the merchant ID together into a number from 0 to 9,999. If that number is below the rollout (500 for 5%), the flag's on. The same merchant always lands in the same bucket, so they get the same answer every time, in every service, without storing anything per merchant.

Two details matter here. Mixing in the flag name means different flags don't all pick the same 5% of merchants first. And when you go from 5% to 10%, everyone below 500 stays below 1,000, so nobody who had the feature loses it.

  • Include and exclude lists load into a hash set, so checking one is a single lookup.
  • Some lists get huge, like 'every merchant in Japan'. Make those named groups that live once and get shared by many flags, instead of copying 100,000 IDs into each one.
  • If code asks for a flag that doesn't exist, maybe a typo, return the default written in the code and log a warning.

Step 2: Getting changes out, and surviving outages (15 minutes)

Each host runs one agent rather than every process talking to the flag service. That cuts 20,000 connections down to a few thousand. The agent sends the version it has and holds the request open until something changes, then gets just the changed flags. Changes land in a second or two.

The agent writes everything to local disk, and the library in each process reads from there. That's caching at every layer, and it's what keeps things working in an outage. If the flag service dies, agents keep the last copy and services keep checking flags as normal. They just don't see new changes until it's back.

That's a deliberate choice of availability over consistency, which is the CAP trade-off. For a few seconds after a change, one service might see the new value and another the old one. For flags that's fine. Make sure people know that, and don't use flags for anything that has to change everywhere at exactly the same moment.

Starting up when things are broken

A brand new host starts by downloading the latest snapshot from file storage, which is simple and easy to keep up even when the flag service isn't. If even that fails, the library uses the defaults written in code. Every flag check in code has a default, and code review should check that it's the safe choice, usually 'off'.

Lots of hosts reconnecting at once

When the flag service restarts, thousands of agents reconnect together. Put a few relay servers between them, each serving a share of agents. Agents wait a random few seconds before reconnecting so they don't all arrive at once.

Step 3: Making changes safe (5 minutes)

A flag change can break production as easily as a code deploy can. So treat it like one: every change needs a reason, gets recorded with who made it, and creates a new version you can roll back to.

For risky flags, ramp up in steps, like 1%, 5%, 25% then 100%, and watch error rates between steps. Flags tagged as critical can require a second person to approve the change.

Old flags pile up. Use the check counts each library reports to find flags that have been at 100% for a month, and nag their owners to delete them.

Choices you should be able to defend

We choseInstead ofBecauseWe'd reconsider if
Check flags in memoryCall a flag service on each checkChecks are nearly free, and outages don't break themFlags had to change everywhere instantly
Hash the merchant into a bucketStore each merchant's assignmentNothing to store, and it's the same in every serviceWe needed to hand-pick exactly who's in
Keep serving old flags in an outageFail closed, or block until freshA flag service outage doesn't become an everything outageA flag guarded something unsafe to run on old data
One agent per hostEach process connects itselfFar fewer connections, and one copy on diskServices ran somewhere with no host agent
Send only what changedResend everything every timeTiny updates, so changes land in secondsThe whole set was small enough to resend cheaply

What could go wrong (5 minutes)

What happensWhat we do about it
The flag service is downServices keep checking flags against their last copy. Changes wait until it's back
Someone sets a flag to 100% by mistakeRoll back to the previous version from the history. It reaches every host in seconds
A host's agent crashesProcesses keep their copy in memory. The agent restarts and catches up from its version
A new host can't reach anythingIt falls back to the defaults written in code, which should be the safe choice
A flag name has a typo in codeIt returns the code default and logs a warning, which shows up in the dashboard

Follow-up questions (5 minutes)

How would you run experiments on top of this?

Log which bucket each merchant landed in alongside the metrics you care about, and compare them in a separate analytics system. The flag system just needs stable buckets, which hashing already gives you.

What about multiple regions?

Keep one main flag store, and put relay servers and snapshot copies in each region. If a region's cut off, it keeps its last copy, same as any other outage.

Can a flag depend on another flag?

You can allow 'only if flag X is on', but check for loops when someone saves the rule. Keep it rare, since chains of flags get hard to reason about fast.

How the 60 minutes add up

MinutesWhat you're doing
0–5Agree on what you're building
5–10Rough numbers
10–20Draw the overall design
20–30Checking a flag
30–45Getting changes out, and surviving outages
45–50Making changes safe
50–55What could go wrong
55–60Follow-up questions

Key concepts used here

Further reading

We wrote this sample answer ourselves.

Practice Loop is independent and isn't affiliated with or endorsed by Stripe.