The 50 operating systems questions interviewers actually ask, with direct answers, small diagrams and code, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.
50 questions with answersKey Takeaways
An operating system (OS) is the software that sits between your programs and the hardware. It manages the CPU (deciding which process runs next), memory (giving each program its own address space), storage (files and directories), and devices (disks, network cards, the keyboard), and it exposes all of that to applications through system calls. Without it, every program would have to talk to the hardware directly and coordinate with every other program by hand. The kernel is the core that runs in privileged mode and does this work; user programs run in a restricted mode and ask the kernel for anything sensitive. In interviews, operating systems questions probe how you reason about the trade-offs the OS makes: why context switching isn't free, how virtual memory lets many programs believe they own all of RAM, why two threads sharing data can corrupt it, and how a deadlock forms and gets prevented. This page collects the 50 questions that come up most, each with a direct answer plus small diagrams and code where a candidate would actually show one. Operating Systems: Three Easy Pieces (OSTEP), a free textbook, is the reference this page leans on for the concurrency and virtualization chapters; pair this bank with our AI interview preparation guides for the live-interview format, since the first technical round is increasingly an AI-driven screen.
Watch: Operating Systems Course for Beginners
Video: Operating Systems Course for Beginners (freeCodeCamp.org, YouTube)
Test yourself and earn a certificate
10 quick questions. Score 70%+ to download your Operating Systems certificate.
The fundamentals every entry-level round checks: what an OS does, processes and threads, scheduling basics, and the memory and file-system ideas underneath. If any answer here surprises you, that's your study list.
An operating system is the software layer that sits between your programs and the hardware. It manages the CPU, memory, storage, and devices, and it lets applications use all of that through system calls instead of talking to the hardware directly, so programs can share one machine safely without stepping on each other.
Its core jobs are process management (deciding what runs when), memory management (giving each program its own space), file systems (organizing storage), and device and I/O management. It also enforces protection so one program can't corrupt another. In short, it turns raw hardware into something programs can safely share.
Key point: Lead with 'it manages hardware and provides services to programs'. Interviewers open here to hear whether you understand the OS as a resource manager, not just 'Windows or Linux'.
Watch a deeper explanation
Video: Introduction to Operating Systems (Neso Academy, YouTube)
Kernel mode is a privileged CPU mode where code can execute any instruction and touch any hardware directly. User mode is a restricted mode where application code runs; it can't directly access hardware or reach into other processes' memory, and any attempt to do something privileged is blocked by the CPU until it goes through the kernel.
The split exists for protection. When a user program needs something privileged, reading a file, allocating memory, it makes a system call, which switches the CPU into kernel mode, runs the trusted kernel code, and switches back. That boundary is what stops a buggy or malicious program from taking down the whole machine.
Key point: The system call as the bridge between the two modes. That connection is the follow-up interviewers push on.
A system call is how a user program asks the kernel to do something privileged that it can't do itself, like reading a file or allocating memory. It traps into kernel mode, the kernel runs the trusted code to do the work, and then control returns to the program in user mode with a result or an error code.
Common ones: fork and exec to create and run processes, read and write for I/O, open and close for files, wait to reap a child, mmap for memory mapping. Every time a program opens a file or sends data on a socket, a system call is doing the actual work underneath the library function you called.
// a system call underneath a library call
int fd = open("data.txt", O_RDONLY); // open() system call
char buf[128];
read(fd, buf, sizeof(buf)); // read() system call
close(fd); // close() system callKey point: Give two or three real system calls by name (fork, read, open). Concrete examples beat a textbook definition here.
A process is a running program with its own isolated memory (address space), open files, and resources. A thread is a unit of execution inside a process; a process can have many threads, and they all share that process's memory.
The trade-off: threads are cheaper to create and switch and can share data directly, which makes them good for parallel work, but that shared memory is exactly why they can corrupt each other and need locks. Processes isolate strongly, so one crashing won't take down another, at the cost of heavier creation and needing IPC to communicate.
| Process | Thread | |
|---|---|---|
| Memory | Isolated address space | Shared with sibling threads |
| Crash impact | Contained | Can corrupt the whole process |
| Communication | IPC (slower) | Shared memory (fast, needs locks) |
Key point: The follow-up is usually 'so when would you use a process over a thread?'. Answer with isolation vs shared-data speed, not just definitions.
Watch a deeper explanation
Video: Operating Systems: Crash Course Computer Science 18 (CrashCourse, YouTube)
A process moves through a small set of states. New (being created), Ready (waiting for the CPU), Running (executing on the CPU), Waiting or Blocked (waiting for I/O or an event), and Terminated (finished). The scheduler moves processes between Ready and Running.
The key transitions: a running process gets preempted back to Ready when its time slice ends, or moves to Waiting when it asks for I/O. When the I/O completes, it goes back to Ready, not straight to Running, because it still has to be scheduled.
How a process moves through its states
The Terminated state ends the cycle. The Waiting-to-Ready step is the transition interviewers most often probe.
Key point: Draw the state diagram if you can. Knowing that I/O completion returns a process to Ready, not Running, is the detail interviewers check.
A context switch is when the CPU stops running one process or thread and starts another. The OS saves the current one's state (registers, program counter, stack pointer) into its control block and loads the next one's saved state so it resumes exactly where it left off.
It costs time because saving and restoring that state is pure overhead, no user work happens during it, and switching between processes also flushes CPU caches and TLB entries, so the new process starts with cold caches. This is why doing too many tiny context switches hurts throughput.
Key point: Mention cache and TLB effects, not just register saving. That's the reason context switches cost more than the raw save/restore, and it's a common follow-up.
Watch a deeper explanation
Video: Operating Systems Course for Beginners (freeCodeCamp.org, YouTube)
CPU scheduling is the OS deciding which ready process runs next on the CPU and for how long. Since there are usually far more processes than CPUs, the scheduler multiplexes the single CPU among them, switching quickly between processes to keep the CPU busy, share it fairly, and keep interactive programs feeling responsive to the user.
It's needed because a single CPU can only run one thing at a time, but users expect many programs to run at once. Good scheduling balances goals that conflict: high throughput, low response time for interactive apps, fairness, and avoiding starvation. Which goal matters most depends on the system.
Key point: The conflicting goals (throughput vs response time vs fairness). Showing you understand scheduling is a set of trade-offs reads better than reciting one algorithm.
First-Come First-Served (FCFS) runs jobs in arrival order: simple but a long job at the front makes everyone wait (the convoy effect). Shortest Job First (SJF) runs the shortest job next, which minimizes average waiting time but can starve long jobs and needs to know or estimate job length.
Round Robin gives each process a fixed time slice and cycles through them, which is fair and responsive for interactive systems but adds context-switch overhead if the slice is too small. Priority scheduling runs the highest-priority job first, and can starve low-priority ones unless you add aging to raise priority over time.
| Algorithm | Strength | Weakness |
|---|---|---|
| FCFS | Simple, no starvation | Convoy effect, poor response time |
| Shortest Job First | Minimizes average wait | Can starve long jobs; needs job length |
| Round Robin | Fair, good for interactive | Overhead if time slice is tiny |
| Priority | Serves important jobs first | Starvation without aging |
Key point: Pair each algorithm with its failure mode (convoy effect, starvation, overhead). Naming the weakness proves you understand it, not just the name.
In preemptive scheduling, the OS can take the CPU away from a running process before it finishes, usually when its time slice ends or a higher-priority process becomes ready. Round Robin and preemptive priority scheduling are the common examples, and modern desktop and server operating systems all schedule preemptively so no single process can hog the CPU.
In non-preemptive scheduling, a process keeps the CPU until it voluntarily yields, blocks on I/O, or finishes. FCFS and basic SJF are non-preemptive. Preemptive gives better responsiveness for interactive systems; non-preemptive is simpler and avoids the overhead and race risks of interrupting a process mid-work.
Key point: preemptive maps to interactive responsiveness and non-preemptive to simplicity. If asked which modern OSes use, the technical answer is preemptive.
Virtual memory is a technique where each process gets its own large, contiguous-looking address space that's independent of actual physical RAM. The OS and hardware translate these virtual addresses to physical ones, and pages not currently needed can live on disk.
It buys three things: isolation (a process can't touch another's memory), the illusion of more memory than you physically have (via paging to disk), and simpler programming (every process sees the same clean address layout). The cost is the translation overhead and the risk of thrashing if too much is paged.
Key point: List the three wins: isolation, more-than-physical memory, and a simple uniform address space. That framing shows you understand why virtual memory exists, not just what it is.
Paging splits both virtual and physical memory into fixed-size blocks: pages (virtual) and frames (physical). A page table maps each virtual page to a physical frame, so a process's memory can be scattered across RAM instead of needing one contiguous block.
This eliminates external fragmentation, because any free frame fits any page, and it lets the OS keep only the pages a process is actively using in RAM while the rest sit on disk. The trade-off is a small amount of internal fragmentation (the last page is rarely full) and the cost of a page-table lookup on every access, which the TLB speeds up.
Key point: Say that paging kills external fragmentation but adds a lookup on every access, softened by the TLB. That cause-and-effect is what the question checks.
Internal fragmentation is wasted space inside an allocated block: you asked for 100 bytes but got a 128-byte block, so 28 bytes are unusable. Fixed-size allocation (like paging) causes this because the last page of a process is rarely completely full.
External fragmentation is wasted space between allocated blocks: free memory exists but it's split into scattered chunks too small to satisfy a request, even though the total is enough. Variable-size allocation causes this. Paging avoids external fragmentation by using fixed-size frames, trading it for a little internal fragmentation.
Key point: Map each type to its cause: internal from fixed blocks, external from variable blocks. Saying 'paging trades external for a little internal' shows the connection.
A file system is how the OS organizes and stores data on a disk. It manages files (named collections of data), directories (the tree that organizes them), and the metadata for each file: size, permissions, timestamps, and where its data blocks physically live.
It also handles allocation (which disk blocks belong to which file), free-space tracking, and access control. Examples are ext4 and XFS on Linux, NTFS on Windows, and APFS on macOS. The file system is what turns raw disk blocks into the files and folders you actually work with.
Key point: Mention metadata (permissions, timestamps, block pointers), not just 'it stores files'. Metadata is where follow-up questions about inodes and permissions go.
Watch a deeper explanation
Video: Files and File Systems: Crash Course Computer Science 20 (CrashCourse, YouTube)
An absolute path specifies a file's location from the root of the file system, so it's unambiguous no matter where you are: /home/user/data.txt on Linux or C:\Users\data.txt on Windows. A relative path is interpreted from the current working directory, like docs/report.txt or ../config.
Relative paths are shorter and portable across machines with the same structure, but they depend on where the process is running. Absolute paths are explicit and safe from that ambiguity, which is why scripts that must run anywhere often use them.
Key point: relative paths maps to the current working directory. The gotcha interviewers like is that a relative path breaks if the working directory changes.
Concurrency is dealing with many tasks at once by interleaving them, making progress on several even on a single CPU by switching between them quickly. Parallelism is actually executing multiple tasks at the same instant, which needs multiple CPUs or cores.
The one-liner: concurrency is about structure (handling many things), parallelism is about execution (doing many things simultaneously). A single-core machine can be concurrent but not parallel. Every parallel system is concurrent, but not every concurrent system runs in parallel.
Key point: Use the line 'concurrency is structure, parallelism is execution'. the key signal is exactly that distinction, and the single-core example nails it.
A deadlock is when a set of processes are stuck forever because each is waiting for a resource that another one in the set is holding. No process can proceed, so none release what they hold. Two threads each grabbing one lock and waiting for the other is the classic example.
Four conditions must all hold for deadlock (the Coffman conditions): mutual exclusion, hold-and-wait, no preemption, and circular wait. Break any one of them and deadlock can't happen, which is exactly how prevention strategies work.
Key point: The all four Coffman conditions. The follow-up is always 'how do you prevent it?', and the technical answer is 'break any one of these four'.
Multiprogramming keeps several programs loaded in memory so the CPU always has work: when one blocks on I/O, the CPU switches to another, maximizing utilization. Multitasking is multiprogramming with fast time-slicing so it feels like programs run at once, which is what interactive systems do.
Multiprocessing means having more than one physical CPU or core, so multiple programs genuinely run in parallel. The first two are about sharing one CPU efficiently; the third is about having several CPUs. Modern systems combine all three.
Key point: Keep them distinct: multiprogramming and multitasking share one CPU, multiprocessing adds more CPUs. Blurring them is the common slip here.
A CPU cache is a small, fast memory close to the processor that holds recently and frequently used data and instructions. Main memory (RAM) is much slower than the CPU, so without a cache the CPU would stall waiting on memory for most operations.
Caches work because of locality: programs tend to reuse the same data (temporal locality) and access nearby data soon after (spatial locality). A cache hit returns data in a few cycles; a miss means fetching from slower RAM. The whole memory hierarchy, registers, L1/L2/L3 cache, RAM, disk, exists to hide that speed gap.
Key point: Bring up locality (temporal and spatial) as why caches work. That's the concept behind the answer, and it leads naturally into the memory hierarchy.
Buffering uses a region of memory to temporarily hold data while it moves between a fast producer and a slow consumer, like reading from disk into a buffer before your program processes it. It smooths out speed mismatches so neither side has to wait on the other constantly.
Spooling (Simultaneous Peripheral Operations On-Line) queues jobs for a slow device, classically a printer, on disk so many programs can submit work without waiting for the device to be free. The device pulls from the spool one at a time. Buffering overlaps one transfer; spooling queues many jobs.
Key point: Distinguish them clearly: buffering smooths one transfer, spooling queues many jobs for a shared slow device. The printer example makes spooling concrete.
For candidates with a solid course or some experience: synchronization, memory management internals, deadlock handling, and the reasoning questions that separate memorizers from people who understand the machine.
A race condition is a bug where the outcome depends on the unpredictable timing of threads accessing shared data. Two threads incrementing the same counter can both read the old value, both add one, and both write back, so one increment is lost. The result changes run to run, which makes it hard to reproduce.
You prevent it by making the critical section (the code touching shared state) mutually exclusive, using a lock, mutex, or atomic operation so only one thread runs it at a time. The skill is identifying which sections are actually shared and locking exactly those, since locking too much kills performance.
// unsafe: two threads can lose an increment
counter = counter + 1;
// safe: guard the critical section
pthread_mutex_lock(&m);
counter = counter + 1;
pthread_mutex_unlock(&m);Key point: Show you can point at the critical section, not just say 'add a lock'. Knowing what to protect and how little to lock is the real signal.
A mutex is a lock for mutual exclusion: one thread owns it, does its work in the critical section, and releases it, and only the owner can unlock it. It's binary, locked or unlocked, and it's the right tool for protecting shared data.
A semaphore is a counter used for signaling and for limiting access to N copies of a resource. A counting semaphore lets up to N threads through (say, N connection slots); a binary semaphore looks like a mutex but has no ownership, so any thread can signal it. Use a mutex to protect data, a semaphore to coordinate or count.
| Mutex | Semaphore | |
|---|---|---|
| Purpose | Mutual exclusion on shared data | Signaling / limit to N holders |
| Ownership | Only the locker unlocks | No ownership; any thread signals |
| Values | Locked or unlocked | Counter (0 to N) |
Key point: Stress ownership: a mutex has an owner, a semaphore doesn't. That distinction is the crux, and 'mutex for data, semaphore for signaling' is the memorable summary.
The producer-consumer problem has one or more threads producing items into a shared bounded buffer and others consuming them. The challenge is coordination: producers must wait when the buffer is full, consumers must wait when it's empty, and both must access the buffer without corrupting it.
The standard solution uses a mutex to protect the buffer and two condition variables (or two counting semaphores), one to signal 'space available' and one to signal 'item available'. A producer waits on space, adds under the lock, and signals item; a consumer waits on item, removes under the lock, and signals space. This avoids busy-waiting and race conditions.
The producer-consumer pattern
A mutex protects the shared buffer; condition variables (or semaphores) make threads wait for space or items instead of busy-looping.
Key point: The pieces: mutex plus two condition variables (or semaphores) for full and empty. the question needs the coordination mechanism, not just a description of the problem.
There are four broad approaches. Prevention breaks one of the four Coffman conditions by design (for example, forcing all resources to be requested at once, or imposing a global lock ordering to kill circular wait). Avoidance uses runtime information to only grant requests that keep the system in a safe state, as in the Banker's algorithm.
Detection and recovery lets deadlocks happen, finds them by looking for a cycle in the resource-allocation graph, and recovers by killing or rolling back a process. And the ostrich approach ignores the possibility, which is what many general-purpose OSes actually do because deadlocks are rare and the prevention cost is high. The right choice depends on how costly a deadlock would be.
Key point: Know all four (prevent, avoid, detect-recover, ignore) and that real OSes often 'ignore'. Admitting the ostrich approach is real shows practical understanding, not just textbook coverage.
The Banker's algorithm is a deadlock-avoidance method. Before granting a resource request, it checks whether doing so would leave the system in a safe state, meaning there's some order in which all processes can still The final step is the resources available. If the request keeps the system safe, it's granted; otherwise the process waits.
It needs each process to declare its maximum resource needs up front. It's called the banker's algorithm because it works like a bank deciding whether it can grant a loan while still being able to satisfy all customers. In practice it's rarely used in real systems because knowing maximum needs in advance is unrealistic, but it's a clean teaching model for avoidance.
Key point: Explain 'safe state' as the core idea, and admit it's mostly theoretical because you rarely know max needs ahead of time. That honesty indicates real understanding.
When memory is full and a new page must come in, the OS picks a page to evict. That choice is a page replacement algorithm, and it directly affects how often expensive page faults happen. Optimal (evict the page used furthest in the future) is the theoretical best but needs the future, so it's only a benchmark.
Practical ones: FIFO evicts the oldest page but can suffer Belady's anomaly (more frames causing more faults). LRU (Least Recently Used) evicts the page unused longest, which approximates optimal well but is costly to track exactly, so real systems use approximations like the clock (second-chance) algorithm. The goal is always to minimize faults for a given amount of RAM.
| Algorithm | Idea | Downside |
|---|---|---|
| Optimal | Evict page used furthest ahead | Needs the future; benchmark only |
| FIFO | Evict the oldest page | Belady's anomaly possible |
| LRU | Evict least recently used | Expensive to track exactly |
| Clock | Approximate LRU with a use bit | Slightly less accurate than true LRU |
Key point: Mention Belady's anomaly for FIFO and that real systems approximate LRU with clock. Those two facts show you've gone past the textbook list.
Thrashing is when the system spends most of its time swapping pages between RAM and disk instead of doing useful work. It happens when the combined working sets of active processes exceed physical memory, so every process constantly page-faults, evicting a page another process immediately needs back.
The fixes: reduce the degree of multiprogramming (run fewer processes so each has enough frames), use the working-set model to give each process the pages it actively needs, or simply add more RAM. The tell-tale sign is high paging activity with low CPU utilization, which counterintuitively means you should run fewer things, not more.
Key point: The counterintuitive point is the payoff: high paging plus low CPU means run fewer processes. Interviewers love that because the naive instinct is to add load.
The TLB (Translation Lookaside Buffer) is a small, fast cache inside the CPU that stores recent virtual-to-physical page translations. Without it, every memory access would need a page-table walk in RAM first, which for multi-level page tables means several extra memory accesses per real access.
On a TLB hit, translation is nearly free. On a miss, the hardware or OS walks the page table and caches the result. This is why the TLB matters so much: it makes virtual memory practical instead of ruinously slow. A context switch between processes usually flushes the TLB, which is part of why switching is expensive.
Key point: the TLB connects to context-switch cost (flushing it). That link ties two intermediate topics together and shows real understanding of why switches are slow.
Demand paging loads a page into memory only when it's actually accessed, not when the process starts. A process begins with almost nothing resident; the first access to each page triggers a page fault, and the OS loads that page from disk on demand.
The win is fast startup and low memory use, because you never load pages the process doesn't touch. The cost is that the first access to each page is slow (a page fault), so there's a warm-up penalty. It relies on locality: after the initial faults, most accesses hit pages already in memory.
Key point: Frame it as lazy loading with a warm-up cost. Saying pages load 'on the first access, via a page fault' is the precise mechanism the question needs.
Paging divides memory into fixed-size pages with no meaning attached: it's a mechanical mapping that eliminates external fragmentation. Segmentation divides memory into variable-size segments that match logical program units: a code segment, a data segment, a stack segment, each with its own size and protection.
Segmentation matches how programmers think and makes protection per logical unit easy, but it suffers external fragmentation because segments vary in size. Paging avoids that fragmentation but ignores program structure. Many real systems combine them: segmentation for logical division, paging underneath for physical allocation.
| Paging | Segmentation | |
|---|---|---|
| Block size | Fixed | Variable (logical units) |
| Fragmentation | Internal only | External |
| Maps to | Mechanical page/frame | Logical program parts |
Key point: Say paging is fixed and mechanical, segmentation is variable and logical. The note that real systems combine both is a nice senior touch.
Since processes have isolated memory, they need explicit channels to talk. The common ones: pipes (a one-way byte stream between related processes) and named pipes (FIFOs) for unrelated ones; message queues for structured messages; shared memory, where two processes map the same physical memory for the fastest communication; and sockets for talking across machines or between unrelated local processes.
The trade-off is speed versus ease and safety. Shared memory is fastest because there's no copying through the kernel, but it needs its own synchronization (a semaphore or mutex) to avoid races. Message passing (queues, pipes, sockets) copies data through the kernel, which is safer and simpler but slower.
Key point: Contrast shared memory (fast, needs your own sync) with message passing (safe, copies through the kernel). That trade-off is the point of the question.
User-level threads are managed by a library in user space; the kernel sees only the process, not the threads. They're fast to create and switch because no kernel call is involved, but if one thread makes a blocking system call, the whole process blocks, and they can't run in parallel on multiple cores.
Kernel-level threads are known to and scheduled by the OS. They can run truly in parallel on different cores and one blocking won't stall the others, but creating and switching them costs a system call. Most modern systems use kernel threads or a hybrid model that maps user threads onto kernel threads.
Key point: The key contrast is that one blocking user thread blocks the whole process, while kernel threads block independently and run in parallel. That's the follow-up interviewers push.
Priority inversion is when a high-priority task is blocked waiting for a resource held by a low-priority task, and a medium-priority task keeps preempting the low-priority one, so the low task never releases the lock and the high task is stuck indefinitely, effectively running at low priority.
The common fix is priority inheritance: while a low-priority task holds a lock a high-priority task wants, it temporarily inherits the high priority so it can't be preempted by medium tasks, finishes quickly, and releases the lock. This is a famous real bug, the Mars Pathfinder rover hit it and was fixed by enabling priority inheritance.
Key point: The priority inheritance as the fix and, if you can, the Mars Pathfinder story. It makes the answer memorable and shows you've read beyond the definition.
A spinlock has a waiting thread loop (spin) checking the lock repeatedly until it's free, burning CPU but avoiding the cost of sleeping and waking. A mutex puts a waiting thread to sleep and the OS wakes it when the lock frees, which frees the CPU but adds context-switch overhead.
The rule of thumb: use a spinlock when the lock is held very briefly and you're on a multiprocessor, because spinning a few cycles beats a context switch. Use a mutex when the wait might be long or you're on a single core, where spinning just wastes the CPU that the lock-holder needs to make progress.
Key point: Give the decision rule: spinlock for very short holds on multicore, mutex for longer waits. Spinning on a single core is a classic mistake to call out.
A zombie process has finished executing but its entry still sits in the process table because the parent hasn't read its exit status with wait(). It holds no memory or CPU, just a table slot, but too many zombies can exhaust the process table. The parent calling wait() reaps it and clears the entry.
An orphan process is a running process whose parent has terminated first. Orphans get adopted by the init process (PID 1), which periodically calls wait() to reap them when they finish, so orphans don't become permanent zombies. Zombie is 'child done, parent hasn't reaped'; orphan is 'parent gone, child still running'.
Key point: Keep the two straight: zombie means dead child not yet reaped, orphan means parent died first and init adopts it. Mixing them up is the common error.
Direct Memory Access (DMA) lets a device transfer data to or from main memory without routing every byte through the CPU. The CPU sets up the transfer (source, destination, size), then the DMA controller moves the data and interrupts the CPU only when it's done.
It matters because without DMA the CPU would have to copy every byte itself (programmed I/O), wasting cycles on a slow transfer it could spend on real work. With DMA, a large disk or network transfer runs in the background while the CPU does other things, which is what makes high-throughput I/O practical.
Key point: The point is freeing the CPU: DMA moves data in the background and interrupts only on completion. Contrast it with programmed I/O where the CPU copies every byte.
advanced rounds probe internals, trade-offs, and how OS concepts show up in real systems. Expect every answer here to draw a follow-up about failure modes and design decisions.
A monolithic kernel runs most OS services, file systems, drivers, networking, memory management, in kernel space as one large program. Calls between services are fast function calls, which is why Linux and most production kernels are monolithic. The downside is that a bug in any component can crash the whole kernel, and the code base is large.
A microkernel keeps only the bare minimum (scheduling, basic IPC, low-level memory) in the kernel and pushes services like drivers and file systems into user-space processes. That improves isolation and reliability, a driver crash doesn't take down the kernel, at the cost of performance, because services now talk via IPC message passing instead of direct calls. Most systems chose monolithic (or hybrid) for the speed.
| Monolithic | Microkernel | |
|---|---|---|
| Services location | In kernel space | In user space |
| Speed | Fast (direct calls) | Slower (IPC overhead) |
| Fault isolation | Weak: a bug can crash all | Strong: service crash is contained |
Key point: Frame it as the speed-versus-isolation trade-off, and note real kernels are monolithic or hybrid for performance. Reflexively praising microkernels misses why they lost in practice.
Copy-on-write (COW) is an optimization where multiple parties share the same physical memory as long as they only read it, and a private copy is made only when someone writes. The pages are marked read-only; a write triggers a fault, the OS copies just that page, and the writer gets its own version.
The classic use is fork(): a child process initially shares all the parent's pages instead of duplicating gigabytes of memory, and copies happen lazily only for pages that get modified. This makes fork cheap even for large processes. COW also appears in file systems (snapshots) and language runtimes. The win is avoiding copies you never actually need.
Key point: Use fork() as the anchor example: the child shares pages until it writes. That's the canonical use and shows you understand why fork is cheap.
fork() creates a new process by duplicating the caller: the child is a near-copy of the parent (sharing pages via copy-on-write) and both continue from the point of the fork, distinguished by fork's return value (0 in the child, the child's PID in the parent). At this point the child is running the same program as the parent.
exec() then replaces the child's memory image with a new program, so the child stops being a copy of the parent and starts running the target executable. The parent typically calls wait() to reap the child. This fork-then-exec split is how a shell launches every command: fork a child, exec the command in it, wait for it to finish.
pid_t pid = fork();
if (pid == 0) {
// child: replace image with a new program
execlp("ls", "ls", "-l", NULL);
} else {
wait(NULL); // parent reaps the child
}Key point: Explain why it's two calls: fork clones, exec replaces. The shell's fork-exec-wait loop is the example that proves you understand it.
A memory-mapped file (mmap) maps a file's contents directly into a process's address space, so reading and writing the file is just reading and writing memory, with the OS paging file contents in and out on demand. There's no explicit read/write system call per access.
They're useful for large files you access randomly (no need to load the whole thing), for sharing memory between processes (map the same file), and for loading executables and libraries. The trade-offs: page faults on first access, and error handling is trickier because an I/O error surfaces as a signal on memory access rather than a return code. For simple sequential reads, plain read() is often clearer.
Key point: The concrete wins: large random-access files, shared memory, and loading libraries. the harder error handling matters.
False sharing is a performance bug where two threads modify different variables that happen to sit on the same cache line. Even though they don't share data logically, the cache-coherence protocol treats the whole line as contended, so each write invalidates the other core's cached copy, causing constant cache-line bouncing between cores.
It shows up as code that scales poorly on multiple cores despite having no logical contention. You avoid it by padding or aligning hot per-thread variables so they land on separate cache lines (often 64 bytes apart), or restructuring data so each thread's frequently-written fields are isolated. It's a subtle bug because the code looks correct and even lock-free.
Key point: This is a strong production signal: the code is logically correct but scales badly. Naming cache-line padding as the fix shows you've profiled real multithreaded code.
A memory barrier (fence) is an instruction that constrains the ordering of memory operations around it. Modern CPUs and compilers reorder reads and writes for speed, which is fine for single-threaded code but can break assumptions in concurrent code, where one thread relies on seeing another's writes in a particular order.
Barriers force ordering: a write barrier ensures earlier writes are visible before later ones, a read barrier ensures earlier reads complete first. They're the foundation under locks and lock-free algorithms. Most developers get them implicitly through mutexes and atomics (which include the right barriers), but writing correct lock-free code means reasoning about them directly.
Key point: barriers connects to why locks and atomics work: they insert the right fences for you. Understanding that reordering is the enemy is the production insight here.
With polling, the CPU repeatedly checks a device's status register to see if it's ready, which wastes cycles busy-waiting but has low latency and is simple. With interrupts, the device signals the CPU when it's ready, so the CPU can do other work and respond only when needed, which is efficient but adds interrupt-handling overhead per event.
The choice depends on the device. For rare or slow events (a keypress, a disk finishing), interrupts win because polling would waste the CPU. For very high-rate events (some network cards under heavy load), interrupts can overwhelm the CPU with handling overhead, so hybrid approaches like interrupt coalescing or NAPI-style polling under load are used. It's a latency-versus-CPU-efficiency trade-off.
Key point: Note the high-rate reversal: under heavy load, polling can beat interrupts (interrupt storms). Knowing NAPI-style hybrids exist is what separates production-ready answers.
The working set of a process is the set of pages it's actively using in a recent time window. The model says if you keep each process's working set resident in memory, it'll page-fault rarely; if you can't fit the working sets of all active processes, thrashing follows.
It's the theory behind deciding the degree of multiprogramming: the OS estimates each process's working set and only admits as many processes as their combined working sets fit in RAM. It also guides page replacement, since pages outside the working set are the safe ones to evict. It formalizes the intuition that programs have a locality of reference that shifts over time.
Key point: the working set maps to controlling multiprogramming and preventing thrashing. Showing it's the theory behind an admission decision, not just a definition, indicates senior.
The Completely Fair Scheduler (CFS) aims to give each runnable task a fair share of CPU time. Instead of fixed time slices, it tracks each task's virtual runtime (how much CPU it's received, weighted by priority) and always runs the task with the smallest virtual runtime next, so tasks that have run less get the CPU sooner.
It keeps runnable tasks in a red-black tree ordered by virtual runtime, so picking the next task and reinserting are fast. Priority (nice values) scales how quickly virtual runtime accrues, so higher-priority tasks get more real CPU while the fairness model still holds. The design targets fairness plus good interactive responsiveness without the starvation risks of strict priority scheduling.
Key point: Explain virtual runtime as the core idea: run whoever has gotten the least CPU. Naming the red-black tree shows you know how it stays efficient, a common deep-dive.
A journaling file system writes intended changes to a journal (a log) before applying them to the main file system. If a crash happens mid-update, on reboot the system replays or discards the journal to bring the file system back to a consistent state, instead of scanning the entire disk to find and fix corruption.
The problem it solves is crash consistency: without journaling, a power loss during a multi-step update (say, allocating a block and updating the directory) can leave the file system inconsistent, and recovery meant a slow full-disk check. Journaling makes recovery fast and reliable. The trade-off is some write overhead; many file systems journal only metadata, not file data, to balance safety and speed.
Key point: Frame it as crash consistency without a full-disk scan. metadata-only journaling as the common compromise matters.
An inode is the data structure a Unix-style file system uses to represent a file. It stores the file's metadata, size, permissions, owner, timestamps, link count, and pointers to the disk blocks that hold the actual data, but not the file's name. The name lives in the directory entry, which maps a name to an inode number.
This separation is why hard links work: several directory entries can point to the same inode, so one file has multiple names, and the file's data is freed only when the link count drops to zero. It also explains why renaming a file is cheap (just update the directory entry) and why you can delete a file that's still open (the inode survives until the last reference closes).
Key point: The name-is-not-in-the-inode point is the payoff: it explains hard links, cheap renames, and deleting open files. That's what the question is really probing.
Power on runs firmware (BIOS or UEFI) from ROM, which initializes hardware and runs a power-on self-test, then hands off to a bootloader from a known location on disk. The bootloader (GRUB, systemd-boot) loads the kernel image and an initial RAM disk into memory and jumps to the kernel's entry point.
The kernel initializes core subsystems (memory management, scheduler, device drivers), mounts the root file system, and starts the first user-space process (init or systemd, PID 1). That process then brings up the rest of user space: system services, networking, and eventually a login prompt or desktop. Each stage hands control to a more capable, higher-level component.
Key point: Get the handoff chain right: firmware to bootloader to kernel to init. Interviewers check whether you understand each stage enables the next, not just the names.
The readers-writers problem is a synchronization scenario where a shared resource is read by many threads and written by some. Multiple readers can safely read at once, but a writer needs exclusive access, no other reader or writer. The challenge is allowing concurrent reads while serializing writes, without corruption.
A naive lock that blocks all concurrent access is correct but wastes the parallelism reads could have. Solutions use a reader-writer lock that permits many readers or one writer. The subtlety is fairness: a reader-preference solution can starve writers if readers keep arriving, and a writer-preference one can starve readers, so real implementations balance the two.
Key point: The starvation angle is the senior part: reader-preference starves writers and vice versa. Naming a reader-writer lock plus the fairness trade-off is the full answer.
NUMA (Non-Uniform Memory Access) describes multi-socket systems where each CPU has its own local memory, and accessing another CPU's memory (remote) is slower than accessing local memory. On a single-socket machine memory access is uniform, but scale to multiple sockets and location starts to matter a lot.
It matters because a thread running on one node accessing memory on another pays a latency penalty, so performance-sensitive systems try to keep a thread's memory allocations on the same node it runs on (NUMA affinity). The OS scheduler and memory allocator are NUMA-aware, and databases and high-throughput services often pin threads and memory to nodes deliberately. Ignoring NUMA on a big multi-socket box can quietly halve throughput.
Key point: The practical point is affinity: keep a thread's memory on its own node. Knowing NUMA can quietly halve throughput on multi-socket boxes signals real large-system experience.
Clarify first: what workload (CPU-bound or I/O-bound), expected task rate, and latency goals, because that sets the pool size. Then the design: a fixed set of worker threads pull tasks from a shared, thread-safe queue. Producers submit tasks to the queue; idle workers block on a condition variable until a task arrives, then wake, run it, and loop back. This reuses threads instead of paying thread-creation cost per task.
The OS concepts underneath: a mutex protects the queue and a condition variable avoids busy-waiting (workers sleep until signaled). Pool size ties to the machine, roughly the core count for CPU-bound work (more threads just add context-switch overhead), and higher for I/O-bound work since threads block on I/O and free the CPU. You also need clean shutdown (a sentinel or flag so workers exit) and backpressure when the queue fills. The structure that scores is clarify, queue plus workers, The sync primitives, size by workload, handle shutdown.
# a worker loop: block until work, then run it
def worker(queue, cond):
while True:
with cond:
while queue.empty() and not shutting_down:
cond.wait() # sleep, no busy-wait
if shutting_down:
return
task = queue.pop()
task() # run outside the lockKey point: Open by asking CPU-bound vs I/O-bound, because it decides the pool size. Naming mutex plus condition variable and 'core count for CPU-bound' is what The technical detail is.
A process is a running program with its own isolated memory; a thread is a unit of execution inside a process that shares that memory with its siblings. Threads are cheaper to create and switch between and can pass data by sharing memory directly, but that shared memory is exactly why they can corrupt each other's data and need locks. Processes isolate hard, so a crash in one doesn't take down another, at the cost of slower creation and needing inter-process communication to talk. Knowing where each fits, and saying the trade-off out loud, is itself an interview signal.
| Aspect | Process | Thread |
|---|---|---|
| Memory | Own isolated address space | Shares the parent process's memory |
| Isolation | Strong: one crash won't hit another | Weak: a bad thread can corrupt shared state |
| Creation cost | Heavier (new address space) | Lighter (shares existing space) |
| Communication | IPC: pipes, sockets, shared memory | Direct shared memory, guarded by locks |
| Best at | Isolation, fault tolerance | Parallel work over shared data |
Prepare in layers, and the why out loud is the explanation path. Most operating systems rounds move from definition questions to a reasoning question (walk me through a deadlock) to a small coding or scenario task, so rehearse each stage rather than only reading answers.
The typical operating systems interview flow
Earlier rounds increasingly run as AI-driven technical interviews. The concepts on this page are what gets probed at every stage.
10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.
Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Operating Systems questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.
See how the AI Coding Interviewer works