What is async/await in C# and how does the Task-based model work?
Learn how async/await works in C#, how Task and the compiler state machine free threads during I/O, and how to avoid deadlocks and blocking calls.
Expected Interview Answer
async/await is C#'s language support for asynchronous programming: an async method returns a Task (or Task<T>), and await suspends the method at an awaited operation without blocking the calling thread, resuming it when the operation completes.
The compiler rewrites an async method into a state machine. When execution hits an await on an incomplete Task, control returns to the caller and the thread is freed to do other work; a continuation runs the rest of the method once the awaited Task finishes. This is cooperative, non-blocking concurrency built on the Task Parallel Library, so a single thread can service many in-flight I/O operations instead of one thread sitting idle per call.
- Keeps threads free during I/O instead of blocking them
- Improves scalability of servers and UI responsiveness
- Reads like sequential code, unlike callbacks
- Composes naturally with Task.WhenAll and cancellation tokens
- Propagates exceptions through the returned Task
AI Mentor Explanation
Think of a batter who calls for a third run but, instead of standing frozen watching the fielder, backs up and readies for the next ball while the throw is still travelling. The await point is that call: the batter yields the crease-watching to the umpire and the innings keeps flowing, resuming only when the ball is confirmed home rather than blocking on the outcome.
Step-by-Step Explanation
Step 1
Mark the method async
Add the async modifier and return Task, Task<T>, or ValueTask so the method can use await.
Step 2
Await an async operation
Call an asynchronous API (e.g. HttpClient.GetAsync) and await the returned Task instead of calling .Result or .Wait().
Step 3
Compiler builds a state machine
The C# compiler rewrites the method into a state machine that captures locals and tracks the resume point.
Step 4
Thread is released at await
If the awaited Task is not complete, control returns to the caller and the thread is freed for other work.
Step 5
Continuation resumes the method
When the Task completes, a continuation runs the rest of the method, on the captured context unless ConfigureAwait(false) was used.
Step 6
Result or exception surfaces
The awaited value is returned to the code after await, or an exception stored on the Task is re-thrown there.
What Interviewer Expects
- Understanding that await is non-blocking, not a new thread
- Knowledge of the compiler-generated state machine
- Difference between awaiting and blocking with .Result/.Wait()
- Awareness of ConfigureAwait(false) and deadlocks
- How exceptions propagate through Tasks
Common Mistakes
- Believing async/await always spins up a new thread
- Blocking on async code with .Result or .Wait() and causing deadlocks
- Using async void for anything except event handlers
- Forgetting to await a Task, leaving it as fire-and-forget
- Not passing or observing cancellation tokens
Best Answer (HR Friendly)
“async/await lets a C# program start a slow job, like fetching data over the network, and get on with other work instead of standing still. When the job finishes, the program picks up where it left off, so apps stay responsive and servers handle far more users at once.”
Code Example
public async Task<string> GetUserNameAsync(int id)
{
using var client = new HttpClient();
// Thread is freed here until the response arrives
HttpResponseMessage response = await client.GetAsync($"https://api.example.com/users/{id}");
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
return json;
}
// Running several in parallel without blocking
public async Task<string[]> GetManyAsync(int[] ids)
{
var tasks = ids.Select(GetUserNameAsync);
return await Task.WhenAll(tasks);
}Follow-up Questions
- What does ConfigureAwait(false) do and when should you use it?
- Why can blocking on .Result cause a deadlock in ASP.NET?
- When is async void acceptable?
- How does Task.WhenAll differ from awaiting tasks sequentially?
- What is the difference between Task and ValueTask?
MCQ Practice
1. What happens when execution reaches an await on an incomplete Task?
await suspends the method and returns control to the caller without blocking; a continuation resumes it when the Task completes.
2. Which return type should an async method generally avoid except for event handlers?
async void cannot be awaited and swallows exceptions into the synchronization context, so it is reserved for event handlers.
3. What transformation does the compiler apply to an async method?
The compiler generates a state machine that tracks the resume point and captured locals across await suspensions.
Flash Cards
Does await create a new thread? — No. It suspends the method and frees the current thread; completion is handled by a continuation, often with no extra thread.
What return types can an async method have? — Task, Task<T>, ValueTask/ValueTask<T>, or void (void only for event handlers).
Why avoid .Result on async code? — Blocking on an async Task can deadlock when the continuation needs the captured context that is being blocked.
What does ConfigureAwait(false) do? — Tells the continuation not to resume on the original synchronization context, avoiding deadlocks and reducing overhead in libraries.