Home › CS Fundamentals › Operating Systems for Interviews
O

Operating Systems for Interviews

If you can explain why two threads corrupt a counter, and what the kernel is doing while your code "waits," you clear the OS bar at almost any company. The rest is vocabulary.

CS FundamentalsMedium12 min read

If you remember five things

Interviewers grade the story, not the flashcard. These five are the story.

  1. Process vs thread. A process owns the address space and file descriptors. Threads live inside it and share all of that, each with only its own stack and registers.
  2. The OS is a referee and a liar. It shares a few cores by force and fakes a private, infinite memory for every program. The CPU lie leaks as a context switch, the memory lie leaks as a page fault.
  3. Concurrency breaks because count++ is three steps. Read, add, write back. Interleave two threads and an increment vanishes. Fix with an atomic or a lock.
  4. Idle CPU plus high latency means waiting, not working. Threads are blocked on I/O or a lock. Thread states tell you which in thirty seconds.
  5. Deadlock needs four conditions at once. Break the circular wait: everyone acquires locks in the same global order and no cycle can form.

Why candidates with real coding chops still flunk this. They learned OS as note cards, not as a picture of the machine.

1The mental model (what this really is)#

An operating system is a referee and a liar. Hold that line and most questions decompose.

Referee. It shares scarce hardware by force.

  • Your laptop has maybe 8 or 16 cores, but hundreds of programs are "running."
  • The scheduler decides who gets a core, for how long, and in what order. On Linux, slices are measured in milliseconds, often a few each.
  • A timer interrupt preempts you when your slice is up.
  • It arbitrates shared hardware so your browser and Spotify do not trample each other on the same sound device or network card.

Liar. It tells every program a comforting fiction.

  • "You have the whole CPU." False: a few cores are shared by force.
  • "You have a huge, continuous block of memory starting at address zero." False: memory is scattered physical pages, usually 4KB each, stitched together by a translation table.
  • These lies are the abstractions. Almost every OS question is one of them leaking.

Three abstractions carry most of the weight.

  • Process: the OS's unit of "a running program," with its own private memory space, its own file descriptor table, and its own PID.
  • Thread: a unit of execution inside a process. Threads share the process's memory, heap, and open files.
  • Virtual memory: the per-process illusion of a private address space that looks the same to every process, even though the physical frames behind it differ.
The frame that unlocks the whole topic

The CPU lie leaks as a context switch. The memory lie leaks as a page fault. If you can say which abstraction is leaking in any given question, you already know what the kernel is doing. That framing is worth more than any one definition.

2How it gets probed (the signals in a problem)#

OS questions wear disguises. You rarely hear "tell me about operating systems." Instead you get these.

  • "What's the difference between a process and a thread?" The classic warm-up. Flub it and they worry about everything after.
  • "Two threads increment the same variable a million times each. Final value?" Concurrency. The honest answer is "somewhere between one million and two million, non-deterministic."
  • "Your service is slow but CPU is at 10%. What's going on?" Blocking, I/O, context switches, lock contention.
  • "What happens when you run ./myprogram?" Fork, exec, loader, dynamic linker, page faults.
  • "This code deadlocks sometimes. Why, and how do you fix it?" Locking order, resource ordering.
  • "You have a producer and a consumer. Design the handoff." Synchronization, bounded buffer.

The tell is always the same four words. Any mention of shared state, waiting, scheduling, or memory that misbehaves.

  • The moment a problem has more than one thing running and they touch the same data, you are in OS territory.
  • The interviewer is watching whether you reach for a lock without being told.
  • Write multi-threaded code and never say "lock" or "atomic," and that silence is the thing they grade you down on.
From the interviewer's chair

I am not testing whether you memorized the four conditions for deadlock. I am testing whether you notice shared state on your own and reach for protection before I prompt you. The candidate who says "wait, two threads touch this counter, I need a lock here" without being asked has already passed the concurrency screen.

3The core ideas (how it works)#

