The operating system’s three lies
You know memory is slow and caches are fast; now meet the software that hides the machine from you.
Hundreds of programs share your machine, and none of them are written as if that were true. That is the operating system’s doing. It maintains three convenient fictions for every process, and understanding where each one leaks is most of what “knowing how computers work” means in practice. Take the lies one at a time.
Lie one — “you have the CPU to yourself”
In reality the scheduler gives your process short slices of a core — milliseconds — suspending and resuming it constantly. Each context switch costs a few microseconds directly, plus something sneakier: the incoming thread finds the caches full of someone else’s data. Crossing into the kernel at all (a system call — every read, write, or send) costs on the order of a hundred nanoseconds to a microsecond, which is why batching small I/O into bigger calls is a perennial win.
The units of execution, priced:
| Unit | Memory | Created in | Switched by | Isolation |
|---|---|---|---|---|
| Process | MBs (own address space) | ~ms | kernel, µs | full — crash contained |
| Thread | ~1 MB stack, shared heap | ~10–100 µs | kernel, µs | none — shared memory |
| Async task / goroutine | KBs | ~ns–µs | runtime, ~ns | none — cooperative |
The table explains an architectural fact you will meet again in System Design: a thread per connection stops scaling around a few thousand connections (stacks eat RAM, switches eat CPU), which is exactly why event loops and lightweight tasks exist. It also gives the classic dividing line: CPU-bound work wants roughly one thread per core and no more; I/O-bound work wants many cheap waiters.
Lie two — “you have memory to yourself”
The addresses your program sees are virtual. Hardware and OS translate them, in 4 KB pages, to wherever the physical bytes actually are — with a small cache of translations (the TLB) making the common case free. The fiction buys enormous things: processes cannot read each other’s memory, memory can be handed out lazily, and a file can be mmaped straight into your address space.
It leaks at the page fault. A minor fault (the page exists, just not mapped yet) costs microseconds. A major fault means the page is on disk — and your “memory access” silently became an SSD read from Stage 1’s ladder. This is why swapping servers feel broken rather than slow, and why container memory limits matter: exceed them and the OOM killer ends your process, no exceptions thrown. When you read memory dashboards, know the words: RSS is what you actually occupy in RAM; virtual size is merely what you have promised yourself.
Lie three — “what you wrote is on disk”
A successful write() means your bytes reached the OS page cache — RAM — not the device. The OS flushes them later, in whatever order it likes. Until an fsync on the file completes (and, for new files, one on the containing directory), a power cut can erase what your code considers written, and can even leave later writes present while earlier ones vanished.
Every database you respect is built around taking this seriously: write an intent record to an append-only write-ahead log, fsync it, then touch the real data structures — so a crash at any moment can be replayed or rolled back. You will meet the WAL again under transactions in System Design; it is the same idea both times. The professional habit here is modest: know, for anything you claim is durable, exactly which line of code makes it so.
Where it bites
- “Durable” systems with no
fsyncin sight — find the line or stop using the word. - A working set that quietly outgrows RAM — major page faults turn memory reads into disk reads, and the service feels broken rather than slow.
- Container memory limits treated as decoration — exceed one and the OOM killer ends the process mid-request, no exception thrown.