Design a Mobile Stock Trading App
Getting fast-moving prices onto a phone, reliably and safely — where a few seconds of lag can cost a user real money.
Getting fast-moving prices onto a phone, reliably and safely — where a few seconds of lag can cost a user real money.
Sections build on each other: scope sets the constraints, the API and architecture give you the skeleton, and the two deep dives (charts, then real-time) are where the mobile-specific hard parts live. Anything marked [+] is context added beyond the source chapter — everything else is a rewrite of the original material. Diagrams are original.
Pin down what you're building#
A stock trading app is defined by one hard requirement: price data has to reach the user with almost no delay. Everything else — placing orders, drawing charts, alerts — hangs off a system that can stream fast-changing numbers to a phone and keep them trustworthy. It also handles real money, so security and regulatory compliance are non-negotiable rather than nice-to-haves.
Scoping starts with a conversation. The exchange with the interviewer narrows a huge domain down to something buildable in an hour:
Instruments: stocks only — no options or futures. Auth: assume users are already signed in. Scale: ~500k daily active users across multiple markets, live prices for thousands of stocks. Tech: greenfield, no legacy to carry.
Functional requirements#
- See a portfolio with live prices of held stocks.
- Buy and sell by placing and canceling orders.
- View historical price data as charts.
- Receive price-alert notifications.
Non-functional requirements#
- Real-time data — market updates pushed with minimal delay so decisions use current prices.
- Performance — the UI stays smooth while streaming data and rendering charts; trades execute quickly and reliably.
- Security — financial data protected at all times, strong auth, full compliance with financial regulation.
Home — the financial snapshot: funds, portfolio performance, recent orders. Stock detail — one stock's price, chart, and trading entry points. Place-order modal — a streamlined buy/sell confirmation with market price, quantity, and total. Data flows one direction: portfolio → stock detail → order confirmation.
Options and futures trading, and user registration. A guiding security principle: minimize sensitive data stored on the device — the phone is the least controllable part of the system. [+]
Two protocols for two kinds of traffic#
The app has two fundamentally different communication needs, and forcing both through one mechanism makes at least one of them bad. So the design uses a hybrid protocol strategy — the right tool for each traffic pattern.
- HTTP + REST for anything the client initiates: placing an order, querying the account, fetching history. The request/response model fits these one-shot actions and scales well.
- WebSockets for anything the server initiates: live price ticks, order-status changes. A persistent, bidirectional connection lets the server push the instant something changes, instead of the client polling and hoping.
Polling for prices means constant round-trips — most returning "no change" — with latency bounded by your poll interval and battery drained by the radio. A WebSocket opens once and stays open, so an update arrives in one hop the moment the market moves. That's the difference between "a few seconds stale" and "current." [+] Trade Republic runs exactly this REST-plus-WebSocket split in production.
Endpoints and data models#
Stocks#
GET /v1/stocks/{symbol}— current price, daily high/low, percent change.GET /v1/stocks/{symbol}/history?interval={interval}&period={period}— historical prices, withinterval(e.g.1m,1d) andperiod(e.g.1wk,3mo) controlling resolution and span.
Model prices as BigDecimal (Kotlin) or Decimal (Swift), not
Float/Double. Binary floating-point can't represent many decimal fractions
exactly, so it accumulates rounding errors that are unacceptable for money. Decimal types give exact
arithmetic — slightly slower, but correctness wins over speed when the number is someone's balance.
Portfolio & orders#
GET /v1/portfolio— owned stocks and quantities.GET /v1/orders?status={status}&page={page}&limit={limit}— paginated orders, filterable byopen/executed/canceled.POST /v1/orders— create a buy or sell order.DELETE /v1/orders/{orderId}— cancel an order.
Order history uses offset pagination (page/limit) rather than
cursors. That's the right call here because orders rarely change after creation and users mostly look at recent
activity — so the usual weakness of offset pagination, drift when the underlying list mutates, barely applies.
[+]
| Model | Key fields |
|---|---|
| Stock | id, symbol, name, currentPrice,
changePercent, highPrice … |
| Portfolio | a list of PortfolioStock |
| PortfolioStock | stock, quantity, averagePrice |
| Order | id, stockSymbol, quantity, status,
createdAt … |
| OrderStatus | OPEN · EXECUTED · CANCELED |
Notifications#
GET /v1/notifications— list alerts.POST /v1/notifications— create a price alert.DELETE /v1/notifications/{id}— remove one.
WebSocket channel#
One connection at wss://api.stocktrading.com/ws multiplexes three message types:
stock-prices (live ticks for subscribed symbols), order-updates (status changes), and
portfolio-updates (value shifts as the market moves). Clients declare interest by subscribing to
channels:
{
"action": "subscribe",
"channels": [
{
"name": "stock-prices", "symbols": ["AAPL", "GOOGL", "MSFT"]
}
]
}A layered client and its servers#
The client uses a clean layered architecture: a UI layer that only presents, and a data layer that owns truth. Separating them keeps the code testable and stops network logic from leaking into screens.
The client, layer by layer#
- UI layer — Home (portfolio hub), Stock detail (charts + metrics), and the Place-order modal, each with a state holder that turns data into presentation state.
- Data layer — three repositories, each a single source of truth for its domain: Portfolio (holdings, continuously synced), Stocks (market data), and Orders (creation, tracking, execution status). Each talks to a remote data source.
- Push-notifications client — listens for backend alerts so price moves and order completions reach the user even when the app is backgrounded.
The servers it leans on#
- Backend — REST + WebSocket surface; manages requests and keeps data consistent.
- CDN — caches static content (historical data, chart assets) near users for speed.
- Push provider — delivers background alerts.
- API Gateway — the front door.
It validates and routes every request and blocks bad ones before they touch core systems. Its jobs: security (authn/authz, rate limiting, spotting suspicious clients), traffic management (load balancing, throttling peak-hour volume), request processing (validation, format transforms, headers), monitoring (usage, latency, error rates), and simplification (one clean API that hides backend complexity from the mobile client).
Real-time market data → memory only, never disk. Persisting a price guarantees you'll one day show a stale one after a cold start. Slow-changing data (historical charts, user preferences) → disk cache with clear expiry, trading a little freshness for load speed.
Drawing historical price charts#
A price chart looks simple and hides a real problem: data density. Traders want many time frames — 1D, 1W, 1M, 1Y, MAX — and each carries a wildly different number of points.
A 1-day chart is ~390 points (one per minute over a 6.5-hour session). A 5-year chart could be 1,250+ daily points or 260+ weekly. Too many points → rendering lag and memory bloat on a phone. Too few → you smooth away real price movements and mislead the user. The job is to pick a resolution that's honest and cheap enough to draw.
How do you actually render it on a phone?#
Four approaches, each trading control against effort and consistency:
| Option | Upside | Downside |
|---|---|---|
| Native graphics | Full control; smallest memory footprint; direct hardware acceleration. | Complex, platform-specific, slow to build; duplicated code across iOS/Android. |
| Third-party libraries | Fast to ship; zoom/gesture built in; well-maintained. | Overhead vs native; integration friction with native components. |
| WebView rendering ✓ | Rich web charting ecosystem; one implementation across platforms. | Higher memory/CPU; WebView↔native friction; slightly less native feel. |
| Server-side images | Almost no client work; identical on every device. | Little interactivity; network-dependent; server cost and bandwidth. |
With multiple clients sharing one backend, a single web-charting implementation gives consistent visuals across platforms while tapping mature libraries built for financial data. You pay in performance overhead — which the rest of this section is about controlling.
Making WebView charts behave#
WebViews are flexible but heavy. Complex JS libraries like D3.js or ECharts can strain a device, and the platforms behave differently. Four fronts to manage:
Performance#
- Isolate charts from high-priority flows like order execution.
- Pre-process data server-side to cut client work.
- Avoid redundant redraws; after the initial full load, apply delta updates only.
- For long spans, load low-resolution first, then fill in detail (phased loading).
Interaction & responsiveness#
- Offload computation to the backend or background threads.
- Validate performance across a range of real devices, not just flagships.
Visual integration#
- Align chart styling with the app's design system and tune WebView properties so it doesn't feel bolted on; follow each platform's UI conventions.
Platform-specific traps#
- Android — the WebView updates independently of your app, so behavior varies across devices and OS versions; test across WebView implementations.
- iOS —
WKWebViewhas its own performance profile; local-storage persistence and memory management need care.
eToro embeds the web-based TradingView toolkit for advanced interactive charts. Robinhood went the other way — native drawing APIs for maximum control, and open-sourced Spark, a lightweight Android sparkline view. Same problem, opposite ends of the table above.
Keeping the chart alive in real time#
Intraday prices change several times a second. To stay consistent, the app uses the same WebView charting stack for both historical and real-time views — one technology to master, one look to maintain. The new question is how to feed a live stream into that chart without melting the device.
How often should the chart redraw?#
| Option | Upside | Downside |
|---|---|---|
| Direct updates | Simplest; always shows the latest tick; minimal latency. | UI jank under frequent updates; heavy battery use; can flood the main thread. |
| Buffered updates ✓ | Fewer redraws; battery-friendly; smooths bursts of ticks. | Small display delay; may skip intermediate points; needs interval tuning. |
| Priority-based | Focuses resources on the most important elements. | Complex; needs careful tuning; mixed refresh rates can confuse users. |
For a single intraday chart, buffering gives the best balance of responsiveness and battery. Collect incoming ticks and flush them to the chart on a fixed cadence (every 1–2s) instead of redrawing on every message.
Buffered updates are ideal for a single live chart. If the product later shows several simultaneously, revisit it — you'd likely move to a hybrid that layers priority-based updates on top.
The JavaScript bridge#
The chart runs as JavaScript inside the WebView, but live data arrives at the native layer via the WebSocket — not in the WebView directly. A JavaScript bridge connects the two, and that indirection is deliberate: native code manages connections, validates and formats data, and buffers more efficiently before anything reaches the chart.
Native handles: the WebSocket connection, receiving/validating data, buffering, pre-processing, and deciding when to push. WebView handles: rendering, user interactions (zoom, pan, tap), and animations. The bridge carries data native → WebView (ticks, config) and events WebView → native (taps, zooms, toggles).
Android: @JavascriptInterface for the native↔WebView channel, with Kotlin
Flow + dispatchers to buffer and throttle off the main thread. iOS:
WKUserContentController + WKScriptMessageHandler for messaging, with Combine's
throttle/debounce to pace updates.
The data flow, end to end#
When a user opens an intraday chart, two data sources are combined into one buffered stream:
- The Stocks remote data source fetches the current day's history over REST to establish a baseline.
- In parallel, it opens a WebSocket for live price updates.
- The Stocks repository merges both, applying the buffered strategy: it emits the historical baseline first, then batches incoming ticks and flushes them at intervals rather than one by one.
- The Stock-detail state holder subscribes to that buffered stream and drives the JavaScript bridge.
- The WebView chart renders each batch, producing the appearance of smooth, live motion.
Where it breaks, and how to shore it up#
- WebView redraws — use partial redraws (update only changed elements) and disable animations during high-frequency updates.
- Data processing — move transforms to native background threads, pre-compute derived values instead of doing it in JS, and cache computed results.
- Bridge overhead — every native↔WebView call costs; batch multiple ticks into one message, use compact formats, and keep a steady update interval.
- Flaky networks — clearly mark data as stale on connection loss and reconnect with exponential backoff [+] (retry after growing delays so you don't hammer a struggling server).
- Battery — stop real-time updates when backgrounded; throttle adaptively based on battery level.
Server-side filtering so clients receive only the data they need. Buffer sizing big enough to hold all ticks between flushes even in volatile periods (10–20 points is usually enough per stock). Adaptive scheduling — raise frequency during volatile market hours when the app is foregrounded and the user is watching; let users dial it back for battery.
Institutions like J.P. Morgan and Commerzbank, and platforms like Binomo, use SciChart's native engine for real-time dashboards. Coinbase's exchange exposes a WebSocket feed broadcasting live order and trade data — the same push model, at exchange scale.
Recap#
A trading client is a machine for moving fast-changing numbers to a phone without lying and without draining it. Almost every decision here — the protocol split, memory-only caching, native buffering behind the bridge — trades a little immediacy or simplicity for correctness and battery, because on a phone those are the scarce resources, not the data itself.
Further reading#
Every link below was fetched and confirmed live on 24 August 2026. Two have materially changed since the source chapter was written — flagged inline.
Protocols & architecture
- Trade Republic — State of Android, 2024The REST-plus-WebSocket stack this chapter cites, described by the team that ships it. Note they've since migrated their WebSocket client from Scarlet toward Ktor. note
- API management — the gateway patternBackground on what an API gateway does: throttling, security, routing, and analytics. (Source linked the "#gateway" section.)
Charting on mobile
- Robinhood — using chartsA product-side look at line vs candlestick charts and time spans — the UX decisions that shape chart design.
- Power E*TRADE — charting enhancementsHow a mature platform layers studies, drawing tools, and display types onto a chart.
- MPAndroidChartThe go-to third-party Android charting library — the "third-party libraries" row of the trade-off table. Effectively in maintenance; latest release is v3.1.0 (2019). note
- DGCharts (formerly Charts)The iOS/macOS counterpart to MPAndroidChart. Renamed from "Charts" to DGCharts in v5 to avoid clashing with Apple's Swift Charts. renamed
- TradingViewThe web charting toolkit eToro embeds via a WebView — the "WebView rendering" path in practice.
- Robinhood SparkRobinhood's lightweight native Android sparkline view — the "native graphics" path. Last release 2019; simple by design. note
Real-time engines & feeds
- SciChartHigh-performance native charting engine used in finance, medical, and motorsport real-time dashboards.
- SciChart — native vs JavaScript for data vizThe vendor's case for native over WebView/JS at high data density — a useful counterpoint to this chapter's WebView choice.
- Coinbase Exchange — WebSocket feedA production example of broadcasting live market data over WebSockets. The old URL now redirects into the reorganized Exchange docs; the feed lives under "WebSocket Feed". moved