The Clock Algorithm

A web server holds ten thousand pages in memory. It only has room for one thousand frames. Something must decide which page to evict on every page fault. The theoretically best choice is the optimal replacement policy. It needs to know the future, though. So real systems approximate the past instead. Specifically, they track how recently each page was used. But tracking exact recency for every access has a cost. It would mean updating a timestamp or a linked list on every reference. This is far too expensive to do in hardware or software at nanosecond scale. The Clock algorithm solves this by paying for almost nothing on the common path. Hardware silently flips a single bit — the reference bit — whenever a page is touched. The operating system only inspects that bit lazily. It does so during the comparatively rare event of a page fault. This single design decision converts an O(1)-per-access bookkeeping problem into an O(1)-amortized eviction problem. It does this without ever needing a true recency ordering. This matters because page faults, while rare per access, are inevitable at scale. A bad eviction choice can throw out a page about to be reused. That forces another expensive disk read. Nearly every general-purpose operating system, including Linux, FreeBSD, and Windows, uses some variant of Clock. Linux's is a two-list version, tellingly named "Clock-Pro" in research. In practice it is expressed as active/inactive LRU lists. These systems use Clock rather than exact LRU. This is precisely because Clock captures most of LRU's benefit. It does so at a tiny fraction of its cost. This article builds the Clock algorithm from first principles. It covers the reference bit, the circular buffer of frames, and the sweeping "hand." It then covers the Enhanced Clock refinement. This refinement also uses the dirty bit to prefer evicting clean pages over dirty ones. We derive its complexity and prove why it approximates LRU. We trace a full worked example by hand. We also catalog the classic bugs that trip up implementers.

Key takeaways
  • Clock approximates LRU using one hardware-maintained reference bit per frame instead of exact recency timestamps.
  • A circular list of frames is scanned by a "clock hand"; a bit of 1 is cleared and skipped, a bit of 0 is evicted.
  • Also called second-chance: a recently-referenced page survives one sweep before it becomes evictable.
  • Enhanced Clock adds the dirty bit, creating four classes so clean, unused pages are evicted before dirty ones.
  • Amortized cost per fault is O(1); worst case a full sweep is O(n) but each page is examined O(1) times overall.
  • It underlies real replacement in Linux, BSD, and Windows, and sets up the study of thrashing and working sets.
The core insight

Clock replaces the exact question "which page is least recently used?" with the cheap, hardware-supported question "has this page been used at all since I last checked?" — and that single relaxation is enough to approximate LRU almost perfectly.

The idea

Exact Least Recently Used (LRU) replacement requires a total order on all resident pages by time of last access, updated on every memory reference. Maintaining that order — via a doubly linked list touched on every read or write — is too expensive for hardware to do transparently, and too expensive for software to do on every instruction. The Clock algorithm, invented as a practical approximation of LRU, abandons the total order and instead maintains, for each frame, a single reference bit RR that hardware sets to 1 automatically whenever the page is accessed (read or written). The frames of physical memory are arranged conceptually in a circular list, and a single pointer called the clock hand sweeps around this circle. When a page fault occurs and a frame must be freed, the hand examines the frame it currently points to. If that frame's reference bit is 0, it is evicted immediately — it has not been used since the last sweep passed it. If the bit is 1, the algorithm gives the page a second chance: it clears the bit to 0 and advances the hand, without evicting, continuing until it finds a frame with R=0R=0. This is why the base algorithm is also called the second-chance algorithm. The Enhanced Clock algorithm (used in Mach, VMS-derived systems, and described in Silberschatz's treatment of replacement algorithms) extends this with the modify bit (dirty bit) MM, which hardware sets when a page is written. This produces four classes of pages, ordered by preference for eviction:

(R,M)∈{(0,0), (0,1), (1,0), (1,1)}(R,M) \in \{(0,0),\ (0,1),\ (1,0),\ (1,1)\}

A (0,0)(0,0) page is both unused and clean — the ideal victim, since evicting it costs nothing beyond dropping the frame. A (0,1)(0,1) page is unused but dirty, requiring a costly write-back to disk before reuse. A (1,0)(1,0) page was recently used but is clean. A (1,1)(1,1) page was recently used and is dirty — the worst candidate, evicted only as a last resort. Enhanced Clock sweeps multiple times, first hunting for class (0,0)(0,0), then (0,1)(0,1) (clearing R bits as it goes), converging on a victim that minimizes both the chance of reuse and the cost of write-back.

