Trying to explain the Event Loop/Single-thread with a train
Last week, I was cleaning up my old notes and came across something interesting. A page with some drawings, arrows, and train stations trying to explain the JavaScript Event Loop to a teammate. He lived in a city famous for its train network, so I figured it was a good starting point. It worked for him. Not so much for me at the time haha
But going through those notes again, I thought it was worth sharing. The goal here is the same as it was back then: explain what the Event Loop and single-thread mean in a really simple way without turning it into a lecture. Just a real-world example, as close to everyday life as possible.
Before we start, let's get on the same page
Think of a subway with a single conductor. One conductor, one train, one thing at a time. No multitasking, no skipping steps. That's JavaScript.
Here's how the pieces connect:
- The train moving = the Call Stack running your code
- Each station stop = a Macrotask (setTimeout, click event handler, fetch API response, UI rendering...)
- The conductor's checklist at each stop = Microtasks (Promises, async/await, queueMicrotask, MutationObserver, ...)
The rule is simple: the train doesn't leave until every single item on that checklist is done. Even the ones added while the conductor is already checking.
Let's trace one trip
You're the conductor. You pull into Station A: setTimeout. The moment you arrive, the checklist shows up:
See those last two (Log passenger count/Confirm signal ahead)? Added while the conductor was already working through the list. Doesn't matter. Can't leave until everything is cleared, old and new ones.
Then the whole thing repeats at Station B. And C. And every other station, for as long as the app is running.
Fun fact: the train that never leaves
If you keep adding new items to the checklist every time you finish one, the train never departs. That's exactly what Promise.resolve().then(recursiveCall) does. The queue never empties and the browser just freezes. Nothing moves, no UI updates, nothing.
So, using code as example, here's what that "infinite checklist" looks like:
// ❌ THE "STUCK" TRAINfunction freezeTheBrowser() { function checklistItem() { // This adds a Microtask to the CURRENT station's checklist Promise.resolve().then(checklistItem); } checklistItem(); // The conductor is stuck in a loop and never leaves the station!}// ✅ THE "SMOOTH" TRAINfunction keepItMoving() { function nextStation() { // This schedules a brand NEW station stop (Macrotask) setTimeout(nextStation, 0); } nextStation(); // The checklist stays empty, the train moves, // and the browser stays happy!}The key point is setTimeout(callback, 0). Instead of extending the current checklist forever, it schedules a brand new station stop. Safe, simple, done.
Last stop
That's really it. One conductor, one checklist, one station at a time. Running forever in a loop.
There's more to the Event Loop than this, edge cases, exceptions, browser quirks. But if you leave with this picture in your head, you've got the foundation. The rest is just extra stops on the same line.
You can go deeper by checking out these links below:
- MDN — The Event Loop
- JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue — honestly the best visual explanation I've seen
- JavaScript.info — Microtasks