Company questionsGroundworkOperating systems

Fundamentals · Operating systems

Operating systems: the questions candidates report

  • 19 questions
  • 5 themes
  • 13 companies
  • 16 with candidate reports

Operating systems questions reported in interviews at product and fintech companies, grouped by theme. Try each aloud first, then open the model answer and its usual follow-up.

Processes and threads

  1. How does a process differ from a thread in memory, switching cost and communication?CoreSeveral reports

    A process is a running program with its own address space, file descriptor table and resources. A thread is a unit of execution inside a process: threads of one process share its code, heap and open files, but each has its own stack, registers and program counter.

    • Memory: processes are isolated by the MMU; threads can read and write each other's data, so they need locks.
    • Cost: creating and switching threads is cheaper because there is no address space switch, so page tables and TLB entries stay useful.
    • Communication: threads use shared variables; processes need IPC such as pipes, sockets or shared memory.
    • Failure: a wild pointer in one thread can take down the whole process, while a crashed process leaves others alone.

    Example: a browser runs tabs as separate processes for isolation, and each tab uses threads for its work. Follow-up: what is saved on a thread switch, and how user-level threads differ from kernel threads.

  2. What is the difference between concurrency and parallelism, and between multithreading and multiprocessing?CoreSeveral reports

    Concurrency means structuring a program as several tasks that are in progress at overlapping times; on one core it is done by interleaving. Parallelism means tasks literally executing at the same instant on several cores. You can have either without the other.

    • Multithreading: threads share one address space, so sharing data is cheap, but it needs synchronisation and one bug can corrupt everything. In standard CPython builds the global interpreter lock stops threads from running Python bytecode in parallel.
    • Multiprocessing: separate address spaces give isolation, fault containment and true parallelism even under such a lock, but processes are heavier and data crosses through IPC.

    Rule of thumb: I/O-bound work benefits from concurrency (threads or async I/O), CPU-bound work needs parallelism across cores. Follow-up: Amdahl's law, which caps the speedup by the fraction of work that stays serial.

  3. Given code with several fork calls, how do you work out how many processes exist and what it prints?CoreOne report

    fork() creates a child that is a copy of the caller. It returns 0 in the child, the child's pid in the parent and -1 on failure, and both processes continue from the next statement. Modern systems copy memory lazily with copy-on-write.

    Counting rule: every existing process executes each fork(), so n unconditional calls in sequence give 2^n processes in total, including the original, and 2^n - 1 new ones. Example: fork(); fork(); printf("x"); prints x four times. A loop that forks three times also ends with 8 processes.

    Conditions change the count. In if (fork() == 0) fork(); only the child forks again, giving three processes. Output order between processes is not deterministic, and a buffered stdout copied into the child can make text appear twice when no newline flushed it first.

    Follow-up: how wait, exec and zombie processes fit in.

CPU scheduling

  1. Compare FCFS, SJF, round robin and priority scheduling, and say which suits which workload.CoreSeveral reports

    A CPU scheduler picks which ready process runs next. The goals pull against each other: throughput, turnaround time, waiting time, response time and fairness.

    • FCFS: a plain queue, non-preemptive. One long job delays everything behind it (the convoy effect).
    • SJF: running the shortest next burst first minimises average waiting time when burst lengths are known, but they must be predicted and long jobs can starve. The preemptive form is shortest remaining time first.
    • Round robin: each process gets a time quantum, then goes to the back of the queue, which gives good response time. A tiny quantum wastes time on context switches; a huge one degrades into FCFS.
    • Priority: the highest priority runs first, and low priorities can starve unless aging raises them over time.

    Interactive systems favour round robin or multilevel feedback queues; batch systems favour SJF-like policies. Follow-up: implement round robin with a queue, or explain how Linux's CFS picks the task with the smallest virtual runtime from a red-black tree (kernel 6.6 and later moved to its successor, EEVDF).

  2. What happens in a context switch, and how does the dispatcher differ from the scheduler?CorePrep guides

    A context switch saves the state of the running process (registers, program counter, stack pointer and memory-management information) into its process control block and loads the saved state of the next one. It is pure overhead: no useful work runs during it, and the new process starts with colder caches and TLB entries. A process switch costs more than a switch between threads of one process because the address space changes.

    The scheduler makes the policy decision: which ready process runs next. The dispatcher carries it out. It performs the context switch, switches the CPU to user mode and jumps to the right instruction in the new process. The time this takes is the dispatch latency.

    Switches happen when a time slice expires, when a process blocks on I/O, on an interrupt, or when a higher priority process becomes ready. Follow-up: why a very small quantum in round robin hurts throughput.