How it works

The core data structure is an array (or circular doubly linked list) of frame descriptors, each holding a page identifier, a reference bit, and — for Enhanced Clock — a dirty bit. A single integer `hand` indexes the current frame under examination.

Second-chance (basic Clock) proceeds as follows on every page fault: ``` // Frame table: circular array of size N // frame[i].page = resident page (or EMPTY) // frame[i].ref = reference bit, set by hardware on access // hand = index of the clock's current position function CLOCK_REPLACE(new_page): loop forever: f = frame[hand] if f.page == EMPTY: frame[hand] = { page: new_page, ref: 1 } hand = (hand + 1) mod N return hand_position_used if f.ref == 0: // victim found: evict it evicted_page = f.page if f.page is loaded from disk before use, flush if dirty // basic version: assume clean or write-through frame[hand] = { page: new_page, ref: 1 } hand = (hand + 1) mod N return evicted_page else: // f.ref == 1 f.ref = 0 // give a second chance hand = (hand + 1) mod N // continue loop; do not evict yet // Hardware side, on every memory access to page p resident in frame i: on_access(i): frame[i].ref = 1 ``` The crucial invariant is that the hand only ever advances forward, and every frame it passes either gets its bit cleared (if it was 1) or gets evicted (if it was already 0). Since there are only NN frames, after at most NN steps of "clear and advance," the hand must encounter a frame whose bit is already 0 — either one it cleared on a previous full lap, or one that was never referenced. Termination is therefore guaranteed within at most 2N2N examinations in the worst case (one lap to clear everything, a second lap to find a zero), though in practice it terminates far sooner because most working sets are smaller than NN.

Enhanced Clock replaces the single-bit test with a class search, sweeping up to twice through the circle: ``` function ENHANCED_CLOCK_REPLACE(new_page): // Phase 1: look for (ref=0, dirty=0) without modifying bits, // but clear ref bits of (1, *) pages as we pass them. // Phase 2: if no (0,0) found, look for (ref=0, dirty=1). // At most two full sweeps of the circle are needed in total. start = hand target_class = (0, 0) // phase 1 target: unused & clean cleared_any = false for pass in {1, 2}: i = hand repeat: f = frame[i] if (f.ref, f.dirty) == target_class: evict f, place new_page in frame[i], set ref=1 hand = (i + 1) mod N return if f.ref == 1: f.ref = 0 // demote toward class (0, dirty) cleared_any = true i = (i + 1) mod N until i == start target_class = (0, 1) // phase 2: unused but dirty allowed // note: ref bits are already cleared from phase 1, // so phase 2 only needs to check dirty bits now // Guaranteed to succeed by end of phase 2: every frame now has ref=0, // so either (0,0) or (0,1) must match every remaining frame. unreachable ``` The key correctness fact for Enhanced Clock: by the end of the first full sweep, every reference bit in the circle has been cleared to 0 (either because it was already 0, or because the algorithm cleared it while passing). Therefore the second sweep is guaranteed to find a match in class (0,0)(0,0) or (0,1)(0,1) — every frame now satisfies R=0R=0. This bounds Enhanced Clock to at most two full sweeps, i.e. O(N)O(N) frame examinations per fault in the worst case, same asymptotic order as basic Clock.

Figure
The clock hand sweeping a circular buffer of frames, second-chance in action
P4 R=1 P7 R=0 P2 R=1 P9 R=1 P1 R=0 P5 R=1 P3 R=1 P6 R=1 hand → victim HAND R=1 (second chance) R=0, will be scanned next R=0, already a candidate

Complexity & correctness

Time complexity. Each individual page fault triggers a scan that, in the worst case, visits every frame twice: once to clear all reference bits down to 0, and once more to find a frame that already has R=0R=0 (for Enhanced Clock, up to two full sweeps for the two classes, so up to 4N4N examinations). This gives a worst-case cost of O(N)O(N) per fault, where NN is the number of physical frames. However, this worst case is misleading in isolation. Consider the total work done across a sequence of FF faults. Each time the hand passes over a frame and clears its bit, that frame cannot be cleared again until it is referenced afresh (setting R=1R=1) — and references are driven by the running program, not by the replacement algorithm. In the amortized sense, if we charge each bit-clearing operation to the memory access that most recently set that bit to 1, then each memory access is charged for at most one clear. Combined with the fact that a fault always terminates in the frame it evicts, the amortized cost per fault is O(1)O(1) in typical workloads where the total number of "clear" operations is bounded by the total number of memory accesses, not by NN alone. In the pathological case where a workload has no locality at all and every bit is constantly re-set, the algorithm degrades toward O(N)O(N) per fault, matching the theoretical worst case — but this is precisely the regime where any replacement policy performs poorly, foreshadowing the discussion of Thrashing.

