Problem RestatementProblem
Amazon asked: design a financial dashboard that shows a user's bank account balances, stock holdings, cryptocurrency holdings, and derived portfolio metrics (total net worth, allocation by asset type, daily change). Data comes from multiple external providers (banks via aggregators like Plaid, brokers, crypto exchanges, price feeds), each with different formats, update frequencies, rate limits and reliability.
ArchitectureArchitecture
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
U["User"] --> API["Dashboard API"]
API --> DB[("Normalized holdings + balances")]
API --> DER["Derived metrics service"]
DER --> PR[("Price cache - stocks, crypto, FX")]
SCH["Refresh scheduler"] --> AD["Provider adapters"]
AD --> P1["Bank aggregator"]
AD --> P2["Broker API"]
AD --> P3["Crypto exchange API"]
AD --> DB
PF["Market price feeds"] --> PR
VAULT[("Token vault - encrypted credentials")] --> ADDeep Dive — Keeping a portfolio value freshDeep dive
The dashboard shows total net worth across banks, brokerages and crypto exchanges. What counts as "fresh" is different for each part of that number.
Store the value each provider reports
Fetch the portfolio value from each provider and add them up.
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
P1["Brokerage - portfolio value at 09:31"] --> SUM["Total stored"]
P2["Exchange - value at 09:28"] --> SUM
SUM --> SHOW["Dashboard shows a stale total all day"]
REF["To update, re-fetch every provider"] --> RL["Provider rate limits - refreshes are expensive and slow"]
RL --> MIX["Different parts of the total are from different times"]The total is a mixture of values captured at different moments, and the only way to refresh any of it is a provider call — so freshness is bounded by the slowest, most rate-limited integration.
Refresh from providers more often
Poll each connection on a schedule and after user activity.
Values get fresher and the cost scales badly: each user's each connection is a metered API call, and crypto prices move by the second regardless of how often you poll. Aggressive polling hits provider limits and still shows stale prices between refreshes.
Separate what changes rarely from what changes constantly
A holding is a quantity; its value is that quantity times a price. Those two facts have completely different refresh needs:
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
PROV["Provider adapters"] --> QTY[("Quantities - 12 AAPL, 0.4 BTC, balances")]
QTY --> NOTE["Change only when the user trades - refresh per connection, on a schedule"]
FEED["One market price feed - shared by all users"] --> PX[("Price cache")]
PX --> LIVE["Prices update continuously, for everyone at once"]
QTY --> CALC["value = quantity x latest price"]
PX --> CALC
CALC --> DASH["Total updates live without touching any provider"]- One price feed serves every user. AAPL's price is not per-user data, so fetching it per connection is the mistake that makes the whole design expensive. One feed, cached, and every portfolio revalues for free.
- Quantities are refreshed per connection, on a schedule and when the user opens the app — because they only change when someone trades or transfers.
- Bank balances are the exception: they are a value, not a quantity times a price, so they follow the connection's refresh cadence and should be shown with an
as_oftimestamp.
Label every number with what it is as-of. A dashboard that silently mixes a live crypto price with a bank balance from four hours ago is not wrong, but it is misleading unless it says so — and once the timestamps are visible, the refresh strategy stops being something users have to guess at.
Reliability and Security
- Provider outages: circuit breakers per provider, retries with backoff, and "reconnect required" states when tokens expire (the user must re-authorize).
- Security: provider credentials and OAuth tokens are encrypted in a vault (never in logs), with read-only scopes where possible. MFA for the app, and encryption at rest.
- Idempotent upserts of balances and holdings keyed by (user, provider, account/asset), with events for changes (alerts like "large deposit").
ScaleScale
- Millions of users × several connections each. The refresh workload is the main cost, so prioritize active users, lower the frequency for inactive ones, and use provider webhooks where offered (push instead of poll).
- Price computations are shared: 10K distinct assets priced once, then applied to millions of holdings.
Wrap-UpWrap-up
Connect each provider through an adapter that normalizes accounts and holdings into one model, refreshed by a rate-limited, jittered scheduler (and on app open) with secrets kept in a vault. Keep quantities per user but prices in a shared market cache, compute net worth, allocation and daily change with FX conversion, and snapshot daily history. Always show "as of" freshness and last-known values with warnings when providers fail.