Process vs thread. The single most-asked question, so nail it cold.

  • A process owns an address space, file descriptors, and a security context (its user, its permissions).
  • Threads live inside a process and share all of that, except each thread has its own stack and its own register set, including the program counter.
  • Threads are cheap to create (no new address space to build) and communicate instantly through shared memory.
  • But one bad thread that scribbles over memory or crashes can take down the whole process.
  • Processes are isolated and safe but talk only through heavier channels: pipes, sockets, or shared-memory segments you explicitly set up.
  • One line for the room: threads trade safety for speed and sharing.

The context switch (the CPU lie leaking).

  • When the OS moves a core from thread A to thread B, it saves A's registers and program counter into A's kernel structure and loads B's.
  • If B is in a different process, it also swaps the memory mapping (the page table base register) and flushes or tags the TLB.
  • Cost of the switch itself: hundreds of nanoseconds to a few microseconds, plus a tail of cache and TLB misses as B's working set gets pulled back in.
  • This is why spawning ten thousand threads for ten thousand tasks is usually a mistake: scheduling overhead and cache thrash eat you alive.
  • You also burn roughly 1MB of stack per thread by default on Linux.

Why concurrency breaks. count++ is not one step.

  • It compiles to read, add one, write back: three separate machine operations.
  • Interleave two threads and both read 41, both add to get 42, both write 42. You lost an increment.
  • That is a race condition: the result depends on timing you do not control.
  • The fix is to make the read-modify-write atomic (a single hardware instruction like lock xadd on x86) or guard it with a lock so only one thread is inside the critical section at a time.
# The bug
load count -> reg # thread A reads 41
 # thread B reads 41 too
add reg, 1 # both compute 42
store reg -> count # both write 42. one increment vanished.

# The fix
lock(mutex)
count = count + 1 # critical section: exclusive
unlock(mutex)
Pause: two threads each run count++ one million times on a shared counter starting at zero. What is the range of possible final values, and why?
Reveal the answer
Anywhere from 1,000,001 to 2,000,000. The low bound is not 1,000,000: in the worst interleave, thread A reads the counter, then B runs all its increments, then A writes back its single stale value, clobbering nearly everything B did, but A still completed one real increment on its first pass before stalling. The high bound is the correct answer you only get if every read-modify-write happens to be atomic by luck. Non-determinism between those bounds is the whole point: the result depends on timing you do not control.

Synchronization primitives, ranked by when I reach for them.

  • Mutex: mutual exclusion, one holder at a time. Default choice for "protect this shared data." A blocking mutex parks the waiter so it stops using the CPU.
  • Semaphore: a counter. Great for "at most N users of a resource" (a connection pool of size N) or signaling between threads (post from one, wait in another).
  • Condition variable: lets a thread sleep until some condition is true, so it stops burning CPU in a spin loop. Always paired with a mutex, and always re-check the condition in a while loop after waking, because of spurious wakeups.
  • Atomics: lock-free single-word operations like compare-and-swap (CAS). Fast, no kernel on the happy path, but easy to misuse. Use them for counters and flags, not for anything that needs to update two fields together.

Virtual memory (the memory lie leaking).

  • Each process sees a flat address space, typically 48 bits of usable range on modern x86-64.
  • The hardware MMU walks the page tables the OS maintains, translating virtual pages to physical frames, and caches the hot translations in the TLB.
  • Touch a page that is not mapped to RAM and you get a page fault: the CPU traps into the kernel, the OS checks whether the access is legal, fetches or allocates the page (maybe from disk or a file), updates the page table, and resumes your instruction as if nothing happened.
  • This is how memory can look bigger than physical RAM (the rest lives on disk as swap).
  • It is also how processes stay isolated: process A has no page table entry pointing at process B's frames, so A cannot even name B's memory.
