InterviewLegend

Design a Market Data System

Commonly asked
Alex Chen32 min readUpdated 392 real interview reports
In this lesson
  • Scale a market-data system to a million ticks a second, hundreds of consumers and a provable history
  • Keep a trader's latest-price read off the disk, and never quietly lose a tick
  • Handle feed gaps, the market-open burst, and the line between speed and durability

Understanding the problem

The prompt

Build the market-data system that sits between the exchanges and the desk. It takes a high-rate feed of price ticks, normalizes it, and distributes it to many downstream consumers: trading systems, risk engines, and dashboards. A trader has to be able to read the latest price instantly, and we have to keep a durable history we can replay and audit. Walk one tick from the wire to a consumer, and keep the feed correct under the market-open burst.

A market-data system is the plumbing every other system on the floor drinks from. An exchange publishes a firehose of price updates, one message each time a stock trades or a quote changes. This system takes that firehose, turns each venue's private format into one canonical shape, and hands it to everyone downstream who needs a price. A trading system needs the latest price the instant it moves, a risk engine needs a consistent view across thousands of names, and a dashboard needs a readable stream. Months later, a compliance team needs to reconstruct exactly what the price was at the moment the market opened. One feed, four very different consumers.

The arc of one tick is short to say. A price changes on an Exchange feed. Our Feed handler reads it off the wire, our Normalizer turns it into a canonical tick, and it lands on the Distribution bus. From there it fans three ways at once. It goes into the Latest-price cache so a read is instant, into the History store so it is on the record forever, and out through the Distribution service to every subscribed consumer. The whole design is deciding how those three destinations differ and making sure the fast one never waits on the durable one.

Functional requirements

Keep the scope to the path a tick takes, and let the interviewer widen it. The core is ingest, normalize, distribute and remember. Pull ticks off the exchange feed and put them in one canonical form. Deliver them to subscribed consumers in the order the exchange sent them, serve the latest price on demand, and keep a durable history to replay. Everything else is a consumer of this system rather than part of it.

In scope

  • Ingest a high-rate tick feed from one or more exchange venues
  • Normalize each venue format into one canonical tick and symbol
  • Distribute ticks in order, per symbol, to subscribed consumers
  • Serve the latest price for any symbol from memory, instantly
  • Keep a durable, replayable history for audit and backtesting

Below the line (name it, then skip)

  • Order entry and execution (a separate order-management system downstream)
  • Building the full limit-order book per venue (a book-building service, gesture at it)
  • Pricing derivatives and computing risk (handled by the risk engine, which is a consumer downstream)
  • Charting and dashboard rendering (a front-end that subscribes to our stream)
  • Exchange connectivity certification and market-data entitlements billing

Non-functional requirements

Reason through each target, because the requirements are what draw the component lines later. Correctness is the consistency-critical property here, and it means two specific things. Ticks for one symbol have to arrive in the order the exchange sent them, because a price that goes back in time gives a trader a wrong number. And the durable history has to be complete, because a compliance team reconstructing the day cannot have a hole in it. A market-data system feeds decisions that move real money, so a dropped or reordered tick is not a cosmetic glitch. It is a wrong price on someone's screen.

Latency is the other headline, and saying where it binds scores. The live path from the exchange wire to a consumer is measured in single-digit milliseconds, because a stale price is a wrong price the moment the market moves. Name the relaxation. The durable write to history can lag behind the live tick without hurting anyone, and an audit query over months-old data can take seconds. So we run one fast in-memory path for live prices and one slower durable path for history, and the whole design leans on that split.

Now put numbers down by hand, because the rates are what force every later choice. Say we cover about ten thousand instruments across a few venues. In steady daytime trading the aggregate feed runs on the order of a hundred thousand ticks a second. At the market open, when everyone repositions at once, the rate spikes roughly tenfold, so plan for about a million ticks a second for the first minutes of the session. A normalized tick, with a symbol, a price, a size, a venue, a sequence number, and two timestamps, is around a hundred bytes. So the peak feed is about a hundred megabytes a second flowing across the bus.

