Open a browser and watch a video in one tab while a runaway script in another tab wedges itself into an infinite loop. The frozen tab spins — but your video keeps playing, your other tabs stay alive, and when you close the bad one nothing else even flinches. That containment is not luck. It is a design decision made out of two building blocks that every operating system offers for running things "at the same time": the process and the thread.
This page is about the difference between them — the single most important distinction in all of concurrent programming. Get it right and you can reason about isolation, cost, and crashes. Get it wrong and you will either leak data between things that should be sealed off, or pay a fortune in overhead for isolation you never needed.
In a sentence: processes are islands; threads are workers on the same island. Everything else — cost, crash behaviour, how they talk to each other — falls out of that one fact.
Before we compare the two models, we need to nail down what "at the same time" even means, because it has two meanings and they are not the same thing.
A neat way to remember it (paraphrasing Rob Pike): concurrency is about dealing with many things at once; parallelism is about doing many things at once. Concurrency is a way to write your program; parallelism is a way it can run. You can have concurrency with no parallelism (one core, time-sliced), and you can only get parallelism by having concurrency to begin with.
Threads and processes are the two vehicles for both. Multiple threads or processes on a single core give you concurrency; spread across many cores they give you real parallelism. Which vehicle you pick is what the rest of this page is about.
The diagram below shows both models at once. On the left, a single process holds one block of memory, and several threads all reach into that same block — they share everything. On the right, two separate processes each own a private memory block the other cannot touch; to exchange anything they must send it down an explicit channel. Step through it:
Look at what "sharing an address space" buys and costs. Threads on the left can hand each other data by just writing to a shared variable — instant, free, and dangerously easy to get wrong (two threads writing at once is a race). The processes on the right can never accidentally corrupt each other's memory, but the price is that talking to each other takes real work — copying bytes across an operating-system channel.
Every practical difference between threads and processes traces back to that one question: do you share the address space, or not?
| Property | Threads (in one process) | Separate processes |
|---|---|---|
| Memory | Share one address space — same heap, same globals, same file handles | Each has its own private, isolated address space |
| Creation cost | Cheap — no new address space; just a new stack and registers | Expensive — the OS must set up a whole new address space and resources |
| Context-switch cost | Lighter — same memory map, so the CPU can keep much of its cached state | Heavier — the memory map changes, flushing caches and the TLB |
| Crash blast-radius | One thread crashing (e.g. a segfault) usually kills the whole process — all its threads | One process crashing is contained; the others keep running |
| Communication | Directly through shared memory — fast, but needs locks to stay race-free | Through IPC: pipes, sockets, shared-memory segments, message passing |
Read the table as a single trade-off with two ends. Threads trade isolation for speed and easy sharing; processes trade speed and easy sharing for isolation and safety. There is no free lunch — only the question of which cost you would rather pay for the job at hand.
A browser is the textbook example of using both deliberately. Modern browsers run one process per tab (roughly — the details vary), for isolation: if a page in one tab crashes or gets compromised, its process dies alone and your banking tab in another process is untouched. That is the crash blast-radius argument, chosen on purpose. The cost — more memory, slower tab startup — is judged worth it for the safety and security.
Inside each tab's process, though, the browser runs many threads: one to lay out and paint the page, others to decode images, run JavaScript, and handle the network — all sharing that tab's memory so they can cooperate cheaply and keep the page responsive while work happens in the background. Isolation between tabs (processes), responsiveness within a tab (threads). The same design shows up in servers, databases, and game engines: processes to draw hard security or fault boundaries, threads to squeeze parallel work out of the cores inside each boundary.
Here is the sharing/isolation split as runnable code. Two threads increment a shared counter; because they share one variable, a bad interleaving lets one increment clobber the other — a lost update. Two processes each increment their own private counter; the very same interleaving is harmless, because there is no shared state to corrupt — but equally, there is no single shared result to read out afterwards. Press Run ▶:
Same interleaving, opposite outcomes. Sharing memory made the threads fast and fragile; isolating memory made the processes safe and mute — they can't collide, but they also can't combine their work without explicitly shipping data between them.
A very common beginner picture is that each thread gets its own little copy of everything. Not so. Threads in a process share the heap, the globals, and the open file handles — one address space, seen by all of them. What a thread does get privately is small: its own stack (for local variables and the call chain) and its own registers (including the program counter). That is exactly why threads are cheap to create and why they can race: the big shared pool of memory is the whole point, and the whole danger. A process, by contrast, really does get its own everything.
Threads are cheaper to create and switch between, so people conclude "always use threads." But cheaper-to-spawn is not the same as faster-overall. Because threads share memory, keeping them correct needs locks — and locks introduce waiting, contention, and the risk of deadlock, which can make a heavily-threaded program slower and far buggier than a clean multi-process design. Processes also give you fault isolation and can sidestep runtime-level bottlenecks (a famous one: Python's Global Interpreter Lock, which forces CPU-bound Python work into multiple processes for real parallelism). "Faster" depends entirely on the workload.
Since threads share memory, they communicate by simply reading and writing the same variables — no machinery required, just discipline (locks) to avoid races. Processes have no shared variables by default, so the operating system provides explicit inter-process communication (IPC) channels to move data across the isolation boundary:
Notice the spectrum: message passing sits at the "total isolation, copy everything" end (safe, a bit slower), and a shared-memory segment sits at the "share the raw bytes" end (fast, race-prone) — the very same trade-off threads and processes embody, now offered as a menu.
The threads we have described are OS (kernel) threads — the operating system knows about each one and schedules it. But a language runtime can offer its own lightweight threads on top, invisible to the OS: green threads, fibers, or goroutines. The runtime multiplexes thousands of these onto a small pool of real OS threads. They are wonderfully cheap to create (you can spawn a million goroutines), which makes them ideal for handling huge numbers of concurrent connections. The catch: because many green threads may sit on one OS thread, a single blocking system call can stall all of them unless the runtime is careful — which is exactly the kind of plumbing Go's scheduler works hard to hide from you.