Busy Polling vs Interrupts
Two ways a program finds out that new data has arrived — sit and check in a tight loop, or let the operating system tap it on the shoulder — and why low-latency systems deliberately choose the wasteful-looking option.
Prerequisites: Jitter and Determinism
A program waiting for a network packet has two fundamentally different ways to find out one has arrived. It can ask the operating system to notify it — go to sleep, and let an interrupt wake it up the moment data is ready — or it can simply check, over and over, in a tight loop that never sleeps at all: busy polling. Interrupts are the sensible default for almost all software, because sleeping costs nothing while idle. For a latency-critical trading system, that "cost nothing while idle" default is exactly the problem.
An interrupt-driven wakeup is not free — it takes the CPU time to run interrupt-handling code, switch the CPU's context from whatever else it was doing back to your thread, and resume execution, typically several microseconds all told. On a system where reacting to a price update in under a microsecond is the entire point, several microseconds of interrupt-and-context-switch overhead can be the majority of the total latency budget, dwarfing the actual work done once the data arrives. Busy polling sidesteps this: a thread sits in a loop continuously checking a memory location or a network buffer for new data, with no OS involvement and no context switch — the instant data appears, the very next loop iteration sees it, often in tens of nanoseconds rather than microseconds.
The cost is that busy polling burns 100% of a CPU core the entire time, whether or not there's anything to do — it never sleeps, so it never yields the core to other work. That tradeoff — spend a whole core doing "nothing" most of the time, in exchange for the lowest possible reaction latency the instant something happens — only makes sense when latency is worth far more than the spare core, which is exactly the situation on a trading system's hottest path.
Worked example: budgeting a reaction-time target
A strategy needs to react to an incoming quote within 1 microsecond end to end, and the actual decision logic itself takes 300 nanoseconds once the data is in hand. Using interrupts, the wakeup path alone costs roughly 3,000 nanoseconds (3 microseconds) — already three times the entire 1 microsecond budget before any decision logic even runs, making the target impossible with this approach regardless of how fast the logic is. Switching the packet-receiving thread to busy-poll on a dedicated, pinned CPU core cuts the "notice the data" cost to roughly 50 nanoseconds. Total latency becomes about nanoseconds — comfortably inside the 1 microsecond budget, at the cost of permanently dedicating one full CPU core to a loop that spends most cycles finding nothing new.
What this means in practice
Busy polling is reserved for the specific threads on the most latency-critical path — a market-data receiver, an order-send thread — and is paired with core pinning and isolation so the polling thread isn't itself preempted by the OS scheduler, which would defeat the purpose. It's a deliberate, expensive trade of hardware (a dedicated core, more power draw) for latency, and doesn't make sense for the majority of a system's threads, which are far better served sleeping via interrupts and freeing the CPU for other work.
Interrupts let a thread sleep and get woken up when data arrives, at the cost of a multi-microsecond wakeup path; busy polling burns a full CPU core checking continuously so it can react in tens of nanoseconds. Latency-critical systems accept the wasted core to buy that reaction time.
Busy polling is often adopted wholesale — "poll everything, it's faster" — without accounting for the fact that it needs a dedicated, isolated core to actually deliver on its latency promise. A busy-polling thread sharing a core with other work will itself get preempted by the scheduler, reintroducing the exact interrupt-like jitter it was meant to avoid.
Related concepts
Practice in interviews
Further reading
- Gregg, Systems Performance, ch. 10 (Network)