Two consequences fall straight out of those numbers. First, the durable history is large but not scary: a hundred megabytes a second across a six-and-a-half-hour session is a little over two terabytes a day before compression, which an append-only columnar store swallows. Second, the fan-out is the number that would blow up if you were naive about it. Five hundred consumers each demanding every tick is tens of gigabytes a second of egress, which nobody does. Consumers subscribe only to the symbols they care about, so the design has to make that filtering cheap, which is a whole deep dive.

  • ~1M ticks/s Peak ingest at the open. ~10x the ~100K/s steady rate; ~100 MB/s across the distribution bus.
  • Single-digit ms Live path budget. Exchange wire to consumer; the latest-price cache read is microseconds.
  • ~500 consumers Subset subscriptions. Naive every-tick fan-out would be ~50 GB/s; consumers take the symbols they need.
  • ~2 TB/day Durable history. Append-only, off the hot path; the record a compliance team replays.
The Goldman Sachs angle
A Goldman Sachs panel decides early whether you understand that this is a correctness problem wearing a throughput costume, and the tell is where you put your attention first. The million ticks a second is real, but the thing the grader leans on is that this data feeds trades and risk numbers. Money rides on every price being right and in order. So the winning first move is to name the invariant out loud: ticks stay ordered per symbol, the history is complete, and the live read is never stale enough to trade on wrongly. Draw the components that hold that invariant before you size anything. A candidate who opens by sizing for a planet-scale feed has missed that the person across the table has built the real thing and cares first that it is correct.

Breaking the system into components

A component boundary belongs where two jobs genuinely differ, rather than wherever a noun changes. Three differences cut this system cleanly: how strict the ordering has to be, how fast the path must run, and whether the data is disposable or must be kept forever. Reading a raw exchange packet, turning it into a canonical tick, holding the single latest price and keeping the whole history are four jobs that disagree on those axes. They become four separate pieces plus the spine that links them. Say the reasoning out loud; a Goldman grader is checking for it.

1

Feed handler

Connects to the exchange, reads raw packets off the wire, and handles the venue transport: sequence numbers, the redundant A and B lines, and gap detection. It is its own component because it has to keep up with the wire at all costs and speaks a different low-level protocol per venue. It cannot slow the exchange down, so it must be sized for the peak.

2

Normalizer

Maps each venue-specific message into one canonical tick and one canonical symbol, so everything downstream sees a single shape regardless of which exchange it came from. It splits from the feed handler because normalization logic changes per venue and per instrument type, and you want to add a venue without touching the transport code that is holding the wire.

3

Distribution bus

A durable, partitioned log that every canonical tick lands on. It is the spine: the cache, the history writer, and the live fan-out all read from it independently. Because it is a real log and not a direct call, the durable slow path and the fast live path consume the same stream at their own speeds. Neither blocks the other.

4

Latest-price cache

An in-memory store holding one value per symbol, the current price and its metadata. It is its own component because of the read pattern: "give me the price for this symbol right now." That is a microsecond point lookup that must never touch disk or contend with the write firehose. It is disposable, rebuilt by replaying the log.

5

History store

An append-only, durable, columnar store that keeps every tick for replay, backtesting, and audit. It splits out because its job is the opposite of the cache: it is write-heavy, time-ordered, kept forever, and queried in big ranges rather than point reads. It sits on the slow path where its disk writes never slow a live tick.

Two more pieces finish the picture. The Distribution service is the component that holds the many consumer connections open and pushes each subscriber only the symbols it asked for, reading the live stream off the bus and filtering. And a Query service answers the on-demand reads: the latest price from the cache, and a history range from the store. Splitting query from stream keeps the push fan-out and the request-response reads on separate fleets that scale on their own curves.

Here is the shape end to end. Read the colors as roles: blue is an actor at the edge, violet is a service we run, amber is the distribution bus, and green is a datastore.

Loading diagram...

1) Ingest and normalize. The Exchange feed streams raw messages to the Feed handler, which reads them off the wire, arbitrates the redundant lines, and forwards an in-order, deduplicated stream. The Normalizer maps each raw message into one canonical tick, resolving the venue's symbol to our own and stamping our own per-symbol sequence number. This is the stretch that has to keep pace with the wire, so it does nothing slow: no database call, no lookup that touches disk. It publishes each canonical tick to the Distribution bus and moves on. Nothing on this stretch waits.

