Design Robinhood for mobile engineers
The easy version of this post would be a trading backend: market data comes in, users connect over WebSockets, an order service routes orders, and a ledger records the result.
That is all real, but it misses the part I care about most as a mobile engineer.
A trading app is not hard because it can show a price. It is hard because the price is moving, the phone can go offline, the user can tap Buy twice, the order might be accepted even if the response never reaches the app, and the portfolio screen cannot just guess what happened.
So I want to design a Robinhood-style app from the mobile edge inward: quote freshness, watchlists, trade tickets, retry-safe order submission, order state, fills, buying power, positions, notifications, ledgers, and reconciliation.
This is not Robinhood’s actual architecture. I am using public product pages, support docs, and general brokerage/trading references to reason about a similar product.
This is a system design exercise, not financial advice. I am also not giving legal or regulatory advice.
Practice quiz: Want to test the ideas first or come back later? Try the Robinhood mobile design quiz. It covers quote freshness, idempotent order submission, buying power, fills, ledgers, push notifications, and mobile failure modes.
This post is part of my System design for mobile engineers series.
Problem statement
Design a Robinhood-like mobile trading app.
Robinhood’s public home, investing, and trading pages describe a product that is broader than a simple stock order form. The app includes stocks, ETFs, options, crypto, retirement, cash movement, market data, watchlists, alerts, account security, and premium features like Gold.
For this walkthrough, I am going to scope the first design around the core mobile investing path:
- open the app
- view portfolio and watchlist state
- inspect an instrument
- see quotes and chart data
- place a stock or ETF order
- track pending, partial, filled, canceled, rejected, and expired states
- update buying power, positions, activity, and notifications from authoritative server state
I would defer options chains, multi-leg strategies, crypto wallets and transfers, retirement rules, margin, tax documents, banking features, and advanced desktop layouts. Those are real surfaces, and the architecture should leave room for them, but they would make the first design too wide.
The mobile-specific goal is this:
The app should help the user make a high-stakes decision with clear, fresh, recoverable state, even when the network and market are not behaving nicely.
Requirements
Functional requirements
For a first version, I would include:
- Users can sign in securely and approve trusted devices.
- Users can view portfolio value, positions, cash, buying power, watchlists, and recent activity.
- Users can search instruments and open detail pages.
- Users can see last price, bid, ask, chart history, and freshness indicators.
- Users can create market and limit orders for stocks and ETFs.
- Users can review the order before submit.
- Users can submit, cancel, and sometimes replace eligible pending orders.
- Users can see order status updates, including partial fills.
- Users receive push notifications for important order and account events.
- Users can recover after app backgrounding, device sleep, reconnects, and request timeouts.
Robinhood’s support docs describe similar user flows for buying a stock, selling a stock, order types, canceling or replacing pending orders, and partial fills. I would use those public behaviors to ground the product flow, not to claim anything about the internal implementation.
Non-functional requirements
The important requirements are:
- Low-latency reads for portfolio, watchlist, and instrument detail screens.
- Realtime or near-realtime quote updates while the relevant screen is visible.
- Explicit quote freshness and delayed-data handling.
- Retry-safe order submission with an idempotency key-style contract.
- Durable audit trail for order intent, risk checks, routing, fills, cancels, corrections, and user-visible state changes.
- Strict separation between display state and authoritative financial state.
- Reconciliation between orders, fills, positions, buying power, cash, and ledger records.
- Strong security for sessions, devices, passkeys, biometrics, and step-up checks.
- Degraded modes that tell the truth when part of the system is down.
- Backward-compatible mobile APIs for older app versions.
The phrase I keep coming back to is “trustworthy latency.” The app can be fast, but it cannot be fast by pretending something is final before the source of truth says it is final.
Product behavior first
Before drawing services, I would define the mobile behavior.
Portfolio home
The home screen should show:
- total portfolio value
- day change
- buying power
- cash and unsettled cash when relevant
- positions
- watchlist snippets
- pending orders
- important alerts
- recent activity
The client can cache this for startup, but it needs freshness labels. A stale watchlist is not ideal, but it is survivable. A stale buying power value on a trade review screen is dangerous.
For example, if the user opens the app underground with no signal, I would rather show the last known portfolio with a clear “last updated” state than block the whole screen. But I would not let the user submit an order from that stale state.
Instrument detail
An instrument detail page has a different shape:
- last traded price
- bid and ask
- chart range
- market status
- user’s position
- user’s pending orders for that instrument
- Buy and Sell entry points
- news or educational content
Robinhood’s market data docs explain that market price is based on recent trades and that quote data can include last sale, bid, ask, quantity, and exchange. For the system design, I would keep these concepts separate. Last sale, quote, chart candle, and depth are not the same product object.
Trade ticket
The trade ticket should feel simple, but internally it is one of the most sensitive flows in the app.
A simple buy flow might be:
- Choose Buy.
- Enter amount or shares.
- Choose market or limit order.
- See estimated cost, buying power impact, session, and risk disclosures.
- Review.
- Submit.
- Show pending state until the server confirms the authoritative order state.
The important moment is submit. Once the user confirms, the mobile client should create or reuse a client operation ID, send it with the order request, and then treat any timeout as unknown, not failed.
Unknown is a real state. If the app times out after submit, the next screen should not say “order failed” unless the server says it failed. It should say something closer to: “Checking order status” and query the order by client operation ID.
Scale assumptions
I would avoid pretending I know Robinhood’s actual numbers. For a design exercise, I would pick numbers that force the right tradeoffs:
- tens of millions of accounts
- millions of daily mobile sessions
- large bursts at market open and close
- many more quote reads than order writes
- high fanout for popular symbols
- high write sensitivity for order creation, cancel, replace, transfer, and account changes
- many asynchronous events from brokers, exchanges, clearing systems, risk systems, market data vendors, and internal jobs
The key consequence: reads and writes should not be treated equally.
Quote reads, charts, watchlists, and portfolio views need scale and freshness. Order submission and ledger writes need correctness, auditability, and controlled state transitions.
Core concepts
Account
A user may have multiple account containers:
account
- id
- user_id
- account_type
- status
- eligibility_flags
- restrictions
- created_at
The account type matters. A taxable brokerage account, retirement account, crypto account, and cash or banking-like account have different rules. Robinhood’s public pages mention retirement, crypto, and Gold surfaces, so I would not bake the assumption that every balance behaves like simple cash.
Instrument
An instrument is the thing being traded or watched:
instrument
- id
- symbol
- asset_class
- exchange
- tradability_status
- market_session_rules
- quote_policy
This should support stocks and ETFs first, but the model should not make options or crypto impossible later.
Quote and market data snapshot
I would separate market data objects:
quote_snapshot
- instrument_id
- bid_price
- bid_size
- ask_price
- ask_size
- last_price
- last_trade_time
- market_session
- source
- freshness_status
- received_at
For charts, I would use candle data. For Level II, I would use a depth model. Robinhood has public docs for Level II market data, which is a different product surface than the simple last price on an instrument card.
Order intent
The order intent is what the user asked for:
order_intent
- id
- client_operation_id
- account_id
- instrument_id
- side
- order_type
- quantity_or_notional
- limit_price
- time_in_force
- submitted_from_device_id
- status
- created_at
I like separating order_intent from execution events because the user’s tap and the eventual fill are not the same thing.
Order event
Order state should be event-driven:
order_event
- id
- order_id
- event_type
- source
- payload
- occurred_at
- received_at
Possible events:
- created
- risk_checked
- rejected
- accepted
- routed
- partially_filled
- filled
- pending_cancel
- canceled
- expired
- replaced
- corrected
- busted
A public broker API such as Alpaca’s orders API is useful as a reference for how many states an order lifecycle can have. I would not copy it exactly, but it shows why a single isComplete boolean is not enough.
Ledger and positions
The ledger is not the same thing as the portfolio screen.
The ledger records durable financial effects. Positions and buying power are derived or operational views that must reconcile with ledger entries, order events, clearing records, and broker records.
ledger_entry
- id
- account_id
- asset_id
- debit_account
- credit_account
- amount
- currency_or_asset
- source_event_id
- created_at
For a brokerage app, the ledger has to survive corrections, busts, corporate actions, settlement changes, and operational repairs. The portfolio UI should never invent position state locally just because the user saw a fill animation.
High-level architecture
At a high level, I would split the system into these areas:
- Mobile app
- API gateway and session service
- Market data platform
- Quote fanout gateway
- Portfolio and watchlist read APIs
- Order API
- Order management system
- Risk and eligibility service
- Execution routing integration
- Order event stream
- Ledger and position projection
- Notification service
- Compliance and audit store
- Reconciliation and back office jobs
The shape matters more than the specific technology. You could use Kafka, Pulsar, Kinesis, Postgres, Redis, Cassandra, Flink, or something else. The core decision is that quote display, order submission, order events, and ledger effects are separate concerns.
Robinhood has public engineering history around stream processing through Faust and public talks about Kafka operations. I would use that only as evidence that event streams are a reasonable design tool here, not as proof of current internals.
Mobile quote streaming
A naive design says: use WebSockets for prices.
That is not wrong, but it is incomplete.
On mobile, the quote stream has to answer questions like:
- Which symbols are visible right now?
- Which symbols are in the watchlist but offscreen?
- Is the app foregrounded, backgrounded, or suspended?
- Is the device on a weak network?
- Can the UI render every update without burning battery?
- Is the quote stale, delayed, or outside regular market hours?
I would use a quote subscription model:
client_visible_symbols -> quote_gateway -> market_data_projection
The app sends visible symbols and the server decides what cadence and payload shape to return. A detail page for one symbol can get richer updates. A watchlist with 80 symbols might get coalesced snapshots. A backgrounded app gets no live stream and relies on push or refresh on resume.
I would also avoid letting the quote stream directly mutate every screen. The client should route updates into a local in-memory store or screen model, then the UI observes that state. This keeps the rendering path controlled and gives the app a place to apply throttling, stale indicators, and resync logic.
Order submission and idempotency
Order submission is the part I would design most carefully.
A mobile request can fail in several ways:
- The request never reached the server.
- The server received it, but the response was lost.
- The order was accepted, but routing is pending.
- The order was rejected, but the client did not receive the rejection.
- The app was killed after the user tapped submit.
- The user reopened the app and tapped again.
The fix is not just disabling the button.
The client should create a client_operation_id before submit. The server should enforce idempotency for that account, instrument, side, and order payload. If the same operation is retried, the server returns the existing order or status instead of creating a duplicate order.
The mobile UX should treat submit as a state machine:
editing -> reviewing -> submitting -> checking_status -> accepted_or_rejected
If the network times out after submit, move to checking_status, not failed. Query by client_operation_id. If the server cannot answer, show a recoverable state and keep checking.
The principle is simple: never let a lost response become a duplicate trade.
Buying power is not just cash
Buying power can look like one number in the UI, but it is not one simple number in the system.
It can depend on:
- cash
- unsettled funds
- pending orders
- pending cancels
- restrictions
- margin eligibility
- account type
- asset type
- trading session
- deposits and holds
- risk rules
Robinhood’s transfer and withdrawal docs talk about transfer types and withdrawals. SEC’s T plus 1 settlement change is a good reminder that settlement timing affects what a user can do with funds.
I would model buying power as a server-computed projection. The mobile app can display it, cache it, and request refreshes, but it should not calculate final buying power locally.
On the trade review screen, I would call a server-side preview API:
POST /orders/preview
- account_id
- instrument_id
- side
- order_type
- quantity_or_notional
- limit_price
- client_context
The response includes:
- estimated cost or proceeds
- fees if any
- buying power impact
- warnings
- disclosures
- session and time-in-force rules
- whether step-up authentication is required
- whether submit is allowed
That preview can expire quickly. If the user waits too long, the app should refresh it before submit.
Order events, fills, and portfolio projection
Once an order exists, the user needs a trustworthy timeline.
A market order might fill quickly. A limit order might sit open. An order can partially fill. A cancel request can be pending while a fill also arrives. A correction can happen later.
The backend should process order events into several views:
- order detail timeline
- activity feed
- positions
- buying power
- portfolio value
- tax and statement data
- audit and compliance logs
I would make the mobile app read from these projections rather than trying to combine raw fill events locally.
The client can still be responsive. For example, after submit it can show a pending order row immediately. But that row should be marked as pending local display until the server returns the accepted order. The fill state, position update, and buying power update should come from server projections.
This is where an event stream helps. A fill event updates the order timeline. It also updates position projection, activity, notification jobs, and ledger-related workflows. These consumers should be idempotent because duplicate events and replay are normal in distributed systems.
Market data freshness and trust UX
Trading apps need to be honest about time.
Some data is realtime. Some can be delayed. Some can be from a different session. Some can be stale because the client disconnected.
I would make freshness a first-class field in the API:
freshness
- as_of
- received_at
- source
- delayed_by_seconds
- market_session
- stale_after
- is_tradeable_now
The UI can use that to decide how much confidence to show.
Examples:
- Watchlist card: subtle stale badge after missed heartbeat.
- Instrument detail: visible last updated time if the stream reconnects.
- Trade review: refresh quote and preview before enabling submit.
- Market closed: make the session explicit.
- Halted or restricted: block submit with a clear reason.
The worst version of this product is a screen that looks live when it is not.
Notifications and resync
Push notifications are useful, but I would not treat them as the source of truth.
A push can say:
- order filled
- order rejected
- price alert fired
- transfer completed
- device login detected
- account action required
But the push payload should be a routing hint. When the user taps it, the app should fetch the latest server state for that order, instrument, or account.
This matters because pushes can be delayed, duplicated, dropped, or tapped from an old device state. The notification can get the user to the right screen. It should not be the final proof that a trade happened.
Robinhood’s notification settings page lists many alert categories, including stock, options, crypto, order status, bank activity, payments, and investor updates. That points to a broader event-notification platform, not a few hardcoded mobile pushes.
Security and sensitive actions
Security is part of the product experience.
Robinhood has public support pages for device approvals, passkeys, and its security guarantee. For a Robinhood-like app, I would design:
- MFA and passkey support
- trusted device registration
- session rotation
- biometric unlock for returning sessions
- step-up authentication for sensitive actions
- device inventory and remote sign out
- jailbreak or root risk signals
- secure storage following mobile security guidance such as the OWASP Mobile Application Security Cheat Sheet
Sensitive actions include order submission in some contexts, options enablement, withdrawals, bank linking, profile changes, password changes, and new device approvals.
The product tension is that trading needs to feel fast, but account takeover protection cannot be an afterthought.
Compliance and auditability
I would not build compliance as a logging sidecar at the end.
A brokerage app has to care about identity, suitability or eligibility workflows, anti-money laundering, order routing, best execution, customer communications, and audit trails. Public references such as SEC trade execution, FINRA Rule 5310, FINRA Know Your Customer, and FINRA AML are enough to show why this matters in the architecture.
For system design, that means:
- store order intent and user confirmation context
- store quote context at submit time
- store device, session, and app version metadata
- store risk decisions and reason codes internally
- store routing and execution events
- keep immutable audit logs separate from mutable read models
- build retention and supervision workflows from the start
Payment for order flow is also a public investor education topic from Investor.gov. A system like this needs routing metadata and disclosure support, even if the mobile UI only shows a simple confirmation screen.
Failure modes I would design explicitly
The app times out after submit
The client moves to checking_status, then queries by client operation ID. It does not create a second order.
Quote stream disconnects
The UI marks visible prices stale after missed heartbeats. Trade review refreshes quote and preview before submit.
Order fills while app is backgrounded
The backend emits order and position events. Push may notify the user, but app resume fetches authoritative order and portfolio state.
Partial fill arrives after cancel request
The order timeline can show both events. The position projection updates only for the filled quantity. The remaining open quantity can be canceled if accepted.
Market data works but order submission is down
The app can run in read-only mode. It should not hide the outage behind a spinner on submit.
Portfolio projection lags order events
The order detail can show a fresh fill while the portfolio screen says it is updating. This is better than showing a false final position.
User opens from an old push
The notification routes to the order or instrument, then the app fetches latest state. It does not trust old push payload data.
Observability
For this app, I would want traces that follow the user action end to end:
tap submit
-> mobile request started
-> API accepted idempotency key
-> risk check completed
-> order accepted or rejected
-> routed
-> fill event received
-> position projection updated
-> push sent
-> mobile screen displayed final state
The most interesting metric is not only backend latency. It is time to trustworthy state.
I would track:
- quote stream reconnect rate
- stale quote time on visible screens
- order submit timeout rate
- duplicate idempotency attempts
- time from submit to accepted or rejected
- time from fill event to portfolio projection update
- notification tap to fresh state loaded
- client versions causing unusual order errors
- mismatch rate between order events, ledger, and portfolio projections
Mobile logs should include trace IDs, operation IDs, app version, device state, network type, and foreground or background status. They should not include sensitive account data in plain text.
Testing scenarios
I would test the boring scary cases:
- user taps Buy twice
- app dies after submit
- request times out but server accepted the order
- WebSocket disconnects during a fill
- push arrives before app has the latest order state
- partial fill then cancel
- market closed while user is on trade review
- stale quote shown on instrument detail
- preview expires before submit
- buying power changes because another order filled
- account restriction appears mid-session
- old app version submits an older payload shape
- device clock is wrong
- reconciliation job finds a break between fills and positions
A lot of these tests are not normal unit tests. They need integration tests, event replay tests, mobile network simulation, contract tests for old app versions, and operational game days.
What I would keep out of v1
I would not try to design every product surface in one post.
I would defer:
- multi-leg options strategy builder
- crypto send and receive
- retirement contribution rules
- margin and advanced risk modeling
- tax lots and tax forms
- statements and confirms
- corporate actions
- securities lending
- banking and card products
- desktop trading layouts
- social or content feeds
The architecture should leave room for these, but the first walkthrough should stay focused on the core trust loop:
fresh quote -> reviewed order -> idempotent submit -> authoritative order state -> fill events -> ledger and portfolio projection -> user-visible reconciliation
Final architecture takeaways
If I were reviewing this design, I would care less about whether someone named the database I prefer and more about whether they respected the product truth.
For a mobile trading app:
- The phone is not the source of truth.
- The quote stream is not the order system.
- The order acknowledgement is not the fill.
- The fill is not the portfolio projection.
- The portfolio projection is not the ledger.
- Push is not proof.
- Fast UI is only useful if it stays honest.
That is the part most generic Robinhood system design answers skip. They draw a backend that can trade. I want the system to explain what the user can trust at every step.
If you want to test the design choices, try the Robinhood mobile design quiz after reading. It is focused on the failure modes I would expect a strong mobile system design answer to cover.
Further reading
- Robinhood: Invest, Trading, buying a stock, selling a stock, order types, using market data, notification settings, device approvals, and passkeys.
- Robinhood Engineering: Faust stream processing, robinhood/faust, and Kafka journey from EC2 to Kubernetes.
- Brokerage references: SEC trade execution, FINRA best execution, FINRA KYC, FINRA AML, SIPC what SIPC protects, and SEC T plus 1 settlement.
- General design references: Stripe idempotent requests, Alpaca orders API, NYSE real-time market data, and OWASP Mobile Application Security Cheat Sheet.
If this was useful, you can buy me a coffee ☕. If you have a question, correction, or a product you want me to think through next, leave a comment.
If you have seen a version of this question in an interview, I would love to hear what part felt hardest: requirements, APIs, mobile state, scale, offline behavior, or tradeoffs.
Comments
Loading…