Free lesson · Operating Systems
Processes Vs Threads
The Comparison Every Interviewer Expects You to Know
If there is one comparison table in this entire course that you should be able to recite from memory, it is the process-versus-thread comparison. This question appears in system design rounds ("why would you use threads vs processes here?"), concurrency rounds ("what is the difference between a process and a thread?"), and fundamentals screens ("compare processes and threads"). It is the single most frequently asked OS question in FAANG interviews.
This topic gives you the complete comparison - not just the definitions, but the tradeoffs that drive real engineering decisions. By the end, you should be able to explain not just how processes and threads differ, but when you would choose one over the other and why.
The Side-by-Side Comparison
Here is the comprehensive comparison table. We will walk through each row in detail below.
| Property | Process | Thread |
|---|---|---|
| Definition | Independent execution unit with own address space | Execution unit within a process, shares address space |
| Memory | Own virtual address space (isolated) | Shares process's address space (heap, globals, code) |
| Creation cost | Heavy - new page tables, PCB, address space setup | Light - new stack, TCB, shared everything else |
| Creation time | ~50-200 microseconds (with COW) | ~10-50 microseconds |
| Context switch cost | Expensive - TLB flush, page table swap, cache cold | Cheaper - no TLB flush (same address space), cache warmer |
| Communication | Requires IPC (pipes, sockets, shared memory) | Direct memory access (shared heap and globals) |
| Communication speed | Slow - involves system calls and kernel overhead | Fast - just a memory read/write (but needs synchronization) |
| Isolation | Strong - one process cannot access another's memory | Weak - threads share memory, one can corrupt another's data |
| Failure impact | Contained - one process crash does not affect others | Cascading - one thread crash kills the entire process |
| Security | Better - OS enforces address space boundaries | Worse - a compromised thread has access to all process memory |
| Scalability | Higher overhead per unit | Lower overhead per unit |
| Debugging | Easier - deterministic behavior, isolated state | Harder - race conditions, non-deterministic bugs |
Memory: The Fundamental Difference
The memory model is what everything else follows from. A process has its own virtual address space. A thread shares its process's virtual address space with all other threads in the same process.
When the OS creates a new process (via fork()), it sets up a new page table that maps virtual addresses to physical addresses. Even with copy-on-write, the page table itself is a new data structure. The new process's virtual address space is separate from the parent's - they cannot see each other's memory unless they explicitly set up shared memory.
When the OS creates a new thread (via pthread_create() or clone() with CLONE_VM), it does not create a new page table. The new thread uses the same page table as the existing threads in the process. It can read and write the same heap memory, the same global variables, and the same code. The only new memory allocated is the thread's stack - typically 1-8 MB depending on the system.
This difference in memory model drives every other difference in the table. Creation is cheaper because there is less to set up. Context switching is cheaper because there is no page table to swap. Communication is faster because there is no boundary to cross. But isolation is weaker because there is no boundary to protect.
graph TD
subgraph PROC_MODEL["Process Model"]
PA["Process A<br/>────<br/>Address Space A<br/>Page Table A"] ---|"IPC<br/>(system call)"| PB["Process B<br/>────<br/>Address Space B<br/>Page Table B"]
end
subgraph THREAD_MODEL["Thread Model"]
subgraph SP["Single Process"]
TA["Thread A<br/>────<br/>Stack A"]
TB["Thread B<br/>────<br/>Stack B"]
SHARED_MEM["Shared:<br/>Heap, Globals, Code<br/>One Page Table"]
end
TA ---|"Direct memory<br/>access<br/>(no system call)"| TB
end
This diagram contrasts the process model with the thread model. In the process model, Process A and Process B have separate address spaces and page tables, and they communicate through IPC which requires a system call. In the thread model, Thread A and Thread B live in a single process with one shared address space. They communicate by reading and writing shared memory directly, without any system call. Each thread has its own stack but everything else is shared.
Creation Cost: Why Threads Are Faster to Spawn
Creating a process requires the OS to allocate a new PID, create a new PCB (the task_struct on Linux, which is several kilobytes), set up new page tables (copy-on-write pages from the parent), duplicate the file descriptor table (each entry is copied, though the underlying file objects are shared), set up signal handlers, and register the process in the scheduler.
Creating a thread requires the OS to allocate a new thread ID, create a new TCB (Thread Control Block - much smaller than a PCB), allocate a stack (typically 1-8 MB), and register the thread in the scheduler.
The difference is not enormous in absolute terms - both happen in microseconds - but it compounds when you are creating thousands of execution units. A web server that creates a new thread for each of 10,000 concurrent connections pays much less overhead than one that creates a new process for each connection. This is one reason the thread-per-connection model (MySQL, early Apache) was popular for servers with moderate concurrency.
Context Switch Cost: The TLB Is the Key
When the OS switches between two processes, it must swap the page table pointer (the CR3 register on x86) because each process has a different address space. Changing the page table invalidates the Translation Lookaside Buffer (TLB) - the CPU's cache of virtual-to-physical address translations. After a process switch, every memory access requires a page table walk until the TLB is repopulated. This is called a TLB flush, and it is one of the most expensive parts of a process context switch.
When the OS switches between two threads within the same process, it does not need to change the page table. The threads share the same address space, so the same page table is valid for both. The TLB remains populated. The CPU caches are more likely to contain useful data because both threads access the same heap and global memory. The only things that change are the register values, program counter, and stack pointer.
This makes thread context switches significantly cheaper than process context switches - typically 2-5x faster. For systems that switch frequently (high-concurrency servers, interactive applications), this difference is meaningful.
Interview: When the interviewer asks "why are thread context switches faster than process context switches?":
"Thread switches are faster because threads share the same address space. A process switch requires changing the page table pointer, which invalidates the TLB - the CPU's cache of virtual-to-physical address translations. After a process switch, every memory access incurs a TLB miss until the cache is repopulated. A thread switch within the same process keeps the same page table and TLB, so the cost is limited to saving and restoring registers and the stack pointer."
This answer demonstrates understanding of the hardware mechanism (TLB), not just the high-level concept.
Communication: Fast but Dangerous
Processes communicate through Inter-Process Communication (IPC) mechanisms - pipes, message queues, shared memory segments, sockets. All of these involve system calls. Even the fastest IPC mechanism (POSIX shared memory) requires initial setup via system calls and careful synchronization with semaphores or mutexes.
Threads communicate by reading and writing shared variables in the heap or global memory. No system call is needed. One thread writes a value to a shared variable; another thread reads it. The cost is a memory access - nanoseconds, not microseconds.
But this speed comes with a price. Because threads can access shared memory at any time without coordination, concurrent access to shared data without proper synchronization leads to race conditions. A process model forces you to use explicit communication channels (pipes, sockets), which are inherently sequential - data flows through the channel in order. A thread model gives you the responsibility to ensure ordering and safety yourself using mutexes, semaphores, and atomic operations.
Isolation and Failure Impact: The Safety Argument
This is where the tradeoff becomes most stark.
Processes are isolated. If Process A has a bug that causes a segmentation fault, the OS terminates Process A. Process B continues running, completely unaffected. The OS's virtual memory system guarantees that Process A cannot corrupt Process B's memory, because they have separate address spaces.
Threads are not isolated. If Thread A has a bug that causes a segmentation fault, the OS terminates the entire process - every thread dies, including Thread B, Thread C, and Thread D. A memory corruption bug in one thread can silently corrupt data that other threads depend on. A deadlock in one thread can freeze the entire process.
This is why Chrome uses a multi-process architecture for browser tabs. In early single-process browsers, a crash in one tab (usually caused by a buggy plugin or a malicious website) would crash the entire browser. Chrome gave each tab its own process, so a crash is isolated to one tab. The cost is higher memory usage (each process has its own address space), but the benefit is stability. Firefox eventually followed suit with its Project Fission, adopting a similar multi-process architecture after years of suffering from single-process instability.
Debugging: Processes Are Easier
Multi-threaded programs are notoriously difficult to debug. The bugs that threads introduce - race conditions, deadlocks, data corruption from unsynchronized access - are non-deterministic. They depend on the exact timing of thread execution, which varies from run to run. A race condition might manifest once in a thousand runs, making it nearly impossible to reproduce consistently. You might run your test suite a hundred times with all tests passing, and then in production, the exact right interleaving of threads triggers a bug that corrupts data.
Multi-process programs are easier to debug because each process has its own isolated state. A bug in Process A cannot silently corrupt Process B's data. If something goes wrong, you know which process is responsible, and you can examine that process's state independently. The communication between processes is explicit (through pipes, sockets, or message queues), which means the data flow is visible and traceable. With threads, a shared variable can be modified by any thread at any time, making it much harder to trace where a bad value came from.
This debugging difficulty is a real cost that engineering teams factor into architecture decisions. Companies building safety-critical systems (banking, healthcare, avionics) often prefer multi-process architectures specifically because the deterministic behavior makes verification and debugging tractable. The performance cost of IPC is acceptable when correctness is the primary concern.
Security Implications
Security is another dimension where the process model wins. A process has a well-defined security boundary - the address space. If an attacker compromises one process (through a buffer overflow, for example), they gain access to that process's memory but not to any other process's memory. The OS's virtual memory protection ensures this boundary.
With threads, a compromised thread has access to the entire process's memory - every thread's data, every shared variable, every open file descriptor. If an attacker gains code execution in one thread, they effectively own the entire process. This is why security-sensitive applications (like web browsers handling untrusted JavaScript, or container runtimes isolating tenant workloads) use process isolation rather than thread isolation for their most critical security boundaries.
graph TD
subgraph PROC_FAIL["Process Failure: Contained"]
PF1["Process A<br/>(crashes)"]
PF2["Process B<br/>(unaffected ✓)"]
PF3["Process C<br/>(unaffected ✓)"]
PF1 -->|"crash does NOT<br/>propagate"| PF2
PF1 -->|"crash does NOT<br/>propagate"| PF3
end
subgraph THREAD_FAIL["Thread Failure: Cascading"]
subgraph DIES["Entire Process Dies ✗"]
TF1["Thread A<br/>(crashes)"]
TF2["Thread B<br/>(killed ✗)"]
TF3["Thread C<br/>(killed ✗)"]
end
TF1 -->|"crash kills<br/>the process"| TF2
TF1 -->|"crash kills<br/>the process"| TF3
end
This diagram shows the difference in failure behavior. On the left, in a multi-process model, when Process A crashes, Process B and Process C continue running unaffected - the crash is contained. On the right, in a multi-threaded model, when Thread A crashes, the entire process dies, killing Thread B and Thread C along with it - the failure cascades.
When to Use Processes vs Threads
The choice between processes and threads is one of the most important architectural decisions in system design. Here are the guidelines:
Use processes when isolation matters more than performance. When running untrusted code (browser tabs, sandboxed plugins). When a failure in one unit should not affect others (microservices, worker pools for unreliable tasks). When you need strong security boundaries between units.
Use threads when performance matters and you can manage the complexity. When units need to share large amounts of data frequently. When creation and switching speed matter (high-concurrency servers with many short-lived tasks). When you need shared access to a common resource like a connection pool or in-memory cache.
Use both when you need the benefits of each at different levels. Many production systems combine both: multiple processes for isolation at the service level, multiple threads within each process for concurrency at the request level. A microservices architecture where each service is a separate process, and each service uses a thread pool to handle concurrent requests, is using both models simultaneously.
Tip: The interview answer pattern for "when would you use threads vs processes?":
- State the tradeoff: "Threads are lighter and share memory, making them faster for creation, switching, and communication. Processes are heavier but provide stronger isolation - a crash in one does not affect others."
- Give the process use case: "I would use processes when I need isolation - like running untrusted code, or when one unit's crash should not bring down others."
- Give the thread use case: "I would use threads when units need to share data efficiently and I can manage the synchronization complexity - like a thread pool in a web server."
- Give the combined use case: "In practice, many systems use both - processes for service-level isolation and threads for request-level concurrency within each service."
Exercise: Starter: For each of the following applications, decide whether you would use processes, threads, or both, and explain why: (a) a web server handling 1,000 concurrent requests, (b) a code editor with spell-checking running in the background, (c) a system that runs user-submitted code safely, (d) a video encoding application processing multiple files.
Intermediate: Research how PostgreSQL and MySQL handle concurrent connections differently (process-per-connection vs thread-per-connection). What are the tradeoffs? Which approach uses more memory? Which is more resilient to crashes? Which performs better under high concurrency?
Advanced: Consider a system design scenario: you are building a real-time chat application that needs to handle 100,000 concurrent connections on a single machine. Would you use a thread-per-connection model, a process-per-connection model, or something else entirely? What are the OS-level constraints that limit your choice? (Hint: think about the maximum number of threads, memory per thread stack, and context switch overhead.)