Concurrency vs Parallelism

RoadmapsPython Backend

Scenario

Your server handles 1,000 requests per second. Does it have 1,000 CPUs, or is it juggling them really fast?

Mental model

Concurrency is about dealing with lots of things at once (juggling). Parallelism is about doing lots of things at once (having multiple jugglers).

Concurrency is a design structure. You break your program into independent tasks that can pause and resume. Parallelism is hardware execution. You literally run multiple tasks at the exact same nanosecond on different CPU cores.

Deep dive

Concurrency is about structure. When one task has to wait (like waiting for a database to respond), it pauses, and the CPU switches to another task. It's one chef making a salad while the steak is in the oven. The tasks make progress interleaved.

Concurrency = interleaved execution (one CPU core bouncing between tasks).

Parallelism is about hardware. Two tasks run at the exact same time on different CPU cores. It's two chefs in the kitchen: one making the salad, one cooking the steak, simultaneously.

Parallelism = simultaneous execution (multiple CPU cores at the same time).

Code examples

Concurrency (Interleaved - One Core)

import asyncio

async def fetch_data():
    print("Start fetching")
    await asyncio.sleep(1) # Simulates network I/O, yields control
    print("Done fetching")

async def main():
    # Both start, but they take turns on the single event loop thread
    await asyncio.gather(fetch_data(), fetch_data())

asyncio.run(main())

These two tasks run concurrently. While the first task waits for `asyncio.sleep(1)`, it yields control, allowing the second task to start. They don't run at the exact same nanosecond, but they overlap in time.

Parallelism (Simultaneous - Multiple Cores)

from multiprocessing import Process
import time

def crunch_numbers():
    print("Crunching")
    time.sleep(1) # Simulates CPU work
    print("Done crunching")

if __name__ == '__main__':
    # Spawns separate OS processes, each can run on a different CPU core
    p1 = Process(target=crunch_numbers)
    p2 = Process(target=crunch_numbers)
    
    p1.start(); p2.start()
    p1.join(); p2.join()

These processes can run parallelly. The OS can schedule `p1` on Core 0 and `p2` on Core 1, meaning they physically execute instructions at the exact same time.

Common mistakes

Recall questions

Questions & answers

If you have a Python web scraper downloading thousands of images, should you use concurrency or parallelism?

Concurrency. Downloading images is I/O-bound (most of the time is spent waiting for the network). A single core can concurrently manage thousands of active network connections using `asyncio` or threads.

Approach: Identify the bottleneck (I/O). State that concurrency is the right tool to overlap waiting times without the overhead of multiple processes.

Is Node.js concurrent or parallel?

Concurrent. Node.js runs on a single thread (event loop) and uses concurrency to handle many requests at once. It achieves high throughput by never blocking on I/O, but it does not execute parallelly on multiple cores by default.

Approach: Mention the single-threaded event loop and how it interleaves tasks.

Continue learning

Next: The GIL (Global Interpreter Lock)

Next: Threading vs Multiprocessing

Return to Python Backend Roadmap