Back to Blog
GuidesAugust 27, 20265 min read

C# async/await Interview Questions That Trip Up Even Senior Developers

You probably know how async and await work. But can you predict what an async method actually does, explain why .Result can cause deadlocks, or reason about Task, Task.Run, SynchronizationContext, and exception handling under interview pressure? This guide covers the C# async/await questions that expose the difference between knowing the syntax and actually understanding asynchronous programming.

Share this article:

C# async/await Interview Questions That Trip Up Even Senior Developers

You know what async and await do.

You've used Task<T> hundreds of times.

You've probably written something like this more times than you can count:

public async Task<User> GetUserAsync()
{
    return await userService.GetUserAsync();
}

So when an interviewer says:

"You are comfortable with async/await, right?"

You confidently say yes.

Then they ask:

"What exactly happens when execution reaches await?"

And suddenly, things get interesting.

Because there is a big difference between using async/await and understanding async/await.

Senior-level C# interviews often don't test whether you remember the syntax.

They test whether you understand what the compiler, thread pool, Task, continuation, and synchronization context are actually doing.

This guide covers the questions that tend to expose those gaps.


1. What does async actually do?

Let's start with the most basic-looking question.

Consider:

public async Task<int> GetNumberAsync()
{
    return 42;
}

Does async create a new thread?

No.

This is one of the most common misconceptions.

async by itself does not create a new thread.

Instead, it allows the method to use await and changes how the compiler generates the method's asynchronous state machine.

For example:

public async Task<int> GetNumberAsync()
{
    var result = await GetFromDatabaseAsync();
    return result;
}

The important thing is what happens around the await.

If the awaited operation hasn't completed yet, the method can return control to its caller rather than blocking the current thread.

Later, when the operation completes, execution can continue from where it left off.

Think of it like this:

Start method
    ↓
Start asynchronous operation
    ↓
Is it complete?
   / \
 Yes  No
  ↓    ↓
Continue   Return control
              ↓
        Operation completes
              ↓
        Continue method

So:

async ≠ new thread

That's an important distinction.


2. Does await create a new thread?

Again:

No.

Suppose you write:

await httpClient.GetAsync(url);

await isn't saying:

"Create another thread and run this operation there."

For I/O operations such as HTTP requests, the operating system and underlying networking infrastructure can handle the I/O while your application thread is free to do other work.

Once the operation completes, the asynchronous method can resume.

That's one of the biggest benefits of asynchronous programming.

Instead of:

Thread
  ↓
Send HTTP request
  ↓
WAIT
  ↓
Response arrives
  ↓
Continue

you can have:

Thread
  ↓
Send HTTP request
  ↓
Thread is free
  ↓
Do other work
  ↓
HTTP request completes
  ↓
Continue async method

This is why async programming is particularly useful for I/O-heavy applications.


3. Then what is Task?

This is where many interviews become more interesting.

Consider:

Task<User> task = GetUserAsync();

A Task is not a thread.

It's an object representing an asynchronous operation and its eventual result.

You can think of it roughly as:

"The operation isn't necessarily finished yet, but here's an object representing its progress and eventual result."

For example:

Task<int> task = CalculateAsync();

The Task<int> can eventually be:

Running
   ↓
Completed → Result = 42

or:

Running
   ↓
Faulted → Exception

or:

Running
   ↓
Canceled

This distinction is extremely important:

Task ≠ Thread

A task represents work or an asynchronous operation.

A thread is an actual execution resource.


4. What's the difference between Task.Run() and await?

This is a favorite interview question.

Consider:

await DoSomethingAsync();

versus:

await Task.Run(() => DoSomething());

They are not equivalent.

await is about asynchronously waiting for an operation.

Task.Run() explicitly schedules work on the ThreadPool.

This makes Task.Run() particularly useful for CPU-bound work when you don't want to perform that work on the current thread.

For example:

var result = await Task.Run(() => PerformHeavyCalculation());

Here, you're asking the ThreadPool to execute the CPU-bound calculation.

But doing this:

await Task.Run(() => httpClient.GetAsync(url));

is generally unnecessary.

Why?

Because the HTTP operation is already asynchronous.

You're effectively adding a ThreadPool hop around an operation that doesn't need one.

A good rule of thumb:

I/O-bound work
    → async/await

CPU-bound work
    → consider Task.Run()

Not every situation fits perfectly into this rule, but it's a good interview starting point.


5. What happens when execution reaches await?

Now we're getting into senior-level territory.

Consider:

public async Task<User> GetUserAsync()
{
    var user = await GetUserFromDatabaseAsync();

    return user;
}

