Why these 6 questions, and how to use this
2DSA Problems
4System Design
~45mPer Question
∞Trade-offs
The 6 Questions, Classified
Q1Max profit, ≤ k transactions. DSA. LeetCode 188. Tests dynamic programming maturity. State design is the whole game.
Q2Real-time trading system, millions of TPS. System design. The flagship finance question — order book, matching engine, market data, audit log.
Q3Median from a price stream in O(log n). DSA. LeetCode 295. Two-heap pattern; tests data-structure intuition and the ability to drop in the right specialised structure.
Q4Distributed cache, strong consistency. System design. CAP, consensus (Raft/Paxos), quorum reads, leader leases. The "do you really understand consistency?" check.
Q5Rate limiter — per-user, per-endpoint. System design with a coding lean. Token bucket vs sliding window; Redis + Lua for atomicity.
Q6Real-time anomalous trading pattern detection. System design + ML. Tests stream processing (Kafka, Flink), detection layering, and surveillance/governance instincts.
The Meta-Signal Across All 6
What is being scored isn't whether you reach the "right" answer — it's whether you reason like an engineer who can be trusted with money.
- Clarify before you compute. Every problem here has hidden ambiguity. Surface it before writing.
- Name trade-offs out loud. Latency vs throughput. Consistency vs availability. Recall vs precision. False positive vs false negative. Minimum 3 per answer.
- Fail-closed thinking. In finance, when in doubt, refuse the trade — don't auto-approve. This is the opposite of consumer software. Show you know that.
- Auditability is non-negotiable. Append-only log, immutable, replayable. If you don't mention it once across the four system designs, you look junior.
- Determinism. Same input → same output. Regulators require you to reconstruct any decision. Random seed your ML models, log every input.
📖Read first
Each Q tab opens with the interview-room first move — what to clarify, how to frame, what to write down before code.
🧮Solve second
DSA problems give progressive solutions (brute-force → optimal → space-optimised). Don't skip the brute-force narration in the room.
🏗️Design third
System design walks the same template every time: clarify → estimate → architecture → trade-offs → failure modes. Memorise the template, not the answers.
🎯Follow-ups
Each question has a "if they push you" section. The interviewer almost always pushes — these are the next 2-3 turns.
⭐STAR bridges
Several questions have a "STAR connection" box — concrete ways to bridge to a project from your story bank when asked "have you done anything like this?"
📅Practice plan
Last tab gives a 3-day study plan for these six. Don't try to absorb all of this in one sitting — it won't stick.
Note on framing. Question 2 ("real-time trading system, millions of TPS") and Question 1 of the existing Superday Prep ("Real-Time Order Book / Matching Engine") are very close cousins. Treat them as one body of knowledge — the questions overlap by ~70%. Same for Question 6 here and the existing PII-Detection design — both are streaming Kafka → Flink → ML/rules → audit log architectures.
Q1 — Max Profit with at most k Transactions
Classic DP. LeetCode 188 ("Best Time to Buy and Sell Stock IV"). The hardest of the six "stock" problems on LeetCode and the most general — every other variant collapses out of this one. If you can do it cleanly, you've shown DP fluency.
Problem (paraphrased)
You're given an array prices where prices[i] is the stock price on day i, and an integer k. You may complete at most k transactions, where one transaction = one buy + one matching sell. You can hold at most one share at a time. Maximise total profit.
DifficultyHard
Pattern3D dynamic programming with state machine
GeneralisesLC 121 (k=1), LC 122 (k=∞), LC 123 (k=2), LC 309 (cooldown), LC 714 (fee)
In the Room — First 90 Seconds
1
Restate & clarify
"At most k — so we may use fewer if it's not profitable, right?" "Can prices be negative or zero?" "Is k bounded? If k ≥ n/2, the constraint is essentially vacuous." "Define a transaction — buy + sell, counted once?" These three clarifications buy you 30 seconds and signal seriousness.
2
Walk a tiny example
Take k=2, prices=[3,2,6,5,0,3]. Buy at 2, sell at 6 → +4. Buy at 0, sell at 3 → +3. Total = 7. Doing this aloud convinces the interviewer (and yourself) that you understand the problem before you start coding.
3
Name the approach
"Brute force is exponential — every day has buy/sell/skip choices. I'll go to dynamic programming with a 3-dimensional state: day, transactions used, currently holding. Then optimise space."
4
Edge case first
"Quick edge: if k ≥ n/2, transactions are unbounded — I can just sum every positive day-over-day delta. That's O(n). Otherwise I'll do the full DP." This single line is what separates "studied" from "studied well."
On any given day, you're in one of two states: holding a share or not holding. From each state, two actions: do nothing, or transition. Count a transaction at the buy (one valid convention; sell-counting also works, just be consistent).
# State: dp[i][j][h]
# i = day index (0..n-1)
# j = transactions still available (0..k)
# h = holding flag (0 = no share, 1 = holding)
#
# Transitions:
# dp[i][j][0] = max(
# dp[i-1][j][0], # rest, still not holding
# dp[i-1][j][1] + prices[i] # sell today
# )
# dp[i][j][1] = max(
# dp[i-1][j][1], # rest, still holding
# dp[i-1][j-1][0] - prices[i] # buy today (uses 1 txn)
# )
#
# Base cases:
# dp[*][0][0] = 0 no transactions, not holding -> 0
# dp[*][0][1] = -inf no transactions left but holding is impossible
# dp[-1][*][0] = 0 before day 0, not holding -> 0
# dp[-1][*][1] = -inf
#
# Answer: dp[n-1][k][0] (must end NOT holding to realise profit)
💡 The "convention question": do you decrement j on buy or on sell? Either is fine, but commit to one and say it out loud. Switching mid-derivation is where interviewers see candidates wobble.
Solution v1 — Naive 3D DP
Direct translation of the recurrence. Easy to read, easy to debug, gets full marks if you stop here cleanly.
def maxProfit(k: int, prices: list[int]) -> int:
n = len(prices)
if n < 2 or k == 0:
return 0
# Edge: enough transactions = unconstrained
if k >= n // 2:
return sum(max(0, prices[i] - prices[i-1]) for i in range(1, n))
NEG = float('-inf')
# dp[j][h]: best profit on current day with j txns left, holding h
dp = [[NEG] * 2 for _ in range(k + 1)]
for j in range(k + 1):
dp[j][0] = 0 # start flat, no profit, no shares
for i in range(n):
new = [row[:] for row in dp]
for j in range(1, k + 1):
# not holding: rest, OR sell today
new[j][0] = max(dp[j][0], dp[j][1] + prices[i])
# holding: rest, OR buy today (consume 1 txn)
new[j][1] = max(dp[j][1], dp[j-1][0] - prices[i])
dp = new
return dp[k][0]
TimeO(n · k) — every (day, txn) cell does O(1) work
SpaceO(k) — only need last day's dp row
Solution v2 — Buy/Sell Two-Array Form
Equivalent — sometimes easier to write because the state names are intuitive ("max profit on day i after at most j buys, currently in state X"). Pick whichever you can write without bugs.
def maxProfit(k: int, prices: list[int]) -> int:
n = len(prices)
if n < 2 or k == 0:
return 0
if k >= n // 2:
return sum(max(0, prices[i] - prices[i-1]) for i in range(1, n))
# buy[j] = best profit after at most j buys, currently HOLDING
# sell[j] = best profit after at most j sells, currently FLAT
buy = [float('-inf')] * (k + 1)
sell = [0] * (k + 1)
for price in prices:
for j in range(1, k + 1):
buy[j] = max(buy[j], sell[j-1] - price)
sell[j] = max(sell[j], buy[j] + price)
return sell[k]
⚠️ Order of inner-loop updates matters — make sure buy[j] is updated before sell[j] in the same iteration; otherwise you'd allow buy and sell on the same day, which is fine here (zero profit) but in cooldown variants will be wrong. Walk the interviewer through this.
If They Push You — Likely Follow-Ups
- "Add a transaction fee." → Subtract
fee on every sell: sell[j] = max(sell[j], buy[j] + price - fee). LC 714.
- "Add a 1-day cooldown after sell." → Now buy on day i requires sell happened ≤ day i-2. State becomes
(holding, just_sold); or carry one extra row of dp. LC 309.
- "Now there are M stocks running in parallel — same k pooled." → Concatenating arrays doesn't work because order matters per stock. You'd merge in time order and add a "which stock" dimension. Discuss the curse of dimensionality and propose greedy heuristics if k is small.
- "Prices are streamed; recompute online as each new price arrives." → Hard problem. The DP is forward-only in
i, so you can incrementally extend each day. Just keep the current buy/sell arrays, do one inner loop per new tick → O(k) per tick. This is a great answer; emphasise it.
- "What if k can also change between ticks?" → If k only increases monotonically, you can extend
buy/sell with new entries (each new buy[j] initialised from sell[j-1] - price). If k can shrink, you have to discard and recompute from history — flag this.
- "Prove correctness of the edge case
k ≥ n/2." → A transaction needs ≥ 1 day to buy + ≥ 1 day to sell. With n days, you can do at most ⌊n/2⌋ disjoint transactions. So when k ≥ ⌊n/2⌋, k is non-binding and the optimum equals the unconstrained optimum, which by a classic exchange argument = sum of positive consecutive deltas.
Common Mistakes That Cost the Offer
- Forgetting the
k ≥ n/2 shortcut. For large k (LC test cases go up to 10⁹), the O(n·k) DP TLEs and you fail. The shortcut is mandatory, not optional.
- Counting transactions inconsistently. Saying "I'll count on buy" and then writing the sell line as if you counted on sell. Off-by-one is the death of DP problems.
- Initialising
buy to 0 instead of -inf. Then "holding without ever buying" looks free.
- Updating
dp in place when transitions reference both old and new values. Use a fresh row, or order updates carefully and explain why the order is safe.
- Returning
dp[k][1]. The answer must be in the flat (not-holding) state; otherwise you're holding a share whose value isn't realised profit.
STAR bridge
If they ask "have you ever solved a problem with state-space explosion?" — your chess pipeline story (Stockfish + Julia, 200GB+ data) is a good match. The framing: "I had to compress a stateful pipeline that originally tried to hold full game trees in memory; what I learned about identifying which state dimensions are independent transferred directly to how I think about DP problems like this one — most state explosions come from not noticing one of the axes can be collapsed."
Practice (do these in order)
- LC 121 (one transaction) — 8 minutes from cold start
- LC 122 (unlimited) — 8 minutes
- LC 123 (exactly 2) — 15 minutes, write the 4-variable form
- LC 188 (this one) — 25 minutes, time yourself, then again from scratch the next day
- LC 309 (cooldown) — 15 minutes, the cooldown extension
- LC 714 (fee) — 10 minutes
Do all six in one week. The point is to see the family — by the end, LC 188 should feel like a special case, not a one-off.
Q2 — Real-Time Trading System, Millions of TPS
The flagship finance system-design question. "Design a system that can handle millions of transactions per second" is a deliberately broad prompt — the interviewer wants to see how you decompose, not whether you've memorised an answer. There is heavy overlap with the order book / matching engine prompt in the Superday Prep; treat this as the same body of knowledge with a different starting hook.
Mental model: what does "transaction" actually mean?
First clarification. "Transaction" is overloaded. It could mean:
- Order event — a new order, cancel, or modify hitting your gateway. (Highest volume; this is what exchanges measure.)
- Match / trade — a buy crossed with a sell, producing a fill. (Lower volume; bounded by liquidity.)
- Settlement transaction — moving cash + securities post-trade. (Much lower volume; T+1 or T+2.)
"Millions per second" most naturally means order events. Confirm: "Are we sizing for ingress order rate, or for matched trades?" That single question signals you understand the domain.
Step 1 — Clarify (do this aloud)
- Scale. Peak vs average TPS? "Millions" — let's pin to 5M order events/sec at peak, 100K matched trades/sec.
- Latency budget. HFT-grade microseconds (sub-100µs end-to-end) or institutional ms? Different architecture.
- Order types. Market, limit, stop, IOC, FOK, GTC? Cancels and modifies?
- Universe. Single-asset class? Equities + futures + options? Symbol count (1K? 100K?).
- Geography. One datacenter or globally distributed? (Multi-region matching is not a thing in serious exchanges; matching is single-DC, market data is replicated globally.)
- Regulation. Is this an exchange (subject to MiFID II / Reg NMS), a broker (best-execution + audit), or an internal trading desk?
- Read/write ratio. Order book reads (market data subscribers) vastly exceed writes — pin a number.
Always do back-of-envelope before architecture. Rough numbers:
Ingress5M orders/sec × ~200B per order = ~1 GB/sec wire-level (achievable with kernel bypass + 25/100 GbE).
Matching shardsA single-threaded matcher comfortably does 100K–500K orders/sec for a hot symbol. 5M total / 200K per shard ≈ 25 matcher shards; round up to 50–100 for headroom & fault domains.
Audit logEvery order persisted. 5M × 200B × 86,400 sec/day = ~85 TB/day raw. Compress + tier hot/warm/cold.
Market data fanout100K subscribers × 5M ticks/sec = 500B msg/sec — only feasible via UDP multicast, never unicast.
┌─────────────────────────────┐
Client ── FIX/binary ──→ │ Edge Gateway (per region) │
│ · TLS, auth, rate-limit │
│ · Pre-trade risk (fat-finger)
│ · Sequence-number stamping │
└─────────────┬───────────────┘
│
shard by symbol
│
┌───────────┴───────────┐
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ Matcher (AAPL) │ │ Matcher (MSFT) │ ← single-threaded
│ in-memory book │ │ in-memory book │ per-symbol leader
└────────┬────────┘ └────────┬────────┘
│ │
└──────────┬───────────┘
↓
┌────────────────────────┐
│ Append-only event log │ ← Kafka (or LMAX-style ring buf)
│ immutable, ordered │ SOURCE OF TRUTH
└─────────┬──────────────┘
│
┌────────────────────────┼─────────────────────────────┐
↓ ↓ ↓
┌──────────────┐ ┌────────────────────┐ ┌───────────────────┐
│ Market data │ │ Risk & surveillance│ │ Clearing/ │
│ publisher │ │ stream processor │ │ settlement │
│ (UDP mcast) │ │ (Flink/Kafka Strms)│ │ (downstream) │
└──────────────┘ └────────────────────┘ └───────────────────┘
The 5 Decisions That Define the System
A
Shard by symbol, single-threaded matching per symbol
Industry standard. Every serious exchange (NASDAQ INET, NYSE, LSE) uses this pattern. Single-threaded = no lock contention, no race conditions, deterministic for replay. The order book is two priority queues: bids (max-heap on price, then time) and asks (min-heap). Operations are O(log n) per insert; for hot symbols a sorted bucket-per-price-level array is even faster.
B
Append-only event log is the source of truth
In-memory book is a cache of the event log. On crash, replay the log from the last snapshot. This pattern (LMAX Disruptor, Kafka, event sourcing) gives you crash recovery, auditability, and the ability to spin up a parallel "shadow" matcher for upgrades — all from one design choice.
C
UDP multicast for market data, TCP/FIX for orders
Market data is broadcast to thousands of subscribers; unicast would be O(N) per tick. UDP multicast = one packet for all subscribers. Lossy by definition, so attach sequence numbers and have subscribers request gap-fill via TCP if they detect missing seq. This is exactly how NASDAQ ITCH/OUCH works.
D
Pre-trade risk in the gateway, post-trade risk in the stream
Pre-trade = synchronous (must finish before order touches matcher): basic checks like notional size cap, fat-finger, balance > required margin. Microseconds. Post-trade = asynchronous, in the stream processor: portfolio risk, VaR, exposure aggregation. Milliseconds to seconds is fine. Splitting these is non-negotiable; doing all risk synchronously kills your latency.
E
Fail-closed, not fail-open
If any critical service is unhealthy (auth, risk, log), reject the order. Never auto-approve "to keep things flowing." This is the cardinal rule that distinguishes finance from consumer software. Mention it explicitly.
Trade-offs to Verbalise (3+ minimum)
- Single-threaded matching sacrifices CPU parallelism for correctness. A multi-threaded matcher would shave microseconds but introduce ordering bugs that are nightmarish in finance. The trade is correct.
- Strong consistency within a symbol; eventual across symbols. AAPL's order book is perfectly serialised. Cross-symbol arbitrage signals see microsecond skew between matchers — that's acceptable; clients with co-located strategies handle it.
- UDP for market data trades reliability for fanout. You build sequence-number gap detection on top.
- Memory speed vs persistence. Order book lives in RAM (microseconds); log writes go through SSD or NVMe with battery-backed cache. The synchronous "write to log before ack" step is your latency floor.
- Hot/warm/cold tiering for log storage. Last hour in NVMe, last week in SSD, archival in S3-class object storage. Most queries hit hot.
Failure Modes (the interviewer will probe)
Matcher crash
Replay event log from last snapshot. Standby matcher takes over. Snapshots every N seconds.
Fail-stop
Kafka unavailable
Reject new orders. Fail-closed. Don't accept orders you can't durably log.
Fail-closed
Network partition
Affected symbol(s) halted; healthy symbols keep trading. Per-symbol fault domains.
Isolate
Risk service slow
Circuit breaker → reject new orders requiring that risk check. Don't queue indefinitely.
Fail-closed
Market data lag
Subscribers detect via sequence number. Snapshot recovery via separate channel.
Detect & recover
"Locked / crossed" book (bid ≥ ask)
Pause symbol, alert. Should never happen but does on data feed bugs.
Halt
Vocabulary That Earns Points
- Time-in-force modifiers: GTC (good-til-cancel), IOC (immediate-or-cancel), FOK (fill-or-kill), DAY.
- Order types beyond market/limit: stop, stop-limit, peg, iceberg (display only part), hidden.
- Price-time priority — the universal matching rule: best price first, ties broken by submission time.
- Co-location — clients renting rack space inside the exchange to minimise wire latency.
- Self-match prevention (SMP) — preventing a client from crossing their own order (regulatory).
- FIX protocol — text-based; OUCH/ITCH — binary, what NASDAQ uses internally.
- Kernel bypass (DPDK, Solarflare OpenOnload), RDMA, FPGA-offload matching — for HFT-grade latency.
💡 You don't need to know all these in detail. Sprinkling 2–3 of them naturally signals "I've read about real exchanges, not just System Design Interview Vol. 1."
STAR bridge
If you can pivot to: "On the chess data pipeline I built, the throughput pattern was structurally similar — append-only event log, partition by game ID for parallelism, batch flush to durable storage. The decisions on partition key and batch size translated almost directly to how I'd think about partitioning the order log here." This shows you've thought about throughput at a non-trivial scale, which is rare for a new analyst.
Q3 — Median Price from a Stream in O(log n)
A pure data-structure problem. LeetCode 295 ("Find Median from Data Stream"), exactly. The reason this comes up in finance interviews is that streaming statistics — VWAP, rolling median, percentile bands — are bread-and-butter for trading dashboards and risk monitors.
Problem (paraphrased)
Implement a data structure with two operations:
insert(x) — add a price to the stream
findMedian() — return the current median over all prices seen so far
Required: insert in O(log n), findMedian in O(1) ideally. The interviewer's required bound (O(log n) for both) leaves several solutions on the table.
Reasoning to the Two-Heap Trick
Walk it from naive to optimal. This narration is what gets you the offer; jumping straight to "two heaps" looks pre-memorised.
1
Naive: keep an unsorted list
Insert O(1), median O(n log n) via sort or O(n) via quickselect. Insert is fast but median is too slow on every call.
2
Sorted list (binary insert)
Insert O(n) (linear shift), median O(1). Now median is fast but insert is too slow at scale.
3
Self-balancing BST or order statistic tree
Both operations O(log n). Works, but a heavy structure for what we actually need.
4
Key insight: we only ever need the middle
We never query the 17th element or the 89th percentile here — only the middle. So we don't need full ordering. Split the stream into two halves, keep the bottom half's max and the top half's min cheap to access. Two heaps: max-heap for the lower half, min-heap for the upper half.
Maintain three invariants and the median is always free:
- Every element in
lo ≤ every element in hi.
- Sizes balanced:
|lo| - |hi| ∈ {0, 1}. (We let lo be the bigger one when odd.)
- Median: if odd total →
lo.peek(); if even → (lo.peek() + hi.peek()) / 2.
import heapq
class MedianFinder:
def __init__(self):
# Python heapq is a min-heap; for max-heap we negate values
self.lo = [] # max-heap (negated): bottom half
self.hi = [] # min-heap: top half
def insert(self, x: int) -> None:
# Step 1: tentatively put x in lo (negated)
heapq.heappush(self.lo, -x)
# Step 2: move lo's largest into hi (rebalance ordering invariant)
heapq.heappush(self.hi, -heapq.heappop(self.lo))
# Step 3: rebalance sizes (lo should be ≥ hi by at most 1)
if len(self.hi) > len(self.lo):
heapq.heappush(self.lo, -heapq.heappop(self.hi))
def findMedian(self) -> float:
if len(self.lo) > len(self.hi):
return -self.lo[0]
return (-self.lo[0] + self.hi[0]) / 2.0
insertO(log n) — at most 3 heap operations
findMedianO(1) — peek both heaps
SpaceO(n) — every value retained
💡 The "tentatively push to lo, then move max to hi, then rebalance sizes" pattern handles all cases including the empty-heap edge cleanly without nested ifs. Cleaner code = faster narration.
If They Push You — Likely Follow-Ups
- "Sliding window of size W instead of all-time median." → Now you also need
removeOldest. Heap doesn't support arbitrary delete in O(log n). Two answers:
- Lazy deletion: tag entries with index, mark removed, skip on peek. Amortised O(log n) but pathological cases.
- Two SortedList / multiset (Python
SortedList from sortedcontainers, C++ multiset): supports add and remove in O(log W). Cleaner. This is LC 480.
- "Now find the 95th percentile, not the median." → Two heaps generalise: keep the bottom 95% in one heap, top 5% in the other, maintain ratios on insert. Or use an order statistic tree (balanced BST with subtree size). Cleaner.
- "What if the stream is too big to fit in memory?" → You can't compute the exact median anymore. Approximate with:
- t-digest — clustered quantile sketch, mergeable across machines. State of the art.
- Greenwald-Khanna (GK) summary — bounded-error quantiles in O(log n / ε) space.
- Reservoir sampling — uniform random sample, then take median of sample. Crude but trivial to implement.
Mentioning t-digest by name is the easy way to look like you've worked with streaming statistics before.
- "How would you make this distributed across N shards?" → Each shard maintains its own sketch (t-digest), then merge centrally. The two-heap structure does NOT merge cleanly — you'd have to ship all elements. T-digest is mergeable; that's its whole reason for existing.
- "What if prices can be deleted by sequence number?" → Index-based lazy deletion in the heaps; or move to a balanced BST keyed by (price, seq_id).
- "What's the median of medians?" → Different topic — that's the deterministic O(n) selection algorithm. If they ask, redirect: "That's the linear-time selection trick — O(n) to find the kth element by partitioning around medians of 5-element groups. Different problem from the streaming version, but worth flagging."
Why Finance Cares About This
- Robust statistics. In market data, mean is corrupted by fat-finger trades and outliers. Median and quantile-based stats (median absolute deviation, IQR) are far more robust — they're what risk monitors use.
- VWAP and TWAP. Volume-weighted and time-weighted average prices use streaming aggregations of the same shape (running sums, running quantiles).
- Anomaly detection thresholds. "Flag any trade more than 3 MAD from the rolling median" — directly uses streaming median + streaming MAD. Same data structure pattern.
- Risk reporting. P&L distributions are reported with quantiles (VaR is the 95th or 99th percentile loss). Online quantile estimation is the production use case.
Practice (do these in order)
- LC 295 (this one) — 15 minutes from cold
- LC 480 (sliding window median) — 25 minutes; the lazy-deletion vs SortedList trade-off
- LC 703 (Kth largest in stream) — 10 minutes; warm-up that uses one heap
- Bonus: read 2 paragraphs of the t-digest paper. You don't need to implement it; just be able to say "it's a clustered quantile sketch, mergeable, used in DataDog and influxDB" if asked.
Q4 — Distributed Cache, Strong Consistency for Financial Data
A CAP-theorem question wearing a distributed-systems costume. The interviewer is checking whether you understand why "strong consistency" is in fundamental tension with availability under partition — and whether you know the standard tools (Raft, Paxos, quorum reads, leader leases) to engineer a usable CP system.
First clarification: do you actually want strong consistency?
In finance, the answer is yes for some data, no for others. Don't rush to "strong consistency" for the entire cache. State this clearly:
Strong neededAccount balances, position state, order status, cash ledger, P&L of record. If two reads disagree on a balance, money goes wrong.
Eventual is fineMarket data ticks (with sequence numbers for gap detection), historical aggregates, dashboards, audit log views.
Bounded staleness OKReference data (security master, FX rates that update every few seconds), risk dashboards refreshed every few seconds.
Saying "let me clarify which subset of financial data needs strong consistency" buys you 30 seconds and shows you don't blindly apply patterns.
CAP, Briefly — Mention by Name
CAP theorem: in a distributed system, under network partition, you cannot have both consistency and availability. Pick one. A strongly consistent cache is a CP system: it accepts writes only when a majority quorum of replicas can ack; if you're partitioned into the minority side, you reject writes.
- CP example: etcd, ZooKeeper, Spanner, FoundationDB, TiKV. Built on consensus.
- AP example: Cassandra (default), DynamoDB (default), Riak. Eventual consistency.
- Redis Cluster: default mode is asynchronous replication = AP-leaning, NOT strongly consistent. WAIT command + Redis Sentinel can give bounded staleness, but for true linearisable strong consistency you'd reach for Redis Enterprise's CRDB or layer Raft on top yourself.
⚠️ A common candidate trap: saying "I'll use Redis for strongly consistent cache." Redis is NOT strongly consistent by default. If you say that, the interviewer will ask follow-ups you can't answer. Either say "Redis with WAIT and Sentinel for bounded staleness — but if I need true linearisability I'd use etcd or build on Raft" or skip Redis entirely for this answer.
Step-by-Step Architecture
┌────────────────────────┐
write ────→ │ Cache Client SDK │
│ (knows leader per key)│
└─────────┬──────────────┘
│
partition by key hash → shard
│
┌───────────────────────┴────────────────────────┐
↓ ↓
┌────────────────┐ ┌────────────────┐
│ Shard 1 │ │ Shard N │
│ Raft group: │ │ Raft group: │
│ L F F F F │ │ L F F F F │
│ └─→ replicate │ │ │
│ log entry │ │ │
└────────────────┘ └────────────────┘
↑
└─ Writes only ack'd to client AFTER
majority quorum (3 of 5) durably writes the log entry.
That's strong consistency.
1
Consensus protocol — Raft (preferred over Paxos for clarity)
Raft elects a single leader per shard. The leader appends entries to a replicated log; entries become "committed" once a majority of replicas (e.g., 3 of 5) durably persist them. Production references: etcd (the canonical Raft impl), CockroachDB, TiKV, Consul. Paxos is older, more academic; Multi-Paxos and EPaxos are practical variants. Just say "Raft, à la etcd" and you're aligned with industry default.
2
Sharding — partition by key hash
One Raft group per shard. With 50 shards and 5 replicas each, that's 250 nodes. Adding a shard = rebalancing some keys, which is operationally expensive — design the hash space carefully (consistent hashing or rendezvous hashing).
3
Read paths — three options, pick deliberately
(a) Read from leader, with leader lease: the leader holds a time-bounded lease (say, 9 seconds renewable). Within the lease, reads are linearisable without contacting followers. Cheapest. Used in Spanner, Raft with optimisations.
(b) Quorum read: read from a majority of replicas, take the most recent. Always correct, but 1 RTT to multiple replicas — slow.
(c) Read from any replica with sequence-number freshness check: client supplies a "read-after" sequence number; replicas wait until they've caught up. Bounded staleness, not strict linearisability.
Default to (a). Be ready to discuss why.
4
Cross-shard transactions — 2PC or Calvin-style
A single financial operation often touches multiple keys (debit account A, credit account B). Single-key linearisability isn't enough. Two options: two-phase commit (2PC) across the involved Raft groups (slow, blocking on coordinator failure), or a deterministic transaction order (Calvin / FaunaDB-style) where transactions are sequenced upstream and replicas execute in lockstep. Spanner uses Paxos + 2PC + TrueTime for global transactions.
5
Failure handling
Leader fails → election (3-15 seconds typical) → unavailability window for that shard. Minority partition → can't commit, so rejects writes. Network split healing → log reconciliation; uncommitted entries on minority side are discarded. This is by design, not a bug — it's the price of strong consistency. Mention it.
- Latency floor. A write commits only after the leader plus a majority of followers have durably persisted it. That's 1 RTT to followers + 1 fsync per replica. Realistically: 1–5ms in a single DC, 50–100ms cross-region.
- Throughput is leader-bound per shard. Single leader means single write pipeline per shard. To scale total throughput, add shards.
- Availability dips during leader election. Up to ~10s where that shard accepts no writes. The fix: more shards = smaller blast radius per election.
- Strong consistency vs read scalability. Linearisable reads only from leader (or with quorum). Followers can serve stale reads if you accept bounded staleness. Choose per workload.
- Geographic distribution = latency tax. Cross-region Raft means committing across continents. Spanner pays this; most finance systems instead deploy region-local Raft groups + asynchronous cross-region replication for DR (with explicit "this region's data may be stale" semantics).
Reference Implementations to Name-Drop
System
Why it's relevant
Used by
etcd
Pure Raft KV store. Best canonical reference. Strong consistency, used as the metadata store of Kubernetes.
K8s, Cilium
ZooKeeper
Older. Uses Zab (Paxos variant). Strong consistency. Industry workhorse for coordination.
Kafka, Hadoop
Google Spanner
Globally distributed strong consistency using TrueTime (synchronised atomic clocks) + Paxos + 2PC. The gold standard.
Google internal
CockroachDB / TiKV
Open-source Spanner-likes built on Raft. Strong consistency at scale.
Many fintechs
FoundationDB
Apple's strongly-consistent transactional KV store. Famous for rigorous testing.
iCloud, Snowflake metadata
Redis Enterprise CRDB
Active-active geo-replication for Redis. CRDT-based, gives "strong eventual" guarantees, NOT linearisable.
Some HFT shops for caches
- "Why is this not just Redis?" → Redis async replication = bounded staleness, not linearisable. Failover can lose the last few writes. Unacceptable for ledger data.
- "Walk me through Raft leader election." → Followers heartbeat from leader; if missed, follower becomes candidate, increments term, requests votes from peers. Majority vote wins. Term numbers prevent split-brain. Be able to draw this on paper.
- "What happens during a network partition?" → Side with majority retains a leader, keeps committing. Side with minority loses leader, can't commit (no quorum), serves only reads (stale). Heal: minority side replays log from majority leader, discards uncommitted entries.
- "Cache invalidation strategy?" → For a cache backed by an authoritative store, write-through (write to store + cache atomically — hard) or write-around with TTL (simple, but stale window). For a strongly consistent primary KV store, this question doesn't apply — there's no "source of truth" behind it.
- "How do you handle a clock skew between nodes?" → Raft itself doesn't depend on clocks for correctness — uses logical sequence numbers (terms, log indices). Clocks affect performance (election timeouts) but not safety. Spanner is the exception: it uses TrueTime with bounded clock uncertainty, requires GPS + atomic clocks in every DC.
- "Read-your-own-writes guarantees from a different datacenter?" → Either pin the client to the leader's region, or have the client carry a "read-after-seq-N" token that any replica can wait on. Sticky-by-session works in practice.
Don't confuse "cache" with "datastore." Many candidates default to "memcached / Redis" because the question says "cache." Push back gently: "When you say cache, do you mean a write-through cache backed by an authoritative store, or a primary KV store that's designed to be cache-fast? The architecture is different." This is a great clarifying question that sets up your whole answer.
Q5 — Rate Limiter, Different Limits per User and per Endpoint
A hybrid problem: half system design, half coding round. Token bucket and sliding window are the two algorithms you must know cold, and the implementation question almost always lands on Redis with Lua scripts because that's where the atomicity discussion lives.
First clarification
- Limiting dimensions. "Per user, per endpoint" — does that mean each (user, endpoint) pair has its own bucket? Or do users have a global cap AND each endpoint also has a per-user cap (i.e., layered limits)? Layered is the realistic answer.
- Granularity. Requests per second, per minute, per hour, per day? Multiple at once?
- Bursty vs strict. Do we allow short bursts up to capacity (token bucket), or strictly smooth (leaky bucket)?
- Where to enforce. API gateway (cheapest, coarse), per-service middleware (finer), or both?
- Failure mode. If the rate-limiter service is down, fail-open (let traffic through) or fail-closed (reject)? In finance, fail-closed for trading endpoints, fail-open for read-only metadata.
- Scale. "Millions of users × hundreds of endpoints" needs centralised state. A small internal service can do it in-memory.
Algorithms — Know All Four, Pick Two
Algorithm
Behavior
Memory / Note
Fixed window counter
Count requests in each clock-aligned window (e.g., 0:00–0:01). Reset at boundary. Simple. Boundary burst attack: a user can fire 2× the limit by hitting just-before and just-after the reset.
O(1) per user. Cheap, weak.
Sliding window log
Store timestamp of every request in a sorted set. On request, prune entries older than window, count remaining, accept if < limit. Exact, no boundary issue.
O(N) per user where N = limit. Memory-heavy at scale.
Sliding window counter
Hybrid: keep counts for current and previous fixed window, weight previous by overlap fraction. Approximate but very good in practice.
O(1) per user. Best general default.
Token bucket
Each user has a bucket of capacity C, refilled at rate R. Each request consumes 1 token; reject if empty. Allows bursts up to C, sustained rate R.
O(1) per user. Best when you want bursts.
Leaky bucket
Requests enter a queue draining at fixed rate. No bursts. Good for shaping outbound traffic to fragile downstreams.
O(queue) per user.
💡 In an interview, name token bucket as your default with this reasoning: "It accommodates bursty real-world traffic, has O(1) state per user, and is what Stripe and most major APIs use." If asked for strictness without bursts, switch to leaky bucket. If asked for accuracy without storage cost, sliding window counter.
Architecture — Distributed
┌──────────────────────────┐
request ──→ │ API Gateway / middleware│
└──────────┬───────────────┘
│
│ key = user_id : endpoint
↓
┌───────────────────────────────┐
│ Rate-limit logic (Lua-atomic)│
│ · fetch (tokens, last_ts) │
│ · refill = min(C, tokens + │
│ (now-last_ts)*R) │
│ · if refill ≥ 1: │
│ tokens = refill - 1 │
│ allow │
│ else: reject (429) │
│ · write back atomically │
└──────────┬───────────┬────────┘
│ │
┌──────────┴────┐ ┌───┴────────────┐
│ Redis │ │ Config store │
│ (sharded) │ │ (limits per │
│ per-user │ │ user_tier × │
│ bucket state│ │ endpoint) │
└───────────────┘ └────────────────┘
Token Bucket — Reference Implementation
In-process Python first (interview-quality, ignores distribution):
import time
from threading import Lock
from collections import defaultdict
class TokenBucketLimiter:
def __init__(self, config):
# config: {(user_tier, endpoint): (capacity, refill_per_sec)}
self.config = config
self.buckets = {} # (user, endpoint) -> (tokens, last_ts)
self.lock = Lock() # single-process atomicity
def allow(self, user_id, user_tier, endpoint):
key = (user_id, endpoint)
capacity, rate = self.config[(user_tier, endpoint)]
now = time.monotonic()
with self.lock:
tokens, last = self.buckets.get(key, (capacity, now))
# Refill
tokens = min(capacity, tokens + (now - last) * rate)
if tokens >= 1:
self.buckets[key] = (tokens - 1, now)
return True
self.buckets[key] = (tokens, now)
return False
For a distributed deployment, replace the lock + dict with Redis + Lua:
-- Lua script, runs atomically inside Redis (single-thread server)
-- KEYS[1] = "rl:{user_id}:{endpoint}"
-- ARGV = capacity, refill_per_sec, now_ms
local bucket = redis.call('HMGET', KEYS[1], 'tokens', 'last_ms')
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now_ms = tonumber(ARGV[3])
local tokens = tonumber(bucket[1]) or capacity
local last_ms = tonumber(bucket[2]) or now_ms
local elapsed = math.max(0, now_ms - last_ms) / 1000.0
tokens = math.min(capacity, tokens + elapsed * rate)
if tokens >= 1 then
tokens = tokens - 1
redis.call('HMSET', KEYS[1], 'tokens', tokens, 'last_ms', now_ms)
redis.call('EXPIRE', KEYS[1], 3600)
return 1 -- allowed
else
redis.call('HMSET', KEYS[1], 'tokens', tokens, 'last_ms', now_ms)
return 0 -- rejected
end
💡 The whole point of using a Lua script is atomicity. Without it, two concurrent gateways doing read-modify-write on the same bucket will race and both let the request through. Mention this race condition explicitly — it's what separates candidates who've actually built rate limiters from those who've only read about them.
Per-User × Per-Endpoint Configuration
Limits aren't constants — they vary by user tier and endpoint. Keep the config in a separate store, query it (cached) on each request:
# Example config — typical layered structure
{
("tier=basic", "GET /quotes"): (capacity=100, rate_per_sec=10),
("tier=basic", "POST /orders"): (capacity=10, rate_per_sec=1),
("tier=premium", "GET /quotes"): (capacity=1000,rate_per_sec=100),
("tier=premium", "POST /orders"): (capacity=100, rate_per_sec=10),
("tier=internal", "*"): (capacity=10000,rate_per_sec=1000),
}
# Layered enforcement (composite limits):
# global per-user cap (across all endpoints)
# AND per (user, endpoint) cap
# Both must allow.
def allow_layered(user, endpoint):
return bucket_global[user].consume() and \
bucket_per_endpoint[(user, endpoint)].consume()
⚠️ Order matters with layered limits. If you consume the global bucket and then the endpoint bucket fails, you've "spent" a global token unfairly. Solutions: (a) check both before consuming either, (b) compensate / refund the global on endpoint denial, or (c) accept the small over-spend (simplest). Mention the trade-off.
- Centralised state vs eventual consistency. One Redis cluster = strong limits but adds 0.5-2ms per request. Per-gateway local state with periodic sync = faster but allows brief over-limit windows.
- Memory vs accuracy. Sliding window log is exact but stores every request timestamp; sliding window counter is O(1) but assumes uniform distribution within windows.
- Burstiness vs smoothness. Token bucket allows up-to-capacity bursts (good for legitimate user behaviour like batch syncs); leaky bucket strictly smooths (good for protecting fragile downstream services).
- Fail-open vs fail-closed. If Redis dies, do you let everything through or block everything? In finance: fail-closed for trading APIs (an outage rejecting orders is recoverable; rogue throughput is not), fail-open for read-only market data (accepting a brief unprotected window is fine).
- Clock skew. Token-bucket refill depends on time. If gateway clocks are skewed, refill rates become inconsistent. Use Redis TIME command (server-side single source) inside the Lua script to eliminate skew.
- "Race condition between two gateways?" → Lua script in Redis runs atomically. Without it, two reads + two writes race and both succeed when only one should.
- "What HTTP status do you return?" → 429 Too Many Requests. Include
Retry-After header (seconds until next token available) and X-RateLimit-Remaining for client visibility.
- "How do you communicate limits to clients?" →
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset response headers on every request, plus 429 with Retry-After on rejection. Documented in API docs.
- "What about geo-distributed users?" → Per-region limits with periodic global sync. Accepts that a user can briefly exceed their global limit by spreading requests across regions; flag the trade-off.
- "How would you protect against a single bad actor flooding the rate limiter itself?" → Defense in depth: drop traffic at the LB / edge before it reaches the limiter; per-IP fallback limit at the LB layer; circuit breaker on the limiter to fail-closed if Redis is overwhelmed.
- "Difference between rate limiting and throttling?" → Rate limiting = hard reject when over. Throttling = slow down, queue, or degrade quality. Different actions on the same signal.
Why finance interviewers like this question. Rate limiting in trading systems isn't just abuse prevention — exchanges require it. NYSE, NASDAQ, and most others impose order-rate caps per session (anti-spam, anti-quote-stuffing). Brokers must throttle clients to stay under exchange limits, and many regulatory regimes (MiFID II, SEC market-access rules) require pre-trade controls including rate limits. So this is a real production need, not just a textbook problem.
Q6 — Real-Time Anomalous Trading Pattern Detection
Surveillance is a real, critical engineering function inside investment banks. The question tests three things at once: streaming architecture (Kafka + Flink), ML-vs-rules tradeoffs, and your awareness of the regulatory / governance dimension that makes finance different from consumer ML.
First clarification
- What kind of "anomalous"? Market manipulation (spoofing, layering, wash trades), insider trading patterns, fat-finger errors, or operational anomalies (a strategy gone rogue)? Different signals.
- Latency budget. Real-time intervention (block the trade pre-execution, ~100ms) vs near-real-time alerting for analyst review (seconds to minutes) vs end-of-day surveillance (overnight batch). Each has a wildly different architecture.
- Action on detection. Block the order, alert a compliance officer, log for review, automatic kill-switch on a strategy?
- Universe. A single trading desk's flow vs firm-wide vs all market participants (exchange surveillance like SEC's CAT)?
- False positive tolerance. In compliance, false negatives are catastrophic (missed manipulation = SEC fine); false positives are merely expensive (analyst time). Recall almost always outweighs precision — F-beta with β > 1.
Pattern
What it looks like
Detection signal
Spoofing
Place large orders one side of the book to push price, then cancel before fill and trade the other side.
High order-to-trade ratio, large orders cancelled near top-of-book, repeated pattern
Layering
Multiple orders at successive price levels to give false impression of depth, then cancel.
Many same-side orders cancelled within ms of placement
Wash trades
Trader buys and sells to themselves to inflate volume / paint a price.
Self-match: same beneficial owner on both sides; same algo, same machine
Marking the close
Trades concentrated in last seconds to manipulate closing print.
Volume / order count spike in last N minutes for thinly traded names
Momentum ignition
Rapid orders to trigger algo follow-on, then exit at the moved price.
Burst of aggressive orders + price move + flat position quickly after
Insider trading
Unusual volume in a stock just before material news.
Volume z-score spike + correlation with subsequent news event
Fat-finger / runaway algo
Order at unrealistic price (off by factor of 10), or thousands of orders/sec from a runaway loop.
Pre-trade risk: notional / price / rate vs daily-historical baseline
💡 Mentioning even 2-3 of these by name (spoofing, layering, wash trade) immediately moves you from "generic ML candidate" to "candidate who actually thought about finance."
Architecture — Layered Detection
┌──────────────────────────────────────────────────────────────────┐
│ Order events + trades + market data │
│ ──→ Kafka topic (partitioned by symbol or trader_id) │
└─────────────────┬────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ Flink stream processor │
│ │
│ Tier 1: deterministic rules (fast, microseconds) │
│ · price ≥ 10× last trade → fat-finger flag │
│ · order rate ≥ 1000/s/account → suspected runaway │
│ · self-match (same beneficial owner) → wash trade │
│ │
│ Tier 2: statistical (windowed, milliseconds) │
│ · rolling mean/stdev/MAD per symbol │
│ · z-score threshold; ARIMA forecast deviation │
│ · order-to-trade ratio over sliding window │
│ │
│ Tier 3: ML models (asynchronous, seconds) │
│ · isolation forest on multivariate feature vectors │
│ · autoencoder reconstruction error │
│ · sequence model (LSTM/Transformer) on order trajectories │
└─────────────────┬────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Action router │
│ · severity ≥ HIGH → kill order, page on-call│
│ · severity = MED → alert queue + freeze │
│ · severity = LOW → log + dashboard │
└────────────────┬────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Immutable audit log (S3 + Glacier) │
│ every decision + features + model version │
└─────────────────────────────────────────────┘
1
Cost
Tier 1 rules cost CPU pennies. Tier 3 ML inference (especially on GPU) costs orders of magnitude more. Catching the obvious 80% with rules saves your ML capacity for the hard 20%.
2
Latency
Pre-trade kill must happen in microseconds. Only deterministic rules can do that. ML can be in the loop for post-trade flagging but rarely for pre-trade blocking.
3
Explainability & regulation
Under SR 11-7 (Federal Reserve model risk management) and similar regimes, ML models must be documented, validated, and explainable. A rule "blocked because price was 50× last close" is trivially explainable; an autoencoder anomaly score is not. Rules carry the load on must-explain decisions; ML supplements.
4
Adversarial robustness
Bad actors actively try to evade detection. Rules can be enumerated; ML models drift and can be probed. Layering means an attacker must defeat all tiers, not just one. Defense in depth.
- Kafka — high-throughput partitioned event log; partition by symbol or trader_id for in-order processing.
- Apache Flink — stateful stream processor. Industry default for real-time fraud / anomaly; ING Bank, Capital One, Grab, Kakao Games are public references. Flink's CEP (Complex Event Processing) library expresses patterns like "BUY followed within 100ms by SELL of the same beneficial owner" in a few lines.
- Kafka Streams / ksqlDB — lighter alternative if your team is already JVM-heavy.
- Feature store — Feast, Tecton. Avoids training/serving skew on rolling features (mean, stdev, volume).
- Model serving — Seldon, KServe, Triton. Or a lightweight Python service if latency budget allows. Always shadow-deploy new versions.
- Audit storage — S3 + Glacier (cheap immutable archival), or hot tier in TimescaleDB / ClickHouse for analyst queries.
- Approximate sketches for high-cardinality streams: Bloom filter (membership), Count-Min Sketch (frequency), HyperLogLog (cardinality), t-digest (quantiles). These are the right tools when "how many distinct accounts traded this symbol in the last hour" must be O(1) memory.
- Isolation Forest. Unsupervised, tree-based, fast. Industry workhorse for tabular anomaly detection. Good first ML model.
- Autoencoder reconstruction error. Train a NN to reconstruct "normal" trade vectors; high reconstruction error = anomaly. Catches more subtle / multivariate anomalies but harder to explain.
- One-class SVM. Older, doesn't scale as well, mostly mentioned for completeness.
- Sequence models (LSTM, Transformer). For order trajectories (sequence of place/modify/cancel events), capture temporal patterns spoofing rules can miss.
- Online / adaptive models (River library, Hoeffding trees). Update on new data; useful when the "normal" distribution drifts.
- ARIMA / EWMA for univariate time-series — Confluent and AWS both ship managed ARIMA-based anomaly detection on Flink streams.
- Recall vs precision. Compliance prefers recall — missing manipulation is catastrophic; investigating a false positive is annoying. Optimise F-beta with β > 1; tune threshold to favor recall. Almost always cite this trade-off explicitly.
- Latency vs sophistication. Pre-trade gates (microseconds) get rules only. Post-trade can use heavier ML.
- Static rules vs adaptive ML. Rules don't drift but bad actors learn them. ML adapts but needs retraining and validation cycles. Layering is the answer.
- False positive cost vs analyst capacity. Each alert consumes ~30 minutes of analyst time. Set thresholds against analyst headcount, not just statistical significance.
- Online learning vs validated batch retraining. Online is responsive but can be poisoned by adversaries. Batch retraining with validation is safer in regulated environments. Most production systems batch-retrain weekly.
Governance Talking Points (Big GS Win)
- SR 11-7 — Federal Reserve model risk management. Mention by name. Requires every model have documented purpose, inputs, training data lineage, validation tests, known limitations.
- SEC Consolidated Audit Trail (CAT) — every order in US equities/options must be reported. Surveillance systems are downstream consumers of CAT data.
- MAR (Market Abuse Regulation, EU) / MiFID II — explicit requirements for surveillance and pre-trade controls.
- Model versioning + auditability. Every detection logged with model version + feature snapshot. If a model is retrained, old decisions can still be reproduced.
- Shadow deployment for new models. New version runs in parallel, outputs compared, doesn't act for 2-4 weeks of validation. Standard at GS, JPM, etc.
- "Four eyes" override. Compliance officer review for any auto-block; reversibility on false positives.
- "How do you handle concept drift?" → Monitor input feature distributions (KL divergence vs training set), monitor prediction distributions, alert on drift. Scheduled retraining + shadow validation. Mention data drift vs concept drift distinction.
- "How do you label training data?" → Hard problem. Confirmed manipulation cases are rare and lagged. Use weak labels (analyst-flagged + investigated), synthetic injection of known patterns, semi-supervised methods. Acknowledge the difficulty — interviewers respect honesty here.
- "What if the system itself is exploited?" → Detection thresholds become public via model probing. Defenses: rotate thresholds, add randomness, layered system means defeating one layer doesn't help, audit the auditor.
- "How would you scale this from one desk to firm-wide?" → Partition by trader_id × symbol; aggregate at desk and firm rollup levels. Per-desk Flink job, central aggregator, hierarchical thresholds.
- "What if a detection is wrong and you blocked a legitimate trade?" → SLA on false positive review (e.g., compliance reviews within 5 minutes), reversibility via cancel-and-resubmit, post-incident review every Friday. False-positive rate is a key SLO.
- "How do you detect coordinated manipulation across multiple accounts?" → Graph-based detection. Build trader/account graph; look for subgraphs with correlated trades, common beneficial owner, common IP / device fingerprint. ING Bank is on record using graph methods for this.
STAR bridge — your strongest pivot opportunity
Your PII / PHI classifier project is a near-perfect parallel: regulated environment, recall-over-precision optimisation, layered detection (regex fast-path + BERT for ambiguous), audit log requirement, shadow deployment of new model versions. When asked "have you built anything like this?" — that's the answer. The framing: "I built exactly this architecture for HIPAA-bound PII detection. The two domains share the recall-first ML problem, the audit-trail requirement, and the layered rules-plus-model design. The transferable insight: compliance domains let you optimise for recall and trust the human-in-the-loop to handle false positives — the cost asymmetry is the core design constraint."
Cheat Sheet — One-Page Recall
Print this. The night before the interview, read this once, sleep, don't re-read in the morning.
Q1 · k-Transactions Stock
Approach3D DP — (day, txns_left, holding). Edge: k ≥ n/2 → sum positive deltas.
State (terse)buy[j], sell[j] arrays; for each price: buy[j] = max(buy[j], sell[j-1] - p); sell[j] = max(sell[j], buy[j] + p).
ComplexityO(n·k) time, O(k) space.
TrapForgetting k ≥ n/2 shortcut → TLE.
Q2 · Real-Time Trading System
AnchorShard by symbol → single-threaded matcher per symbol → append-only event log → market data via UDP multicast.
Latency layersPre-trade risk synchronous (µs); post-trade async via stream processor.
Failure stanceFail-closed (reject orders) when log unavailable. Per-symbol fault domains.
VocabTime-in-force (GTC, IOC, FOK), price-time priority, FIX/ITCH/OUCH, co-location, kernel bypass.
ApproachTwo heaps. Max-heap (lower half) + min-heap (upper half). Balance sizes within 1.
InsertPush to lo (negated for max-heap); move lo's max to hi; rebalance sizes if hi > lo.
Complexityinsert O(log n), median O(1), space O(n).
ExtensionsSliding window → SortedList. Distributed → t-digest (mergeable). Memory-bounded → reservoir sample or t-digest.
Q4 · Distributed Cache, Strong Consistency
CAPCP system. Strong consistency forfeits availability under partition.
AnchorRaft consensus (à la etcd) per shard. Majority quorum on writes. Leader leases for fast reads.
Cross-shard2PC or Calvin-style deterministic ordering. Spanner = Paxos + 2PC + TrueTime (atomic clocks).
Don't say"Just use Redis" — Redis is async-replicated, NOT linearisable by default.
Referenceetcd, ZooKeeper, Spanner, CockroachDB, FoundationDB.
Default algoToken bucket (capacity C, refill rate R). Allows bursts, O(1) state per user.
AlternativeSliding window counter — most accurate at low memory cost.
DistributedRedis + Lua script for atomicity (eliminates read-modify-write race).
Layered limitsPer-user × per-endpoint config; both must allow.
Response429 + Retry-After + X-RateLimit-* headers.
FailureFail-closed for trading; fail-open for read-only.
AnchorKafka → Flink → tiered detection (rules → stats → ML) → action router → immutable audit.
Recall vs precisionRecall wins; F-beta with β > 1. Tune threshold to favor recall.
Patterns to nameSpoofing, layering, wash trade, marking-the-close, momentum ignition.
GovernanceSR 11-7. Model versioning. Shadow deployment. Auditable decisions.
DriftMonitor input distribution (KL divergence). Scheduled retraining + shadow validation.
- "Let me clarify..." Always your first sentence. Buys 30 seconds, signals seriousness.
- "The trade-off here is X vs Y. I'd pick X because..." Minimum 3 trade-offs per system design.
- "Let me name the failure mode..." Don't wait to be asked.
- "In finance, fail-closed." If you only remember one phrase across all six, this one.
- "Append-only audit log." Mention once per system design.
- "I don't know — but here's how I'd figure it out..." Beats bluffing every time. GS scores this higher than confident wrong answers.
Practice Plan — 3 Days
These six are dense. Don't try to absorb them in one sitting. Spread the load. Each day combines reading, solving, and verbalising. Verbalising is not optional — most candidates fail interviews not because they don't know the material but because they can't narrate it under pressure.
AM
Stock IV (Q1)
Read the Q1 tab once. Then solve LC 121, 122, 123 from cold (45 minutes total). Then solve LC 188 — write the buy/sell two-array form, time yourself at 25 min. Don't peek. If you fail, look at the solution, close it, and re-write from scratch the same day. Then LC 309 (cooldown) and LC 714 (fee) as variations.
PM
Median Stream (Q3)
Read Q3 tab. Solve LC 295 (15 min). Solve LC 480 (sliding window median, 25 min). Read 2 paragraphs about t-digest — just enough to name-drop it convincingly.
Eve
Verbalise
Record yourself solving LC 188 out loud, from scratch, for 20 minutes. Watch it back. Note where you said "uh" and where you got stuck on convention. Re-record. This is the single highest-leverage exercise.
Day 2 — System Design Half-Day Marathon
AM
Trading System + Distributed Cache (Q2 + Q4)
Read Q2 tab. Then with a blank sheet of paper, draw the architecture from memory in 25 minutes. Compare to the diagram. Note what you missed. Read Q4 tab. Repeat the draw-from-memory exercise.
PM
Rate Limiter + Anomaly Detection (Q5 + Q6)
Same drill. Read tab → draw from memory → compare. For Q5, additionally implement the in-process Python token bucket from scratch in < 15 minutes. For Q6, list the 5 manipulation patterns by name and what signal would catch each.
Eve
Verbalise (mock interview style)
Pick 2 of the 4 system design questions. Record yourself going through CORE-R framework end-to-end (clarify, requirements, architecture, trade-offs, failure modes) at 35 min each. Watch back. Note where you started skipping clarification and went straight to architecture — that's the cardinal sin.
Day 3 — Integration + Cheat Sheet
AM
Cold-recall drill
Print the cheat sheet tab. Cover it with paper. For each of the 6 questions, write down on a blank page: the anchor approach, 3 trade-offs, and the failure mode. Compare to the cheat sheet. Whatever you missed, that's where to focus the rest of the day.
PM
Mock interviews
Pramp or a friend. Two 45-minute mocks: one DSA, one system design. Have them pick from these 6. Get external feedback on pacing, clarity, and trade-off habit.
Eve
STAR bridges
Q1 → state-space compression project (chess pipeline); Q2 → throughput pipelines you've built; Q6 → PII classifier. Practice the transition once each — "have you built anything like this?" → 90-second STAR.
- Don't memorise solutions verbatim. The interviewer's variant will be slightly different and you'll panic. Memorise patterns, derive solutions.
- Don't read silently. If you can't explain it out loud to an empty room, you can't explain it under pressure.
- Don't skip the brute-force narration. Even when you know the optimal answer immediately, walk through "naive O(n²) would work but..." — interviewers value process.
- Don't claim depth you don't have. Saying "I'm familiar with TrueTime" and then floundering when probed is worse than saying "I know Spanner uses synchronised clocks but I haven't read the paper in detail." Honest gaps + curiosity scores higher than fake depth.
- Don't grind day-of. The Saturday before the interview should be light review only. New material the night before doesn't stick and crowds out what you've already learned.
Final nudge. Most candidates obsess about correctness ("did I get the right answer?") and underweight communication ("did the interviewer feel they were watching a senior engineer think?"). The bar at GS Superday is the second one. The cheat sheet is the floor; the verbal habits are the ceiling. Practice both.