Concurrency Is About Causality, Not Parallelism
The Hacker News thread that prompted this piece asked a familiar question: what’s the real difference between concurrency and parallelism? The usual answer—"concurrency is structure, parallelism is execution"—is close, but it misses the deeper principle. Concurrency is not just decomposition, nor is it "different pieces running at the same time." It is an assertion about causality.
Two operations are concurrent if neither one depends on the other’s result. In other words, f(a) and g(b) are concurrent as long as a does not need g and b does not need f. If you’re familiar with special relativity, think of concurrency as “spacelike”: two events that can only share information through a common past, never directly with each other.
What Concurrency Buys the Compiler
The concurrency invariant is a license for transformation. A compiler, interpreter, or CPU can reorder independent operations without changing the program’s functional result. Take a sequence of computations:
x = f(a)
y = g(b)
The generated code may not preserve that order at all:
y = g(b)
x = f(a)
This can happen because, say, b becomes available before a. Strictly speaking, side effects like I/O or queue operations break the concurrency invariant, but in practice such reorderings are treated as acceptable. The same invariant lets compilers pack independent operations into SIMD instructions or vector pipelines:
PIPELINE1 PIPELINE2
x = f(a) y = g(b)
More commonly, pure functions get vectorized variants:
[x1, x2, x3, x4] = [f(a1), f(a2), f(a3), f(a4)]
where f might be as simple as “multiply by 2.”
From Reordering to Real Parallelism
Concurrency also enables cooperative-multitasking optimizations. Unix processes, for instance, are typically concurrent with each other, which lets the kernel schedule them freely across CPU cores. Beyond that, concurrency is the basis for thread-level, CPU-level, and even machine-level parallelism—the ability to execute non-dependent instructions at the same wall-clock time in multiple places.
CPU1 CPU2
x = f(a) y = g(b)
Languages expose concurrency through a spectrum of constructs aimed at parallelism: compiler optimizations that turn for loops into vector instructions, GPU offloading of matrix operations, Thread.new, Erlang processes, coroutines, futures, agents, actors, and distributed map-reduce. Different kinds of parallelism can emerge from the same logical concurrency, with the kernel cooperating to run, say, four threads out of sixteen at once because that is the number of CPUs actually available.
The Practical Takeaway
Fewer causal dependencies in your program give you, the library, the language, and the CPU more freedom to rearrange instructions in pursuit of better throughput and lower latency. The recipe is straightforward: build small components with well-described inputs and outputs, discipline the use of mutable shared state, and pick the right synchronization primitive—shared memory, compare-and-set, concurrent collections, message queues, transactional memory—for the task at hand. Do that, and your code can run faster without you having to manage every detail of where and when it executes.