Space complexity is O(N)O(N): one reference bit and (for Enhanced Clock) one dirty bit per physical frame, plus the single hand pointer — a dramatic improvement over exact LRU's need for either a full access-time stamp per page (O(N)O(N) words) or a linked list with pointer updates on every reference.

Correctness argument — why Clock approximates LRU. The claim to prove is not that Clock evicts the exact least-recently-used page, but that it evicts a page that has not been referenced during the most recent full sweep of the hand — a bounded window of accesses, playing the role of an approximate recency threshold. Invariant: At any point after the hand has completed a full lap without finding a victim, every frame's reference bit reflects whether that page was accessed since the hand last passed it (because passing clears the bit to 0, and any subsequent access sets it back to 1). Argument: Suppose the hand is examining frame ff. If R(f)=0R(f) = 0, this means no access to ff's page has occurred since the hand last visited ff and cleared its bit (or since ff was loaded, if never visited). This is exactly the property LRU wants: a page unused for a "long time" (one full sweep) is evicted. If R(f)=1R(f) = 1, the page has been used since the last sweep, so evicting it would be a mistake under an LRU-like policy — hence Clock correctly grants it a second chance rather than evicting it blindly. Termination: The hand advances monotonically and clears every 1-bit it encounters. Since there are finitely many frames (NN) and no new 1-bits can appear on a frame without a genuine memory access (an external event, not caused by the algorithm), within one full lap every previously-1 bit becomes 0, and the next pass is guaranteed to find at least one frame with R=0R=0 unless every single frame was re-referenced during the very lap that cleared it — a case handled correctly by simply continuing the sweep, at worst costing another full lap.

Honest tradeoff. Clock is not exact LRU: it can evict a page referenced two accesses ago but not since the hand's last pass, while retaining one referenced long ago but touched once, coincidentally, just before the hand arrived. It sacrifices strict recency ordering for O(1) amortized bookkeeping — a tradeoff that is almost always worth it, because the bookkeeping savings on every memory access dwarf the marginal replacement-quality loss measured in cache-hit-rate studies (Clock typically comes within a few percentage points of exact LRU on realistic workloads).

Worked example

Take a system with 4 frames, all initially holding pages A, B, C, D (loaded in that order, so all reference bits start at 1 after their initial load), and hand starting at frame 0 (page A). Suppose the reference string of subsequent accesses, before the next fault, is:

B, D, EB,\ D,\ E

where E is a new page not currently resident, forcing a fault. Reference bits before the fault, reflecting the accesses to B and D (E is not yet resident so its access is the fault):

FramePageR (after B, D accessed)
0A1 (from initial load, untouched since)
1B1 (re-referenced)
2C1 (from initial load, untouched since)
3D1 (re-referenced)

All four bits happen to be 1 — a deliberately non-trivial case where the first lap finds no immediate victim. Hand is at frame 0 when the fault for E occurs. Trace:

StepHand atR beforeActionR afterVictim?
1frame 0 (A)1clear, advance0no
2frame 1 (B)1clear, advance0no
3frame 2 (C)1clear, advance0no
4frame 3 (D)1clear, advance0no
5frame 0 (A)0evict A, load E, set R=11yes

The hand takes a full lap (4 steps) clearing every bit, then on the second pass immediately evicts frame 0 (page A) because its bit is now 0 — nobody touched A during the lap. Note this matches intuition: A was the only page in the set never re-referenced (B and D were touched, C was untouched too but happened to sit later in the sweep order). Had C been checked before A on the second lap, the outcome would depend on hand position — this is precisely the source of Clock's approximation error relative to exact LRU, which would need to distinguish "A untouched longer" from "C untouched," a distinction Clock's single bit cannot make.

