Processes vs threads
In one line
A process is a running program with its own private memory; threads are separate lines of execution inside one process that share that memory.
What it is
A process is an instance of a running program. The operating system gives each process its own virtual address space (code, data, heap and stack), its own open files and its own security context. One process cannot read another's memory unless they set up shared memory or talk through pipes, sockets or files.
A thread is the unit the CPU actually runs. Every process has at least one thread. Threads in the same process share the code, heap, global variables and open files, but each thread has its own stack, registers and program counter. That makes threads cheap to create and quick to communicate, but it also means one thread can corrupt data another thread is using.
Why interviewers ask
This is the foundation of every concurrency question. Interviewers check that you know what is shared and what is private, why threads are lighter but riskier, and how real systems choose: Chrome isolates sites in separate processes for safety, while a web server uses threads or an event loop for cheap concurrency. It also leads into follow-ups on context switching, the Python GIL and race conditions.
How it works
- 1Creating a process: the OS allocates a process control block (PCB) holding the process ID, state, registers, memory map and open files, then sets up a fresh address space. On Unix, fork() copies the parent and exec() loads a new program; copy-on-write means pages are only copied when one side writes.
- 2Creating a thread: the OS (or runtime) allocates a new stack and a thread control block with its own registers and program counter, inside the existing address space. No memory map has to be built, so it is much cheaper.
- 3Each thread or process moves between states: new, ready (waiting for a CPU), running, blocked (waiting for I/O or a lock) and terminated.
- 4A context switch saves the registers of the current thread and loads those of the next. Switching between threads of the same process keeps the same address space; switching between processes also changes the page table, which makes cached address translations (the TLB) less useful, so it costs more.
- 5Processes communicate via inter-process communication (IPC): pipes, sockets, message queues or shared memory. Threads communicate by reading and writing shared variables, which needs locks or other synchronisation.
Worked example · One list, changed by a thread and by a child process
- A Python program starts with an empty list called data. The main thread starts a new thread that runs data.append(1).
- The thread runs inside the same process, so it appends to the same list in the same memory. After join(), the main thread prints len(data) and gets 1.
- Now the main program starts a child process that runs the same append. The child gets its own copy of memory (via fork with copy-on-write, or by re-importing the module with spawn).
- The child appends to its own copy of data, which then has length 1 inside the child. The parent's list is untouched.
- After join(), the parent prints len(data) and still gets 1. To get results back from a child process, you must use IPC, such as a multiprocessing Queue or Pipe.
Picture it
A process is a separate house with its own kitchen and locked doors. Threads are people living in the same house: they share the kitchen and fridge, which is convenient, but if two of them cook at once without coordinating, someone's ingredients get used up.
Reference code
import threading
import multiprocessing
data = []
def append_one():
data.append(1)
if __name__ == "__main__":
# A thread shares the process's memory: it changes the same list.
t = threading.Thread(target=append_one)
t.start(); t.join()
print(len(data)) # 1
# A child process gets its own copy of memory: our list is untouched.
p = multiprocessing.Process(target=append_one)
p.start(); p.join()
print(len(data)) # still 1
The thread modifies the parent's list because threads share the address space. The child process modifies only its own copy, so the parent still sees one element. The __main__ guard is required for multiprocessing on platforms that start children by re-importing the module.
Key patterns
Threads for I/O-bound work
When: Work mostly waits on network, disk or a database
How: Use a thread pool or async I/O; waiting threads use no CPU, and sharing memory is convenient.
Processes for CPU-bound work or isolation
When: Heavy computation, untrusted code, or crash isolation
How: Use a process pool (one per core) and communicate through queues; a crash stays inside one process.
Hybrid: processes of threads
When: Large servers
How: Run one process per core for parallelism and isolation, each with threads or an event loop for concurrency, as Nginx and Gunicorn do.
Real-world use
Google Chrome
Chrome runs a separate renderer process per site (site isolation). If one tab crashes or is compromised, the others and the browser keep running, at the cost of more memory.
PostgreSQL vs MySQL
PostgreSQL starts a separate backend process for each client connection, which is robust but heavier, so people put a pooler like PgBouncer in front. MySQL serves connections with threads inside one server process.
Nginx and Gunicorn
Nginx runs a few worker processes, each handling thousands of connections with an event loop. Gunicorn runs several worker processes for Python web apps, partly so CPU-bound code can use multiple cores despite the GIL.
Python's GIL
In standard CPython, the global interpreter lock lets only one thread run Python bytecode at a time, so threads help with I/O-bound work but not CPU-bound work; multiprocessing is used for CPU-bound parallelism. Python 3.13 added an optional free-threaded build without the GIL.
In a project
You are building a service that receives uploaded videos, fetches metadata from two external APIs, and generates thumbnails, which is CPU-heavy.
- Use threads (or async I/O) for the API calls, because they spend most of their time waiting on the network and threads share memory cheaply.
- Use a pool of worker processes, one per CPU core, for thumbnail generation so CPU-bound work runs in parallel and a crash in one image library call does not kill the whole service.
- Pass jobs to the processes through a queue and return only small results (file paths), since copying large data between processes is expensive.
- Measure CPU usage and throughput before and after to confirm the split.
Impact: Thumbnail throughput scales with the number of cores (for example roughly 4x on a 4-core machine instead of being stuck on one core), API waiting no longer blocks CPU work, and a crashing worker is restarted without taking the service down.
Trade-offs
Use it when
- Threads: many I/O-bound tasks that need to share data cheaply
- Threads: low-latency communication between tasks in one program
- Processes: CPU-bound work in runtimes with a GIL, or when you need fault and security isolation
Avoid it when
- Threads when tasks are untrusted or crash-prone: one bad thread can take down the whole process
- Processes when tasks must share large data structures constantly; IPC and copying are expensive
- Thousands of OS threads: each needs its own stack (often 1-8 MB of reserved virtual memory), so use a pool or async I/O
Cost: Thread creation and switching are cheaper than for processes because the address space is shared; process creation must set up a new address space (fork uses copy-on-write to make this cheaper).
Common mistakes
- Saying threads have separate memory. They share heap and globals; only the stack and registers are per thread.
- Saying processes cannot communicate. They can, through IPC: pipes, sockets, shared memory and message queues.
- Assuming more threads always means more speed. CPU-bound threads in CPython are limited by the GIL, and too many threads add switching overhead.
- Forgetting that a crash or memory corruption in one thread affects the whole process.
- Confusing concurrency (tasks making progress in overlapping time) with parallelism (tasks running at the same instant on different cores).
Interview questions
Quick revision
- Process: own address space, PCB, isolated, heavier.
- Thread: shares code, heap, globals and files; own stack, registers, program counter.
- States: new, ready, running, blocked, terminated.
- Process switch changes the page table; thread switch in one process does not.
- IPC: pipes, sockets, shared memory, message queues.
- fork + copy-on-write makes process creation cheaper.
- CPython GIL: threads for I/O, processes for CPU.
- Concurrency is overlapping progress; parallelism is simultaneous execution.
Check yourself
1. Which of these does each thread have its own copy of?
2. Why is switching between two processes usually more expensive than between two threads of the same process?
3. A Python service does heavy image processing on an 8-core machine. What gives real parallel speed-up in standard CPython?
4. What happens to the parent's memory when a forked child writes to a variable?
Related
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.