Times in the System If you've ever worked on network protocol stacks, kernel/driver code, or any system-level application, you've run into the same deceptively simple problem: how do I make something happen after a certain amount of time has passed — reliably, and at scale? It sounds trivial until you're managing thousands of concurrent timers — per-neighbor BGP hold timers, per-tunnel keepalives, retransmission timeouts — and your naive implementation starts burning CPU cycles just walking a list. Over the years, systems engineers have converged on a handful of proven patterns for this. Here's a rundown of the most common approaches, from the hardware level up to modern event-driven frameworks. 1. Hardware Timers/Interrupt-Driven Timers At the lowest level, you have actual timer peripherals on the chip. These generate a hardware interrupt the moment they expire, and an Interrupt Service Routine (ISR) picks up the signal and fires off the corresponding timeout handler. This approach is the backbone of embedded systems and low-level networking stacks — think BGP hold timers or keepalive timers implemented close to the metal. It's fast and precise, but it's also the least flexible option since the number of physical timer peripherals available binds you. 2. OS-Provided Timer APIs Most of the time, you don't need to touch hardware directly — the operating system already exposes timer primitives for you. User space (Linux): setitimer(), timer_create() / timer_settime() (POSIX timers), and the classic alarm(). Kernel space: the timer_list structure, managed through add_timer(), mod_timer(), and del_timer(). These APIs lean on the OS scheduler and clock tick to invoke your callbacks, which makes them convenient — but you're still at the mercy of scheduling jitter and tick resolution. 3. Timer Wheels (Hashed/Hierarchical) This is where things get interesting for anyone building networking software at scale. A timer wheel buckets timers into slots based on their expiry time, and a "wheel" rotates through those slots, firing whatever falls into the current one. The payoff is huge: O(1) insertion and deletion, even with thousands of active timers. That's exactly why timer wheels show up everywhere in serious networking code — the Linux kernel, BGP and OSPF implementations, anything managing large numbers of per-neighbor or per-tunnel timers. 4. Min-Heap / Priority Queue Based Timers Here, timers are stored in a heap ordered by expiry time, so the next timer to fire is always sitting at the top. You only ever need a single system timer armed for that soonest expiry. This works well when the timer count is moderate, and you care about precision — but insertion and deletion cost O(log n), so it doesn't scale quite as gracefully as a timer wheel once you're dealing with very large numbers of timers. 5. Sorted Linked List of Timers The simplest possible approach: keep a linked list sorted by expiry time. It's easy to reason about and trivial to implement, but insertion is O(n), which makes it a poor fit once your timer count grows. Fine for small systems or prototypes; not something you'd want in production networking code at scale. 6. Software Timer Task/Thread (Polling-Based) Instead of relying on interrupts, a dedicated thread or task simply wakes up on a fixed tick and checks the list of active timers for expiry. This tick-based, polling style is common in event-driven or cooperative multitasking systems — you'll see it in routing daemons like Quagga/FRR and ExaBGP. It trades some precision (you're bound by the tick interval) for simplicity and predictable CPU usage. 7. Event-Driven / Async Frameworks Modern user-space daemons increasingly lean on event loops — epoll, select, or libraries like libevent, libev, or boost::asio — where timers are just another type of registered event. The loop dispatches your callback the moment the timer expires, right alongside your I/O events. This is the pattern of choice for BGP, OSPF, and NDO-style controller daemons, because it unifies timer handling and I/O handling in a single, coherent event loop instead of running them as separate subsystems. 8. Delta Queue / Calendar Queue A clever variation on the sorted list: instead of storing each timer's absolute expiry time, you store the delta — the difference from the previous timer's expiry. This means you're not rewriting every timer's value on every tick, which shaves off unnecessary overhead compared to a naive sorted list. Which Should You Use (And When)? For anything resembling BGP or tunnel keepalive/hold timers — where you might be tracking thousands of sessions or tunnels simultaneously — timer wheels and event-loop-integrated timers (via libevent or epoll/timerfd) are the two patterns you'll see in production routing and networking software again and again. They scale cleanly and integrate naturally with the rest of an I/O-driven architecture, which is exactly what high-performance networking daemons need. Everything else on this list still has its place — hardware timers for embedded precision, heaps for moderate-scale accuracy, sorted lists for quick prototypes — but if you're designing for scale, start with a timer wheel or an event loop and work backward from there. Have you built or debugged a timer subsystem at scale? I'd love to hear what trade-offs you ran into — drop a comment below.
8 Timer Implementation Patterns for Systems and Network Software
Full Article
Original Source
Read the full article at Hackernoon →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.