Build it. The server tables, API, code and platform limits for in-app purchases are on a separate page: Build it: in-app purchases and subscriptions on Android and iOS (16 min read).
How to read this. This page is the 45 minutes, in the order I would spend them. It uses one subscription and one one-time unlock as the example. The follow-up questions are my reconstruction from public prep material, not transcripts of real rounds.
By the end you can:
- Say why the server, and not the app, decides what a user owns.
- Walk one purchase from tap to access, including when it is still pending.
- Say what the app does when it is killed after the user has paid.
Read section 4.1 twice. It is the part most answers miss.
TL;DR. My answer: the store is the seller, so the store's record is the truth, not a flag in the app. The app never grants access because a button said "paid". It sends the store's proof to my server. The server checks it with the store, saves it once under the store's purchase token, and decides what the user owns. The app reads that answer. On every start and every resume the app asks the store again, because purchases can finish while the app is closed.
Patterns in this round.
- State machine (one purchase has a short list of states)
- Idempotency key (the store's purchase token is the key)
- Scoped owner (one manager lives as long as the app)
More in design patterns in mobile rounds.
1. Scope and constraints (minutes 0 to 4)
In short: One subscription and one one-time unlock, on both stores. The server decides what the user owns. Promo codes, plan switching and family sharing wait.
A prompt I would expect: "Design the purchase and subscription flow for our app." I ask five questions, with a default for each:
- What do we sell? One monthly subscription and one one-time unlock. Consumables only if you ask.
- Do we have a backend? Yes. It stores the user and what they own.
- Which stores? Google Play and the App Store.
- Do users have an account? Yes, so a purchase can be tied to a person.
- Offline? The app shows what the user owns with no signal. A new purchase needs the network.
| In scope | Out of scope | Left out until you ask |
|---|---|---|
| Buy, restore, renew, cancel, refund | Pricing and tax | Promo codes and offers |
| The server decides what the user owns | Web checkout | Family sharing |
| Pending payments | Fraud scoring | Switching plans |
| A second phone | Accounting receipts | Price change consent |
The phone and the store do four things to this design:
| What happens | Requirement at risk | Deep dive |
|---|---|---|
| The app is closed or killed while the user pays | A paid user gets access | Section 4.1 |
| A payment is still pending (bank, cash, parent approval) | No access before the money is taken | Section 4.1 |
| The store refunds or takes back a purchase later | Access can go away | Section 4.3 |
| The user installs on a second phone | The purchase follows the account | Sections 2 and 5 |
Your decision. The interviewer says: "Also add promo codes, plan switching and family sharing." Which do you keep?
First idea, and why it fails. Say yes to all three. Each one adds states to the same flow, and time runs out before the "paid but the app never knew" path, which is the real hard part.
What I would say.
Say this. "One subscription and one unlock are in. Promos, plan switching and family sharing each add states to the same flow, so they wait. Tell me if you want one earlier."
2. Data and contracts (minutes 4 to 14)
In short: The server keeps one row per store purchase, keyed by the store's token. The app reads what the user owns from the server, and keeps a copy for offline.
Your decision. Where does "this user owns Pro" live? In a flag on the phone, in the store only (ask the store every time), or on my server?
First idea, and why it fails. A flag on the phone, set when the purchase callback says OK. A user who clears data, installs on a second phone, or edits the flag, gets or loses access by mistake. A refund never reaches the app. Both stores point the same way: Google says to "always verify the legitimacy of purchases before granting benefits" (Play Billing), and Apple offers server tools to verify signed transactions (StoreKit Transaction).
What I would say. My server keeps two tables:
- Purchases: one row per store purchase. The key is the store's token (Google's purchaseToken, or Apple's transaction id). Columns: user, product, store, state (pending, active, expired, revoked) and expires at.
- Entitlements: what the user owns, worked out from the purchases. One row per user and feature, with a valid-until time.
Google says the purchase token is globally unique, so it can be the primary key, and says not to use the order id for this, because not every purchase has one (Play Billing security). A unique key makes a repeat harmless: the same token twice is one row.
The contract has four calls:
- Submit purchase: the app sends the store's proof (a token on Android, a signed transaction on iOS) and gets back what the user owns.
- Get entitlements: the app asks what the user owns.
- Store notifications in: each store calls my server when something changes, such as a renewal or a refund.
- Link account: attach a store purchase to a user, for a buyer who signs in later.
The follow-up question I expect:
"The user buys on Android, then opens the app on an iPhone. Do they get Pro?"
Only if the product is meant to work across stores and my server maps both purchases to the same user. A store purchase belongs to one store. My server can grant the same access from either, but a refund on one store must remove only what that purchase gave.
These are my own assumptions, not measurements:
| Assumption | Arithmetic | What it decides |
|---|---|---|
| 5% of purchases are pending at first (bank, cash). 1,000 purchases a day | 1,000 x 5% = 50 pending a day | Pending is a state of its own, not an error |
| 100,000 daily users start the app 3 times a day. The app asks the server at most once per 15 minutes. Half of the starts fall inside an earlier 15 minute window | 100,000 x 3 = 300,000 starts. Half need a call: 150,000 a day, about 2 a second on average | A short cache on the phone, and a cheap server call |
| A subscriber keeps access offline for 3 days | 3 x 24 = 72 hours | The phone trusts its saved answer for 72 hours, then asks again |
3. Client structure (minutes 14 to 22)
In short: One purchase manager that lives as long as the app. Screens only show state and ask to buy.
Your decision. Who owns the store connection and the purchase result? The paywall screen, or something that lives as long as the app?
First idea, and why it fails. The paywall's ViewModel (the class that holds one screen's state) starts the purchase and handles the answer. If the user pays and the app is killed, nobody is there to receive the result. Google says that without an active billing connection, onPurchasesUpdated is not called. On iOS, a purchase can also finish outside the app, on another device. Apple delivers that through Transaction.updates, which only works if something is listening.
What I would say. One PurchaseManager, created when the app starts. It owns three things:
- The store connection. One connection at a time: Google recommends one active
BillingClientso one event does not call the listener twice. On iOS it starts theTransaction.updateslistener at launch. - The reconcile step. On start and on resume it asks the store for everything the user owns, and sends anything new to my server. On Android that is
queryPurchasesAsync(), and Google says to call it inonResume()too. On iOS it readsTransaction.currentEntitlementsand the unfinished transactions. - The entitlement cache. The last answer from my server, with a valid-until time, saved on the phone.
The paywall sends a "buy" request and shows the current state. It never decides anything.
Say this. "One manager owns the store connection and listens from app start. It reconciles with the store on start and on resume, sends proof to my server, and saves the server's answer. Screens only show that answer."
The follow-up question I expect:
"Two screens both show a paywall. Who handles the result?"
The manager. Both screens watch its state. Neither one talks to the store.
4. Make the hard part work (minutes 22 to 40)
In short: A purchase is a small state machine. The app grants nothing until the server says so, and it tells the store "delivered" only after that.
4.1 One purchase, from tap to access (minutes 22 to 31)
Your decision. The user taps Buy and pays. What happens, in order, before the app unlocks the feature?
First idea, and why it fails. When the callback says OK, unlock the feature and acknowledge the purchase. If the app dies between the unlock and the acknowledge, or my server was never told, no record exists where I control it. On Android there is also a clock: Google says a purchase must be acknowledged within three days, or it is refunded automatically and the entitlement is revoked.
What I would say. The states of one purchase:
- Started: the user tapped Buy.
- Pending: the store says payment is not done. Grant nothing. Google says to grant access only in the PURCHASED state, and that the three-day window starts only when a pending purchase becomes PURCHASED.
- Purchased, not verified: the store has the money. Send the proof to my server.
- Verified: my server checked it with the store, saved the row and granted access.
- Delivered: the app has the access. Now tell the store. Google says to acknowledge right after granting access. Apple says to finish a transaction after the app delivers the content.
- Revoked: the store refunded it or took it back. The server removes access.
The order matters: verify, grant, then tell the store. If the app dies at any step, the next reconcile finds the purchase again, because the store still shows it as not finished.
Say this. "A purchase has states: pending, purchased, verified, delivered. I send the store's proof to my server. The server checks it and grants access. Only then do I acknowledge or finish. If the app dies in the middle, the store still shows an unfinished purchase, so the next start finds it and goes on."
The follow-up question I expect:
"The user paid, the app crashed, and they open it a week later. What happens?"
On Android, the three-day window has passed, so Google has refunded the purchase and the user was not charged in the end. The app should show that cleanly: no purchase found, and a Buy button. The fix is to reconcile on every start and resume, so the gap is hours, not days. On iOS, the transaction is still unfinished, so Apple hands it to the app at launch, and the same steps run.
4.2 The same purchase twice (minutes 31 to 36)
Your decision. The proof arrives twice: once from the purchase callback and once from the reconcile on the next start. How many times does the user get access?
First idea, and why it fails. Grant on each call. A user can get access twice, or a consumable (something the user can buy again) can be credited twice. Google warns about this: a consumption request can fail, so check on your backend that each token has not been used, so you do not grant the same purchase twice.
What I would say. The server inserts the purchase with a unique key on the store token. A second send finds the row and returns the same answer. Granting is an upsert (insert, or update if it exists), never an increment. For a consumable, the server records the credit against the token, so it can happen only once.
The follow-up question I expect:
"My server is down when the app sends the proof."
The app keeps the purchase in a local queue (the store also still holds it unfinished), retries with backoff, and shows "Processing your purchase" with access still off. It must not say the purchase failed, because the user was charged. On Android the three-day window is my deadline, so retries must fit inside it.
4.3 Renewals, refunds and the closed app (minutes 36 to 40)
Your decision. A subscription renews, or a refund happens, while the app is closed. How does my server know?
First idea, and why it fails. Wait until the app asks. A refunded user keeps access until they next open the app. A user whose card failed gets cut off with no warning.
What I would say. The stores call my server. Apple sends signed App Store Server Notifications (the StoreKit page says they use the same signed format as transactions). Google has its own real-time notifications. My server updates the purchase row and the entitlement from each one, and the app sees the change on its next check. On Android, only the first purchase of a subscription needs an acknowledge: Google says renewals do not.
Say this. "The stores tell my server about renewals and refunds, so I do not wait for the app. The server changes the purchase and the access. The app sees it on the next check."
5. Break it, ship it, roll it back (minutes 40 to 45)
In short: Each failure ends in a clear state: access, no access with a reason, or "processing". Never a paid user with no access and no message.
| Failure | User sees | What holds the truth | Recovery | Basis |
|---|---|---|---|---|
| App dies after payment | Access on the next start | The store still shows the purchase unfinished | Reconcile finds it, the server verifies, grant, then acknowledge or finish | Android: documented (acknowledge, query on start). iOS: unfinished transactions are documented. The full path is my design |
| Payment pending | "Waiting for payment" | Store state is pending | No access and no acknowledge until it is purchased | Android: documented. iOS: my reading |
| The same proof twice | One purchase | Unique key on the store token | The second send returns the first answer | Google advises using the token as the key. The rest is my design |
| Server is down | "Processing your purchase" | The store still shows it unfinished | Retry with backoff, inside the Android window | My design |
| Refund or revoke | Access removed | Server row set to revoked by a store notification | The app sees it on the next check | Notifications exist on both stores. Timing is my reading |
| A second phone | Access after sign in | Server entitlements per user | Restore: reconcile, then link the purchase | My design |
| No signal | Saved access for 72 hours | Local cache with a valid-until time | After 72 hours, ask the server again | My assumption |
Releases:
- A purchase bug ships to everyone. A staged rollout can be halted, but installed copies keep the version they have, as in the chat app case.
- A remote flag turns the new purchase path off. The server stays the source of truth, so the old app and the new app read the same entitlements.
- I would watch: purchases pending for over a day, purchases stuck at "purchased, not verified" for over an hour, "I paid and have no access" reports, and the share of starts that find an unfinished purchase.
Say this. "The store is the seller and my server is the source of truth. I would watch purchases stuck before verified, and paid-but-no-access reports. I left out promos, plan switching and family sharing."
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.
- Add credits you can buy again. Strong: the server credits once per store token, and consumes the purchase through the store only after the credit is saved. Weak: credit on the phone, then consume.
- Add promo codes. Strong: they arrive as normal store purchases, so the same pipeline works. Weak: a second code path in the app.
- A user says they paid and have no access. Strong: look up the store token, check for a pending state, run a restore. Weak: "reinstall the app."
- Someone replays an old proof. Strong: the server verifies with the store, not from the proof alone, and the unique token key stops a second account from using the same purchase. Weak: trusting the app.
- Two accounts on one phone. Strong: a purchase links to one account; the second account sees "owned by another account" and a restore path. Weak: whoever is signed in gets it.
- The app must work offline for a week. Strong: raise the offline window and say what it costs: a refund reaches the phone late. Weak: never expire.
7. What changes by level
In short: As I read it, the levels differ in who raises the problem: the interviewer, the candidate, or the candidate with a rollout plan.
These levels are my own reading of public prep material, not any company's rubric.
- Mid-level: A buy flow, a restore button and a server check. I want to hear: "The server decides, not the app."
- Senior: The purchase states, acknowledge or finish after granting, a unique store token, reconcile on start and resume, and pending handled. I want to hear: "If the app dies in the middle, the store still shows an unfinished purchase, so the next start goes on."
- Staff: Store notifications for renewals and refunds, access across stores, support tools and metrics, rollout behind a flag, and what a refund policy does to the offline window. I want to hear: "Here is what I would watch, and here is the flag that undoes it."
Common weak answers. These repeat across public prep material. They are not a dataset.
- Unlocking the feature when the button callback says OK.
- No server check, so a replayed or edited proof works.
- Acknowledging before granting access.
- No plan for a pending payment.
- Nothing about refunds.
Test yourself
In short: Six questions from this case. Answer each one out loud first, then open the answer.
1. Why should the server, and not a flag on the phone, decide that a user owns Pro?
A flag breaks when the user clears data, uses a second phone or edits it, and a refund never reaches it. The server keeps one row per store purchase and works out what the user owns. The app reads that answer and keeps a copy for offline use.
2. What is the primary key of the purchases table, and why?
The store's own token: Google's purchaseToken or Apple's transaction id. Google says the token is globally unique, and not every purchase has an order id. With a unique key, the same token sent twice is still one row.
3. The store shows a purchase as pending. What does the app do?
Nothing is granted and nothing is acknowledged. Pending is a state of its own, not an error. Access starts only when the store says the purchase is done and my server has checked it.
4. In what order do verify, grant and tell the store happen, and why?
Verify with my server, grant access, then acknowledge (Android) or finish (iOS). If the app dies at any step, the store still shows an unfinished purchase, so the next start finds it and runs the same steps again.
5. The user pays, the app is killed, and they open it a week later. What happens on each platform?
On Android the three-day window has passed, so Google has refunded the purchase. The app shows no purchase and a Buy button. On iOS the transaction is still unfinished, so Apple hands it to the app at launch and the same steps run. Reconciling on every start and resume keeps the gap to hours.
6. A user is refunded while the app is closed. How does the app find out?
The store sends my server a notification. The server updates the purchase and the entitlement. The app sees it the next time it asks the server what the user owns.
Further reading (official docs)
- Design a state machine for a mobile flow: the checkout screen flow and the lost-answer path
- Design a hotel or ride booking app: payments and holds for a booking
- Guide: idempotency keys
- Integrate Google Play Billing
- Google Play Billing: security
- Apple StoreKit: Transaction
Words on this page (4)
- Entitlement
- What a user is allowed to use because they paid. My server decides it.
- Acknowledge
- On Google Play, telling the store that the app has given the user what they bought. Without it, the purchase is refunded after three days.
- Purchase token
- A unique ID that Google Play gives to each purchase. The server can use it as the key for the purchase.
- Consumable
- A product the user can buy again, such as credits.
Verification notes (last reviewed 16 Oct 2026)
My own opinion or design.
- The section order, decision beats, spoken scripts, failure table cells, strong and weak notes and level text are new wording and my opinion.
- The purchase states, the two server tables, the reconcile step and the 72 hour offline window are my design.
- The follow-up questions 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. The 45 minutes is the interview length, not a documented fact.
Fetched and read on 2026-10-16. I wrote down what each page says.
- Google Play Billing, integrate:
- A purchase must be acknowledged within three days, or it is refunded automatically and the entitlement is revoked.
- Grant access only in the PURCHASED state. The three-day window starts when a pending purchase becomes PURCHASED.
- Initial subscription purchases need an acknowledge. Renewals do not.
- Call queryPurchasesAsync when the app connects, and in onResume.
- Without an active billing connection, onPurchasesUpdated is not called.
- Keep one active BillingClient connection.
- Consume requests can fail, so check on the backend that a token was not used twice.
- Google Play Billing, security:
- Verify and acknowledge on a secure backend.
- Use the purchase token as the key. Do not use the order id.
- Do not grant access while the state is PENDING.
- Acknowledge right after granting access.
- Apple StoreKit, Transaction:
- Transaction.updates delivers transactions made outside the app or on another device.
- currentEntitlements lists what the customer has paid access to.
- Finish a transaction after the app delivers the content. Unfinished transactions are available.
- Transactions are signed (JWS). A server can verify them. Server notifications use the same signed format.
Not verified.
- Google real-time notifications: I read only that the security page links to them.
- Apple server notification types for renewal and refund.
- Whether Apple has an acknowledgement deadline.
- How pending states differ between subscriptions and one-time products on Android.
- The 5%, 300,000, 150,000 and 72 hour figures are my assumptions. I recomputed: 1,000 x 5% = 50; 100,000 x 3 = 300,000; half of that is 150,000; 150,000 / 86,400 is about 1.7 a second; 3 x 24 = 72.
Changes by review pass.
- Source-check pass, 2026-10-16: read the three pages above and wrote the claims from them.
- Numbers pass, 2026-10-16: fixed the call-rate row so it shows its arithmetic.
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.