- Home
- Matching Engine Architecture
Matching Engine Architecture
How Price-Time Priority Works in Production
By Oleksii Vasylenko, Technical Lead · Updated
Matching engine architecture decides which orders trade, in what sequence, at what price, and what happens after a partial fill. The production design is usually a deterministic single writer per market, surrounded by parallel gateways, risk checks, persistence, settlement, and market-data consumers. This guide explains matching engine technology for stock and cryptocurrency exchanges, including order-book structures, single-thread versus parallel execution, distributed Redis cells, duplicate-match protection, recovery, and measured throughput.
In This Article
- Why it matters
- Limit Orders, Market Orders, and Stop Orders
- Matching Engine Architecture: One Order from API to Trade
- Production evidence
- How to Build a Matching Engine: Start with One Trade
- Matching Engine Technology: Data Structures and Complexity
- Matching Engine Architectures: Pros and Cons
- Matching Engine Threads: Single vs Parallel Architecture
- Stock Exchange vs Cryptocurrency Matching Engine Architecture
- Durability, Replay, and Crash Recovery
- Preventing Duplicate Orders and Double Matches
- Failure Modes Worth Testing Before Launch
- Measuring Low-Latency Matching Engine Performance
- The Trade Log Is the Source of Truth
- Frequently asked questions
What This Means for Your Business
A matching engine is the exchange component that pairs compatible buy and sell orders. It owns the active order book and applies one execution policy, commonly price-time priority: the best price wins, then the earliest accepted order at that price wins. A buy order crosses when its limit price is equal to or higher than the lowest sell price. A sell crosses when its limit is equal to or lower than the highest buy price. The engine consumes resting orders until the incoming quantity is filled, its price no longer crosses, or the book has no compatible liquidity.
The difficult part is preserving causality. If a partial fill updates the book but the trade event is lost, balances and charts disagree with the engine. If a retry enters twice, the customer may trade twice. If two workers mutate one market concurrently, arrival order becomes ambiguous. Good matching engine architecture reduces these cases to a single rule: accept commands in a total order, mutate authoritative state once, and derive every external view from the resulting event sequence.
Limit Orders, Market Orders, and Stop Orders
Order types are instructions to the engine, not separate matching algorithms. Once an order becomes executable, the same book priority rules decide which resting liquidity it consumes.
| Order type | What the trader controls | Engine behavior | Primary risk |
|---|---|---|---|
| Limit order | Maximum buy price or minimum sell price | Trades at acceptable resting prices; any eligible remainder may join the book | It may never fill |
| Market order | Quantity or spend, but no execution price | Immediately consumes the best available price levels until filled or liquidity ends | Slippage across a thin book |
| Stop order | A trigger condition plus the order released after triggering | Remains outside the active book, then becomes a market or limit order when the trigger fires | Trigger and final execution price can differ |
| Post-only order | A limit price and a requirement not to take liquidity | Joins the book or is rejected/canceled if it would execute immediately | Missed execution when the market moves |
A production specification must also define time-in-force, self-trade prevention, tick size, lot size, minimum notional, and what happens to an unfilled remainder. Those are business rules with financial consequences, not API decoration.
Matching Engine Architecture: One Order from API to Trade
The cleanest design keeps the deterministic core small. Network handling, authentication, reporting, and chart generation surround it, but none of them decides execution order. A typical command follows this path:
Validate at the boundary
Authenticate the account; check market status, tick size, lot size, order type, and a client-supplied idempotency key. Reject malformed commands before they enter the ordered stream.
Assign an authoritative sequence
A sequencer gives each accepted command a monotonic position. Wall-clock timestamps are useful for audit records, but they are a poor tie-breaker because clocks drift and concurrent requests can share a timestamp.
Persist the command intent
Append the accepted command to a durable journal before acknowledging a state change that the engine could not reconstruct after a crash.
Mutate one in-memory order book
The market partition processes the next command, walks the opposite side from the best price, creates zero or more fills, and either rests or closes the remaining quantity.
Emit sequenced domain events
OrderAccepted, TradeExecuted, OrderPartiallyFilled, OrderFilled, and OrderCanceled events carry the sequence needed by downstream consumers to detect gaps and replay safely.
Apply financial effects
A ledger or settlement component reserves and releases funds, posts trade movements, and makes retries idempotent. The matching core should decide trades; the ledger should prove the money moved exactly once.
Build disposable projections
WebSocket feeds, order-history screens, OHLCV candles, risk views, and analytics consume the event log. If one projection breaks, rebuild it from the source sequence instead of repairing it by hand.
The boundary is deliberate: matching is synchronous and ordered; distribution is asynchronous and replayable. Mixing those concerns makes latency unpredictable and recovery harder to reason about.
How I Have Used This in Production
Price-Time Priority Matching Engine
As Technical Lead, I architected and shipped a Node.js and TypeScript matching engine processing more than 4,000 orders per second with sub-millisecond engine latency. It implemented price-time priority for limit, market, and stop orders, with RabbitMQ for reliable ingestion and KeyDB atomic operations for hot order-book state.
Partitioned Matching Cells and Recovery
I later re-engineered the matching boundary around deterministic market partitions: one RabbitMQ quorum queue, one active conductor, one Redis-compatible primary/replica cell, and one ordered outbox per partition. State, book indexes, command idempotency, permanent order identities, and events commit atomically; Valkey WAITAOF provides an explicit replicated-durability mode.
Market Maker Integration Layer
I engineered liquidity bots that connected multiple market-maker APIs and improved measured order-book depth by 3x. Automated spread control adjusted quotes around the reference market, while inventory rebalancing limited directional exposure across trading pairs.
OHLCV Candle Engine
I rebuilt the OHLCV candle engine after the earlier implementation drifted under load. It had treated WebSocket delivery as the source of truth. The replacement derived open, high, low, close, and volume directly from the ordered trade log, making candle intervals reproducible through replay.
How to Build a Matching Engine: Start with One Trade
Suppose the sell side contains 2 BTC at $100 from order A, then 3 BTC at $100 from order B, followed by 4 BTC at $101. A new limit buy for 4 BTC at $101 arrives. Price priority selects $100 before $101. Time priority selects A before B because A entered the $100 queue first. The engine fills 2 BTC against A, closes A, then fills the remaining 2 BTC against B. B stays open with 1 BTC. The incoming buy is complete, so it never rests. Both trades execute at the resting orders' $100 price under the policy documented by Coinbase Exchange. Here A and B are makers because their orders supplied liquidity before the buy arrived. The incoming buy is the taker. Recording both sides, the matched quantity, resting price, fees, and engine sequence gives settlement and audit systems one exact trade fact to consume.
Now change the incoming limit to $99. Nothing crosses because the buyer will not pay the best ask of $100. The 4 BTC buy joins the bid side at $99. Change it to a market buy instead and price protection disappears: the engine consumes $100 liquidity first, then continues to $101 if quantity remains. This is why a matching engine must use fixed-point integers for price and quantity, state rounding rules explicitly, and reject values outside the market's tick and lot sizes. Binary floating-point has no place in the financial core.
Matching Engine Technology: Data Structures and Complexity
The book needs two ordered maps of price levels: bids sorted from highest to lowest, asks from lowest to highest. Each price level holds a FIFO queue of orders. The top keys give the best bid and ask; the queue preserves time priority among equal prices. A separate order-id index points to each live order so cancellation does not scan the whole book. Depending on the language and workload, the ordered map may be a balanced tree, skip list, radix structure, or bounded price array.
Big-O notation is only the start. Memory layout, allocation rate, cache locality, queue churn, and cancellation patterns decide the tail latency users feel. An array indexed by tick can be excellent for a narrow, dense price range and wasteful for sparse markets. A tree handles sparse prices but adds pointer chasing. Benchmark with a realistic mix: new limits, aggressive orders crossing several levels, partial fills, cancels near and far from the touch, and traffic bursts. A benchmark made entirely of tiny orders at one price is theatre.
Matching Engine Architectures: Pros and Cons
The main matching engine architectures differ in where authoritative state lives and how they recover. There is no universally best technology. The correct design follows the fairness policy, latency target, number of independent markets, durability requirement, and operational skill of the team.
For most continuous stock and cryptocurrency markets, a single-writer in-memory core with a durable journal is the clearest default. Database-centric and Redis-backed designs trade some latency for simpler integration or atomic persistence. Active-active shared writers promise scale but make deterministic price-time priority substantially harder to prove.
- Single-writer in-memory book — lowest and most predictable matching latency; requires a journal, snapshots, replay, and disciplined failover.
- Partitioned matching cells — scales across independent symbols and bounds failures; hot symbols still have a single-writer ceiling and partition movement needs an explicit migration.
- Redis or Valkey-backed state — convenient atomic state, idempotency, and outbox transactions; network and persistence round trips reduce command throughput.
- Database-centric matching — straightforward durability and auditing at modest volume; row locks and index churn usually make it a poor low-latency core.
- Active-active writers on one book — may help specialized batch auctions; for continuous price-time matching, coordination cost and ambiguous ordering usually outweigh parallelism.
Matching Engine Threads: Single vs Parallel Architecture
A matching engine should usually be single-threaded at the decision point for one market. One writer gives every command an unambiguous position, avoids locks in the hot path, and makes replay deterministic. Adding threads to the same BTC-USD book does not automatically add useful throughput: the threads must still agree which order arrived first and commit one resulting sequence.
The system around that loop should be parallel. Gateways validate connections concurrently; independent symbols run on separate partitions; outboxes, settlement, WebSocket delivery, candles, surveillance, and analytics consume events independently. In the Bitsten cell design, pairId maps deterministically to a partition, and each partition owns one quorum command queue, conductor, Redis-compatible cell, and outbox. Scale comes from more independent cells on isolated CPU and disk—not multiple writers racing inside one order book.
Stock Exchange vs Cryptocurrency Matching Engine Architecture
Stock and cryptocurrency matching engines share the same core mechanics: an ordered command stream, bid and ask books, a priority policy, fixed-point quantities, fills, cancels, and an authoritative execution log. A stock exchange may use price-time, pro-rata, or venue-specific allocation rules; a crypto exchange commonly uses continuous price-time matching but must publish its exact policy rather than assume it.
The operational environment differs. Stock venues inherit trading sessions, opening and closing auctions, regulatory halts, market-wide controls, consolidated-market obligations, and formal surveillance. Cryptocurrency exchanges commonly operate continuously across many pairs, integrate custody and blockchain deposits or withdrawals, manage fragmented external liquidity, and face 24/7 recovery without a nightly maintenance boundary. Those differences change risk, operations, and settlement, but they do not justify weakening deterministic order priority.
Durability, Replay, and Crash Recovery
A restart plan is part of the algorithm. The engine needs a durable command or event journal, periodic snapshots, monotonic sequence numbers, and idempotent consumers. Recovery loads a verified snapshot, replays subsequent records in sequence, and checks that the reconstructed book reaches the expected checksum or state marker before trading resumes. An acknowledgement policy must say exactly when a client may believe an order exists.
Sequence numbers turn silent corruption into a detectable gap. If a market-data consumer receives event 8402 after 8400, it pauses, retrieves 8401 or a fresh snapshot, then resumes. The same principle applies to balances and candles. Exactly-once delivery is often a marketing phrase; durable at-least-once delivery plus idempotent application is easier to prove. The business invariant is not that a message moved once. It is that each accepted trade changes financial state once.
Preventing Duplicate Orders and Double Matches
Command idempotency alone does not prevent every double match. A broker can redeliver the same command ID, but an API retry may create a new command ID for the same order. The engine therefore needs two durable identities: a processed-command record and a permanent accepted-order marker. In the Bitsten design, the order marker remains after the live order closes, so a new command identity cannot place and match that order again.
The marker, book mutation, stable event IDs, processed-command result, and outbox append commit in one partition-local transaction. A quorum queue with a single active consumer enforces one writer, while the service also serializes local entry points. This prevents the engine from executing the same order twice. It does not create exactly-once delivery across Redis and RabbitMQ: downstream balance and order consumers still need a transactional inbox keyed by event ID so a repeated outbox publication cannot apply financial effects twice.
Failure Modes Worth Testing Before Launch
Test duplicate submissions, cancel-versus-fill races, insufficient reserved funds, partial fills across many price levels, self-trade prevention, market halts, expired orders, invalid increments, journal write failures, downstream backpressure, reconnecting consumers, and snapshot corruption. Property-based tests can generate command sequences and assert invariants: total executed buy quantity equals total executed sell quantity; remaining quantity never becomes negative; FIFO order is preserved within a price; and replay produces the same final book.
Chaos tests should kill the process between journal append, book mutation, event publication, and acknowledgement. Then recover and compare every order, fill, balance movement, and sequence number with an uninterrupted reference run. If the team cannot state what happens at each interruption point, the engine is not ready for real funds. Happy-path throughput does not compensate for ambiguous recovery.
Measuring Low-Latency Matching Engine Performance
Report the latency boundary before reporting the number. Engine latency may mean time inside the match loop, gateway-to-engine time, or client round-trip time; these figures are not interchangeable. Measure p50, p95, p99, and p99.9 under a declared order mix, book depth, market count, payload size, persistence mode, and hardware profile. Record throughput at the same time because a low median at low load says little about saturation.
The boundary changes the result dramatically. Bitsten's production matching loop processed more than 4,000 orders per second with sub-millisecond engine latency. In the later architecture benchmark, pure matching exceeded 100,000 calculations per second, the conductor with Redis sustained roughly 300–380 commands per second, and the original confirmed full pipeline reached about 106 commands per second. A synthetic Dragonfly state transaction reached 25,621 transactions per second in memory mode; durable Valkey with AOF and replica acknowledgement reached 3,254. These numbers describe different workloads and cannot be compared as one leaderboard.
Two cells sharing one Docker Desktop host were 9–10 percent slower than one because they competed for the same CPU and disk. Distributed Redis increases matching throughput only when independent partitions receive independent resources and traffic. Watch queue depth, reject rate, journal flush time, event-consumer lag, allocation rate, and the distance between median and tail latency. Require a sustained workload and zero invariant violations before calling horizontal scaling successful.
The Trade Log Is the Source of Truth
Market data is a projection. WebSocket messages can be delayed, batched, dropped, or received after a reconnect; they should never become the authoritative input for accounting or OHLCV generation. At Bitsten, deriving candles from WebSocket delivery caused drift under load. Rebuilding them from the matching engine's ordered trade log fixed the causality error and made historical regeneration possible.
This separation also makes observability useful. Each command, execution, and projection update can carry market, order, trade, and sequence identifiers. Operators can trace a customer's request from gateway acceptance to book mutation, ledger posting, and public feed without placing logging calls inside the hottest comparison loop. Alerts should focus on invariants and lag: sequence gaps, negative quantities, crossed books after processing, ledger mismatches, journal failures, and consumers falling behind.
Technologies
Supporting Engineering Guides
Market rules, financial consistency, capacity, recovery, security, audit, compliance, and launch acceptance — each one traced back to what made it necessary.
The Redis lock that could delete another owner's lock, the queue setting that replaced it, and where parallelism genuinely pays in a price-time engine.
A production design for deterministic market partitions, same-slot transactions, durability acknowledgements, failover, outboxes, and horizontal matching cells.
Four measurement boundaries, four correct answers, and three experiments that disproved what we expected — including a faster datastore that turned out slower.
Encoding FIFO priority into a Redis sorted-set member, plus price levels, cancellation indexes, fixed-point arithmetic, memory layout, and benchmark design.
Related Expertise
A matching engine without real-time delivery is useless. See how I built the WebSocket layer that feeds live order books to thousands of concurrent traders.
Real-Time Systems — WebSockets, Message Queues, and Live DataThe data layer under a matching engine must handle high-frequency writes and analytical reads simultaneously. Here is how I designed that split.
Database Architecture and Performance — Designing Data Systems That ScaleThe trading UI re-renders hundreds of times per second. See how I kept it at 60fps under continuous data streams.
Frontend Architecture for Financial Systems — When Every Frame and Every Millisecond CountsFrequently Asked Questions
What is a matching engine?
A matching engine is the exchange component that maintains the active order book and pairs compatible buy and sell orders according to a published priority policy. It produces fills, changes order states, and emits the authoritative sequence used by settlement and market-data systems. It does not need to own charts, WebSocket connections, or long-term analytics.
How does price-time priority work?
The engine first selects the best available price: the highest bid for a seller or the lowest ask for a buyer. If several resting orders share that price, it fills the earliest accepted order first. Nasdaq describes displayed limit orders at the same price as executing in receipt order, and Coinbase Exchange documents the same price-time policy for its continuous order book.
Should matching engine threads be single or parallel?
Use one matching writer per market or deterministic partition so order priority is unambiguous and replayable. Parallelize independent markets and the surrounding work: gateways, stateless risk checks, persistence pipelines, settlement, WebSocket delivery, surveillance, and analytics. Multiple threads mutating one continuous order book must coordinate on the same sequence, so they usually add synchronization without increasing useful matching throughput.
What are the pros and cons of different matching engine architectures?
A single-writer in-memory engine offers predictable latency and simple ordering but needs journaling and replay. Partitioned matching cells scale across symbols and isolate failures but cannot remove one hot market's serial ceiling. Redis or Valkey-backed engines simplify atomic state, idempotency, and outbox writes but pay network and durability latency. Database-centric engines are easy to audit at modest volume but usually have worse tail latency. Active-active writers are the hardest design for continuous price-time priority because every writer must agree on one order.
Is matching engine architecture different for a stock exchange and a crypto exchange?
The deterministic order-book core is similar. Stock exchanges add sessions, auctions, regulatory halts, venue-specific allocation, surveillance, and consolidated-market obligations. Cryptocurrency engines commonly run 24/7 across many pairs and integrate custody, blockchain settlement, and fragmented external liquidity. Both require a published priority policy, fixed-point arithmetic, replayable execution records, and idempotent financial effects.
How should a matching engine recover after a crash?
Load the latest verified snapshot, replay later journal records by sequence, validate the reconstructed state, and resume only after downstream consumers know the recovery boundary. Idempotency keys prevent retried client commands from becoming new orders, while consumer checkpoints prevent a replayed trade from changing balances twice.
What latency metrics should a matching engine publish?
Publish percentile latency rather than one average, define the measured boundary, and pair latency with throughput. A useful report includes p50 through p99.9, traffic mix, book shape, persistence mode, market count, payload size, test duration, and hardware. Client round-trip, gateway, sequencing, matching, and settlement latency should remain separate.
How much throughput can a matching engine process?
Throughput depends on the measured boundary. Bitsten's production matching loop exceeded 4,000 orders per second with sub-millisecond engine latency, while a later Redis-backed conductor measured roughly 300–380 commands per second and the original confirmed end-to-end pipeline about 106. Pure calculations and synthetic datastore transactions can be far higher. A credible result must disclose order mix, depth, durability, hardware, duration, percentiles, and whether settlement and event confirmation are included.
What production matching engine experience does Oleksii Vasylenko have?
As Technical Lead for Bitsten, Oleksii architected and shipped a Node.js and TypeScript engine processing more than 4,000 orders per second with sub-millisecond engine latency. He also built market-maker integrations that improved measured book depth by 3x and replaced a drifting WebSocket-derived candle process with replayable OHLCV generation from the ordered trade log.
When should a team build a custom matching engine?
Build one when the execution policy, asset model, custody boundary, latency target, or audit requirements cannot be met by an existing venue or licensed component. A custom engine creates permanent responsibility for fairness, recovery, surveillance, operations, and financial correctness. For many products, integrating an established venue is the better engineering decision.
What technology is used to build a matching engine?
Matching engine technology is a stack of decisions rather than one language: an ordered command stream, in-memory bid and ask structures, an order-id index, fixed-point arithmetic, a durable journal, snapshots, sequenced events, and idempotent consumers. Language choice matters less than deterministic ownership of each market and a recovery model the team has tested.
What does cryptocurrency matching engine management include?
Cryptocurrency matching engine management covers market configuration, tick and lot sizes, market status, liquidity monitoring, sequence integrity, journal health, snapshot verification, consumer lag, ledger reconciliation, and incident recovery. Operators need alerts for crossed books, sequence gaps, negative quantities, stale projections, persistence failures, and differences between trades and balance movements.
Further Reading
Need a senior engineer to de-risk a matching engine before scale or launch?
I audit live order paths, define the ordering, durability, duplicate-protection, and recovery contracts, measure the real throughput boundary, and work hands-on through implementation. Send the current architecture, target throughput, and hardest failure case; that is enough to begin a useful technical review.
Request a matching engine architecture reviewEngagement options and availability →