Enhanced Clock variant of the same scenario. Now suppose D is dirty (written to) and A, B, C are clean. Enhanced Clock's phase 1 seeks (R=0,M=0)(R{=}0, M{=}0):

StepHand at(R, M) beforeMatches (0,0)?Action
1frame 0 (A), clean(1, 0)noclear R → (0,0) now matches but only checked before clearing per algorithm variant; here we clear then continue since check was pre-clear
2frame 1 (B), clean(1, 0)noclear R → (0, 0)
3frame 2 (C), clean(1, 0)noclear R → (0, 0)
4frame 3 (D), dirty(1, 1)noclear R → (0, 1)
5frame 0 (A), clean(0, 0)yesevict A (clean, no write-back cost)

Enhanced Clock reaches the same eviction choice here, but critically, if D had instead been the only untouched page, Enhanced Clock would skip past it (since (0,1)(0,1) is a worse class than (0,0)(0,0)) to find a clean victim first, avoiding an unnecessary disk write-back — a distinction basic Clock cannot make since it has no dirty bit.

Common pitfalls & traps

PitfallWhy it happensConsequence
Forgetting to clear R when granting a second chanceImplementer evicts on R=1R=1 by mistake, or forgets the clear stepDegrades to FIFO-like behavior; loses the "second chance" property entirely
Not advancing the hand after evictionOff-by-one: hand stays put or double-advancesNext fault re-examines the just-placed page or skips a frame, unbalancing the sweep
Assuming Clock always finds a victim in one passConfusing "amortized O(1)" with "worst case O(1)"Code that assumes a single comparison per fault breaks under low-locality workloads
Ignoring the dirty bit entirely in a system needing write-backUsing basic Clock when disk write cost varies by pageEvicts dirty pages unnecessarily, causing extra disk I/O that Enhanced Clock would avoid
Setting R=1 in software only on explicit "touch" callsRelying on software emulation instead of hardware MMU supportReference bit becomes stale or wrong, since ordinary loads/stores never update it
Treating hand position as global recency timeAssuming frame just after the hand is "oldest"Two frames both with R=0 are treated as equally old, though one may have been 0 far longer — Clock cannot distinguish this

Pioneers & history

The reference-bit approximation to LRU emerged from the practical constraints of early time-sharing and virtual memory systems, where hardware could cheaply provide a single "was I touched" bit per page but not a full access-time log.

Origins · 1 minFernando J. Corbató and how this was discovered
FJ
Fernando J. Corbató
1926–2019 · MIT, virtual memory and time-sharing systems

Corbató led the development of CTSS and Multics, systems that pioneered paging and virtual memory management under real hardware constraints. The practical need to approximate recency without expensive per-access bookkeeping, in systems his teams built, directly motivated reference-bit-based schemes like Clock as a feasible alternative to theoretically ideal but impractical policies.

History
1959
Time-sharing concepts and early paging systems begin appearing at MIT, motivating practical replacement policies.
1965
Multics development explores virtual memory management strategies including reference-bit hardware support.
1969
Richard Carr and John Hennessy's contemporaries and related researchers formalize the Clock/second-chance algorithm as a practical LRU approximation.
1976
Enhanced Clock (also called "Clock with two hands" or reference+dirty variants) is described for systems like DEC's VAX/VMS.
1990
s–present: Linux, BSD, and Windows adopt Clock-derived and multi-list LRU-approximation schemes as their production page replacement policies.

Where this matters