Memory management

  1. What is virtual memory, and how does paging map a virtual address to a physical one?CoreSeveral reports

    Virtual memory gives each process its own large address space that looks contiguous, and the OS and MMU map it onto physical RAM with disk as backing store. It matters for isolation, for running programs larger than RAM, and for allocating memory lazily.

    With paging, virtual memory is split into fixed-size pages (commonly 4 KB) and RAM into frames of the same size. A virtual address is a page number plus an offset. The page table maps the page number to a frame number and holds valid, dirty and protection bits. The MMU translates every access and caches recent translations in the TLB. If the page is not present, the access raises a page fault and the OS loads it.

    Example: with 4 KB pages the low 12 bits of the address are the offset and the remaining bits select the page. Large address spaces use multi-level page tables so no huge flat table is needed. Follow-up: how a page fault is handled.

  2. How are page faults handled, which page replacement policies exist, and what is thrashing?CoreSeveral reports

    A page fault occurs when a process touches a page that is not mapped to a frame. The CPU traps, the OS checks the access is legal, finds a free frame or evicts a victim, reads the page from disk, updates the page table and restarts the instruction. A disk read takes milliseconds against nanoseconds for RAM, so the fault rate dominates performance.

    • FIFO: evict the oldest page. Simple, but it can show Belady's anomaly, where more frames cause more faults.
    • Optimal: evict the page whose next use is furthest away. It is a benchmark and cannot be implemented.
    • LRU: evict the least recently used page. It is close to optimal but costly to track exactly, so systems approximate it with the clock algorithm and a reference bit.

    Thrashing is when processes lack enough frames for their working sets, so the system spends most of its time faulting and CPU utilisation collapses. Fixes: working-set or fault-frequency control, running fewer processes, more RAM. Follow-up: implement an LRU cache with a hash map and a doubly linked list.

  3. What is the difference between stack and heap memory, and what happens on a stack overflow?CoreSeveral reports

    The stack holds a thread's call frames: return addresses, arguments and local variables. Allocation is a pointer move and memory is released when the function returns, so it is fast and cache friendly, but each thread's stack is small and fixed in size, often 1 to 8 MB. The heap is a shared region for objects whose size or lifetime is not known at compile time. An allocator such as malloc or a garbage collector manages it, so allocation is slower, fragmentation can occur and objects live until freed.

    Example: a local int x lives on the stack; the buffer returned by malloc lives on the heap and only its pointer is on the stack.

    A stack overflow happens when the stack grows past its limit, usually through unbounded or very deep recursion. In C and C++ on Linux it hits a guard page and the process receives SIGSEGV; Java throws StackOverflowError instead. Fix it by bounding depth or by turning recursion into iteration with an explicit stack on the heap. Follow-up: what a memory leak is.

  4. What are the CPU cache and the TLB, and what is each used for?CoreOne report

    A CPU cache is small, fast memory (L1, L2, L3) between the CPU and RAM that keeps recently used data. It relies on temporal locality (data is reused soon) and spatial locality (neighbouring addresses are used soon). Data moves in cache lines, typically 64 bytes. A hit costs a few cycles, while a miss goes to RAM and typically costs on the order of a hundred cycles.

    The TLB (translation lookaside buffer) is a small cache in the MMU that holds recent virtual-to-physical page translations. Without it every memory access would first need extra reads to walk the page table. On a hit the translation is almost free; on a miss the hardware or the OS walks the page table and fills an entry. On a process context switch the TLB is flushed or its entries are tagged with address-space IDs.

    Effective access time = hit rate x hit time + miss rate x miss time. Follow-up: designing an LRU cache in software, which uses the same eviction idea.

  5. What are internal and external fragmentation, and how do allocation strategies and paging deal with them?CorePrep guides

    Fragmentation is memory that is free but cannot be used.

    • Internal: an allocated block is larger than the request and the rest is wasted inside it. Fixed-size partitions and the last page of a paged process cause it.
    • External: enough memory is free in total but scattered in holes that are each too small. Contiguous allocation, segmentation and general heap allocators cause it.

    Contiguous allocation chooses a hole by strategy: first fit takes the first hole that is big enough and is fast, best fit takes the smallest hole that fits and leaves tiny unusable slivers, and worst fit takes the largest hole and usually performs poorly.

    External fragmentation is cured by compaction, which moves blocks and is costly, or by paging, where fixed-size frames mean any free frame fits, at the price of a little internal waste. Allocators such as malloc coalesce adjacent free blocks and use size classes; the buddy system splits blocks into powers of two. Follow-up: paging versus segmentation.