2) Fan out three ways off the bus. The Distribution bus is partitioned by symbol, so every tick for one symbol lands on one partition in order. Three consumers read it independently. A lightweight cache writer updates the Latest-price cache with the newest value per symbol, and a history writer appends every tick to the History store. The Distribution service streams ticks out to subscribers. Because all three read the same log at their own pace, a slow disk write on the history side never holds up the live push, and a lagging dashboard never holds up the cache.

3) Serve the two kinds of read. A Trader app does two things. It holds a live subscription so ticks are pushed to it the instant they move, served by the Distribution service filtering the bus down to the trader's symbols. And it asks point questions, "what is the price of this name right now," which the Query service answers from the Latest-price cache in microseconds without touching the bus. A history range for a backtest or an audit is the same query service reading the History store. The live push, the instant point read, and the slow range read each hit the component built for that shape.

Data and the hot read

Core entities

Name the objects the system moves before arguing about stores. Four carry the design, and the tick is the spine the other three hang off.

1

Tick

One canonical price update: the symbol, the price, the size, the side, the venue, our own per-symbol sequence number, and both the exchange timestamp and our ingest timestamp. This is the unit that flows through the whole system, and its sequence number is what makes ordering and gap detection possible downstream.

2

Instrument

Reference data for one tradable symbol: the canonical id, the venue mappings, the tick size, the lot size, and the trading status. It is small and slow-changing, read constantly to normalize incoming messages, and it lives in a plain relational store rather than on the hot tick path.

3

Subscription

A consumer's standing request: which consumer, which set of symbols, and the delivery mode, every tick or conflated to the latest. The distribution service reads it to decide what to push to whom, and it is the object that keeps the fan-out from being every tick to everyone.

4

Snapshot

The latest known state for one symbol, the current price and its metadata, which is what the cache holds and what a late-joining consumer needs before it can start applying live ticks. A snapshot plus the tick stream after it fully reconstructs a consumer's view.

The data splits cleanly by how it is used, so name each home with its reason. A live Snapshot per symbol lives in an in-memory store, Redis, keyed by symbol. The read is a microsecond point lookup, and the write rate is the full tick firehose that a disk-backed database would choke on. Historical Tick data goes to a durable, append-only, columnar store, something like ClickHouse, because it is written once and queried in big time ranges, which columnar storage and compression are built for. The slow-changing Instrument reference data sits in Postgres, where a normal relational store with real constraints is exactly right and the load is trivial. And the Tick stream in flight rides Kafka, the distribution bus, durable and ordered per symbol and replayable. Say those four homes out loud with the reason attached, because a Goldman interviewer will ask where each entity lives and why.

The latest-price read is the one that would melt a database if you let it, so it earns the in-memory cache outright. Thousands of consumers ask for the current price of thousands of symbols continuously. Every one of those reads is a point lookup that must not queue behind the write firehose or touch a disk. So a writer keeps one hot value per symbol in the Latest-price cache, updated straight off the bus as ticks arrive, and the Query service serves every latest-price read from memory. This is the live materialized view of the stream rather than a copy of a database, so if a node is lost it rebuilds by replaying the recent tail of the log. Until it does, a consumer can be told a symbol is briefly stale rather than handed a wrong number.

The API

Keep the surface thin and notice which read each call is. A consumer subscribes once to a set of symbols and then receives ticks pushed to it. Separately, it can ask a point question, the latest price, or a range question, a slice of history. The subscribe call carries the delivery mode so a dashboard can ask for conflated updates while a trading system asks for every tick.

