Guide · System design
Design a Stock Trading App: Order Matching and Market Data
26 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- Stock trading system design is planning the software that takes a buy or sell order, checks it, matches it and sends prices back. It splits into an order path that carries each order from the app to the exchange, a matching engine and a market data path that carries live prices.
- A matching engine is the program that pairs buyers with sellers. It uses price-time priority: the best price trades first, and at the same price, the earlier order trades first.
- In one well-documented design, from the trading platform LMAX, the engine runs on one thread, a single line of work, in memory. Every input is first saved to a journal, an append-only log on disk, so after a crash the state is rebuilt by replaying it.
- Market data, the live stream of order and price changes, arrives as numbered messages. A missing number means a lost message, so the app asks again or reloads a snapshot of the current prices.
- Beginners can stop after the matching walkthrough. Engineers get a runnable engine and gap check, and the maths showing where a broker's real load is. They also get the story of Knight Capital, a US broker whose 45 minutes of runaway orders in 2012 cost it over $460 million.
What goes wrong in a trading app when the market opens?
At market open, thousands of people act in the same few seconds. Orders, price updates and balance checks all spike together, and every weak part of a trading app shows at once. Stock trading system design is about building those parts so they hold.
Picture a common setup. A discount broking app serves first-time investors in India, and most of its users bought their first share less than a year ago. The setup is made up, but the problems are real.
One Monday, a mid-sized power company announces a big order win before trading starts. At the opening bell, about 60,000 users of the app try to buy its shares within five minutes. Three things go wrong.
- Prices on the app lag by several seconds. People bid Rs 412 while the stock already trades at Rs 418, so their orders sit there unfilled.
- Some users tap Buy twice on a slow screen. They get two orders and twice the shares they wanted.
- One user sees his order as open for an hour, though the exchange filled it in the first minute.
Each problem starts in a different part of the system. This guide follows one order from the Buy button to the exchange and back, and fixes all three on the way.
Which words do you need before designing a trading system?
You need six words: order, order book, bid and ask, matching engine, fill and market data. Each one gets a plain meaning here, and the rest of the guide uses them the same way.
Order. An order is an instruction to buy or sell some number of shares. A limit order names the worst price you will accept. A market order takes the best price available at that moment.
Order book. The order book is the exchange's list of all waiting orders for one stock. Buy orders sit on one side and sell orders on the other. Every stock has its own book.
Bid and ask. The bid is the highest price any buyer is waiting to pay. The ask is the lowest price any seller is waiting to accept. The gap between them is called the spread.
Matching engine. The matching engine is the exchange program that checks each new order against the book. When a buyer's price meets a seller's price, it creates a trade.
Fill. A fill is a trade that completes all or part of your order. An order for 100 shares can come back as two fills, say 60 shares and then 40.
Market data. Market data is the stream of orders, prices and trades that an exchange publishes. Every price screen in the app is built from it.
What is stock trading system design?
Stock trading system design is planning the software that takes an order from a user, checks it, matches it, and reports prices and trades back. It can cover a broker app, an exchange, or both.
Start with one fact many beginners miss. In India, a broker does not match orders for exchange-listed shares itself. It sends them to a stock exchange such as NSE, the National Stock Exchange of India, whose system matches them against orders from every other broker.
So the broker's job is to check orders, send them, track them and show prices. The diagram shows its main parts. Follow the order path down the middle, then notice that prices take their own road back.
A broker app's trading system
Drag to exploreOrders flow down the middle. Fills and prices come back on separate paths, so a flood of prices does not queue in front of an order.
Diagram connections:
- Broker app to API gateway (Place order)
- API gateway to Order management system
- Order management system to Pre-trade risk checks (Check first)
- Pre-trade risk checks to Exchange gateway (Passed)
- Exchange gateway to Exchange matching engine (Send order)
- Exchange matching engine to Order management system (Fills come back)
- Order management system to Ledger (Record each fill)
- Exchange matching engine to Market data service (Market data feed)
- Market data service to Price push servers
- Price push servers to Broker app (Live prices)
- Broker app: Buy button, watchlist, order screen
- API gateway: Logs users in and limits request rates
- Order management system: Tracks the state of every order
- Pre-trade risk checks: Funds, holdings, price and size limits
- Exchange gateway: The broker's link to the exchange
- Exchange matching engine: Pairs orders from every broker
- Ledger: Every movement of cash and shares
- Market data service: Turns the exchange feed into live prices
- Price push servers: Keep open connections to phones
Each part has one job, so each can grow and fail on its own. A slow price screen should never block an order. A busy order path should never freeze prices.
How does an order matching engine work?
An order matching engine keeps a book of waiting orders for each stock and pairs every new order with the best waiting order on the other side. At the same price, whoever arrived first goes first.
This rule is called price-time priority. NSE defines it on its trading system page. Price priority means the order with the best price gets the higher priority. Time priority means that, at equal prices, the order entered first wins.
As of 19 October 2026, the same page says NSE's trading system numbers and time-stamps each order on receipt, then immediately checks it for a match. Orders that do not match wait in the book.
Picture a diamond broker, a dalal, on a busy street in Surat. Sellers tell him the lowest price they will take for a parcel of stones. Buyers tell him the highest they will pay. He notes each one, with the time, in a small price book.
When a new buyer walks up, the dalal looks at his cheapest seller first. If the buyer offers enough, the deal is done at the seller's price. If two sellers asked the same price, the one who came earlier gets the deal.
The mapping is direct. The dalal's notebook is the order book, the buyer who walks up is the new order, and the dalal is the matching engine. Where the picture breaks: a dalal can bend the rules for a favourite client, while an engine applies the same rule to every order.
Exchanges also fix the trade price the same way. NSE says orders are always matched at the passive order price. A passive order is one already waiting in the book, and the new order that triggers the trade is the active order.
These rules describe NSE's normal, continuous session. As of 19 October 2026, NSE's pre-open page describes a 15-minute pre-open session from 9:00 to 9:15 am, run as a call auction. Orders are collected first, then matched at one equilibrium price, the price at which the most shares can trade. Orders left unmatched move to the normal market and keep their original time stamps.
Here is the book for the power company's stock as continuous trading starts. Three sellers wait: S1 offers 300 shares at Rs 418.00, S2 offers 200 at Rs 418.00 but arrived later, and S3 offers 500 at Rs 418.50.
The order arrives
A limit order to buy 600 shares at up to Rs 418.50 reaches the engine. It gets a sequence number and a timestamp before anything else happens.
Check the best ask
The cheapest seller is S1 at Rs 418.00. The buyer will pay up to Rs 418.50, so the prices cross and a trade is possible.
Fill against S1
300 shares trade at Rs 418.00, S1's price. S1 goes before S2 because it arrived first at the same price. S1 leaves the book.
Fill against S2
S2 is next, also at Rs 418.00. All 200 of its shares trade. The buyer still needs 100 shares.
Fill against S3
S3 asks Rs 418.50, which still meets the limit. 100 shares trade at Rs 418.50. S3 keeps its other 400 shares in the book.
Report everything
The engine sends three fills to the buyer's broker and publishes three trades on the market data feed.
What just happened? The buyer offered up to Rs 418.50 but paid Rs 418.00 for 500 of the 600 shares. That saving comes straight from the passive price rule: each trade happens at the waiting seller's price.
If the buyer's limit had been Rs 418.00, only 500 shares would trade. The last 100 would rest in the book as a new bid at Rs 418.00 and become the best bid.
A small matching engine you can run
The code below is a toy engine for one stock, written in plain Python with no libraries to install. Each side of the book is a heap, a structure that always keeps its smallest item on top.
import heapq, itertools
from dataclasses import dataclass
@dataclass
class Order:
name: str
side: str # "BUY" or "SELL"
price_paise: int # Rs 418.50 is 41850 paise
qty: int
ioc: bool = False # immediate or cancel: never rests in the book
arrival = itertools.count() # time priority: lower number came first
bids, asks = [], [] # heaps of (sort key, arrival, order)
def rupees(paise):
return f"Rs {paise // 100}.{paise % 100:02d}"
def report_trade(price_paise, qty, active, resting):
print(f"{active.name} x {resting.name}: {qty} @ {rupees(price_paise)}")
def submit(order):
is_buy = order.side == "BUY"
book, other = (bids, asks) if is_buy else (asks, bids)
while order.qty > 0 and other:
resting = other[0][2]
gap = order.price_paise - resting.price_paise
if (gap < 0) if is_buy else (gap > 0):
break # prices do not meet: stop matching
qty = min(order.qty, resting.qty)
report_trade(resting.price_paise, qty, order, resting)
order.qty, resting.qty = order.qty - qty, resting.qty - qty
if resting.qty == 0:
heapq.heappop(other)
if order.qty > 0 and not order.ioc:
key = -order.price_paise if is_buy else order.price_paise
heapq.heappush(book, (key, next(arrival), order))
for o in [Order("S1", "SELL", 41800, 300), Order("S2", "SELL", 41800, 200),
Order("S3", "SELL", 41850, 500)]:
submit(o)
submit(Order("B1", "BUY", 41850, 600))
best = asks[0][2]
print("best ask:", best.name, best.qty, "shares at", rupees(best.price_paise))Save it as engine.py and run python3 engine.py. We ran it on Python 3.13, and it prints exactly these four lines, the same fills as the walkthrough above:
B1 x S1: 300 @ Rs 418.00
B1 x S2: 200 @ Rs 418.00
B1 x S3: 100 @ Rs 418.50
best ask: S3 400 shares at Rs 418.50Four details matter. Prices are whole paise, where 100 paise make a rupee, never floats. Run print(0.1 + 0.2) in Python and it prints 0.30000000000000004, because binary floating-point numbers cannot store 0.1 exactly.
The arrival counter breaks ties, so orders at equal prices queue by time. Buy keys are negative, so the highest bid sits on top of its heap. Every trade uses resting.price_paise, the passive price.
To try a market order, give it an extreme limit and set ioc=True. NSE describes IOC, short for immediate or cancel, as an order whose unmatched portion is cancelled immediately. Without ioc, the toy would park the leftover in the book at that absurd price.
Real exchanges set their own rules for market orders, order types and price bands, so check your exchange's documents. We also checked this toy against a slow brute-force version on 2,000 random books, and every fill matched.
Why do some matching engines run on a single thread?
Some matching engines run their core on one thread, a single line of execution inside the program, because the order of events must be exact and repeatable. One thread makes that order simple, and with all data in memory it is also very fast.
Go back to the Surat street. If two dalals kept separate price books for the same parcel, both might sell it to different buyers in the same minute. So one dalal keeps the only price book.
LMAX, a retail financial trading platform, is a well-documented example. Martin Fowler's 2011 article on it says its business logic ran on a single thread, entirely in memory, with no database. The article reports a benchmark of 6 million orders per second on that one thread.
Memory is lost in a crash, so in LMAX's design every input is first saved to a journal, an append-only file on disk. After a crash, the engine replays the journal and rebuilds the same state.
This is event sourcing: you store the input events, and the current state is whatever they add up to. LMAX also took a snapshot every night. Fowler reports that a full restart, loading a snapshot and replaying a day's journals, took less than a minute.
Replay only works if the engine is deterministic, meaning the same inputs in the same order always give the same outputs. A practical rule follows: take times from the stamp on each input, never from the clock while matching.
You can check this with the toy engine. In our test, replaying the same 500-order journal twice gave the same 388 trades and the same final book both times. The toy has no clock reads or randomness, so this check mainly guards against them creeping in as the engine grows.
The diagram below shows the order of steps in the LMAX design. Notice that nothing is matched until it has a place in line, a copy on disk and a copy on the followers.
Inside a single-threaded order core
Drag to exploreIn LMAX's design, journaling and copying can finish in either order, but both finish before the engine touches the input. That is what makes replay and failover safe.
Diagram connections:
- Order arrives to Give it a place in line
- Give it a place in line to Write to the journal
- Give it a place in line to Copy to followers
- Write to the journal to Match against the book (Saved)
- Copy to followers to Match against the book (Copied)
- Match against the book to Publish fills and trades
- Order arrives: Sent in by a broker
- Give it a place in line: The leader fixes one order for all inputs
- Write to the journal: Saved to disk for replay
- Copy to followers: Followers get the same inputs
- Match against the book: One thread, all in memory
- Publish fills and trades: Sent to brokers and the data feed
The followers give fast failover, which means switching to a spare machine. In LMAX's design, the leader runs a replicator that broadcasts input events to follower nodes. Every node processes the same inputs, but only the leader's output is used.
If the leader stops sending heartbeats, another node becomes leader and carries on. It already holds the same state, so nothing needs reloading.
How is stock market data sent to millions of app users?
Market data travels as a numbered stream of small change messages. The broker turns that stream into live prices and pushes them to phones over connections that stay open. Each step thins the stream.
Nasdaq's TotalView-ITCH 5.0 feed has a public specification and makes a good example. It describes a series of sequenced messages that track the life of each order. There are messages to add, execute, cancel, delete and replace an order.
Two details in that specification are worth copying. Timestamps are nanoseconds since midnight. Most prices are whole numbers with 4 implied decimal places, so a price of 418.50 would travel as 4185000.
The sequence numbers come from the transport protocol, the layer that carries the messages over the network, such as MoldUDP64. Its packet header, the label on each network packet, holds the sequence number of the first message in the packet, and the messages after it count up.
If message 1,042 arrives right after 1,040, then 1,041 is missing, and the local copy of the book may now be wrong. The client must recover before it shows any more prices.
MoldUDP64 lets a receiver send a request packet to get missed messages again. After a late start or a large gap, a client loads a snapshot instead. The ITCH specification names Nasdaq's GLIMPSE service as a way to get the current state of the book.
Catching a gap in Python
Here is a gap check you can run. The two stand-in helpers print what a real feed handler would do: apply_to_book updates the local book, and reload_snapshot fetches a fresh one.
from collections import namedtuple
Msg = namedtuple("Msg", "channel seq")
def apply_to_book(msg): # stand-in: update the local book
print("apply", msg.seq)
def reload_snapshot(channel): # stand-in: fetch a fresh book
print("gap found: reload snapshot")
return 1045 # the snapshot covers everything before 1045
expected = {} # next sequence number per feed channel
def on_message(msg):
nxt = expected.get(msg.channel, msg.seq)
if msg.seq < nxt:
return # old or repeated message: skip it
if msg.seq > nxt:
expected[msg.channel] = reload_snapshot(msg.channel)
return # gap: rebuild before trusting prices
apply_to_book(msg)
expected[msg.channel] = nxt + 1
for seq in [1039, 1040, 1040, 1042, 1045]:
on_message(Msg("A", seq))Run python3 gapcheck.py and you should see these four lines. The repeated 1040 prints nothing because it is skipped, and the gap at 1042 triggers the reload:
apply 1039
apply 1040
gap found: reload snapshot
apply 1045The first message on a channel sets the starting point. A lower number is a repeat, so it is skipped. A higher number is a gap, so the code reloads instead of guessing.
This sample always reloads to stay short. A production handler would first re-request small gaps, and would buffer messages that arrive while a snapshot loads. The rule stays the same: never show a price from a book you know is broken.
From one feed to lakhs of phones
A lakh is 100,000, and a busy broker can have lakhs of phones open at the bell. The full exchange feed stops at the broker's market data service, because no person can read a stock that changes many times a second.
So the service conflates the stream. Conflation means keeping only the latest price for each stock and sending it at a steady pace, say every 250 milliseconds. The app's lag came from doing the opposite: its push servers queued every change, and slow phones fell further behind.
Here is the fixed price path. Look at where the stream gets thinner, and which store still keeps every trade.
A broker's market data path
Drag to exploreThe full stream ends at the feed handler. Phones get only the latest price of the stocks they watch (250 ms is made up).
Diagram connections:
- Exchange data feed to Feed handler
- Feed handler to In-memory books
- Feed handler to Trade history store (All trades)
- In-memory books to Conflation
- Conflation to Price topics
- Price topics to Push servers
- Push servers to Users' phones (Only watched stocks)
- Exchange data feed: Numbered order and trade messages
- Feed handler: Checks sequence numbers, rebuilds books
- In-memory books: Latest book for every stock
- Conflation: Latest price per stock, every 250 ms
- Price topics: One topic per stock
- Push servers: WebSocket connections to phones
- Users' phones: Watchlist and order screens
- Trade history store: Every trade, kept for charts
Each phone subscribes only to the stocks on its screen. A topic is a named channel with one stock's prices, so a user watching five stocks gets five topics, not the whole market. A common choice for the link is a WebSocket, a two-way connection that stays open so the server can push without being asked.
Different readers of market data need very different things. The table shows one way to split them.
| Reader | What it needs | How often | Can it skip updates? |
|---|---|---|---|
| Pre-trade risk checks | Last traded price of each stock | Every change | No, limits must use fresh prices |
| Watchlist screen | Last price, best bid and best ask | About 4 times a second | Yes, only the latest matters |
| Market depth screen | Top few price levels of the book | About 2 times a second | Yes, only the latest matters |
| Price charts | Every trade, with time and size | Batched every few seconds | No, but gaps can be refilled later |
| Order status | The user's own fills | Every one, at once | Never, and it comes from fills, not the feed |
What checks should a trading app run before an order reaches the exchange?
A trading app should check funds, holdings, price and size before any order leaves for the exchange. It should also make every order safe to send twice. These checks catch both user mistakes and the app's own bugs.
- Funds: a buy needs enough cash or approved margin, which is buying power the broker extends. Block that money the moment the order is accepted, not when it fills.
- Holdings: a sell needs the shares in the account, minus shares already promised to other open sell orders.
- Price sense: reject a limit price far away from the last traded price. It is most likely a typo.
- Size: cap the quantity and value of each order, so 10,000 shares never go out when the user meant 100.
- Rate: cap orders per user per second, which also catches a runaway script.
Missing checks have a real price. On 1 August 2012, Knight Capital Americas, a US broker-dealer, sent millions of orders into the market over 45 minutes. The US regulator, the SEC, published the chain of causes in a 2013 order.
A technician did not copy new code to one of eight servers. The new code reused a flag that once switched on Power Peg, old code Knight had stopped using in 2003 but never removed. On the server without the new code, the flag woke Power Peg up.
While handling 212 small retail orders, Knight's order router sent millions of child orders. A child order is a smaller order a system sends out to fill a bigger parent order. They produced over 4 million executions in 154 stocks.
Knight was left holding about $3.5 billion of unwanted shares in 80 stocks, and owing about $3.15 billion of shares in 74 others. It lost over $460 million. Before the 9:30 a.m. open, its system had emailed staff 97 messages about the error. They were not designed as alerts, and staff generally did not read them.
The SEC also found Knight had no procedures to halt the system when it misbehaved. Its order found that Knight violated Rule 15c3-5, the market access rule. That rule requires controls reasonably designed to stop erroneous orders and orders that exceed preset credit and capital thresholds.
Making a double tap harmless
The double-tap bug needs an idempotency key. That is a unique id the app creates once per Buy screen and sends with every retry. The order system stores it, and a repeat with the same id returns the first order instead of making a second.
Towards the exchange, orders need ids too. In FIX, a common message standard for orders, the firm sending an order gives it an id called ClOrdID, unique within a trading day. Execution reports, the messages that confirm fills, carry it back for orders sent electronically, so the broker can tie each fill to its order. The FIX 4.2 definition puts the uniqueness duty on the sender; it does not say the exchange rejects a repeat.
How does a trading app keep orders, cash and shares correct after a trade?
It treats each order as a state machine moved by exchange messages, and it records every change of cash and shares in a ledger. An end-of-day match against the exchange's trade records catches anything missed.
A state machine is a fixed list of states with allowed moves between them. An order can be received, sent, open, partly filled, filled, cancelled or rejected. Once an order is sent, only exchange messages move it.
That explains the order stuck as open for an hour. The exchange connection dropped for a moment, and the fill message was lost. The gateway reconnected but never asked for the messages it missed.
The fix mirrors the market data gap check. FIX gives its messages sequence numbers, and a side that detects a gap sends a Resend Request. It names the first and last message numbers it needs, and the other side sends them again.
A ledger is an append-only list of money and share movements. You never edit a balance in place. For a buy of 100 shares at up to Rs 418.50, the app first blocks Rs 41,850, plus charges, which this example leaves out.
If all 100 shares fill at Rs 418.00, it debits Rs 41,800 and releases the Rs 50 left blocked. Each step is a new entry, so the user's history explains every rupee.
- The balance is the sum of the entries, so any number can be traced back to the orders behind it.
- A mistake is fixed with a new reversing entry, never by deleting an old one.
- Each evening, the ledger's trades are matched against the exchange's trade records, and any difference raises an alert.
How do you explain a stock trading system in a system design interview?
Ask whether you are designing the broker or the exchange, then walk through the order path, the matching engine and the market data path. Name one hard problem in each and how you solve it.
Pin the scope
Ask broker or exchange, which order types, and how many users. Say what you will leave out, like settlement or options.
Draw the order path
App, gateway, order system, risk checks, exchange gateway. Add idempotency keys so a retry never creates a second order.
Explain matching
Describe one book per stock with price-time priority, trades at the passive price, and integer prices.
Make it safe to crash
One leader fixes the input order, a journal saves the inputs, and followers process the same inputs. Keep the engine deterministic.
Design market data
A numbered feed, gap checks with resends or snapshots, conflation for phones, and WebSockets that send only watched stocks.
Close with money safety
Pre-trade checks, a kill switch, an append-only ledger and an end-of-day match against exchange records.
Do rough numbers out loud. Say 60,000 orders arrive in the first five minutes. That is 60,000 divided by 300 seconds, only 200 orders a second, which one order system handles easily.
Now say 4 lakh (400,000) phones are open, each getting 4 price updates a second. That is 16 lakh, or 1.6 million, messages a second. Market data, not orders, is the heavy load for a broker.
Questions people ask
- Does a stock broker match buy and sell orders itself?
- Not for exchange-listed shares in India. A broker checks your order and sends it to a stock exchange such as NSE. The exchange's system matches it against orders from every other broker. The broker's own systems handle accounts, risk checks, order tracking and showing prices.
- What is price-time priority in order matching?
- Price-time priority is the rule that decides which waiting order trades first. The best price always goes first: the highest bid or the lowest ask. Among orders at the same price, the one entered earlier goes first. NSE describes its matching this way. In its continuous session, trades happen at the waiting order's price; the morning pre-open call auction uses one equilibrium price instead.
- What is the difference between a limit order and a market order?
- A limit order sets the worst price you will accept, so it may wait in the book or never trade. A market order takes the best price available when it is entered, so it trades quickly but the price can surprise you. A limit order protects you from that surprise.
- Why do some matching engines run on a single thread?
- Because the order of events must be exact and repeatable. One thread working in memory never has two orders racing for the same shares. LMAX is a well-known example: Martin Fowler's 2011 article reports 6 million orders per second on one thread. You scale such a design by giving different stocks different engines.
- What data structure is used for an order book?
- An order book needs fast access to the best price and to the earliest order at that price. Two common designs are a pair of heaps, as in this guide's code, or a sorted map from price to a queue of orders. An array indexed by price level also works when prices sit on a fixed grid.
- How do trading apps show live stock prices?
- The broker reads the exchange's market data feed, rebuilds each order book in memory, and pushes updates to phones. A common choice is WebSockets, which keep a two-way connection open. To avoid flooding slow phones, the server sends only the latest price for each watched stock at a steady pace.
- How do you stop a double tap from placing two orders?
- Give each Buy screen an idempotency key, a unique id the app sends with the first try and every retry. The order system stores the key and, when it sees the same key again, returns the existing order instead of creating a new one.
- What happens if a matching engine crashes?
- In a design like LMAX's, no input that reached the journal is lost. Every input is journaled and copied to followers before the engine processes it, so a new copy can replay the journal and rebuild the same state. A follower that already processed the same inputs can take over faster.
Sources and further reading
- Martin Fowler: The LMAX Architecture (12 July 2011)
- NSE: Equity market trading system (price-time priority, passive price, order types)
- NSE: Pre-open session (call auction, equilibrium price, 9:00 to 9:15 am)
- Nasdaq TotalView-ITCH 5.0 specification (PDF)
- Nasdaq MoldUDP64 protocol specification 1.00 (PDF)
- SEC order: In the Matter of Knight Capital Americas LLC, Release No. 34-70694 (16 October 2013)
- OnixS FIX 4.2 dictionary: Resend Request message
- OnixS FIX 4.2 dictionary: ClOrdID field (tag 11)
- OnixS FIX 4.2 dictionary: Execution Report message
- SEBI memorandum: Enhancement of scope of optional T+0 settlement cycle (2024, T+1 since January 2023)
- IETF RFC 6455: The WebSocket Protocol (December 2011)