Operating System

15 Core Concepts — The backbone of every systems interview. Know these, own the room.

Process vs Thread

Easy 3 min
Process = independent program with own memory. Thread = lightweight worker inside a process sharing the same memory. Process is the house, threads are the people living in it.

When you double-click Chrome, the OS creates a process. It gets its own memory space, file handles, security context — completely isolated from other processes. If Chrome crashes, your Word document stays safe. That's isolation.

Now inside Chrome, there are many threads — one for UI, one for network, one for each tab's JavaScript. They all share Chrome's memory, so they can easily talk to each other (just read/write shared variables). But they each have their own stack and registers, so they can run independently.

The key insight: OS allocates resources to processes, but schedules threads. A process with 4 threads can use 4 CPU cores simultaneously. A single-threaded process wastes 3 cores. That's why multi-threading matters for performance.

Creating a process is expensive — OS must copy or allocate entire address space, file descriptors, etc. Creating a thread is cheap — just allocate a new stack and register set. Thread switching within the same process is also faster than process switching (no need to change page tables).

PROCESS A Code + Data (Shared by all threads) Heap Memory (Shared) Thread 1 Own Stack Thread 2 Own Stack Thread 3 Own Stack ISOLATED PROCESS B Completely separate memory Cannot directly access Process A's memory

Threads share memory within a process. Processes are isolated from each other.

Key Points for Interview

  • Process: Own address space, heavier, isolated, crash-safe
  • Thread: Shared address space, lighter, faster communication, shared fate (one crashes = all die)
  • Thread creation: ~microseconds. Process creation: ~milliseconds (10-100x slower)
  • Each thread has: own stack, own registers, own program counter. Shares: code, data, heap, files
  • Inter-process communication (IPC) needs pipes, sockets, shared memory (slow). Inter-thread = just variables (fast, but needs sync)

Common Trap

"Threads share everything" — Wrong! Each thread has its own stack and registers. They share heap, code, and data segments. Getting this wrong shows you haven't thought deeply about it.

Competitive Edge

Mention PCB (Process Control Block) vs TCB (Thread Control Block). PCB stores: process state, program counter, registers, memory info, open files, scheduling info. TCB is smaller: just thread state, stack pointer, registers. Also know that modern Linux treats threads as "lightweight processes" (LWP) — uses same task_struct, just with shared memory mappings.

Follow-up Questions

Why does Chrome use multiple processes instead of threads?
Security and stability. Each tab is a separate process. If one tab crashes (or has a security exploit), it can't affect other tabs or steal their data. The isolation is worth the overhead for a browser handling untrusted web content.
Can one thread access another thread's stack?
Technically yes (they're in the same address space), but it's extremely dangerous and undefined behavior. The other thread's stack might get deallocated when it returns. Never do this in real code.
What's the difference between user-level and kernel-level threads?
User-level threads are managed by a user library (green threads), invisible to OS — fast to create but can't run in parallel on multiple cores. Kernel-level threads are managed by OS — can be scheduled on different CPUs but have more overhead. Most modern systems use kernel threads (pthreads, std::thread).

Context Switching

Medium 3 min
Context switch = OS saves the state of current process/thread and loads the state of the next one. It's the overhead price of multitasking — necessary but expensive.

Imagine a chef cooking 5 dishes simultaneously. Each time they switch from biryani to pasta, they must: remember where they left off with biryani, put away those spices, pull out pasta ingredients, recall what step they were on. That mental load is context switching.

When a context switch happens, the CPU must: save program counter (where were we?), save all registers, save stack pointer, save memory mappings pointer, update PCB. Then load all these values for the next process. This takes 1-10 microseconds typically — sounds small, but if you're switching thousands of times per second, it adds up.

Thread context switch is faster than process context switch because threads share the same address space — no need to flush TLB (translation lookaside buffer) or switch page tables. That's why servers often use threads instead of processes.

Process A Running PC, Regs, Stack, State SAVE PCB A Saved State PCB B Saved State LOAD Process B Running Loaded from PCB B CPU Time switch

Context switch: Save A's state to PCB, load B's state from PCB. The switch time is pure overhead.

Key Points for Interview

  • Triggers: time slice expired, I/O wait, interrupt, higher-priority process arrived
  • Thread switch (same process): ~1μs. Process switch: ~10μs (TLB flush, page table switch)
  • Saved to PCB: PC, registers, stack pointer, memory maps, process state, scheduling info
  • Context switch is pure overhead — no useful work done. Too many switches = thrashing
  • Voluntary switch: process yields (I/O wait). Involuntary: preempted by scheduler

Common Trap

Don't say "context switch is slow because saving registers is slow." Saving a few dozen registers is trivial. The real cost is cache pollution — the new process has different working data, so CPU caches are full of useless stuff. Cache misses are the real killer.

Follow-up Questions

How can we reduce context switch overhead?
1) Use threads instead of processes (shared address space = no TLB flush). 2) Use async I/O instead of blocking (fewer switches). 3) Pin threads to CPU cores (cache affinity). 4) Use larger time slices (fewer switches, but worse responsiveness). 5) Use user-level threading (like goroutines) — switches happen in user space without kernel involvement.
What triggers an involuntary context switch?
Timer interrupt (time slice expired), hardware interrupt (disk ready, network packet), higher-priority process becomes ready, current process needs resource held by another.