flowchart LR V[Virtual address] --> TLB{In TLB?} TLB -->|hit| PHYS[Physical frame] TLB -->|miss| PT[Walk page tables] PT -->|mapped| PHYS PT -->|not mapped| FAULT[Page fault: trap to kernel] FAULT --> OS[OS allocates or loads page] OS --> RESUME[Update table, resume instruction] RESUME --> PHYS
One memory access, start to finish. Notice the fast path never leaves hardware (TLB hit straight to a physical frame), and the only time your code enters the kernel is the page fault branch. That branch is the memory lie leaking, and it is why a "minor" fault costs microseconds while a swap-from-disk fault costs milliseconds.

4A worked example (walk through one problem)#

The one I ask most: "Your web service handles 200 requests a second fine. At 400 it falls over, latency spikes to seconds, but CPU sits at 15%. Walk me through it."

Start with the one observation that is half the answer.

  • CPU at 15% means the cores are mostly idle, so the work is not CPU-bound.
  • The threads are waiting for something. The question is what.
  • Say this out loud: idle CPU equals waiting, not working. Naming it is the move that lands.

Candidate causes, ranked, with how I narrow.

  1. Blocking I/O with a small thread pool. Suppose each request makes a 50ms database call and the server has a pool of 50 threads. Each waiting thread is parked, holding a slot and doing nothing. By Little's Law, 50 threads over 50ms per request caps you around 1000 requests a second. If the DB slows under load (the call stretches to 200ms), that ceiling drops to 250, every thread sits blocked, and new requests queue and time out. CPU stays low because everyone is sleeping on a socket read. Matches the symptom perfectly.

  2. Lock contention. If every request grabs one global mutex (a shared cache or a request counter), threads serialize behind it. One makes progress, the rest sleep. Low CPU, high latency, and it gets dramatically worse as concurrency rises, because the queue behind the lock grows faster than it drains. Also matches.

  3. Context-switch thrash. If someone "fixed" throughput by spawning a thread per request, 400 concurrent requests means hundreds of threads fighting over a few cores. The scheduler switches instead of working, and every switch pollutes the cache. You would usually see CPU climb here, often toward 100% in system time, so it is lower on my list given 15%. That 15% is a clue, not noise.

How I confirm, and this is the part that lands.

  • "I would take a thread dump or attach a profiler."
  • If most threads are BLOCKED on the same lock, it is contention.
  • If they are WAITING or TIMED_WAITING on socket reads, it is downstream I/O.
  • The state of the stuck threads tells me which, and it takes thirty seconds to check.

Fixes follow the diagnosis, not a reflex.

  • For I/O: async or non-blocking handling so a handful of threads juggle thousands of waiting requests (an event loop, or Java virtual threads), or a bigger pool plus a faster downstream.
  • For contention: shrink the critical section, shard the lock into N stripes so unrelated requests do not collide, or use a lock-free structure.
  • Notice I did not jump to "add more servers." Scaling out a contention bug buys you the same wall at a higher bill, because the contended lock lives in each process and more boxes do not relax it.
Red flag that fails you here

Blurting a confident single cause ("it's the database") and sprinting to a fix before you have looked at a thread state. The round is won by the reasoning trail, not the guess: idle CPU means waiting, waiting means I/O or locks, thread states disambiguate. Skip that chain and even a correct answer reads as luck.

5Variations and gotchas#

Deadlock. Four conditions must all hold at once.

  • Mutual exclusion, hold-and-wait, no preemption, and circular wait.
  • The fix you should name is break the circular wait: every thread acquires locks in the same global order. If everyone grabs A before B, no cycle can form, so no deadlock.
  • Say that and you have shown you can prevent, not just define.
  • A second answer worth naming: lock timeouts with back-off, so a thread gives up and retries instead of hanging forever.

Starvation and livelock.

  • Starvation: a thread never gets the resource because others keep cutting ahead, common with unfair priority schemes where high-priority threads always win.
  • Livelock: threads stay active and keep changing state but make no forward progress, like two people sidestepping in a hallway forever, each politely reacting to the other.