The interface
RequestServer push
subscribe a consumer to a set of symbols
POST /subscriptions { symbols, mode }
{ subscriptionId }
mode is every-tick or conflated
serverconsumerlive ticks for the subscribed symbols
GET /subscriptions/{id}/stream
stream of { symbol, price, size, seq, ts }
served by the distribution service
latest price for one symbol
GET /price/{symbol}
{ symbol, price, size, seq, ts, stale }
served from the latest-price cache
latest prices for many symbols at once
GET /prices?symbols=
{ prices: [ ... ] }
batched cache read
historical ticks or bars over a range
GET /history/{symbol}?from&to&interval
{ bars: [ ... ] }
served from the history store
The Goldman Sachs angle
The two reads split along how fresh and how big they are, rather than along the noun. The latest price and a month of history are both a price for a symbol, yet they sit on different stores and services. One is a microsecond point read of live state; the other is a slow scan of durable data. A Goldman lead is grading that boundary. The interviewer will ask why the latest price cannot come straight from a query on the history store. Answer with the mechanism and skip the word speed. The history store is write-optimized, disk-backed and queried in ranges, so a point read of the current price would contend with the ingest firehose and miss the millisecond budget. An in-memory view keyed by symbol answers in microseconds and rebuilds from the log if it dies. Name the seam and defend it on those terms.

Potential deep dives

Most of the signal lives here, and a Goldman interviewer drives at one seam at a time and pushes until you either defend it or admit where it breaks. Five dives carry this design: recovering a gap in the feed, fanning out to hundreds of consumers, holding the live path fast, choosing where durability costs speed, and surviving the market-open burst. Each is where a clean diagram either survives a hard question or falls over.

1) How do you keep ticks in order and recover a gap?

This is the invariant the whole round is built to test, because a lost or reordered tick is a wrong price feeding a trade. Exchange feeds are fast and lossy by design. Most venues publish over UDP multicast, which does not retransmit on its own, and stamp each message with a sequence number so a receiver can tell what it missed. The failure mode is a dropped packet nobody notices, so a consumer computes risk on a price that never happened. The fix is to detect every gap and to have a way to fill it. Work up from the naive version, because the interviewer wants to see you know why trusting the feed fails.

BadTrust the feed and forward ticks as they arrive

Approach

The feed handler reads each packet and forwards it downstream in whatever order it arrived off the wire. Straightforward, and on a calm session with no drops it produces the right prices.

Challenges

Multicast drops packets silently and can deliver them out of order, so a missed tick is never noticed and a late one overwrites a newer price. Downstream, a risk engine sums exposure on a book with a hole in it, or a trader sees a price tick backwards. There is nothing that even detects the loss, so the error is invisible until someone reconciles against the exchange hours later. Nobody should propose this, but be ready to say exactly why it is dangerous.

GoodSequence-gap detection with A and B line arbitration

Approach

Exchanges publish two identical copies of the feed, an A line and a B line, precisely so a drop on one is covered by the other. The feed handler reads both, takes whichever copy of each sequence number arrives first, and discards the duplicate. It tracks the expected next sequence number per channel. The instant it sees a jump, from 1004 straight to 1006, it knows 1005 is missing and flags the gap rather than silently moving on.

Challenges

Two lines plus gap detection catches and covers the common case, a single-line drop, with no latency cost. What it cannot handle alone is a packet that dropped on both lines at once, or a burst that outran both. Detection is now reliable, but detection without a recovery path just means you know you are missing 1005 and still cannot produce it.

GreatGap fill from a recovery feed, with a clean re-sequenced output

Approach

Keep the A and B arbitration and add recovery. When a gap survives both lines, the feed handler requests the missing sequence numbers from the venue's retransmission service, or applies the periodic full snapshot the exchange publishes to re-sync a symbol from scratch. While a symbol is waiting on a fill, mark it stale rather than publishing a guess. On the way out, stamp every canonical tick with our own monotonic per-symbol sequence number, so every downstream consumer can run the same gap check against our stream that we ran against the venue's.

Challenges

This gives complete, in-order delivery with an explicit stale flag when it cannot be met. The cost is latency during a fill. Recovery takes a round trip, so a symbol in recovery is briefly behind, and you decide per consumer whether to withhold its ticks or publish them stale-flagged. Naming that tradeoff, correctness bought with a few milliseconds of staleness on a rare gap, is what reads as senior.

The Goldman Sachs angle
This is the seam where a Goldman grader leans in hardest, because it is the one where being wrong costs money directly. A market-data engineer who cannot answer "what happens when you drop a packet" has not built the real thing, and the interviewer knows it. A strong answer here is plain and exact. Two lines cover a single drop, and the sequence number makes any gap loud instead of silent. A recovery feed or a snapshot fills what both lines lost, with the symbol marked stale until it is whole again. The machinery stays ordinary. Show that a missing tick is always noticed and either filled or flagged, never quietly passed downstream as if the price were real.

