Heads up: posts on this site are drafted by Claude and fact-checked by Codex. Both can still get things wrong — read with care and verify anything load-bearing before relying on it.
why how

Why monotonic time is different from wall-clock time

Wall-clock time tells you what to put on a calendar. Monotonic time tells you how long something took. Confusing them is how you get bugs that look like physics violations.

Systems intermediate Apr 29, 2026

Why it exists

You write the most natural piece of code in the world:

start = time.time()
do_the_thing()
elapsed = time.time() - start

Most days this works. Then one night, do_the_thing() “takes” negative twenty-three minutes. Or it appears to take three hours when it actually took four seconds. Nothing about the function changed. What changed is that the machine’s clock got adjusted underneath you — by NTP nudging the wall clock backwards to correct drift, by a daylight-saving transition, by a virtual machine pausing and resuming, by an admin running date -s to fix a wrong clock, or by a leap second smearing.

The fix isn’t “use a better clock.” It’s recognising that there are two different questions you might be asking, and they need two different clocks:

  1. What time is it right now, in the human world? That’s wall-clock time: it must agree with calendars, time zones, NTP, and ultimately the rotation of the Earth. It is allowed — required, even — to jump.
  2. How long has it been since X? That’s monotonic time: it must never go backwards, never jump, never be “corrected.” It doesn’t even need to be a meaningful date — it just needs to count seconds since some arbitrary fixed point.

Modern operating systems expose both because trying to make one clock do both jobs is a contradiction. You can’t simultaneously promise “this matches civil time” and “this never goes backwards” — civil time itself goes backwards sometimes.

Why it matters now

Engineers in the AI era are running more code on machines they don’t own: ephemeral cloud VMs, autoscaling pods, GPU boxes spun up for one training run. Those machines come up with bad clocks and have NTP “yank” them into shape minutes later. Anything that timed itself with time.time() during that window measured nonsense.

It also matters for everything where “elapsed time” is load-bearing:

The bugs are nasty because they’re rare, system-dependent, and they look like the laws of physics broke.

The short answer

monotonic time = "seconds since some fixed point" + "never goes backwards, never jumps"

Wall-clock time answers “what time is it?”. Monotonic time answers “how much time has passed?”. They are different problems, so the OS gives you different clocks. Use wall-clock for things humans see (logs, scheduled jobs, “created_at”). Use monotonic for every duration, deadline, and timeout your code computes.

How it works

Underneath, both clocks are usually derived from the same hardware source — on modern x86, typically the TSC or an equivalent counter exposed through vDSO. The difference is what the OS does with that counter before handing you a number.

For wall-clock time, the kernel keeps an offset from the counter to civil time, and ntpd/chrony/systemd-timesyncd continuously adjust that offset. There are two adjustment modes:

For monotonic time, the kernel exposes the counter directly with no offset games. On Linux that’s clock_gettime(CLOCK_MONOTONIC, …). There are several flavours:

In application languages:

The pattern across all of them is the same: there are two functions because there are two questions.

Show the seams

A few things the textbook account skips:

The deeper point: “what time is it” and “how long did this take” are not the same question, and the universe doesn’t owe us a single clock that answers both. The split into two clocks is the OS being honest about that.

Going deeper