-
-
Notifications
You must be signed in to change notification settings - Fork 0
TimerWheel
Hashed timing wheel (Varghese & Lauck) — O(1) schedule, cancel, and reschedule with O(1) amortized cost per timer per tick. The wheel keeps logical time: the caller drives the clock by calling tick()/advance() from its own time source (an interval, a game loop, a simulation), which keeps the structure runtime-agnostic and fully testable.
See also: IndexedHeap / MinHeap — heap-based scheduling with exact arbitrary priorities and no tick driving; Choosing a data structure for the trade-off discussion.
Legend for tables
-
API
-
options- an object with optional properties:-
slots- the number of wheel slots. Default:256(rounded up to a power of two). More slots mean fewer multi-round timers per revolution.
-
-
value- the payload to be scheduled -
entry- a timer handle returned byschedule() -
delay- ticks until due (clamped to ≥ 1 — the soonest a timer can fire is the next tick)
-
-
Complexity
- O - complexity of an operation
- O(1) - constant time
- O(n) - linear time proportional to the number of pending timers
- O(s) - linear time proportional to the number of slots
Implementation of TimerWheel.
import TimerWheel from 'list-toolkit/timer-wheel.js';
const wheel = new TimerWheel({slots: 1024});
const handle = wheel.schedule(task, 150); // due in 150 ticks — O(1)
wheel.reschedule(handle, 42); // move it — O(1)
wheel.cancel(handle); // cancel — O(1)
// drive the clock from any time source:
setInterval(() => {
for (const task of wheel.tick()) task.run();
}, 10); // 1 tick = 10ms of resolutionMethods:
| Member | Return type | Description | O |
|---|---|---|---|
constructor(options) |
this |
constructs a new TimerWheel
|
O(s) |
size |
number | the number of pending timers | O(1) |
isEmpty |
boolean | checks if no timers are pending | O(1) |
position |
number | the current slot position | O(1) |
currentTick |
number | ticks processed since construction | O(1) |
schedule(value, delay) |
entry | schedule a payload; returns the timer handle | O(1) |
isScheduled(entry) |
boolean | checks if the handle is currently pending | O(1) |
cancel(entry) |
boolean | cancel a pending timer; true if it was pending |
O(1) |
reschedule(entry, delay) |
entry | move a timer to a new delay; also revives a fired/cancelled one | O(1) |
remainingTicks(entry) |
number | undefined
|
ticks left until the handle fires | O(1) |
tick() |
array of values | advance one tick; return payloads that came due | amortized |
advance(ticks) |
array of values | advance several ticks; return due payloads in firing order | amortized |
clear() |
this |
cancel everything (handles become unscheduled) | O(n + s) |
[Symbol.iterator]() |
iterator | iterate pending payloads in wheel-slot order (not time order) | O(1) |
Logical time. The wheel never touches setTimeout or Date — a tick means whatever the caller wants (10ms, one frame, one simulation step). This is what makes it portable across runtimes and trivially testable; it also means delays quantize to whole ticks — pick the slot count and tick duration to match the needed resolution and horizon (delays longer than slots are handled with revolution counting).
Handles: schedule() returns the entry — keep it for cancel/reschedule/remainingTicks. isScheduled() is honest (slot = -1 marks fired/cancelled entries), and reschedule() on a fired or cancelled handle revives it. Handles are owned by the wheel that created them; the entry's bookkeeping properties (slot, rounds, next, prev) belong to the wheel. Same-tick timers fire in scheduling order.
Wheel vs heap (measured on bench/bench-timers.js, 2026-07-17, 10k timers, horizon 1k ticks): schedule + drain everything — TimerWheel 402μs vs IndexedHeap 2.12ms (~5×); 100k reschedules — 7.0ms vs 7.4ms (parity). The wheel wins on bulk expiry and O(1) operations; the heap gives exact arbitrary priorities with no tick driving and no resolution quantization. Internally each slot is a node-based List using the raw splice/pop primitives — zero allocation per operation.
TimerWheel is the default export and is also exported by name.
Concepts
DLL: doubly linked lists
SLL: singly linked lists
Unrolled list
List utilities
Caches
Heaps
Queue, Stack, and Deque
Trees
Skip list
Timer wheel
Free list