Multithreading

Medium 3 min
Running multiple threads within one process to achieve concurrency. Threads share memory for fast communication but require synchronization to avoid chaos.

Single-threaded program is like a restaurant with one chef. Multithreaded is like having 4 chefs in the same kitchen. They can cook multiple dishes in parallel, share ingredients, but need to coordinate — or they'll grab the same knife at the same time.

Why use multithreading? (1) Utilize multiple CPU cores — a 4-core CPU with single-threaded code wastes 75% of its power. (2) Keep UI responsive — heavy computation in background thread while UI thread stays smooth. (3) Handle I/O efficiently — while one thread waits for disk, another can do CPU work.

The catch: shared memory makes communication easy but creates problems. Two threads modifying the same variable? Race condition. Two threads waiting for each other's locks? Deadlock. Multithreading is powerful but tricky to get right.

Key Points for Interview

  • Concurrency ≠ Parallelism. Concurrency = dealing with multiple tasks (can be on 1 core via time-slicing). Parallelism = truly simultaneous execution (needs multiple cores).
  • Benefits: better CPU utilization, responsive UI, efficient I/O handling
  • Challenges: race conditions, deadlocks, debugging complexity, thread safety
  • Thread-safe code: works correctly when called from multiple threads simultaneously
  • Synchronization tools: mutex, semaphore, condition variables, atomics, lock-free structures
Threading Model Description Example
Many-to-One Many user threads → one kernel thread. Can't use multiple cores. Green threads (old Java)
One-to-One Each user thread = one kernel thread. True parallelism. Linux pthreads, Windows threads
Many-to-Many Many user threads → many kernel threads. Best of both. Go goroutines, Windows fiber

Competitive Edge

Know Amdahl's Law: Speedup from parallelization is limited by the sequential portion of code. If 10% of your code must be sequential, max speedup is 10x no matter how many cores you add. This is why pure parallelism doesn't scale infinitely. Mention this and watch the interviewer's eyebrows rise.

Follow-up Questions

