--- slug: concurrency type: concept summary: "The property of a system whose activities are in progress at overlapping times, and the vocabulary for how those activities are structured." created: 2026-04-04 updated: 2026-05-23 related: algorithmic-complexity: relation: depends-on note: "Understanding the cost of operations helps you decide what is worth parallelizing." determinism: relation: contrasts-with note: "Concurrency introduces nondeterminism through scheduling, even when individual tasks are deterministic." event: relation: uses note: "Event-driven architectures are a common way to structure concurrent systems." protocol: relation: uses note: "Concurrent systems that communicate need protocols to coordinate safely." side-effect: relation: refined-by note: "Minimizing shared side effects is the most effective way to reduce concurrency bugs." --- # Concurrency *The property of a system whose activities are in progress at overlapping times, and the vocabulary for talking about how those activities are structured.* > **Concept** > > Vocabulary that names a phenomenon. > "Concurrency is not parallelism." > — Rob Pike ## Understand This First - [Algorithmic Complexity](algorithmic-complexity.md) — understanding the cost of operations helps you decide what is worth parallelizing. ## What It Is Concurrency is the property of a system whose activities overlap in time. A web server fields hundreds of requests whose handlers all sit somewhere between "received" and "responded." A mobile app pulls data from the network while keeping the interface responsive. An AI agent calls several tools, plans its next move while waiting on results, and stitches the responses together as they arrive. In every case multiple activities are *in progress* even when only one is actively executing at any given instant. The first thing to nail down is the distinction from parallelism, because the two terms are routinely conflated in code reviews and design docs. Parallelism means two or more computations are literally running at the same physical instant, typically on multiple CPU cores. Concurrency means two or more activities are overlapping in time, regardless of whether they run simultaneously. A chef alternating between chopping vegetables and stirring a pot is concurrent but not parallel. A team of chefs each working their own station in parallel is both. The slogan from Rob Pike's 2012 Waza talk captures it: concurrency is about the *structure* of the program; parallelism is about the *execution*. A concurrent program may or may not run in parallel; a parallel program is almost always concurrent. Concurrency takes a handful of recognizable forms, and the vocabulary of those forms is part of the concept itself. Each form names a different way of organizing overlapping activities: - **Threads with shared memory.** Multiple threads of execution share an address space and coordinate access to shared data through locks, atomics, or other synchronization primitives. This is the traditional shape and the one most prone to race conditions and deadlocks. - **Message passing.** Activities own their own data and communicate by sending messages over channels or queues. Communicating Sequential Processes, as Hoare named it in 1978, is the most studied form; Go's goroutines and channels are a direct descendant. - **Async/await.** A single thread alternates among many activities at explicit suspension points, almost always I/O. This is the dominant shape in JavaScript, Python's asyncio, Rust's async ecosystem, and C#'s Task model. - **Actors.** Each actor is an independent unit with private state that processes messages sequentially; concurrency comes from many actors running together. Erlang, Elixir, and Akka are the canonical examples. These are not different *solutions to the same problem*. They are different *shapes the property of concurrency takes* in real systems. Recognizing which shape a codebase, language, or runtime is using tells you what kinds of bugs you are likely to see and what vocabulary the rest of the conversation will need to use. ## Why It Matters Without the word "concurrency," a practitioner can describe what a concurrent system *does* but not what it *is*. Reviewing code, asking an AI agent to write a fetcher, or arguing for a particular runtime all turn on whether the participants share the vocabulary for overlapping execution. Two recurring failure modes show up when that vocabulary is missing or imprecise. The first is the parallelism conflation. A team asked to "make this faster by running it in parallel" reaches for threads on a single-core embedded device, or wires up an async runtime expecting CPU-bound work to magically run on more cores. The work doesn't get faster, sometimes gets slower, and the team blames the library. The fix is upstream of any library: name the shape. Is the work CPU-bound (needs parallelism, and therefore threads or processes on multiple cores), or I/O-bound (needs concurrency, and async/await or message passing will do)? The question is unanswerable without the concept. The second is the race-condition surprise. Software that worked yesterday produces corrupted data today. The bug is intermittent. After a week of investigation, two threads turn out to be writing the same data structure without synchronization, and the corruption only appears when their writes happen to interleave on the wrong cycle. Race conditions, deadlocks, livelocks, and starvation are not unrelated bugs that happen to coexist in concurrent systems. They are the *consequences* of the property the system has, and they cannot be reasoned about until that property has a name. Concurrency also matters because it forces a different kind of reasoning. Sequential code can be read top-to-bottom; concurrent code has to be read as a set of *interleavings*, the many possible orderings in which operations might occur. The number of interleavings grows combinatorially with the number of activities and the number of synchronization points. That combinatorial explosion is why concurrent bugs are notoriously hard to reproduce and why testing concurrent systems calls for specialized techniques (stress tests, fuzzing schedulers, model checkers) rather than ordinary unit tests. ## How to Recognize It Several signs tell you that you are looking at a concurrent system or a problem whose solution will be concurrent: - **Overlapping activities with different lifetimes.** Requests, jobs, or operations enter the system at different times, take different durations, and exit independently. A queue, an inbox, a request log, or a list of "in flight" operations is the visible artifact. - **Wait points.** The code, the runtime, or the protocol has explicit places where one activity pauses while another runs: `await`, channel receives, lock acquisitions, `select` statements, callback registrations, future/promise resolution. - **Synchronization vocabulary in the codebase.** `Mutex`, `RwLock`, `Semaphore`, `Channel`, `Queue`, `EventLoop`, `Actor`, `goroutine`, `async fn`, `task`, `Future`, `Promise` are all syntactic admissions that the code is concurrent. - **Nondeterministic test failures.** The same code, the same inputs, occasionally fails — and the failure is hard to reproduce. The interleaving was different. This is the diagnostic signature of [Determinism](determinism.md) lost to concurrency. - **Throughput that beats single-task latency.** The system handles N tasks per second even though each task takes longer than 1/N seconds end to end. Some form of overlap is doing that work. The shape of concurrency present is usually identifiable from a few minutes with the code. Threads sharing memory leave locks and atomics everywhere. Message passing leaves channels and send/receive call sites. Async/await leaves `await` keywords and event loops. Actors leave message handlers and mailboxes. A codebase that mixes shapes (an actor framework over a thread pool over a kernel event loop) is normal at runtime but worth naming carefully in design discussions, because each layer has its own failure modes. ## How It Plays Out An AI agent is asked to build a web scraper that fetches data from a hundred URLs. A sequential approach (fetch one, then the next) takes minutes because most of the time is spent waiting on the network. The agent restructures the code to use async/await, launching all fetches concurrently and collecting results as they arrive. The same work finishes in seconds. The agent didn't add parallelism; the program still runs on a single thread. It added concurrency. > **💡 Tip** > > When asking an AI agent to write concurrent code, specify the concurrency shape you want (async/await, threads, message passing, actors) and whether shared mutable state is acceptable. Left to its own devices, the agent may produce code that is technically correct but uses a shape inappropriate for your platform or performance profile. A team discovers that their application occasionally produces corrupted data. The bug is intermittent and resists reproduction. After weeks of investigation, they find that two threads write to the same data structure without synchronization. The bug only manifests when both threads happen to write at the exact same moment. The fix is straightforward (a lock or a switch to an immutable data structure), but the deeper lesson is that concurrent access to shared mutable state has to be designed for at the outset, not patched after the fact. The vocabulary for that design conversation ("shared mutable state," "data race," "happens-before," "memory ordering") is downstream of the concept of concurrency itself. A senior engineer reviewing a colleague's pull request notices that the colleague describes the change as "running these requests in parallel" when the runtime only has a single thread. The engineer doesn't redline the code; the change is fine. They redline the description: "concurrent, not parallel — there's no parallelism here, just overlap." The correction is editorial, but it matters because the next person reading the code will reason about it more accurately when the words are precise. ## Consequences Recognizing concurrency as a property of a system, distinct from parallelism and from speed and from any particular runtime, changes how you read code, design systems, and brief AI agents. **Benefits.** Naming the property unlocks the rest of the vocabulary: races, deadlocks, livelocks, fairness, throughput, latency, backpressure, work stealing, contention. Each of those terms is meaningful only against a baseline understanding of what concurrency *is*. Practitioners who hold the concept can choose a concurrency shape deliberately, read the failure mode of a mismatched choice, and instruct an agent precisely. Performance discussions become tractable: I/O-bound versus CPU-bound is the right axis; async versus threads becomes a choice rather than a guess. **Liabilities.** The concept itself introduces a class of reasoning the sequential reader doesn't have to do. Interleavings grow combinatorially; nondeterminism is permanent; testing techniques for concurrent code (stress tests, fuzzing, model checkers) are heavier than ordinary unit tests. Many real bugs in concurrent systems are still found in production, not in CI. And the vocabulary itself is treacherous: "thread-safe" means different things in different ecosystems, "lock-free" is not the same as "wait-free," and "concurrent" is misused often enough that the Rob Pike correction has become a small ritual in code reviews. The practical upshot is that concurrency is a property worth naming explicitly the moment it shows up in a design conversation. The cost of leaving it implicit (debugging a race condition no one expected, refactoring an async codebase to add threads it didn't need, asking an agent for "parallelism" and getting back a tangle of mutexes) is high enough that the discipline of naming pays for itself within a single project. ## Sources - Edsger Dijkstra founded the study of concurrent algorithms with *[Solution of a Problem in Concurrent Programming Control](https://dl.acm.org/doi/10.1145/365559.365617)* (1965), which defined the mutual exclusion problem and introduced semaphores as a synchronization mechanism. - C.A.R. Hoare introduced Communicating Sequential Processes in a 1978 paper, *[Communicating Sequential Processes](https://dl.acm.org/doi/10.1145/359576.359585)* in *Communications of the ACM*, proposing that concurrent processes communicate through synchronous message passing rather than shared memory. The model influenced the design of Go, Erlang, and other concurrency-oriented languages. - Carl Hewitt, Peter Bishop, and Richard Steiger proposed the actor model in *[A Universal Modular ACTOR Formalism for Artificial Intelligence](https://www.ijcai.org/Proceedings/73/Papers/027B.pdf)* (1973), where independent actors with private state communicate through asynchronous messages. - Rob Pike's 2012 talk *[Concurrency Is Not Parallelism](https://go.dev/blog/waza-talk)*, delivered at Heroku's Waza conference, popularized the distinction between concurrency as program structure and parallelism as simultaneous execution. - The [async/await pattern](https://en.wikipedia.org/wiki/Async/await) originated in F#'s async workflows (2007) and was popularized by C# 5.0 (2012), becoming the dominant concurrency model for I/O-bound work in JavaScript, Python, Rust, and other modern languages. --- - [Next: Event](event.md) - [Previous: Side Effect](side-effect.md)