I’m not sure exactly when it happened, but at some point, I found myself knowing when to use async and await. Not because I truly understood them, but because I had learned to recognize the context: I knew, “this is where an await goes,” much like a driver shifts to second gear instead of third — not because they understand how the gearbox works, but because they’ve learned to read the road.

I knew when to use them, but not why. I never stopped to ask what the real purpose of async and await was. How did they improve my code? What did I gain or lose by using them? And most importantly: what was really happening behind the scenes?

What I thought await did (and why it confused me)

I had a basic idea: using await was like telling a function, “don’t continue until this finishes.” But I assumed it didn’t block the main thread — that part I had learned — because the work was somehow “delegated.” Or at least, that’s what I believed.

The problem is that await sounds like “wait,” and in my head, waiting meant standing still. In other words: blocking. So I had this internal contradiction:

“Am I waiting for something… without blocking? How does that make sense?”

In practice, it felt like the main thread was blocked, because I didn’t see other things happening while waiting. But that was more of a perception than a technical truth. I didn’t really understand what part was being paused — or who was doing what in the meantime.

What actually happens when you use await

When an async method hits an await, and the awaited task hasn’t completed yet, the method is suspended at that point. The current thread is released, meaning it’s free to do other work.

Once the awaited task completes, the method resumes execution right after the await, using an available thread (not necessarily the same one).

This is crucial: await doesn’t block the thread — it just pauses the method, allowing the thread to handle other tasks in the meantime.

The bartender analogy

To better understand how the thread behaves during asynchronous operations, picture a bartender in a bar:

A customer orders a cocktail, and the bartender starts preparing it (which might take some time).

Instead of standing idle while waiting for the drink to be ready, the bartender takes new orders and prepares other drinks in the meantime.

When the cocktail is done, the bartender serves it to the right customer.

This way, the bartender can serve multiple clients efficiently, without wasting time.

In this analogy:

  • The bartender is the thread.
  • The drinks are asynchronous tasks that take time to complete.
  • The customers are the incoming requests or method calls.

If the bartender waited for one drink to be ready before accepting another order, they would only be able to serve one person at a time — which is inefficient.

Can you use async without await?

Technically, yes — you can declare a method as async without using await inside it. But it’s not recommended, because:

  • The method runs synchronously from start to finish.
  • The compiler will warn you that async is unnecessary if there’s no await.
  • It often results in less efficient, harder-to-understand code.

So, async and await usually go hand in hand.

What happens if I don’t use async/await?

If you write everything synchronously, threads will be blocked while waiting for things like I/O operations, which can lead to:

  • Reduced capacity to handle concurrent requests.
  • Unresponsive UIs or slow applications.
  • Inefficient use of system resources.

Conclusion

async and await are powerful tools that help you write code that uses resources more effectively and improves responsiveness and scalability.

Understanding that await doesn’t block the thread — it suspends the method and frees the thread to do other work — is key to using these tools with confidence.

The bartender analogy helped me internalize this concept. I hope it helps you too.