Build it. The database schema, API, code and platform limits for feature flags and remote config are on a separate page: Build it: feature flags and remote config on Android and iOS (28 min read).
How to read this. This page is the 45 minutes, in the order I would spend them. Code, the contract and platform tables are in the build guide. The follow-up questions are my reconstruction from public prep material, not transcripts of real rounds.
TL;DR. My answer: the phone decides once per session, from values saved on disk. Every flag has a safe default in the code. New values wait for the next session. Only kill switches (flags that turn a feature off) change at once. They also have a fallback that works when the flag service does not.
1. Scope and constraints (minutes 0 to 4)
In short: Design the phone side and the contract. Experiments and personal data wait.
A prompt I would expect: "Design feature flags and remote config for our mobile app. Teams ship features behind flags and change settings without a release." I ask five questions, with a default for each:
- Who runs the flag server? Either. I design the phone and the contract.
- What values? On or off, plus short text values.
- Who gets what? A share of users, by platform and version.
- Offline? Yes. The app must start with no network.
- Experiments and personal data? Not now.
| In scope | Out of scope | Left out until you ask |
|---|---|---|
| On/off flags and short values | The flag dashboard | Large JSON config |
| Percentage rollout, platform, app version | The server rule engine | The same user on two phones |
| Saved copy, safe defaults, kill switch | Experiment statistics | Targeting with personal data |
The phone does four things to this design:
| What the phone does | Requirement at risk | Deep dive |
|---|---|---|
| Stops the app process and starts it again | Flags ready at the first screen | Section 2 |
| Stops background network access | A change reaches users who are away | Section 4.1 |
| Loses the signal or hangs during a fetch | The app works with no answer | Sections 2 and 4.2 |
| Runs on a battery and a data plan | No polling loop | Section 4.1 |
Your decision. The scope grows: "Also run A/B tests, and target by email domain." Which do you keep?
First idea, and why it fails. Say yes to both. An experiment needs a fixed group and a record of who saw what. Email targeting needs a privacy decision. Time runs out before the cold start path.
What I would say. Both change the data we send and save, so they wait.
Say this. "I build the flag path first: saved values, defaults, rollout and a kill switch. A/B tests and personal data each change what we send, so they come after. Tell me if you want one earlier."
The follow-up question I expect: "The paywall needs a JSON config, not just flags."
It returns as follow-up 2.
45-minute plan
| Minutes | Step | Drawn by the end | Cut if behind |
|---|---|---|---|
| 0 to 4 | Scope and constraints | Scope list, phone behaviors | Fewer questions |
| 4 to 14 | Data and contracts | Saved copy, defaults, bucket, the contract | The numbers |
| 14 to 22 | Client structure | One store, views per screen | One line per layer |
| 22 to 40 | Make the hard part work | When values apply, the kill switch | iOS notes |
| 40 to 45 | Break it, ship it, roll it back | Failures, release facts, cleanup rule | Two failures |
2. Data and contracts (minutes 4 to 14)
In short: The phone starts from the last saved answer. The code holds a safe default. The server picks the group.
Your decision. At app start, where do flag values come from? Wait for the network, use defaults from the code, or read the last saved answer from disk?
First idea, and why it fails. Fetch on launch and show a spinner until the answer arrives. With no signal, the user waits for a timeout. Firebase says its one-minute timeout may be too long for a good start (Firebase loading strategies). Android suggests a splash screen of at most 1,000 ms (Android splash screen).
What I would say.
Say this. "At start I read the last saved answer from disk. It needs no network. If there is none, each flag uses its default from the code. Then I fetch in the background. The new answer is saved for the next session."
The follow-up question I expect: "A new install, no network. What does the user get?"
The defaults. A default is not a guess. It is the behavior that already shipped and worked.
- A risky new feature defaults to off.
- A proven feature with a kill switch defaults to on.
- Firebase also says to set in-app defaults (loading strategies).
The follow-up question I expect: "You raise a rollout from 10 to 20 percent. How do the first 10 percent stay in?"
The server hashes a stable ID with the flag key into a rollout bucket. A bigger percentage only adds buckets.
- LaunchDarkly hashes the context key and kind into 100,000 buckets. Raising the share adds buckets (LaunchDarkly).
- Each flag picks its people on its own.
- The ID is the account ID when signed in. Firebase's percent rule maps each app instance, so a reinstall can change the group (Firebase client templates).
The contract, in four lines:
- Fetch: app version, platform and a stable ID in. Evaluated values and a version out.
- Values: a key to a boolean or a short string. The app ignores keys it does not know.
- Rules stay on the server. The phone gets answers, not rules (best practices).
- Users can read any fetched value, so no secrets in flags (Firebase get started).
These are my own assumptions, not measurements:
| Assumption | Arithmetic | What it decides |
|---|---|---|
| 40 flags, about 60 bytes each | 40 x 60 = 2,400 bytes | One small saved record, not a database table |
| 8 opens a day, fetch interval 12 hours | 24 / 12 = 2 fetches a day, not 8 | Keep the default interval. Live updates for kill switches only |
| 100,000 users, a bug breaks 1 use in 50, 2 uses a day each | 1% = 1,000 users x 2 = 2,000 uses, 40 failures a day. At 0.1%, 4 | The first rollout step is 1% |
Firebase's default minimum fetch interval is 12 hours (Firebase get started).
3. Client structure (minutes 14 to 22)
In short: One app-wide store owns the flags. A screen takes a view when it opens and keeps it.
Your decision. Who owns the flag state? Each screen's ViewModel (the class that holds a screen's state) asking the SDK, a service, or one store that lives as long as the app?
First idea, and why it fails. Each ViewModel asks the SDK whenever it needs a value. Two problems:
- A fetch can land between two reads. Two screens then disagree, or one screen changes while the user looks at it. Firebase says to avoid changing the UI while the user uses it (loading strategies).
- Android clears a ViewModel when its screen leaves the back stack, the list of opened screens (ViewModel).
What I would say. An app-wide store loads the saved copy before the first screen and hands out views. I reject a service, because nothing here must run in the background.
Say this. "One store lives as long as the app. It loads the saved values before the first frame. A screen takes a view when it opens, and every answer in that view stays the same. A kill switch is the one exception, and it is read each time."
The follow-up question I expect: "Two screens both read the same flag. Which answer wins?"
The same one. Both views come from the same session copy.
Screen (Compose / SwiftUI)
│ takes one FlagView when it opens
ViewModel
│
FlagStore (app-wide) ◄── start(): read saved copy, local only
│ session copy: fixed ▲
│ kill switches: live │ write for the NEXT session
▼ │
FlagSource (vendor SDK) ──► Disk (DataStore / UserDefaults)
▲
└── foreground refresh, live kill-switch updates
On Android the store starts in the Application class and saves to DataStore (a Jetpack store for small settings). On iOS it is a main-actor object with UserDefaults. Three rules:
- Screens never call the vendor SDK.
- The code never asks for a flag without a default.
- Only a kill switch changes while a screen is open.
4. Make the hard part work (minutes 22 to 40)
In short: New values wait for the next session. A kill switch is the one change that cannot wait.
4.1 When the app reads and applies values (minutes 22 to 32)
Your decision. When does the app fetch, and when do new values start to apply? On every launch and at once, on a timer, or in the background for the next session?
First idea, and why it fails. Fetch and activate at every launch, with a short cache. Two problems:
- The values flip while the user is on the screen.
- Ten opens a day make ten fetches. Firebase calls this a pattern to avoid (optimize fetch frequency).
What I would say.
Say this. "I use the values from the last session at once. In the background I fetch new values and save them for the next start. On a return to the foreground I fetch only if the saved copy is old. Kill switches are different: they change while the app runs."
Firebase documents this as the next-startup strategy (loading strategies).
The follow-up question I expect: "Product wants a change on every phone within an hour."
The limits:
- Live updates open one HTTP connection, and only in the foreground (Firebase real-time). LaunchDarkly mobile SDKs stream in the foreground and poll about once an hour in the background (SDK types).
- A push can start a fetch. But Apple calls background pushes low priority and not guaranteed, and advises two or three an hour at most (Apple). A push is an extra.
- Doze suspends network access on an idle Android phone (Doze). So the change waits for the next foreground.
4.2 The kill switch (minutes 32 to 40)
Your decision. A new feature crashes for 5% of users. How does the off switch reach them? The same config path, a faster path, or one that works even when the flag service fails?
First idea, and why it fails. Put the switch in the same config, with the default on, and use the normal timing.
- The saved copy can be up to 12 hours old, and the next-session rule means one more restart.
- A crash at start happens before any fetch can fix it.
- A new install offline gets the default. If it is on, the broken feature is on.
What I would say. The kill switch has four layers. Each covers a gap in the one before.
Say this. "New code defaults to off. A kill switch is read live, and a live update changes it in the foreground. If the app crashes twice at start, it ignores the saved copy and uses the defaults. And the server can refuse the new request, so it works even when the flag service is down."
The follow-up question I expect: "The bad value is already saved, and the app crashes at launch."
Count starts that never finish. After two, use the defaults and delete the saved copy.
- The next fetch can bring the bad value back, so fix the server value too. Firebase lets you roll the template back (client templates).
4.3 Where iOS changes the design
The design is the same. The Swift store is in the build guide.
- The foreground signal is the scene phase. The
activephase means the scene is in the foreground and interactive (Apple ScenePhase). - UserDefaults saves to disk later, so a write just before a kill can be lost (Apple UserDefaults). The next fetch repairs it.
5. Break it, ship it, roll it back (minutes 40 to 45)
In short: Each failure ends with the last good values or the defaults, never a blank screen or a stranger's flags.
| Failure | User sees | Data holds | Recovery | Basis |
|---|---|---|---|---|
| Long offline gap | The last saved behavior | Snapshot and fetch time | Fetch at the next foreground | Documented: set defaults (Firebase); the rest is design intent |
| Half-open connection or captive portal | No wait at start | Last snapshot | Fetch in the background | One-minute timeout documented (Firebase); the rest is design intent |
| Background kill or throttle | Old values for a while | Last snapshot | Next fetch window | Throttling documented (Firebase); the rest is my design |
| Process dies mid-write | Saved copy or defaults | One record, written whole | Read at next start | DataStore is transactional (Android); UserDefaults is my reading |
| Account switch with pending work | Only the new user's values | Snapshot tagged with a user | Refetch for the new user | Design intent |
| Old app version meets new server contract | Unknown keys ignored | Defaults | Version rules | Version rules documented (Firebase); the rest is design intent |
| Bad config published | A broken feature | Older template versions | Roll back | Rollback documented (Firebase) |
Releases:
- A Play staged rollout can be halted. Users who have the build keep it (Play Console help). An App Store phased release can be paused for up to 30 days (Apple).
- Old versions stay installed. Never change a flag's type in place. Add a new key and keep the old one.
- The flag is the kill switch, and it is the only rollback that reaches installed apps.
- The flag client itself cannot be flagged. Ship it one release before the first flag that needs it. Apple wants new features described to App Review, not hidden or dormant (App Review Guidelines 2.3.1).
Say this. "I cannot recall a bad build, so the rollback is a flag plus a fixed build. I would watch crash-free users per flag group, fetch failures and flags past their date. I left out experiments, JSON config and personal data."
6. Follow-ups that change a constraint
In short: Each follow-up changes one constraint. A strong answer says what breaks.
Practice prompts, not a claim about what any interviewer asks.
- A campaign sends ten million users to the app at 9 a.m. Strong: the next-session rule and the fetch interval keep most opens off the network. Add random delay to retries. Live connections have limits: Firebase allows 20 million, then falls back to normal fetch (quotas). Weak: "poll every minute."
- The paywall needs a JSON config. Strong: parse it into a typed object, use the default on any error, carry a schema version. Keep it small: Firebase caps all values at 1,000,000 characters (parameters). Weak: "parse it where it is used."
- One user, two devices, same experience. Strong: bucket by account ID, not by install. Firebase maps each app instance, so send an account bucket as a custom signal (client templates). Tests may need sticky assignment (sticky bucketing). Weak: "random per device."
- Legal says no email or location may leave the phone. Strong: send a bucket number or a coarse group, and keep the rules on the server. Name the cost: no targeting by those fields. IDs sent to a vendor are third-party data to declare to Apple (App privacy details). Weak: "hash the email."
- A bad value went live at 10:00 and 8% of users crash at start. Strong: roll the template back, use the kill switch, and rely on the crash guard for phones that saved the value. Weak: "roll back the release."
- The backend renames a flag and changes its type. Strong: old versions stay for months, so keep serving the old key and add a new one. Weak: assuming everyone updates.
7. What changes by level
In short: As I read it, the levels differ in who raises the problem.
These levels are my own reading of public prep material, not any company's rubric.
- Mid-level: a flag client, defaults, a saved copy, a percentage rollout. I want to hear: "With no network the app uses the default from the code."
- Senior: one decision per session, a stable bucket, a kill switch that works when config fails, and privacy of what is sent. I want to hear: "A new value waits for the next session, except a kill switch."
- Staff: flags with owners and expiry dates, cleanup as a rule, testing, and a rollout plan for the flag system itself (staff guide). I want to hear: "Every flag has an owner and a date, and the build fails when the date passes."
Common weak answers. A fetch that blocks the first screen. No default for offline. A new value applied in the middle of a screen. A random group per launch. A kill switch that needs a working config. No plan to remove flags. These repeat across public prep material. They are not a dataset.
Staff answer: the decision and the rollout
- Decision: one app-wide store, a list of flags declared in code (key, default, owner, expiry date, kill-switch mark), and the vendor behind a small interface. Unleash suggests lifetimes by type, such as 40 days for a release flag (feature flags). I would set my own.
- Alternative: call the vendor SDK from every screen. It is faster for a quarter. Then cleanup means a search across the whole app.
- Revisit trigger: a second app needs flags, or the vendor becomes a risk.
- Rollout: the store ships first with no flags. Then each flag goes out at 1%, 5%, 25% and 100%.
- Guardrail: crash-free users in the flag group must not drop against the control group. A flag past its date fails the build. After an archive, LaunchDarkly serves the fallback value from the code, so remove the code first (archiving).
Opinion vs verified: I read the cited pages on 14 October 2026. The store design, numbers, kill switch layers, crash guard and level text are my opinion.
Further reading (official docs)
- Build it: feature flags on Android and iOS: code, tests and platform limits for this case
- Remote Config loading strategies (Firebase)
- Optimize fetch frequency (Firebase)
- Real-time Remote Config (Firebase)
- Percentage rollouts (LaunchDarkly)
- Feature flag best practices (Unleash)
- Doze and App Standby (Android)
- ScenePhase (Apple)
Words on this page (15)
- Captive portal
- A login page that a Wi-Fi network shows before it gives access to the internet. The phone looks online, but requests fail.
- Cold start
- A start of the app when it was not running. It is slower than a start from the background.
- Crash-free rate
- The share of sessions that end without a crash.
- Doze
- The deep-sleep mode of an idle Android phone. It pauses network access and delays background work.
- Hash
- A short code made from the bytes of a file. Apps compare the codes to see if two files are the same.
- Kill switch
- A remote flag that turns a feature off at once. It is the one rollback that reaches apps that are already installed.
- Polling
- Asking the server again and again if there is news. It costs battery and data.
- Remote config
- Settings that live on a server, and the app downloads them. The team can change a value without a new release.
- Remote flag
- A setting that the server can change. It can turn a feature on or off without a new release.
- Rollback
- Going back to the old behavior after a problem. A build on a user's phone cannot be taken back, so teams use a kill switch.
- Rollout bucket
- A number that the server works out from a user ID and a flag key. A flag set to 10% is on for the users with the lowest 10% of the numbers.
- SDK
- Software development kit. Code that other developers add to their own apps.
- Snapshot
- A copy of the data as it was at one moment. It is not a list of changes.
- Staged rollout
- A release to a growing share of users, not to everyone at once. It limits the harm of a bad release.
- ViewModel
- A class that holds the state of one screen. It survives a screen rotation.
Verification notes (last reviewed 14 Oct 2026)
this is the interview part of the feature flags case. The build material is in the build guide. All section order, decision beats, spoken scripts, failure table cells, strong and weak notes and level text are new wording and my opinion. I fetched these pages on 2026-10-14 and wrote down what they say. Firebase loading strategies: three strategies, the one-minute timeout, set defaults, do not rely on the network, avoid changing the UI while the user uses it. Firebase optimization: avoid fetchAndActivate on every launch, the 24 hour example, no fetch on foreground transitions. Firebase real-time: HTTP connection, foreground only, 20 million limit. Firebase get started: 12 hour default, throttling, no confidential data. Firebase client templates: the random percent rule maps app instances, custom signals allow numeric comparison, rollback of template versions. Firebase quotas: 1,000,000 characters, 20 million connections. LaunchDarkly: 100,000 buckets from key and kind, flags independent, mobile SDKs stream in the foreground and poll hourly, archived flags serve the code fallback. Unleash: lifetimes by type, stickiness by hash, evaluate away from the client. Apple: background push low priority, 2 to 3 an hour, ScenePhase, UserDefaults thread-safe and asynchronous disk write, review guideline 2.3.1 and privacy details. Android: Doze suspends network, splash 1,000 ms, DataStore transactional, process lifecycle. Not verified: that Firebase fetch calls on foreground are cheap is my reading of the interval rule. Firebase advises against fetching on foreground transitions, and I keep a 15 minute check anyway. The statement that a Firebase reinstall changes the group is my inference from the page, which says each app instance is mapped to a random number. The crash guard, the four layers and the 15 minute age are my design. The numbers in section 2 are arithmetic on stated assumptions, not measurements. I recomputed them. 24 / 12 is 2. 40 x 60 is 2,400. 1% of 100,000 is 1,000, times 2 uses is 2,000, and 1 in 50 is 40. The follow-up questions and follow-ups are my reconstruction of challenges that public prep material reports, not transcripts of real rounds. The level text is my reading, not a company rubric. No sentence on this page claims how often a flags prompt is asked. The "User sees" cells are design intent. The 45 minutes is the interview length, not a documented fact.
Keep reading
Get the free one-page cheat sheet
One page: the five steps, a 45-minute time plan and two checklists. Print it or keep it on your phone.
We email you the cheat sheet after you confirm your address. You can also open it now from the Start here page. You get a short note when a new case is published. Unsubscribe any time. Privacy.