When should you use processes instead of threads?
When you need strong isolation (security), crash resistance (one worker failing shouldn't kill others), or when you're calling untrusted code. Also when using languages without true multithreading (Python's GIL limits true parallelism — use multiprocessing instead).
What's the difference between I/O-bound and CPU-bound tasks?
I/O-bound spends most time waiting (disk, network) — benefits from many threads even on single core. CPU-bound spends time computing — only benefits from threads if you have multiple cores. Adding more threads than cores for CPU-bound work just increases overhead.

Scheduling Algorithms

Hard 4 min
CPU scheduling decides which process runs next. Key algorithms: FCFS (first come), SJF (shortest job), Round Robin (time slices), Priority (importance), MLFQ (adaptive). Each optimizes for different goals.

FCFS (First Come First Serve): Simple queue — whoever comes first, runs first. Problem: convoy effect. One long job makes everyone behind wait. Average waiting time can be terrible.

SJF (Shortest Job First): Pick the process with shortest burst time. Mathematically optimal for average waiting time. Problem: starvation of long processes, and how do you even know the burst time in advance? (Answer: predict using exponential averaging of past bursts.)

Round Robin: Each process gets a fixed time slice (quantum), then goes to back of queue. Fairest algorithm — everyone gets a turn. Tuning the quantum is key: too small = too many context switches, too large = behaves like FCFS.

Priority Scheduling: Higher priority runs first. Problem: low-priority processes starve. Solution: aging — increase priority of waiting processes over time. Can be preemptive or non-preemptive.

MLFQ (Multi-Level Feedback Queue): Multiple queues with different priorities and time quanta. New processes start at top (high priority, small quantum). If they use full quantum, they're CPU-bound → demote to lower queue. Interactive processes (short bursts) stay at top. Adapts to process behavior. Most real OS use variants of this.

Round Robin (Time Quantum = 4) Ready Queue: P1 P2 P3 P4 CPU back to queue Gantt: P1 P2 P3 P4 P1 P2 ... 0 4 8 12 16 20

Round Robin: each process gets 4 time units, then goes to back of queue. Fair but more switches.

Key Points for Interview

  • Preemptive = OS can interrupt running process. Non-preemptive = process runs until it yields
  • SJF is optimal for avg waiting time but impractical (can't predict burst). SRTF is preemptive SJF.
  • Round Robin quantum: 10-100ms typical. Trade-off: responsiveness vs overhead
  • Starvation in Priority → fix with Aging
  • Linux uses CFS (Completely Fair Scheduler) — red-black tree of processes by virtual runtime, not queues

Common Trap

"FCFS is preemptive" — No! FCFS is always non-preemptive. Also, "SJF causes starvation" is true, but interviewers sometimes ask "what algorithm causes convoy effect?" That's FCFS, not SJF. Know which problem belongs to which algorithm.

Competitive Edge

Mention Belady's Anomaly in context of page replacement (FIFO can have MORE page faults with MORE frames). Also know that real-time systems use different schedulers: Rate Monotonic (static priority based on period) and EDF (Earliest Deadline First). Shows you understand scheduling beyond textbook basics.

Follow-up Questions

How do you choose the right time quantum for Round Robin?
If too small: too many context switches, overhead dominates. If too large: degrades to FCFS, poor response time. Rule of thumb: quantum should be larger than 80% of CPU bursts but small enough to feel responsive. Typically 10-100ms. For interactive systems, smaller (10-20ms). For batch, larger.
What's the difference between preemptive and non-preemptive SJF?
Non-preemptive SJF: once a process starts, it runs to completion. Preemptive SJF (called SRTF — Shortest Remaining Time First): if a new process arrives with shorter remaining time, current process is preempted. SRTF has lower average waiting time but more context switches.

Deadlock

Hard 4 min
Deadlock = two or more processes stuck forever, each waiting for a resource the other holds. No one can proceed. System freezes.

Two cars on a narrow bridge, coming from opposite sides, meeting in the middle. Neither can move forward, neither wants to reverse. They wait for each other... forever. That's deadlock.

In OS terms: Process A holds Resource 1 and wants Resource 2. Process B holds Resource 2 and wants Resource 1. Neither releases what they have. Both wait indefinitely. Classic example: two threads each trying to acquire two locks in different orders.

Four Necessary Conditions (Coffman, 1971): ALL four must exist simultaneously for deadlock to occur:

  1. Mutual Exclusion: At least one resource is non-shareable (only one process can use it at a time)
  2. Hold and Wait: Process holds some resources while waiting for others
  3. No Preemption: Resources can't be forcibly taken away
  4. Circular Wait: A→B→C→A cycle of waiting

Break ANY one of these conditions and deadlock becomes impossible.

Circular Wait = Deadlock P₁ Holds R₁ P₂ Holds R₂ R₁ R₂ wants R₂ wants R₁

P₁ holds R₁, wants R₂. P₂ holds R₂, wants R₁. Circular wait → Deadlock!

Key Points for Interview

  • Prevention: Break one of 4 conditions at design time (e.g., order all lock acquisitions — breaks circular wait)
  • Avoidance: Banker's Algorithm — before granting resource, check if system stays in safe state
  • Detection: Build resource allocation graph. Cycle detection = deadlock (for single-instance resources)
  • Recovery: Kill a process, preempt resources, rollback to checkpoint
  • Most real OS use Ostrich Algorithm — ignore deadlocks, let user reboot. Full prevention is too expensive.

Common Trap

"Deadlock and starvation are the same" — No! In deadlock, processes are BLOCKED waiting for each other. In starvation, process is READY but never gets scheduled. Deadlock = stuck. Starvation = unfair scheduling. Don't mix them up.

Competitive Edge

Know the Banker's Algorithm in detail — it's a classic interview question. Also mention that databases actively detect and resolve deadlocks (by rolling back one transaction) while OS typically doesn't. Why? Database transactions are designed to be rollback-safe; arbitrary processes aren't.

Follow-up Questions

How does ordering lock acquisition prevent deadlock?
Assign a unique number to each resource. Rule: always acquire locks in increasing order. If P1 wants R1 then R2, and P2 wants R2 then R1, both must acquire R1 first. No circular wait possible. This is the most practical prevention technique in real code.
What's the difference between safe state and deadlock-free?
Safe state means there EXISTS some sequence in which all processes can complete. Unsafe state means deadlock MIGHT happen (not guaranteed). Banker's algorithm keeps system in safe state, which is stricter than just avoiding deadlock.

Starvation

Easy 2 min
Starvation = a process is ready to run but never gets CPU time because higher-priority processes keep jumping ahead. It's not blocked — it's just perpetually ignored.

Restaurant analogy: VIP customers always get seated first. A regular customer arrives, but VIPs keep coming. Hours pass. The regular customer is never served — not because the restaurant is closed, but because they're always deprioritized. That's starvation.

Starvation happens in: (1) Priority Scheduling — low-priority process waits forever if high-priority processes keep arriving. (2) SJF — long jobs starve because short jobs keep coming. (3) Reader-Writer Problem — if readers keep arriving, writer starves.

Solution: Aging. Gradually increase priority of waiting processes. Eventually even the lowest priority becomes high enough to run.

Key Points for Interview

  • Starvation: process is READY but not scheduled. Deadlock: process is BLOCKED.
  • Starvation is a liveness problem (something that should happen eventually, doesn't)
  • Algorithms prone to starvation: Priority, SJF, SSTF (disk scheduling)
  • Round Robin is inherently starvation-free — everyone gets a turn
  • Fix: Aging, fair scheduling, combining priority with round robin

Follow-up Questions

Can FCFS cause starvation?
No. FCFS serves processes in arrival order. A process will definitely run when its turn comes. It may wait a long time (convoy effect) but it won't starve. Starvation requires indefinite postponement, which FCFS doesn't have.

Race Condition

Medium 3 min
Race condition = program's output depends on the unpredictable timing of thread execution. Two threads access shared data, at least one writes, no synchronization → incorrect results.

Two people editing the same Google Doc cell simultaneously without seeing each other's changes. Person A reads "50", adds 10, writes "60". Person B reads "50" (before A's write), adds 20, writes "70". Expected: 80. Got: 70. That's a race condition — the outcome depends on who "wins" the race.

The classic example: incrementing a shared counter. counter++ looks atomic but it's actually: (1) load counter value, (2) add 1, (3) store result. If two threads do this simultaneously, they might both load the same value, both add 1, both store — result: only incremented once instead of twice.

The code region where shared resources are accessed is called the critical section. Race conditions are fixed by ensuring mutual exclusion in the critical section — only one thread at a time.

Key Points for Interview

  • Race condition = bug caused by non-deterministic thread interleaving
  • Occurs when: shared data + concurrent access + at least one write + no synchronization
  • Critical section = code that accesses shared resources
  • Fix: mutex, semaphore, atomic operations, lock-free data structures
  • Even simple operations like x++ or x = y may not be atomic!

Common Trap

"Race conditions only happen with global variables" — Wrong! Any shared data causes races: heap objects accessed by multiple threads, static variables, file handles, database connections. Even reading and writing "different" struct fields can race if they share a cache line (false sharing).

Follow-up Questions

How do atomic operations prevent race conditions?
Atomic operations are guaranteed to complete fully without interruption. The CPU ensures that no other thread can see the operation "in progress" — they see either before or after, never during. Common atomics: compare-and-swap (CAS), fetch-and-add. Used for lock-free programming.
What's a TOCTOU race?
Time Of Check To Time Of Use. You check a condition (e.g., file exists), then act on it (open file). Between check and use, another process might change the state (delete the file). Security vulnerability in many systems. Fix: use atomic operations that check-and-act in one step.

Mutex

Medium 3 min
Mutex (Mutual Exclusion) = a lock that only ONE thread can hold. Lock before critical section, unlock after. Whoever holds the mutex has exclusive access. Simple, effective, but can cause deadlock if misused.

A bathroom with a lock. One person enters, locks the door. Others wait outside. When done, unlocks the door. Next person enters. That's a mutex — only the owner can unlock it.

How it works: Thread calls lock(). If mutex is free, thread acquires it and continues. If mutex is held by another thread, calling thread blocks (sleeps) until mutex is released. Thread calls unlock() when done — wakes up one waiting thread.

Ownership: Mutex has owner. Only the thread that locked it can unlock it. This is THE key difference from binary semaphore. If thread A locks mutex, thread B cannot unlock it.

In C++, use std::lock_guard or std::unique_lock — RAII wrappers that automatically unlock when scope ends. Prevents forgetting to unlock on exceptions or early returns.

Key Points for Interview

  • Mutex = binary lock with ownership. Only owner can unlock.
  • Blocking: if mutex is held, caller waits (sleeps, doesn't spin by default)
  • Recursive mutex: same thread can lock multiple times (must unlock same number)
  • Try-lock: attempt to acquire without blocking — returns immediately if unavailable
  • Deadlock risk: thread holds mutex A, waits for mutex B; another holds B, waits for A
  • Always use RAII (lock_guard) — never raw lock/unlock calls

Common Trap

"Mutex and binary semaphore are the same" — No! Mutex has ownership. Only the locker can unlock. Binary semaphore has no ownership — ANY thread can signal it. Mutex is for protecting critical sections. Semaphore is for signaling between threads. This distinction is asked in almost every OS interview.

Competitive Edge

Know about spinlocks: instead of sleeping when lock is unavailable, thread spins (busy-waits) checking repeatedly. Good for very short critical sections where sleeping overhead is worse than spinning. Bad for long waits (wastes CPU). Linux uses spinlocks in kernel code where sleeping isn't allowed.

Semaphore

Medium 3 min
Semaphore = a counter-based signaling mechanism. wait() decrements (blocks if zero). signal() increments (wakes a blocked thread). Used for limiting concurrent access and thread signaling.

Counting Semaphore: A parking lot with N spaces. Counter shows available spots. Car enters → wait() → counter decrements. Car leaves → signal() → counter increments. When counter is 0, new cars must wait.

Binary Semaphore: Counter is 0 or 1. Works like a lock but without ownership — any thread can signal it, not just the one that waited. Useful for thread-to-thread signaling.

Classic operations: P() or wait() or down() — decrement and block if would go negative. V() or signal() or up() — increment and wake one blocked thread. These operations are atomic.

Classic problems solved by semaphores: Producer-Consumer (bounded buffer), Readers-Writers (shared data access), Dining Philosophers (resource allocation), and synchronizing execution order.

Key Points for Interview

  • Counting semaphore: value can be any non-negative integer. Controls N concurrent accesses.
  • Binary semaphore: value is 0 or 1. Similar to mutex but NO ownership.
  • wait()/P()/down(): if value > 0, decrement; else block
  • signal()/V()/up(): increment value, wake one blocked thread if any
  • No ownership — any thread can signal (unlike mutex)
  • Use case: resource counting (connection pool), ordering (ensure A runs before B), producer-consumer
Aspect Mutex Semaphore
Value Locked or unlocked Non-negative integer
Ownership Yes — only owner unlocks No — anyone can signal
Primary Use Protect critical section Signaling, resource counting
Recursive Can be (recursive mutex) N/A

Follow-up Questions

How does semaphore solve producer-consumer?
Use 3 semaphores: (1) empty — counts empty slots, init to buffer size. (2) full — counts filled slots, init to 0. (3) mutex — protects buffer access. Producer: wait(empty), wait(mutex), produce, signal(mutex), signal(full). Consumer: wait(full), wait(mutex), consume, signal(mutex), signal(empty).
Can semaphore cause deadlock?
Yes. If two processes wait on two semaphores in different order, same circular wait happens as with mutexes. Semaphores don't magically prevent deadlock — you still need to be careful about acquisition order.

Paging

Hard 4 min
Paging divides memory into fixed-size blocks: logical memory → pages, physical memory → frames. A page table maps pages to frames. Eliminates external fragmentation.

Imagine a book (your process) with 100 pages. The library (RAM) has shelf slots (frames). Pages can go into ANY available slot — they don't need to be next to each other. A table of contents (page table) tells you which page is in which slot. Page 1 might be in slot 47, page 2 in slot 3, etc.

Address translation: Logical address = page number + offset. CPU generates logical address, MMU (Memory Management Unit) looks up page number in page table to find frame number, combines with offset to get physical address. This happens for every memory access.

Page table overhead: Each process needs its own page table. With 4KB pages and 4GB address space, you need 1 million entries. Each entry is ~4 bytes → 4MB just for page table! Solution: multi-level page tables (only allocate what's needed) or inverted page tables.

TLB (Translation Lookaside Buffer): Page table is in main memory. Accessing it for every address is too slow. TLB is a small, fast cache of recent page→frame mappings. TLB hit = fast. TLB miss = slow (access page table, update TLB).

Logical Address Page # (p) Offset (d) Page Table 0 → f3 1 → f7 2 → f1 3 → f5 frame # Physical Address Frame # (f) Offset (d) offset unchanged RAM f0 f1 ← f2 f3 ...

Page table maps logical page numbers to physical frame numbers. Offset stays the same.

Key Points for Interview

  • Page size typically 4KB. Page table stored in main memory (PCB points to it)
  • No external fragmentation (fixed-size blocks). Internal fragmentation in last page
  • TLB = cache for page table. TLB miss → 2 memory accesses (page table + actual data)
  • Page fault: page not in RAM → load from disk. Very expensive (~10ms)
  • Multi-level page tables reduce memory overhead for sparse address spaces
  • Inverted page table: one entry per frame (not per page). Used by some architectures

Competitive Edge

Know page replacement algorithms: FIFO (simple but Belady's anomaly), LRU (optimal approximation, expensive to implement), Clock/Second-Chance (practical LRU approximation using reference bit). Also know that Linux uses a variant of LRU with active/inactive lists.

Follow-up Questions

What's the effective memory access time with TLB?
EMAT = hit_rate × (TLB_time + memory_time) + miss_rate × (TLB_time + 2×memory_time). If TLB hit rate is 99%, TLB access is 10ns, memory is 100ns: EMAT = 0.99×(10+100) + 0.01×(10+200) = 108.9 + 2.1 = 111ns. Without TLB: 200ns (two memory accesses every time). TLB is crucial!
Why is page fault so expensive?
Page fault means the page is on disk (swap space). Disk access is ~10ms (10 million ns) vs memory ~100ns — that's 100,000x slower! During page fault, process blocks, context switch happens, disk I/O is initiated, another process runs. It's the most expensive operation in memory management.

Segmentation

Medium 3 min
Segmentation divides memory into variable-size logical units: code segment, data segment, stack segment. More human-friendly than paging but causes external fragmentation.

While paging blindly chops memory into equal-size pages (like cutting a pizza into identical slices without looking at toppings), segmentation respects the logical structure of your program. Code is one segment, data is another, stack is another. Each has a different size based on need.

Segment table: Each entry has: base (starting physical address) + limit (segment size). Logical address = segment number + offset. If offset > limit → segmentation fault! This is protection built into the hardware.

Modern systems use segmented paging: segments are further divided into pages. You get logical grouping of segments + no external fragmentation of paging. x86 supports both, but modern OS (Linux, Windows) use flat segmentation (all segments have base=0, limit=max) and rely entirely on paging.

Key Points for Interview

  • Paging: fixed-size, invisible to programmer, no external fragmentation
  • Segmentation: variable-size, programmer-visible, external fragmentation
  • Segment table: base + limit for each segment
  • Protection: offset must be < limit, else segfault
  • Modern x86-64: segmentation mostly disabled (flat model), paging does all the work
Aspect Paging Segmentation
Division Fixed-size pages/frames Variable-size segments
Visibility Transparent to programmer Programmer-visible
Fragmentation Internal (last page) External (between segments)
Address Page # + Offset Segment # + Offset

Virtual Memory

Hard 4 min
Virtual memory gives each process the illusion of having its own large, contiguous address space — even if physical RAM is smaller. Uses disk as extension of RAM via demand paging.

Every process believes it owns the entire memory — say, 4GB on 32-bit system. But your machine might only have 2GB RAM. How? Virtual memory. Each process has a virtual address space. OS maps only the pages actually being used to physical RAM. Rest stays on disk (swap space).

Demand paging: Don't load entire program at startup. Load pages only when accessed. First access to a page → page fault → OS loads it from disk. Subsequent accesses → fast (already in RAM).

Key benefits: (1) Run programs larger than physical memory. (2) Process isolation — process A can't access process B's memory because they have different page tables. (3) Memory protection — pages can be read-only, no-execute, etc. (4) Shared libraries — multiple processes can share the same code pages.

Thrashing: If working set (frequently used pages) exceeds RAM, system spends all time swapping pages in and out. CPU utilization drops, system becomes unusable. Solution: add RAM, reduce multiprogramming degree, or use working set model.

Key Points for Interview

  • Virtual address → MMU + Page table → Physical address (or page fault)
  • Swap space on disk holds pages evicted from RAM
  • Demand paging: load pages lazily, only when accessed
  • Copy-on-write: parent and child share pages until one writes → then copy
  • Thrashing: too many page faults, system spends all time swapping
  • Working set: set of pages process is actively using. Keep these in RAM

Common Trap

"Virtual memory means using disk as RAM" — Incomplete! Virtual memory is primarily about giving each process its own address space and providing isolation. Disk-backed swapping is just ONE feature. Even with infinite RAM, you'd still use virtual memory for isolation and protection.

Competitive Edge

Know about memory-mapped files: map a file directly into virtual address space. Read/write file as if it's memory. OS handles paging automatically. Used for efficient file I/O and shared memory between processes. Also know copy-on-write (COW): fork() doesn't copy memory — both parent and child share pages (marked read-only). Only when one writes, that page is copied. Huge performance win for fork().

Follow-up Questions

How does virtual memory provide process isolation?
Each process has its own page table. Virtual address 0x1000 in process A maps to different physical frame than 0x1000 in process B. Process A literally cannot express an address that refers to B's memory — the mapping doesn't exist in A's page table. Hardware enforces this.
What triggers thrashing and how to detect it?
Thrashing happens when working sets of running processes exceed RAM. Symptom: high page fault rate + high disk I/O + low CPU utilization (CPU waiting for pages). Detection: monitor page fault frequency. Solution: suspend some processes (reduce multiprogramming), add RAM, or use better page replacement.

Heap vs Stack (Memory Layout)

Medium 3 min
In process memory: Stack grows downward (function calls, local vars), Heap grows upward (dynamic allocation). They grow toward each other. Collision = trouble.

Every process has a memory layout (from low to high address): Text (code) → Data (initialized globals) → BSS (uninitialized globals) → Heap (grows ↑) → ... free space ... → Stack (grows ↓) → Kernel space.

Stack: LIFO structure. Each function call pushes a frame (local vars, return address, params). Function return pops the frame. Super fast allocation (just move stack pointer). Limited size (1-8MB typically). Stack overflow = too deep recursion or huge local arrays.

Heap: Dynamic memory — you request (malloc/new), you release (free/delete). Managed by allocator, can fragment. Slower than stack (must find free block). No size limit (well, virtual memory limit). Memory leak = forgetting to free.

High Low Kernel Space Stack ↓ Function frames, local vars Free Space (Stack & Heap grow here) Heap ↑ Dynamic allocation (new/malloc) BSS (Uninitialized globals) Data (Initialized globals) Text (Code) — Read Only

Process memory layout. Stack grows down, heap grows up. They must not collide!

Key Points for Interview

  • Stack: auto-managed (LIFO), fast, limited size, stores local vars and function calls
  • Heap: manually managed, slower, large, stores dynamic allocations
  • Text segment: read-only, contains executable instructions
  • Data/BSS: global and static variables (initialized vs uninitialized)
  • Each thread has own stack, all threads share heap
  • Stack overflow: too deep recursion. Heap exhaustion: too much allocation without freeing

Follow-up Questions

Why is stack allocation faster than heap?
Stack allocation is just incrementing/decrementing the stack pointer (one CPU instruction). Heap allocation involves searching free lists, potentially coalescing blocks, maintaining metadata — much more complex. Stack is O(1), heap is more expensive.
What happens during a function call (stack frame)?
Push: (1) arguments (right to left in C), (2) return address, (3) old base pointer (frame pointer), (4) local variables. Set new base pointer. On return: restore old base pointer, pop return address, jump back. This is the "calling convention."

Memory Allocation Strategies

Medium 3 min
OS uses strategies to find free memory blocks: First Fit (first block that fits), Best Fit (smallest sufficient block), Worst Fit (largest block). Each trades speed vs fragmentation.

First Fit: Scan from beginning, pick first hole big enough. Fast (stops early). Problem: fragments the beginning of memory.

Next Fit: Like First Fit, but start from where last allocation ended. Distributes allocation more evenly. Slightly better than First Fit in practice.

Best Fit: Search entire list, pick smallest hole that fits. Minimizes wasted space per allocation. Problem: leaves tiny unusable fragments, slow (full scan).

Worst Fit: Pick largest hole. Theory: leftover will be big enough to be useful. Practice: quickly fragments large blocks, performs worst overall.

Buddy System: Memory divided into power-of-2 blocks. Request for 70KB? Allocate 128KB block. To allocate, split larger block into two "buddies." To free, merge buddies back. Used in Linux kernel. Fast and limits fragmentation.

Key Points for Interview

  • First Fit is generally best overall (good balance of speed and fragmentation)
  • Best Fit leaves tiny holes (external fragmentation still happens)
  • Compaction: move processes to create one large free block (expensive, needs relocation)
  • Internal fragmentation: wasted space INSIDE allocated block
  • External fragmentation: wasted space BETWEEN allocated blocks
  • Modern allocators (jemalloc, tcmalloc): size classes, thread caches, much smarter

Competitive Edge

Know that modern allocators like jemalloc (used by Firefox, Redis) and tcmalloc (Google) use per-thread caches, size classes (buckets for common sizes), and sophisticated techniques to minimize fragmentation and contention. Mentioning these shows you know real-world systems, not just textbook algorithms.

Garbage Collection

Medium 3 min
Garbage Collection automatically finds and frees memory that's no longer reachable. Used in Java, Python, Go — not in C/C++ (manual or smart pointers). Trade-off: convenience vs control.

In C/C++, you allocate memory, you free it. Forget to free → memory leak. Free twice → crash. In garbage-collected languages, you just allocate. The GC periodically finds unreachable objects and frees them. You don't call free/delete.

Mark and Sweep: (1) Start from "roots" (stack, globals, registers). (2) Mark all reachable objects. (3) Sweep: free all unmarked objects. Problem: needs to pause program ("stop the world"). Modern GCs use incremental/concurrent marking to reduce pauses.

Reference Counting: Each object has a counter — how many references point to it. When counter = 0, free immediately. Problem: circular references (A→B→A, both have count 1, neither freed). Solution: combine with mark-sweep or use weak references.

Generational GC: Observation: most objects die young. Divide heap into generations: young (Eden), old (Tenured). Collect young generation frequently (fast, most garbage is here). Collect old generation rarely (slow but infrequent). Objects that survive young collections get promoted to old. Java uses this.

Key Points for Interview

  • C/C++ has no GC — uses RAII, smart pointers, manual management
  • Mark-and-Sweep: handles cycles, but needs pauses
  • Reference counting: immediate collection, but can't handle cycles alone
  • Generational: exploits "most objects die young" — very effective
  • GC overhead: memory (tracking info), CPU (collection work), pauses (latency spikes)
  • Real-time systems avoid GC — unpredictable pauses are unacceptable

Common Trap

"GC prevents all memory leaks" — Wrong! GC only frees unreachable objects. If you keep a reference to something you don't need (like adding to a list and never removing), it's still reachable → still a leak. These "logical leaks" are common in GC languages. Close resources explicitly!

Competitive Edge

Know why C++ doesn't have GC: C++ values deterministic destruction (RAII) and zero-cost abstractions. With GC, you can't predict when destructor runs — problematic for resource management (files, locks, connections). RAII + smart pointers give you GC-like safety WITH deterministic timing. This is why system software (OS kernels, game engines, trading systems) uses C++, not Java.

Follow-up Questions

How does Python handle circular references?
Python uses reference counting as primary mechanism (immediate collection for most objects) plus a cycle-detecting garbage collector that periodically runs to find and break circular references. The gc module lets you control this. This hybrid approach gets the best of both worlds.
What's a GC pause and why does it matter?
During GC, application threads may be paused while GC does its work. In a game, this causes frame drops. In a server, this causes latency spikes. In trading, this costs money. GC tuning is a major concern in latency-sensitive applications. Modern GCs (ZGC, Shenandoah) aim for sub-millisecond pauses.

Ready to test OS?

Check process, memory, scheduling, and concurrency recall before moving on.

Start practice
Home 01. DBMS 02. OOPs 03. Computer Networks 04. Operating System 05. C++ Core 05. Java Core 05. Python Core 06. Puzzles 07. Linux 08. Git 09. SQL 10. Projects 11. System Design 12. DSA Concepts