One follow-up almost always comes, so have it ready: how a consumer notices a gap that slips past us. Because the Normalizer stamps every canonical tick with our own monotonic per-symbol sequence number, a downstream consumer runs the very check we run against the venue. If our sequence jumps, the consumer knows it missed a tick. It asks the Query service for a fresh Snapshot of that symbol to re-sync instead of trading on a book with a hole in it. The exchange helps here too, publishing a full snapshot every few seconds, so a consumer that joins late or falls behind waits at most one snapshot interval before it has a complete starting point. Gap detection at the edge and snapshot recovery downstream are the same idea applied twice.

2) How does one feed fan out to hundreds of consumers?

Come back to the fan-out number, because it is the one that breaks a naive design. A million ticks a second times five hundred consumers is five hundred million deliveries a second if everyone gets everything, tens of gigabytes a second of egress from one service. That is a non-starter. The whole art is that almost nobody wants everything. A trader watches a few dozen names, a risk engine tracks a sector, a dashboard refreshes a page of symbols. The design has to make "push each consumer only what it subscribed to" cheap, and it has to make one slow consumer everyone else's problem.

BadOne loop pushing every tick to every socket

Approach

A single distribution loop takes each tick off the bus and writes it to every connected consumer socket, letting each consumer ignore what it does not care about. Straightforward, and it works with ten consumers in a test.

Challenges

It does not survive real numbers. Writing every tick to every socket is the five-hundred-million-a-second egress the math already ruled out. Worse, a single slow consumer whose socket buffer fills blocks the one loop, so a lagging dashboard stalls delivery to a trading system. The fan-out is quadratic in the wrong things and has no isolation between a fast consumer and a slow one.

GoodPartition by symbol and filter per subscription

Approach

Partition the Distribution bus by symbol so ticks for one name stay ordered on one partition, and run a fleet of Distribution service instances, each owning a slice of consumers. Each instance reads only the partitions its subscribers care about and pushes each consumer just the symbols in its Subscription. Because most consumers watch a small subset, the real delivered volume collapses to a tiny fraction of the theoretical every-tick-to-everyone number.

Challenges

Subscription filtering plus a stateless, horizontally scaled distribution fleet handles the volume: add instances as consumers grow, and each one only touches the symbols it serves. The gap it leaves is the slow consumer. One subscriber that cannot keep up still backs up its own delivery, and you have to decide what happens to it without letting it hurt its neighbors.

GreatPer-consumer bounded queues with conflation

Approach

Give every consumer its own bounded outbound queue on the distribution instance. A fast consumer drains its queue and gets every tick. When a slow consumer's queue fills, its updates conflate instead of blocking anyone: for a symbol already waiting in its queue, replace the old value with the newest rather than growing the queue. A dashboard that only wants the latest price loses nothing it could see, and a consumer that falls hopelessly behind is dropped and told to re-snapshot rather than dragging the fleet down.

Challenges

This gives isolation and graceful degradation: one consumer's slowness stays that consumer's problem, and conflation trades intermediate ticks for freshness exactly where intermediate ticks do not matter. The cost is that a conflating consumer no longer sees every tick, so this mode is offered only to consumers that asked for it. A trading system that needs the full sequence stays on every-tick delivery and is provisioned to keep up.

The Goldman Sachs angle
The low-latency fan-out is a place a Goldman interviewer tests judgment, because the naive answer and the right answer look similar on a whiteboard and differ entirely under load. What reads as strong is separating the two consumer classes out loud. A trading system needs every tick in order and is sized to drain its queue, so it never conflates. A dashboard needs only the latest value a human can read, so conflation is free money for it. Saying that a slow consumer is isolated to its own bounded queue, and that conflation is a per-subscription choice rather than a global one, is the operational judgment the round rewards. It shows you have thought about the day the market opens and everyone connects at once, well beyond the clean diagram.