When execution reaches:

await GetUserFromDatabaseAsync();

there are two major possibilities.

Case 1: The task is already complete

The method may continue immediately.

Case 2: The task isn't complete

The method can suspend at that point.

The compiler-generated state machine keeps track of where execution needs to continue.

Once the awaited operation completes, the continuation resumes the method.

Conceptually:

GetUserAsync()
     ↓
GetUserFromDatabaseAsync()
     ↓
Task incomplete
     ↓
Suspend method
     ↓
Return control to caller
     ↓
Database operation completes
     ↓
Resume method
     ↓
return user

This is why await doesn't mean:

"Stop everything until this finishes."

It means something closer to:

"If this operation isn't complete, suspend this method and continue it when the operation completes."

That distinction is critical.


6. What is a SynchronizationContext?

This question can separate someone who has merely used async/await from someone who understands its deeper behavior.

A SynchronizationContext represents an environment where continuations can be scheduled.

Historically, UI frameworks such as:

  • WPF
  • WinForms
  • Xamarin

used synchronization contexts to make sure UI-related code continued on the appropriate UI thread.

Imagine:

private async void Button_Click(...)
{
    var data = await GetDataAsync();

    textBox.Text = data;
}

The continuation after await may need to return to the UI context so that UI updates happen on the appropriate thread.

That's where SynchronizationContext becomes important.

However, modern ASP.NET Core behaves differently.

ASP.NET Core doesn't install the classic request SynchronizationContext that older ASP.NET applications did.

This is one reason why blindly repeating:

"await always returns to the original thread"

is incorrect.

A better answer in an interview is:

await captures the relevant context when one exists, and the continuation may be scheduled through that context. In ASP.NET Core, there generally isn't a classic SynchronizationContext to capture.

That's a much stronger senior-level answer.


7. What does ConfigureAwait(false) do?

You may have seen:

await SomeOperationAsync().ConfigureAwait(false);

What does it mean?

It tells the awaiter that the continuation doesn't need to resume on the captured context.

For example:

await GetDataAsync().ConfigureAwait(false);

After the awaited operation completes, the continuation doesn't need to return to the captured synchronization context.

This can be particularly relevant in reusable libraries where you don't want to impose context requirements on callers.

But don't turn this into another interview myth:

"Always use ConfigureAwait(false)."

That's not a universal rule.

The correct answer depends on the application and execution environment.

A good interview response acknowledges the context rather than memorizing a rule.


8. Why can .Result and .Wait() cause deadlocks?

This is one of the classic async interview traps.

Consider:

public string GetData()
{
    return GetDataAsync().Result;
}

Looks innocent.

But synchronous blocking can become dangerous when an asynchronous operation needs to resume on a context that is currently blocked.

Conceptually:

UI Thread
   ↓
Call async method
   ↓
await
   ↓
Continuation wants UI thread
   ↓
UI thread is blocked by .Result
   ↓
Deadlock

The important problem isn't simply:

".Result is bad."

The deeper problem is:

You are synchronously blocking a thread while the asynchronous continuation may require that same execution context to continue.

This is why "async all the way" is generally preferred.

Instead of:

var result = GetDataAsync().Result;

prefer:

var result = await GetDataAsync();

9. Does async make CPU-heavy code faster?

No.

Suppose you have:

public async Task<int> CalculateAsync()
{
    return CalculateSomethingVeryExpensive();
}

You haven't magically made the CPU-heavy calculation asynchronous.

The expensive calculation still consumes CPU.

async/await is primarily valuable for operations where your program can make progress without occupying a thread while waiting.

For example:

Database
HTTP
File I/O
Network I/O

These are classic I/O-bound scenarios.

For CPU-bound work, you may consider parallelism or explicitly moving work to another ThreadPool thread using Task.Run, depending on the architecture.

So:

async ≠ faster CPU

async = better utilization during asynchronous waits

10. What's wrong with async void?

You might see:

public async void SaveDataAsync()
{
    await SaveAsync();
}

For most application code, this is a problem.

Prefer:

public async Task SaveDataAsync()
{
    await SaveAsync();
}

Why?

Because a Task gives the caller something it can:

  • await
  • observe
  • compose
  • catch exceptions from

With async void, the caller has no Task representing the operation.

The major exception is event handlers.

For example:

private async void Button_Click(object sender, EventArgs e)
{
    await SaveDataAsync();
}

Event handlers often require void, so async void is appropriate there.

A strong interview answer isn't:

"async void is always bad."

It's:

"async void should generally be reserved for event handlers because callers cannot await or directly observe the returned operation."


11. How are exceptions handled with async/await?

Consider:

try
{
    var result = await GetDataAsync();
}
catch (Exception ex)
{
    // Handle exception
}

If the asynchronous operation fails, the exception is propagated through the awaited task and can be caught by the surrounding try/catch.

This is one of the reasons await is so useful.

Compare it with:

var task = GetDataAsync();

var result = task.Result;

Now you're mixing asynchronous work with synchronous blocking, which complicates exception behavior and can introduce additional problems.

The key mental model is:

Async operation
      ↓
Task becomes Faulted
      ↓
await observes the exception
      ↓
Exception can be caught

12. What's the difference between these two?

This is a great interview question:

var a = await GetAAsync();
var b = await GetBAsync();

versus:

var taskA = GetAAsync();
var taskB = GetBAsync();

var a = await taskA;
var b = await taskB;

They aren't necessarily equivalent from a timing perspective.

First version

The second operation doesn't start until the first operation has been awaited and completed.

Conceptually:

GetA
 ↓
wait
 ↓
GetB
 ↓
wait

Second version

Both operations are started before awaiting either one.

GetA ────────┐
             ├── complete
GetB ────────┘

This can be useful when the operations are independent.

For example:

var userTask = GetUserAsync();
var ordersTask = GetOrdersAsync();

var user = await userTask;
var orders = await ordersTask;

Or even:

await Task.WhenAll(userTask, ordersTask);

The key idea is:

Starting tasks and awaiting tasks are separate concepts.

That's something interviewers often want to hear.


13. When should you use Task.WhenAll()?

Suppose you have:

var userTask = GetUserAsync();
var ordersTask = GetOrdersAsync();
var recommendationsTask = GetRecommendationsAsync();

If these operations are independent, you can wait for all of them:

await Task.WhenAll(
    userTask,
    ordersTask,
    recommendationsTask
);

This allows the operations to progress concurrently.

Instead of:

User
 ↓
Orders
 ↓
Recommendations

you can have:

User ──────────────┐
Orders ────────────┤
Recommendations ───┤
                   ↓
                 All done

This can significantly reduce total waiting time for independent I/O operations.

But remember:

Task.WhenAll() doesn't magically create three threads.

It coordinates multiple asynchronous operations.


14. What's the difference between concurrency and parallelism?

This is another question that sounds simple but exposes conceptual understanding.

Concurrency

Multiple operations are making progress during overlapping periods.

Parallelism

Multiple operations are executing simultaneously, typically on multiple CPU cores.

For example, asynchronous HTTP requests can be concurrent without requiring one dedicated thread per request.

Meanwhile:

Parallel.For(...)

is designed around parallel execution of CPU-bound work.

So:

Concurrency
→ multiple operations in progress

Parallelism
→ multiple operations executing at the same time

They're related, but they're not the same thing.


15. What happens if you forget to await a Task?

Consider:

public async Task SaveAsync()
{
    await SaveToDatabaseAsync();
}

And then:

SaveAsync();

Console.WriteLine("Done");

You started the asynchronous operation, but you didn't await it.

That means the caller continues immediately.

So "Done" can be printed before SaveAsync() finishes.

You may also lose the opportunity to properly observe exceptions from the operation.

The better approach is:

await SaveAsync();

Console.WriteLine("Done");

This is sometimes called fire-and-forget, and it's something you should use deliberately rather than accidentally.


16. Why is Task.Run() inside ASP.NET Core often unnecessary?

Suppose you write:

public async Task<IActionResult> Get()
{
    var result = await Task.Run(() => database.GetData());

    return Ok(result);
}

Some developers think this automatically makes the API "more asynchronous."

Not necessarily.

If database.GetData() is synchronous and blocks, you've simply moved that blocking work onto a ThreadPool thread.

For I/O-bound server operations, you generally want the underlying API itself to support asynchronous I/O:

var result = await database.GetDataAsync();

This allows the runtime to avoid unnecessarily occupying a ThreadPool thread while waiting for I/O.

That's a much more scalable design.


17. Can you use await without async?

This is a common syntax question.

Normally, no.

An await expression generally needs to appear inside an async method or another context that supports it.

For example:

public async Task DoWorkAsync()
{
    await DoSomethingAsync();
}

But modern C# also supports top-level statements, where await can be used directly in the appropriate program context.

The important thing for an interview isn't memorizing a one-line rule.

Understand that async and await are language features that work together to express asynchronous control flow.


18. What is the async state machine?