User mode vs kernel mode.

  • Your code runs in restricted user mode (ring 3 on x86).
  • To touch hardware, allocate memory, or read a file, it makes a system call, which traps into the privileged kernel (ring 0).
  • That transition is not free, on the order of a few hundred nanoseconds per syscall.
  • Which is why batching matters (read 64KB at once instead of one byte sixty-four thousand times), and why high-throughput servers use epoll or io_uring to amortize the crossing.

Thread-safe does not mean fast.

  • Wrapping everything in one big lock is correct and slow: you turned your parallel program back into a serial one.
  • Interviewers love to push here with "now make it scale."
  • That is your cue: finer-grained locking, read-write locks, sharded state, or lock-free reads.

Processes are not always heavier in practice.

  • On Linux, fork uses copy-on-write, so creating a process does not duplicate memory until something writes to a shared page.
  • At that point just that one 4KB page is copied.
  • A freshly forked process is cheap, and the cost shows up lazily only on the pages it mutates.
  • Knowing this detail separates you from the crowd.

6Trade-offs that matter#

Threads vs processes vs async. The isolation-versus-speed call.

Fast, risky
Threads
Memory
Shared: communicate instantly.
Blast radius
One bad thread can crash the whole process.
Best when
Tight sharing and you can discipline the locking.
Safe, crash-resistant
Processes
Memory
Isolated: IPC is heavier.
Blast radius
One crash stays contained (Chrome gives each tab a process).
Best when
Fault isolation is worth the IPC cost.
  • Async/event-loop (one thread, many waiting tasks) wins for I/O-heavy work and sidesteps most locking.
  • But one blocking call stalls the whole loop, and a single event loop cannot use multiple cores. You need one loop per core to scale up.

Lock granularity.

  • Coarse locks are simple and correct but serialize everyone.
  • Fine-grained locks scale but invite deadlock, subtle ordering bugs, and higher per-operation overhead.
  • Start coarse, refine only where profiling shows the lock is actually hot.

Spinlock vs blocking lock.

  • Spin if the expected wait is nanoseconds: do not pay the cost of a context switch to sleep and wake.
  • Block if it could be long: do not burn a whole core spinning.
  • The right answer depends on expected hold time and core count. Spinning on a single core against the holder is pure waste.

Virtual memory cost.

  • Isolation and the big-memory illusion are paid for with TLB misses (a full page-table walk is a handful of memory accesses) and page-fault latency (microseconds for a minor fault, milliseconds if it hits disk).
  • Mostly cheap, occasionally brutal, which is why huge pages exist to shrink TLB pressure.

Every OS trade-off is the same bet: isolation and safety on one side, speed and sharing on the other.

7Mistakes that cost people the round#

  • "A thread is a lightweight process," full stop. True-ish, but they want shared address space, separate stack and registers out of your mouth.
  • Forgetting i++ is not atomic. Claim it is safe without a lock or atomic type and the interview turns, because everything downstream builds on it.
  • Reaching for a lock but never naming the cost. No mention of deadlock risk or shrinking the critical section reads as junior. A lock with no plan to scale it is a tell.
  • Confusing concurrency with parallelism. Concurrency is tasks interleaving, possibly on one core. Parallelism is running at the same instant on multiple cores. You can have concurrency on a single core; you cannot have parallelism there.
  • Defining deadlock's four conditions with no prevention. Always land "acquire locks in a consistent global order."
  • Guessing instead of reasoning. "I would check thread states and profile it" beats a confident wrong cause every time. Interviewers reward the method even when the first guess is off.

8Your practice path#