Put one number on it to close the dive. The theoretical every-tick-to-everyone fan-out was five hundred million deliveries a second, and the delivered volume is nothing like that. Say the average consumer subscribes to a few hundred of the ten thousand symbols. Each tick then reaches only the handful of consumers watching that name. Real egress is on the order of the tick rate times a small fan-out factor, well within what a horizontally scaled Distribution service fleet serves. Conflation shrinks it further for the dashboards. Subscription filtering turns an impossible number into an ordinary one, which is why the Subscription is a first-class object rather than an afterthought.

3) How does the live path stay within a few milliseconds?

The latency budget is single-digit milliseconds from the exchange wire to a consumer, and the way to hold it is to make sure nothing on that path ever does something slow. The live path is: read the packet, normalize it, publish to the bus, filter, push. Every step in that chain runs in memory. The slow work, writing to the history store and answering range queries, is deliberately on a different path that reads the same bus but never sits between a tick and a consumer. One rule keeps the budget honest. The live path only ever touches memory and the network, and never a disk.

  • Keep the hot path memory-only. The normalizer resolves symbols against an in-memory copy of the Instrument reference data rather than a database call, so no lookup on the live path waits on Postgres. Reads and writes of the Latest-price cache stay entirely in memory. Its one durable write, appending to the History store, happens on a separate consumer of the bus, so a slow disk flush never lands between a tick and the trader that needs it.
  • Watch the garbage collector, because this is a Java shop. Goldman runs heavily on the JVM, and a stop-the-world garbage collection pause of even a few milliseconds is a latency spike straight through the budget. Name the fixes a Goldman panel expects. Reuse tick objects from a pool instead of allocating one per message, keep large buffers off-heap so they do not enlarge the pause, and tune a low-pause collector. This is where the design round bleeds into JVM fundamentals, so let it.
  • Partition the work so no single core is the bottleneck. Because the bus is partitioned by symbol, the feed handler, normalizer, and distribution fleet all shard by symbol across cores and instances. One symbol is handled start to finish by one path, which keeps its ordering, while the thousands of symbols spread across the fleet. No global lock sits on the hot path, so throughput scales by adding shards rather than by making one thread faster.
  • Measure the tail rather than the average. A market-data system is judged on p99 and p99.9 latency, because the worst ticks are the ones that arrive when the market is moving and the price matters most. The average being a millisecond is no comfort if the open-burst tail is fifty. So the burst behavior, covered in the last dive, is part of the latency story rather than a thing apart from it.

Holding the budget comes down to the split you drew at the start. The fast path is nothing but memory and network operations. The slow path hangs off the same bus for everything durable or queryable. As long as no disk write, no relational query, and no lock ever creeps onto the live path, the milliseconds stay in reach. The moment one does, say a well-meaning "just log each tick to the database inline," the budget is gone. So name that temptation out loud and refuse it.

Put one number on the connection side too, because the open makes it real. If a few thousand consumers each hold an open streaming connection, and some hold several, the Distribution service fleet carries tens of thousands of live sockets. A single instance holds only so many. So the distribution tier is sized by connection count as much as by tick rate. It scales out by adding stateless instances behind a load balancer, each subscribed to the partitions its consumers care about. Holding the sockets and filtering the stream stay cheap per tick, which is what lets one modest fleet serve a floor full of traders without ever touching the ingest path.

4) Where does durability cost you speed, and how do you choose?

The durable history and the fast live path pull in opposite directions, and a Goldman interviewer will make you choose between them explicitly. The tension is at the moment a canonical tick is published to the Distribution bus. If we wait for the tick to be durably replicated before releasing it to consumers, we are safe against loss but we have added the replication round trip to every tick's latency. If we release it to consumers first and let durability catch up, we are fast but a broker crash in that window can lose a tick that a trader already acted on. That is the durability-versus-speed knob, and the honest answer is that different data sits at different settings.

Wait for durable replication, then publish

  • The producer waits for the bus to acknowledge the tick from multiple replicas before it is delivered to consumers.
  • No consumer ever sees a tick that a broker failure could erase, so the live stream and the durable history can never disagree.
  • The cost is the replication round trip added to every tick, which eats into the single-digit-millisecond budget.
  • The right setting for the durable history writer and for consumers whose correctness must be provable, like risk and compliance.