Synchronisation and deadlock

  1. What is a deadlock, which four conditions cause it, and how do you prevent, avoid or detect it?CoreSeveral reports

    A deadlock is a state in which every process in a set holds a resource and waits for one held by another process in the set, so none can ever proceed. The stuck work never recovers on its own, and the rest of the system slowly fills with blocked threads.

    Four conditions must hold together: mutual exclusion, hold and wait, no preemption and circular wait. Removing any one prevents deadlock.

    • Prevention: negate a condition. The practical choice is acquiring locks in one global order, which removes circular wait.
    • Avoidance: grant a request only if the resulting state is safe (Banker's algorithm).
    • Detection and recovery: build a wait-for graph with an edge from each process to the one holding what it wants. A cycle means deadlock when each resource has a single instance. Recover by killing a victim or rolling back.

    Example: thread A holds lock 1 and wants lock 2 while thread B holds lock 2 and wants lock 1. Follow-up: write cycle detection on a directed graph with DFS.

  2. What is the difference between a semaphore and a mutex, and how do they provide mutual exclusion?CoreSeveral reports

    A mutex is a lock with ownership: only the thread that locked it should unlock it, and it guards a critical section so one thread at a time runs it. A semaphore is an integer counter with two atomic operations: wait (P) decrements and blocks if the value would go below zero, and signal (V) increments and wakes a waiter. Atomicity comes from hardware support such as test-and-set or compare-and-swap, or from disabling interrupts inside the kernel.

    • A binary semaphore looks like a mutex but has no owner, so any thread can signal it. That suits signalling between threads, not protecting data.
    • A counting semaphore limits access to N identical resources, such as a pool of 10 connections.

    Example: start a semaphore at 3 so at most three workers use a resource at once. Follow-up: priority inversion, which mutexes can address with priority inheritance, and why forgetting to release causes a deadlock.

  3. What is a race condition, what is a critical section, and how do you make code thread safe?CoreSeveral reports

    A race condition is a bug where the result depends on the unpredictable interleaving of threads that touch shared state. The code touching that state is the critical section and must run with mutual exclusion. The classic case is counter++, which is really read, add, write: two threads can both read 5 and both write 6, losing an update.

    Ways to make code thread safe:

    • Lock the critical section with a mutex, keep it short, and take several locks in one fixed order.
    • Use atomic operations or compare-and-swap for simple counters and flags.
    • Avoid sharing: thread-local data, immutable objects or message passing.

    Typical coding follow-ups are a thread-safe singleton (initialise under a lock or with a once primitive), a hash map guarded by a mutex, and printing 1 to 100 from three threads in turn with condition variables. Follow-up: what a read-write lock buys you and why one coarse lock limits throughput.

  4. How does the Banker's algorithm work, and what are starvation and aging?DeepSeveral reports

    The Banker's algorithm is deadlock avoidance. Each process declares its maximum need in advance. The OS keeps Available, Max, Allocation and Need = Max - Allocation. On a request it checks the request is within Need and Available, pretends to grant it, and runs the safety check: find a process whose Need fits in Work (initially Available), assume it finishes and returns its allocation to Work, and repeat. If every process can finish in some order, the state is safe and the request is granted; otherwise the process waits.

    A safe state cannot deadlock; an unsafe state might. The limits are that maximum claims must be known up front, the set of processes must be fixed, and the check costs O(m n^2) for m resource types and n processes, so general-purpose systems rarely use it.

    Starvation is a process waiting indefinitely while the system makes progress, for example low priority jobs behind a steady stream of high priority ones. Aging fixes it by raising a waiting process's priority over time.

  5. How do you solve the producer-consumer problem, and how does it extend to many producers and consumers?DeepOne report

    Producers add items to a bounded buffer and consumers remove them. A producer must wait when the buffer is full, a consumer must wait when it is empty, and buffer access must be mutually exclusive.

    The classic solution uses three semaphores: empty starts at the buffer size N, full at 0 and mutex at 1. Producer: wait(empty), wait(mutex), insert, signal(mutex), signal(full). Consumer: wait(full), wait(mutex), remove, signal(mutex), signal(empty).

    The order matters. If a producer took the mutex before waiting on empty while the buffer is full, it would sleep holding the lock, no consumer could enter, and the system would deadlock.

    The same code works for many producers and consumers, because the mutex serialises buffer access and the counting semaphores track free and filled slots. In practice use a blocking queue, or a mutex with two condition variables. Follow-up: shutdown handling, and the readers-writers and dining philosophers problems.

Kernel, IPC and boot

  1. What separates kernel space from user space, how does a system call work, and how do monolithic and microkernels differ?CoreSeveral reports

    The kernel runs in privileged mode (ring 0 on x86) with full access to hardware and memory. User programs run in unprivileged mode in their own address spaces, so a buggy application cannot corrupt the kernel or other programs. A system call is the controlled gateway: the program puts the call number and arguments in registers and executes a trap instruction. The CPU switches to kernel mode, runs the handler and returns to user mode. Examples are read, write, fork and mmap.

    • Monolithic kernel (Linux): file systems, drivers and networking all run in kernel space. It is fast because calls are plain function calls, but a faulty driver can crash the system.
    • Microkernel (Minix, QNX): typically only scheduling, basic memory management and IPC stay in the kernel, and other services run as user processes. It is more robust and modular but slower because of message passing.

    Follow-up: why a mode switch costs more than an ordinary function call.

  2. How do processes communicate: shared memory versus message passing, and how do pipes work?CoreSeveral reports

    Processes have separate address spaces, so the OS provides channels for them to exchange data. The two models are shared memory and message passing.

    • Shared memory: the kernel maps one region into both processes. After setup it is the fastest option because there is no copy or system call per transfer, but the processes must synchronise themselves with semaphores or mutexes, otherwise races occur.
    • Message passing: data goes through the kernel with send and receive. It is simpler and safer and also works across machines, but pays a copy and a system call per message.

    Concrete mechanisms: an anonymous pipe is a one-way byte stream between related processes (ls | grep x), a named pipe (FIFO) connects unrelated ones, and there are also message queues, Unix domain sockets, network sockets and signals.

    Follow-up: choose shared memory for large, frequent transfers on one machine and message passing for loose coupling or distribution.

  3. What are signals, and what exactly happens when you run kill -9 on a process?DeepOne report

    A signal is a small asynchronous notification the kernel delivers to a process for events such as Ctrl+C (SIGINT), a termination request (SIGTERM), a bad memory access (SIGSEGV) or a child exiting (SIGCHLD). A process can handle most signals with a handler, ignore them or take the default action.

    kill -9 sends SIGKILL, which cannot be caught, blocked or ignored. The kernel ends the process without running its handlers or cleanup, so user-space buffers are not flushed and temporary files and lock files stay behind. The kernel still frees its memory, closes its file descriptors and passes its children to init. The parent gets SIGCHLD and must reap the exit status; until then the dead process is a zombie. A process in uninterruptible sleep (state D, for example blocked on a dead network mount) may not die until the I/O returns.

    Send SIGTERM first so the program can shut down cleanly, and SIGKILL as a last resort. SIGSTOP also cannot be caught. Follow-up: zombie versus orphan processes.

  4. Walk through the OS boot process and explain what a device driver does.CorePrep guides

    On power-on the CPU starts executing firmware (BIOS or UEFI) from a fixed address. The firmware runs a power-on self test, initialises basic hardware and finds a boot device. It loads the bootloader (for example GRUB), which loads the kernel image and often an initial RAM disk into memory and jumps to the kernel. The kernel initialises memory management, interrupts, the scheduler and drivers, mounts the root file system and starts the first user process (on Linux, init or systemd as PID 1), which brings up services and the login prompt.

    A device driver is kernel code, often a loadable module, that knows one device's registers and protocol and exposes it through a uniform interface such as read and write on a device file. The rest of the OS and applications then do not depend on the hardware details. On a monolithic kernel drivers run in kernel mode, so a bug can crash the system. Follow-up: what an interrupt handler does, and the top half and bottom half split.

Keep going

Companies do not publish their questions and rotate them. These come from public candidate write-ups and prep guides, so treat the labels as one person's account or one list's tag. The answers are written for this site.

Now make it stick