Study in this order. Each step builds on the last.

  1. Process vs thread, out loud, in 30 seconds. Memory, cost, isolation, communication. If you cannot do it cleanly, start here, because it gates the warm-up.
  2. Write the race-condition bug and fix it in your interview language. Actually run it: two threads each incrementing a shared counter a million times, until you see the final value land short of two million. Seeing it beats reading about it.
  3. Producer-consumer with a bounded buffer using a mutex and condition variables. The canonical synchronization exercise, and it shows up constantly. Get the while-loop condition check right.
  4. Dining philosophers. The classic deadlock. Solve it by lock ordering (number the forks, always pick up the lower-numbered one first). Now you can discuss prevention from experience, not theory.
  5. Reader-writer lock. For many readers and rare writers. Teaches you why one blanket mutex is wasteful and when a shared-read lock pays off.
  6. Trace a page fault and a context switch end to end, in words. Narrate what the CPU and kernel do at each step: trap, save state, resolve, resume.
  7. The "low CPU, high latency" drill from Section 4. Practice the reasoning chain until it is automatic, because this exact scenario shows up in system design and debugging rounds alike.

Do these seven and you are past Medium.

9Where this shows up#

OS fundamentals surface most in systems-heavy shops and infra teams.

  • Expect real depth at places that run their own fleets: the big cloud providers, backend and platform roles at large tech companies, databases, high-frequency trading firms (where microseconds and cache behavior are the product), and any embedded or kernel-adjacent job.
  • Concurrency shows up in nearly every backend coding round the moment shared state appears, even when "OS" is never printed on the rubric.
  • Front-end and pure-ML roles lean on it less, though async and the single-threaded event loop still come up, and anyone touching GPUs runs into parallelism and memory-model questions fast.

The pattern: the closer the role is to the machine, the deeper they dig.

  • A general backend interview wants Sections 1 through 4 solid.
  • An infra or systems interview wants all nine, plus a war story where you actually debugged a deadlock or a contention stall in production.
Follow-ups the interviewer will ask (and your answers)
"Two threads increment a shared counter a million times each. What's the final value?"
Non-deterministic, between one million and two million. `count++` is read-add-write, three steps, so interleaved threads can both read the same value and one increment vanishes. Make it right with an atomic (`lock xadd`) or a mutex around the critical section.
"The service is slow but CPU is at 15%. Where do you look first?"
Idle CPU means the threads are waiting, not working. I take a thread dump: `BLOCKED` on one lock means contention, `WAITING` or `TIMED_WAITING` on socket reads means downstream I/O. Thread states disambiguate in thirty seconds, then I fix the actual cause rather than adding servers.
"This deadlocks sometimes. How do you prevent it?"
Deadlock needs all four of mutual exclusion, hold-and-wait, no preemption, and circular wait. I break the circular wait: every thread acquires locks in the same global order, so no cycle can form. Lock timeouts with back-off are a fallback when a global order is impractical.
"You wrapped it in one big lock and now it's slow. Make it scale."
Thread-safe is not fast: one coarse lock serializes everyone. I shrink the critical section, shard the lock into stripes so unrelated work does not collide, or switch hot reads to a read-write lock or a lock-free structure. I refine only where profiling shows the lock is actually hot.
"What exactly happens on a page fault?"
The CPU traps into the kernel. The OS checks the access is legal, allocates or loads the page (from disk or a file), updates the page table, and resumes the faulting instruction as if nothing happened. A minor fault is microseconds; a swap-from-disk fault is milliseconds. It is the mechanism behind both the big-memory illusion and process isolation.

What you can now explain in the room

  • Process vs thread in one clean sentence: shared address space, separate stack and registers.
  • Why `count++` is a race and how an atomic or a lock fixes it.
  • What the kernel actually does during a context switch and why it is not free.
  • How to diagnose "slow service, idle CPU" from thread states, not guesses.
  • The four conditions for deadlock and the one prevention that always works.
  • How virtual memory and page faults give every process a private, oversized address space.

Want to know which rounds at your target company lean on this? Check the company guides to see exactly where these patterns appear.

Get a prep plan built around your Operating Systems for Interviews interview

Tell us the role and your interview date. We will send back a dated, day by day plan. Free.

Thanks. Check your inbox, your Operating Systems for Interviews prep plan is on the way.