How a modern exchange processes millions of orders with low latency while keeping the architecture scalable.
1. Introduction
Building a cryptocurrency exchange is very different from building a typical web application.
Users expect:
- Orders to execute within milliseconds.
- Zero duplicated orders.
- Strict ordering of events.
- High throughput during market volatility.
- Real-time updates for millions of connected users.
Unlike traditional CRUD systems, the matching engine becomes the heart of the platform. Everything else exists to support it.
This article walks through the architecture below and explains why each component exists and which technologies are commonly used in production.
2. High-Level Architecture
The architecture follows one important principle:
Only the Matching Engine is stateful. Everything else should be stateless whenever possible.
a. Load Balancer
The load balancer is the public entry point of the exchange.
Responsibilities:
- Distribute requests across multiple API servers
- Handle SSL termination
- Protect against server failures
- Support horizontal scaling
Common technologies:
- NGINX
- HAProxy
- AWS ALB
- Envoy
Why?
Trading traffic is unpredictable.
During major market events, request volume can increase by hundreds of times within seconds.
Instead of scaling one giant API server, we simply add more REST API instances.
b. REST API Layer
The REST API layer handles everything related to user interaction.
Examples:
- Login
- Authentication
- Place Order
- Cancel Order
- Query Balances
- Open Orders
- Trade History
This layer should remain completely stateless.
Typical stack:
- Go
- Java
- Rust
- Node.js (NestJS)
- Spring Boot
Responsibilities:
- JWT verification
- API validation
- Rate limiting
- Authentication
- Risk checks
- Forward orders
Notice something important:
The REST API never performs order matching.
Its only job is validating requests and forwarding them.
Because it is stateless, we can run hundreds of API servers behind a load balancer.
c. Communication with the Matching Layer
After validation, orders are forwarded to the matching layer.
Several communication protocols are commonly used.
Option 1 — gRPC
Pros
- Easy to develop
- Strong typing
- Protobuf serialization
- Excellent developer experience
Cons
- Higher latency
- HTTP/2 overhead
Good for:
- Internal microservices
- Moderate latency systems
Option 2 — Raw TCP Binary Protocol
Many exchanges implement their own binary protocol.
Instead of JSON:
{
"symbol":"BTC-USDT",
"price":105000,
"qty":1
}
They send something similar to:
[OrderID][Price][Quantity][Side]
Advantages:
- Extremely small packets
- Minimal CPU usage
- Almost zero serialization overhead
This approach is common in high-frequency trading systems.
Option 3 — Aeron
Many modern exchanges and financial systems use Aeron.
Advantages:
- Very low latency
- Lock-free design
- Efficient memory usage
- Reliable messaging
- Millions of messages per second
Companies using Aeron include financial trading platforms and low-latency infrastructures.
Message Broker (like Kafka) or HTTP API take millisecond for delivering orders, not a good choice in low latency system like Trading System
d. Order Router
The most important key:
Multiple matching engines but only every trading pair owns exactly one matching engine.
Reason: Because order matching requires strict sequencing.
A single writer guarantees:
- deterministic execution
- price-time priority
- no race conditions
e. Event Bus Consumers
Multiple services subscribe to exchange events.
- Trade History (store orders to Database)
- WebSocket (notifications)
- Settle Balance
- Risk/Fraud Check
Why Event-Driven Architecture?
- Reduce latency, matching engine no need to wait anything from these tasks
- Loose coupling
- Horizontal scalability
- Fault isolation
- Easy to add new services
- Replay historical events
2. Matching Engine
The Matching Engine is the most latency-sensitive component in the entire exchange.
Unlike a typical backend service that spends most of its time waiting for databases or external APIs, the Matching Engine keeps everything in memory and focuses on executing orders with deterministic, microsecond-level latency.
Why Everything Lives in Memory ?
Accessing memory takes tens of nanoseconds.
Reading from PostgreSQL may take hundreds of microseconds.
Reading from disk may take milliseconds.
Since every incoming order must be processed immediately, the order book cannot live inside a database.
Instead, the database is only used for persistence after matching completes.
a. Core Data Structures
The matching engine typically maintains two order books: Bid and Ask
Price Tree
A balanced tree stores all price levels.
Common implementations:
- Red-Black Tree
- AVL Tree
Why not HashMap?
Because we constantly need:
- Highest Bid
- Lowest Ask
HashMap cannot answer those efficiently.
Tree complexity:
Operation Complexity
Insert Price Level O(log n)
Remove Price Level O(log n)
Find Best Bid O(1)
Find Best Ask O(1)
Most implementations keep pointers to:
`highestBid
lowestAsk`
Therefore finding the best price becomes constant time.
Price Level
Each node inside the tree represents one price.
Orders at the same price are not sorted again.
They simply follow FIFO order following Price-Time Priority.
Order Object
C++
struct Order {
OrderId id;
Side side;
Price price;
Quantity remainingQty;
PriceLevel* level;
Order* prev;
Order* next;
};
Each order keeps a pointer back to its own price level.
This makes cancellation extremely efficient.
Order Index (HashMap)
Why?
Imagine a cancel request arrives.
Without a HashMap, we search price level from the tree, O(n)
With HashMap, O(1)
b. Matching Algorithm
Suppose the order book looks like:
Bid
105 (5 BTC)
104 (3 BTC)
Ask
106 (4 BTC)
107 (2 BTC)
A new buy order arrives:
BUY
106
3 BTC
The engine compares:
Buy Price >= Best Ask ?
Yes.
So matching begins.
Partial Fill
Suppose
BUY
10 BTC @106
Best ask
4 BTC @106
The result:
BUY
Remaining
6 BTC
The first ask disappears.
The remaining quantity continues matching.
Multiple Price Levels
Example
BUY
10 BTC
Book
106
4 BTC
107
3 BTC
108
5 BTC
Execution:
106
Filled
↓
107
Filled
↓
108
Partially Filled
The engine walks through price levels until:
order completely filled
no more executable prices
c. Inserting a New Order
If the order cannot match:
BUY 105
while
Best Ask =106
Then
Insert
↓
Price Tree
↓
Price Level
↓
FIFO Queue
Complexity:
Tree Insert
O(log n)
Linked List Append
O(1)
d. Cancelling an Order
Cancellation is surprisingly one of the most frequent operations.
Flow:
Cancel Request
↓
HashMap Lookup
↓
Order Pointer
↓
Remove From Linked List
↓
Remove From Tree If Empty
Pseudo-code:
order = orderMap[id];
unlink(order);
if(level.empty())
tree.remove(level);
orderMap.erase(id);
Complexity:
HashMap lookup:
O(1)
Linked list removal:
O(1)
Tree removal:
O(log n)
e. Why Linked List Instead of Array?
Imagine 50,000 orders exist at one price.
Removing the first order:
Array
Shift 49,999 elements
Linked List
Reconnect two pointers
Constant time.
f. Why Red-Black Tree Instead of Heap?
Many engineers first think about using a heap because we only care about the best bid and best ask.
However, heaps are poorly suited for exchange order books.
A heap excels at retrieving the minimum or maximum element, but it performs poorly when you need to:
Remove an arbitrary price level.
Iterate through consecutive price levels during large market orders.
Delete an empty price level after the last order is canceled or filled.
A balanced tree provides:
- Ordered traversal.
- Efficient insertion and deletion.
- Fast access to neighboring price levels.
This makes it a much better fit for a continuously changing limit order book.
g. Key Design Principles
A production-grade matching engine achieves high performance by combining several simple but carefully chosen data structures:
Balanced Trees (Red-Black Tree / AVL Tree): Maintain all price levels in sorted order and provide instant access to the best bid and ask.
FIFO Doubly Linked Lists: Preserve price-time priority and allow constant-time insertion and removal of orders at the same price.
HashMap:* Enables O(1) lookup for order cancellation and modification without scanning the order book.
In-Memory Processing: Keeps the critical execution path free from database latency, while persistence is handled asynchronously through an event log or message broker.
h. Time Complexity Summary
| Operation | Complexity |
|---|---|
| Place Order | O(log n) |
| Match Best Price | O(1) per price level |
| Cancel Order | O(1) + O(log n) |
| Find Order | O(1) |
| Find Best Bid | O(1) |
| Find Best Ask | O(1) |
| Append Order at Same Price | O(1) |
| Remove Order from FIFO | O(1) |
3. Practical Issues
a. Handling many trading pairs
A scalable exchange does not create one operating system process for every trading pair. Instead, it uses market partitioning:
- Each trading pair is owned by exactly one matching engine at any given time.
- Each engine can manage many independent order books.
- Hot markets may receive dedicated engines, while hundreds of low-volume markets share a single engine.
- The Order Router maintains a routing table (symbol → engine) so incoming orders are always forwarded to the correct owner.
This design strikes a balance between low latency, efficient resource utilization, and horizontal scalability, and is widely adopted by modern cryptocurrency and traditional financial exchanges.
b. Restoring OrderBook from crash
A production-grade matching engine relies on several complementary techniques to recover safely after failures:
- In-memory order books provide the lowest possible latency during normal operation.
- Append-only event logs record every state-changing operation in execution order.
- Periodic snapshots dramatically reduce restart time by avoiding a full replay from genesis.
- Write-Ahead Logging (WAL) ensures no accepted order is lost even if the process crashes unexpectedly.
- Sequence numbers guarantee deterministic replay and prevent inconsistent order books.
- Active-standby replication minimizes downtime by keeping a synchronized backup engine ready to take over.
Together, these techniques allow an exchange to recover quickly while preserving the exact market state, ensuring that every open order, trade, and cancellation remains consistent even after unexpected failures.
c. Preventing Double Spending: Account Locking
A common architectural principle in modern exchanges is:
- Reserved Balance and Available Balance
- Account Service owns all account balances and is the only component allowed to modify them.
- Risk Engine validates whether an order is financially acceptable.
- Matching Engine never checks balances; it only matches valid orders.
- Settlement Service updates final balances after trades are executed.
By assigning a single writer to every account, exchanges eliminate race conditions without relying on expensive database locks or complex synchronization primitives. The result is a system that remains both correct and highly scalable under heavy trading workloads.
About me
Nguyen Nguyen - Senior Blockchain Engineer
Website: https://portfolio-nguyen-nguyen-vn.vercel.app/


Top comments (0)