Cashiers, Sandwich Artists, and Asynchronous Programming

How I think of concurrency, parallelism, and async/await through analogies.
Programming
Concurrency
Parallelism
Author

Ross Heaton

Published

10th October, 2025

Whenever I teach programming, I try to map new concepts to familiar things in the real world. Explaining variables as labelled jars, or conditional loops as scrolling through Netflix until we find a promising show, can help a beginner build an intuitive mental model relatively quickly.

There are two real-world analogies I’ve found particularly helpful for teaching asynchronous programming.

Cashiers: Concurrency vs Parallelism

The first is to help understand the difference between concurrency and parallelism.

Imagine a grocery store with two cashiers and two separate queues of shoppers. Each cashier is processing their own line simultaneously. This is parallelism. Two tasks are genuinely running at the exact same time, just like a computer with a multi-core processor can run multiple threads in parallel.

Now, let’s imagine a different scenario with only one cashier, but still two queues of shoppers who need to be checked out. The cashier can’t process both queues at the same time. Instead, they can switch between them. They might scan one item for a person in Queue A, then turn and scan an item for the person at the front of Queue B, then switch back to Queue A.

This act of switching between tasks is concurrency. Although only one person is being served at any given instant, both queues are making progress and neither is completely stalled. The single cashier is managing and advancing multiple lines of work over the same period.

In this analogy, a single cashier is a CPU core, each queue is a thread, and each shopper is a task within that thread. Parallelism is multiple cores working on multiple threads, while concurrency is a single core intelligently switching between threads to keep everything moving forward.

Sandwich Artists: async / await

The second analogy is to understand what problem the keywords async and await solve.

Imagine a sandwich shop where the most time-consuming part of any order is toasting the sandwich, which takes a full minute. Now, picture a sandwich artist who works “synchronously.” When you order a toasted sub, they assemble it, place it in the oven, and then simply stand there, watching it toast for the entire 60 seconds. While that oven is running, they don’t take the next person’s order or help anyone else. The entire line of customers grinds to a halt, completely “blocked” by the toasting process. This is what traditional, synchronous code does: when it hits a slow operation, like a network request or reading a large file, the whole program just waits.

Now, let’s think about a more efficient, “asynchronous” sandwich artist. This artist knows that the minute the oven is running is wasted time. When she puts your sandwich in to toast, she doesn’t wait. Instead, she immediately turns to the next customer and starts preparing their order. When she hears the oven beep, she can pause what she’s doing, retrieve the finished sub for the first customer, and then seamlessly resume her work on the second order. The line keeps moving, and more work gets done in the same amount of time.

This is precisely the problem async and await solve. Marking a function with the async keyword is like hiring that efficient sandwich artist; it signals that this part of the program can handle pauses without bringing everything to a halt. The await keyword is the action of putting the sandwich in the oven. It tells the program, “Start this slow task, but don’t just stand around waiting for it. Go do other useful work, and come back here only when you hear the oven beep.”

This allows our applications to remain responsive, handling user input or animations while the slow operation finishes in the background.

I hope you’ve found these analogies helpful.