Publish live, replicate in parallel

  • The tick is pushed to live consumers the instant it is normalized, while replication to the bus happens alongside.
  • Lowest possible latency on the live path, which is what a latency-sensitive trading consumer wants most.
  • A broker crash in the small window before replication can drop a tick from the durable record that a consumer already saw.
  • Acceptable because the durable path reconciles: any tick lost from the log is caught when the feed handler replays against the venue.

The two reconcile because the bus itself is the durability boundary, and it is replicated. A tick is written to the Distribution bus with replication across brokers, so once it is acknowledged it survives a single broker loss. The Latest-price cache needs no durability of its own because it is a materialized view of that log: if a cache node dies, it rebuilds by replaying the recent tail. The History store gets its completeness from the log too, and if the history writer falls behind or crashes, it resumes from its last committed offset and replays, so no tick is skipped. The log can't give back a tick the feed handler never received. So the gap recovery in the first dive carries the durability story: correctness at the edge, and a replicated log behind it.

One number pins the durability setting. Say the Distribution bus keeps three replicas of each partition and acknowledges a write once two of them hold it. Then a single broker can die with no tick lost and no pause for the producer, because the surviving replicas already have it. Require all three to acknowledge and you are covered against two simultaneous failures, at the cost of waiting on the slowest replica, which the latency-sensitive path will feel. Two-of-three is the usual middle setting, durable against the failure you truly expect and skipping the price of the one you do not. Saying why you picked it is the concrete tradeoff the round is after.

The Goldman Sachs angle
A Goldman panel treats this as one of the fundamentals-bearing seams, and it will push into the theory behind your answer. Expect it to ask what the bus's replication acknowledgment really guarantees, how that relates to the isolation and durability a database would give you, and what precisely is lost in the crash window. The strong posture is to answer in real terms rather than adjectives. A tick is durable once a quorum of brokers has it, and the cache and the history store both rebuild from that log by replay. The only unrecoverable loss is at the exchange edge, which the sequence-gap recovery is there to catch. Being able to move from the architecture into the durability guarantee and back, without losing the thread, is the design-plus-fundamentals blend this round rewards.

5) How does it survive the market-open burst?

Come back to the open, because it is the literal hard case in the prompt. For the first minutes of the session the tick rate jumps roughly tenfold, from about a hundred thousand a second to about a million, exactly when every consumer reconnects and every trader is watching. The first thing to say is where the burst can and cannot be absorbed. We cannot slow the exchange down. So the feed handler has to be sized for the wire peak outright. Everything after it has somewhere to put the surge, and the design is about routing the burst to the places that can hold it.

  • The feed handler is provisioned for peak rather than the average. Back-pressure is impossible against an exchange. The one component touching the wire has to keep up with the busiest millisecond of the year, or it drops packets and you're into the gap recovery from the first dive. This is the component you over-provision on purpose, because its failure mode is lost ticks rather than lag.
  • The Distribution bus absorbs the surge as lag rather than loss. Downstream of the wire, a burst becomes a backlog on the log rather than dropped work. The history writer and the cache updater may fall a little behind during the open and catch up within seconds, and because the log is durable and replayable, nothing is lost while they do. The bus is what converts a tenfold spike from an outage into a few seconds of catch-up.
  • Conflation protects the live consumers that can tolerate it. During the burst, dashboards and other latest-value consumers conflate hard: a symbol that ticks fifty times in a second is delivered once with the newest price. That collapses the live egress dramatically at exactly the moment it spikes, and it costs those consumers nothing they could perceive. Every-tick consumers stay full-rate and are sized for it.
  • Shed by consumer class, never by dropping the critical path. If something has to give, it is the low-priority traffic first: throttle or conflate the dashboards and analytics consumers so the trading and risk consumers keep every tick. The Distribution service enforces this per subscription. Better to give a dashboard a slightly slower refresh for two minutes than to let a thundering reconnect starve a trading system.

Put together, the burst story is the same split paying off under stress. The feed handler eats the wire because it has no choice. The durable log turns the surge into a few seconds of lag for the slow path. Conflation shrinks the live egress right where the spike lands, and priority shedding protects the consumers that money rides on. None of it is exotic: a log, a cache and a subscription model, applied where the open-burst numbers say the pressure is.