Now we're entering the question territory that senior developers should be comfortable discussing.

When you write:

public async Task<int> GetNumberAsync()
{
    var value = await GetValueAsync();
    return value * 2;
}

the compiler transforms the async method into a state-machine-based implementation.

You don't manually write that state machine.

The compiler does it for you.

Conceptually, the method has states such as:

State 0
  ↓
Start operation
  ↓
Await incomplete
  ↓
State 1
  ↓
Resume
  ↓
Return result

This is why an async method can pause at an await and later resume without keeping the original call stack exactly as a synchronous method would.

You don't need to reproduce compiler-generated code in an interview.

But if you're interviewing for a senior C# role, understanding the concept is valuable.


19. Is every await asynchronous?

Not necessarily.

Consider:

var task = Task.FromResult(42);

var result = await task;

The task is already complete.

So the method may continue synchronously without actually suspending.

This is an important detail.

When you see:

await something;

don't automatically assume:

"The current thread definitely got released."

Whether the method actually suspends depends on the state of the awaited operation and the awaiter's behavior.

That's one reason the simplistic explanation:

"await always switches threads."

is wrong.


20. The question that separates syntax knowledge from understanding

An interviewer may give you something like this:

public async Task<int> GetValueAsync()
{
    Console.WriteLine("A");

    await Task.Delay(1000);

    Console.WriteLine("B");

    return 42;
}

Then:

var task = GetValueAsync();

Console.WriteLine("C");

var result = await task;

Console.WriteLine("D");

And ask:

"What gets printed, and in what order?"

You shouldn't try to memorize an answer.

Instead, reason through the execution.

GetValueAsync() starts executing synchronously until it reaches the incomplete Task.Delay.

So:

A

is printed first.

Then the method suspends.

Control returns to the caller.

The caller prints:

C

Later, after the delay completes, the method resumes and prints:

B

Then the caller's await completes and prints:

D

So the expected conceptual order is:

A
C
B
D

This kind of question tests whether you actually understand async control flow rather than whether you can define async.


21. The biggest async/await interview mistakes

If you're preparing for a senior C# interview, watch out for these answers:

❌ "async creates a new thread."

Not necessarily.

❌ "await always switches threads."

Not necessarily.

❌ "Task means thread."

No.

❌ "Task.Run makes everything asynchronous."

No.

❌ "ConfigureAwait(false) should always be used."

Not universally.

❌ "async void is always bad."

Event handlers are the important exception.

❌ "ASP.NET Core always resumes on the same request thread."

That's an outdated mental model.

❌ "async makes CPU-heavy code faster."

No.

These answers aren't just technically inaccurate.

They signal that someone knows the syntax of async/await but not the execution model.


What Senior Interviewers Are Actually Looking For

At a junior level, an interviewer might ask:

"What is async/await?"

At a senior level, the question often becomes:

"Why does this code behave this way?"

That's a completely different level of understanding.

A senior developer should be able to reason about:

  • Task vs Thread
  • I/O-bound vs CPU-bound work
  • asynchronous control flow
  • continuations
  • synchronization contexts
  • ConfigureAwait
  • ThreadPool usage
  • exception propagation
  • Task.WhenAll
  • synchronous blocking
  • deadlocks
  • concurrency vs parallelism
  • async state machines

You don't need to memorize every implementation detail of the .NET runtime.

But you should be able to build a correct mental model.


The Mental Model to Remember

If you remember only one thing from this article, remember this:

async
  ↓
Allows asynchronous control flow

await
  ↓
Waits for an awaitable without necessarily blocking the thread

Task
  ↓
Represents an asynchronous operation

Task.Run
  ↓
Schedules work on the ThreadPool

Task.WhenAll
  ↓
Coordinates multiple asynchronous operations

ConfigureAwait(false)
  ↓
Don't require continuation on the captured context

And perhaps the most important distinction:

ASYNC ≠ THREAD

Once that clicks, a lot of C# async behavior becomes easier to reason about.


Final Thought

The hardest async/await interview questions aren't difficult because the syntax is complicated.

They're difficult because they force you to mentally simulate what the program is doing.

You can spend years writing:

await service.GetSomethingAsync();

without ever asking what happens after that line.

And that's exactly where senior-level interviews start digging.

Knowing how to use async/await makes you productive.

Understanding what async/await is actually doing makes you dangerous in an interview.

And if you want to get better at that second part, don't just solve more interview questions.

Practice explaining your reasoning under interview pressure.

Subscribe to our newsletter

Get notified when we publish new engineering resources, guides, and platform updates.