Nearly every production operating system kernel uses a Clock-derived algorithm rather than exact LRU, because the hardware reference bit is universally available on modern MMUs while exact per-access timestamping is not. Linux uses a two-list "active/inactive" LRU approximation heavily influenced by Clock-style periodic bit inspection, scanning pages during memory pressure (`kswapd`) rather than on every fault. FreeBSD implements an explicit Clock-based page daemon with active, inactive, and cache queues. Windows uses a working-set-based variant that also relies on hardware access bits rather than exact recency lists. Beyond OS kernels, the same reference-bit idea appears in database buffer pool managers (e.g., PostgreSQL's clock-sweep buffer replacement policy, used instead of exact LRU precisely to avoid lock contention on a shared recency list under concurrent access), and in CPU cache replacement approximations where full LRU is too expensive to implement exactly beyond a handful of ways. The general principle — approximate an expensive-to-maintain ordering with a cheap, hardware-supported bit sampled lazily — recurs anywhere systems must balance replacement quality against bookkeeping cost, directly motivating the deeper question of how much memory a process truly needs, which leads into Thrashing and the Working Set model.

Ask the tutor: How does the Enhanced Clock algorithm's four-class priority scheme change under heavy write-mostly workloads compared to read-mostly ones?

Is the Clock algorithm the same as FIFO with a correction?

Not conceptually, though implementations can resemble each other. FIFO evicts strictly by arrival order, ignoring any usage since loading. Clock also uses a circular structure and a moving pointer, which superficially resembles FIFO's queue, but the reference bit changes eviction order dynamically: a page can survive many sweeps if it keeps getting re-referenced, which is impossible in true FIFO. Clock is best understood as FIFO order for candidacy combined with an LRU-like veto (the reference bit) that grants reprieves, making it strictly closer to LRU behavior than FIFO in practice, despite its FIFO-like circular bookkeeping.

Does Clock require special hardware support?

Yes, in the general case. The reference bit must be set automatically by the memory management unit (MMU) on every load or store to a page, without trapping into the operating system, or the whole efficiency argument collapses. Nearly all modern MMUs (x86, ARM, RISC-V with the Sv39/Sv48 page table formats) provide a hardware "accessed" bit in the page table entry for exactly this purpose. Software-only emulation is possible — by marking pages not-present and trapping on first access — but this defeats the purpose somewhat, since it turns every reference-bit-setting event into a trap, closer in cost to what Clock was designed to avoid.

How many bits does the reference bit actually need to distinguish "recently used"?

Exactly one bit is the whole design point: it distinguishes only "used since last cleared" from "not used since last cleared," discarding all finer-grained recency information. Some systems soften this with an "aging" scheme, shifting the reference bit into a multi-bit counter periodically (an 8-bit history per page, for instance), which approximates LRU more closely at the cost of slightly more bookkeeping. This aging variant is a middle ground between the single-bit Clock and full LRU, and is used in some historical Unix variants' page replacement, but it is not the Clock algorithm proper.

Why does Enhanced Clock need up to two full sweeps instead of one?

Because it searches two separate classes in priority order: first (R=0,M=0)(R{=}0, M{=}0), then (R=0,M=1)(R{=}0, M{=}1). During the first sweep, the algorithm both searches for class (0,0)(0,0) and clears every R=1R{=}1 bit it encounters as a side effect, since those pages get a "second chance" too. By the end of one full lap, every reference bit in the system is guaranteed to be 0. This guarantees the second sweep, searching for (0,1)(0,1), will succeed immediately upon encountering any frame, because all frames now satisfy R=0R=0 and therefore match either (0,0)(0,0) or (0,1)(0,1).

Can Clock evict a page that was just accessed a moment ago?

Yes, and this is its main deviation from true LRU. If a page is accessed, then the hand happens to sweep past every other frame first (clearing their bits), and then circles back around and the same page is not accessed again before the hand returns, it can be evicted even though it was "recently" used in absolute wall-clock time. What Clock actually guarantees is coarser: a page is not evicted if it was used since the hand's previous pass over that specific frame, not since some fixed global time window. The approximation quality depends on how many frames the hand must traverse between visits to the same frame, which shrinks as memory pressure and fault frequency increase.

How does Clock interact with thrashing?

When a process's working set exceeds available frames, every frame gets referenced constantly, so reference bits are almost always 1 when the hand arrives. This forces Clock toward its worst-case behavior: full sweeps that find little difference between candidates, because nearly everything looks "recently used." The algorithm still terminates and still picks a victim, but replacement quality degrades toward arbitrary, since the reference bit carries little signal when everything is being referenced. This scenario is the entry point into the study of Thrashing, where the fix is not a better replacement algorithm but reducing the number of resident processes or applying a working-set-based admission policy.

Is Clock used for anything other than page replacement?

Yes. The same reference-bit-and-sweep idea is used in buffer pool eviction inside databases (PostgreSQL's clock-sweep algorithm for its shared buffer cache is a direct descendant), because a global LRU list under high concurrency requires a lock on every access, which serializes all readers. A Clock-style approximate scheme lets the reference bit be set with a lightweight atomic operation instead of taking a lock on a shared list, trading exact recency for scalability — exactly the same tradeoff that motivates its use in OS kernels.

Before this

Where this leads