Skip to the solution
MediumDSA13 min read

What is the difference between concurrency and parallelism in Python?

123 views

The problem

Question presented to candidate: "People use concurrency and parallelism as if they were the same word. What is the actual difference, and what does each one look like in Python β€” with threads, asyncio and multiprocessing?"

What a strong answer should cover:

  • Concurrency is about structure: several tasks are in progress during the same period and their steps interleave, possibly on one core. Parallelism is about execution: several steps run at the same instant on different cores.
  • Parallelism needs more than one core; concurrency does not. Every parallel program is concurrent, not every concurrent program is parallel.
  • In CPython with the GIL, threads give concurrency but not parallel Python bytecode; blocking I/O and many C extensions release the GIL, so waits overlap.
  • asyncio is concurrency on a single thread (cooperative, switches only at await); multiprocessing (or ProcessPoolExecutor) gives parallelism with one interpreter and one GIL per process.
  • Which one you need follows from the workload: waiting (I/O-bound) is helped by concurrency; computing (CPU-bound) needs parallelism.
  • Version detail: the free-threaded build (experimental in 3.13, officially supported but optional in 3.14) lets threads run Python code in parallel.

Code / implementation expected: Optional β€” sketch interleaving vs. parallel execution, or explain why four CPU-bound threads are not faster.

  • concurrency
  • parallelism

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

13 min read Β· 11 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

PythonConcurrency on one thread: a round-robin scheduler of generators
PythonReal threads vs real processes on CPython (OS threads, not in the browser)
# Real threads vs real processes on CPython with the GIL.
# Not runnable in the browser: needs OS threads and processes.
import os
import sys
import threading
import time
from multiprocessing import Pool

def count_down(n):
    while n > 0:
        n -= 1
    return os.getpid()

def wait_io(_):
    time.sleep(0.2)  # a blocking call that releases the GIL
    return threading.get_ident()

def run_threads(target, arg, n):
    results = []
    def worker():
        results.append(target(arg))
    threads = [threading.Thread(target=worker) for _ in range(n)]
    start = time.perf_counter()
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    return results, time.perf_counter() - start

if __name__ == "__main__":
    print("GIL enabled:", getattr(sys, "_is_gil_enabled", lambda: True)())
    print("switch interval (s):", sys.getswitchinterval())

    # 1. I/O-bound: 4 threads that each wait 0.2 s
    idents, took = run_threads(wait_io, None, 4)
    print("I/O: 4 waits of 0.2 s overlapped:", took < 0.6)
    print("I/O: distinct thread ids:", len(set(idents)))

    # 2. CPU-bound in threads: one process, the GIL lets one thread run at a time
    pids, _ = run_threads(count_down, 2_000_000, 4)
    print("CPU threads: distinct PIDs:", len(set(pids)))
    print("CPU threads ran in this process:", set(pids) == {os.getpid()})

    # 3. CPU-bound in processes: each has its own interpreter and its own GIL
    with Pool(4) as pool:
        pids = pool.map(count_down, [2_000_000] * 4, chunksize=1)
    print("CPU processes ran outside this process:", os.getpid() not in pids)
    print("CPU processes: at least 2 worker PIDs used:", len(set(pids)) >= 2)

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 109 of 139 in Python

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Python questions
Up nextMedium

What does typing.Self do and why does it beat TypeVar for methods?

Continue