What the round rewards

The bar here is correctness and judgment over raw scale. Open on the invariant, ordered ticks and a complete history, then draw the components and defend where each boundary falls, then go deep on the one seam the interviewer picks. Expect the round to drift into JVM behavior, concurrency, and database theory, and let it, because at Goldman the design and the fundamentals under it are one conversation.

Reads as strong

  • Opens on the invariant: ticks ordered per symbol, history complete, live reads never stale enough to trade on wrongly
  • Splits the fast in-memory path from the slow durable path, and keeps every disk write off the live tick
  • Detects every gap with sequence numbers and A and B lines, and fills it from a recovery feed or a snapshot
  • Makes fan-out cheap with symbol partitioning, per-subscription filtering, and conflation for consumers that can take it
  • Answers a pushed seam by moving into the fundamentals: GC pauses, replication guarantees, isolation, and back

Reads as off-target

  • Sizes for a planet-scale feed when the real target is a million ticks a second and correctness
  • Puts the latest-price read on the history store, or logs each tick to a database inline on the hot path
  • Trusts the exchange feed and has no answer for a dropped packet
  • Fans every tick out to every consumer, or lets one slow consumer stall the whole distribution loop
  • Names Kafka, Redis, or a columnar store but cannot say what it guarantees when the interviewer pushes on failure

Common questions

What system design does Goldman Sachs ask?

A pragmatic, finance-flavored one, and often a discussion rather than a full diagram. The recurring shapes are a component or low-level build and a finance-shaped system: a market-data feed, an order book, a real-time risk engine, or handling data that does not fit in memory. The round rewards a clean boundary, a data-store choice you can defend, and correctness under money, rather than a web-scale template. A market-data system is a natural fit because it is the plumbing the firm runs on.

How do you keep a market-data feed from losing ticks?

Sequence numbers plus redundancy plus recovery. Exchange feeds number every message and publish two identical lines, so the feed handler takes whichever copy arrives first and knows instantly when a number is missing. A gap that survives both lines is filled from the venue's retransmission service or a periodic snapshot, and the affected symbol is marked stale until it is whole. On the way out, stamp your own per-symbol sequence number so every consumer can run the same check.

Should the latest price live in a database or a cache?

In an in-memory cache, keyed by symbol. The read is a microsecond point lookup done constantly by thousands of consumers. The write rate is the full tick firehose, so a disk-backed database would both miss the latency budget and contend with ingest. This is the live materialized view of the tick stream rather than a copy of a database, and it rebuilds by replaying the log if a node is lost. Durable history lives in a separate, columnar store on the slow path.

How is this different from a big-tech design round?

It is lighter on planet-scale and heavier on fundamentals. A Goldman panel treats the architecture and the theory beneath it as one conversation. A design question slides into GC and concurrency on the JVM, into what your message bus guarantees on replication, and into isolation and durability. Prepare the design and the fundamentals together, so when the round narrows to "how would you build this piece and why," you can answer in real terms without losing the thread of the design.

Key Takeaways

  • ✓The prompt is a correctness problem in a throughput costume. You ingest a million ticks a second, but the round tests that ticks stay ordered per symbol and the history stays complete, because money rides on every price.
  • ✓Split the fast in-memory path from the slow durable path. The latest price lives in a Redis cache read in microseconds, the history lands in a durable columnar store off the hot path, and a partitioned Kafka bus is the spine both consume independently.
  • ✓Hold the invariant at the edge. Detect every gap with sequence numbers and redundant A and B lines, fill it from a recovery feed or a snapshot, and mark a symbol stale rather than publish a guess.
  • ✓Make fan-out and the open burst cheap with symbol partitioning, per-subscription filtering, and conflation for consumers that can take it, and expect the round to bleed into JVM, concurrency, and database fundamentals.
Had your Goldman Sachs interview?Tell us what they asked. Reviewed before it goes in, never with your name.
Goldman Sachs interview guide

Know what Goldman Sachs asks before you walk in.

16 coding · 3 system design · 22 behavioral questions

Get Premium$49 a year · 30-day money-back guarantee
Or start freeNo card.

Get their questions first.

InterviewLegend is independent and not affiliated with or endorsed by any company it covers.