This page is for 3–5 year interviews (and anyone rehearsing those loops). async/await let an API wait on SQL or HTTP without holding a ThreadPool worker. Reciting “async does not create a new thread” is table stakes. Interviewers then ask you to diagnose starvation, unobserved exceptions, and EF Core misuse under load.
If Task vs Thread is still fuzzy, read Task vs Thread and async/await in ASP.NET Core first. If the loop is staff (Channels, Span, tenant maps), use expert C# interview questions.
How-tos for the same traps: starvation, Task.Run vs await, ConfigureAwait. Hub: async & threading.
Crisp definitions (read before the scenarios).
| Term | Crisp answer |
|---|---|
async | Enables await; method returns an awaitable state machine |
await | Yields the worker during incomplete awaitables; resumes later |
Task | Representation of ongoing work / result — not an OS thread |
| Thread-pool starvation | Too many workers blocked; async continuations cannot run promptly |
| CancellationToken | Cooperative cancel signal; pass it to I/O APIs |
C# async await interview questions (the list)
These are the C# async await interview questions I actually ask. Full scenario answers are below — this list is the prompt set, not a second article.
.Result/.GetAwaiter().GetResult()— deadlock in UI vs thread-pool starvation in ASP.NET Coreasync voidon a controller or Minimal APITask.WhenAllsharing one EF CoreDbContext- Angular navigates away; SQL keeps running (
CancellationToken) - “Will marking everything
asynccut latency in half?” - Fire-and-forget email after checkout
ConfigureAwait(false)on an ASP.NET Core APIValueTaskvsTask(when allocation actually matters)- Sync-over-async inside a helper called from an async action
If the loop is senior/staff runtime (channels, spans, linked tokens), use expert C# interview questions instead of stretching this page. Implementation deep-dives for the same traps: starvation, Task.Run vs await, ConfigureAwait, WhenAll vs WaitAll, IAsyncEnumerable.
Scenario 1: .Result “just for now”
Definition: ASP.NET Core has no request SynchronizationContext. .Result still occupies a ThreadPool worker until the Task finishes → starvation (idle CPU, 504s), not the classic UI deadlock. How-to: thread pool starvation.
Prompt: A shared library exposes Task<Customer> GetCustomerAsync. A legacy sync layer calls GetCustomerAsync(id).Result. In a WPF tool it deadlocks. In ASP.NET Core under morning load, requests hang while CPU stays low. Explain both worlds and the fix.
Detailed answer
UI / classic sync-context case: await marshals back to the captured synchronization context by default in some app models. Blocking the context thread with .Result while the continuation needs that thread → deadlock.
ASP.NET Core case: .Result occupies a thread-pool thread until the task finishes. Under concurrency, workers block waiting on work that needs workers → thread-pool starvation. Symptoms: rising queue length, healthy SQL, unhappy Angular timeouts.
// Interview red flag on a request path
var customer = customerService.GetCustomerAsync(id).Result;
// Also a red flag
customerService.GetCustomerAsync(id).GetAwaiter().GetResult();
// Fix
public async Task<ActionResult<Customer>> Get(Guid id, CancellationToken ct)
{
var customer = await customerService.GetCustomerAsync(id, ct);
return customer is null ? NotFound() : Ok(customer);
}
Fix: make the call chain async end-to-end (async Task controllers/handlers). If you truly cannot, isolate the sync boundary carefully (rare) — never as the default pattern in new API code.
Weak answer: “Async is broken in .NET.”
Strong answer: Names starvation vs classic deadlock and refuses .Result on hot paths.
Scenario 2: async void on an API
Definition: async void is for UI event handlers that cannot return Task. On an API the host cannot await completion; exceptions are unobserved. How-to: async/await in ASP.NET Core.
Prompt: Candidate writes:
[HttpPost]
public async void Import(ImportRequest request)
{
await _importer.RunAsync(request);
}
They say “there is no return value.” Critique.
Detailed answer
async void exists primarily for UI event handlers that cannot return Task. On ASP.NET Core:
- The framework cannot await completion reliably the same way as
Task - Exceptions from
async voidare harder to observe/handle consistently - Your action may look “finished” to the pipeline while work continues (or fails unnoticed)
Correct shapes:
[HttpPost]
public async Task<IActionResult> Import(ImportRequest request, CancellationToken ct)
{
await _importer.RunAsync(request, ct);
return Accepted();
}
Or Minimal APIs returning Task<IResult>.
Follow-up interview question: “Where is async void acceptable?” — UI events, not controllers, not middleware, not domain services.
Scenario 3: Task.WhenAll on one DbContext
Definition: DbContext is not thread-safe. WhenAll of two queries on one instance is a race. How-to: WhenAll vs Parallel.ForEach.
Prompt: To “speed up” a screen, a developer does:
var openTask = _db.Orders.Where(o => o.Open).ToListAsync(ct);
var closedTask = _db.Orders.Where(o => !o.Open).ToListAsync(ct);
await Task.WhenAll(openTask, closedTask);
What goes wrong? How would you rewrite it?
Detailed answer
DbContext is not thread-safe. Concurrent operations on one instance cause races, exceptions (A second operation was started...), or subtle corruption.
Safe options:
- Sequential awaits (often fast enough):
var open = await _db.Orders.Where(o => o.Open).ToListAsync(ct);
var closed = await _db.Orders.Where(o => !o.Open).ToListAsync(ct);
- True parallelism with separate scopes/contexts:
await using var scope2 = _scopeFactory.CreateAsyncScope();
var db2 = scope2.ServiceProvider.GetRequiredService<AppDbContext>();
// query on _db and db2 in parallel, then WhenAll
- One SQL query that returns what the UI needs (usually best).
Strong candidates ask whether parallelism is necessary at all before doubling connections.
Scenario 4: Angular navigates away; SQL keeps running
Definition: Cancellation is cooperative — nothing stops unless you pass the token to ToListAsync(ct). How-to: CancellationToken in ASP.NET Core.
Prompt: User opens a heavy report route, then clicks away. Kestrel shows the request aborted, but SQL still runs ~30 seconds. Why, and how do you fix it?
Detailed answer
ASP.NET Core ties request abortion to a CancellationToken. If you never pass it to EF Core / HttpClient, the backend keeps working for a client that no longer cares.
public async Task<List<ReportRow>> GetAsync(ReportQuery query, CancellationToken ct)
{
return await _db.ReportRows.AsNoTracking()
.Where(/* filters */)
.Take(500)
.ToListAsync(ct); // critical
}
Wire the token from the controller/Minimal API parameter (framework binds it) down through services.
Angular side: canceling the HttpClient subscription/signal load is good UX; server cooperation is what saves database capacity.
Scenario 5: “Will async cut our latency in half?”
Definition: Async frees workers during I/O. It does not shrink a 200ms SQL query to 100ms.
Prompt: A PM read a blog post and wants every method marked async to make pages twice as fast. How do you respond in an interview (and in real life)?
Detailed answer
Separate three ideas:
| Goal | Tool |
|---|---|
| Higher concurrency under I/O wait | async/await done correctly |
| Lower CPU time per request | algorithms, less work, caching, better SQL |
| Parallel independent I/O | WhenAll with safe resources |
Async does not shrink a 200ms SQL query to 100ms. It prevents that wait from monopolizing a thread so other requests progress.
Strong answer offers measurement: before/after thread-pool waits, not vibes.
Scenario 6: Fire-and-forget email after checkout
Definition: Fire-and-forget means you start work and do not await it before return Ok(). The request token is already canceled; exceptions are unobserved. How-to: Channel producer-consumer.
Prompt: After SaveChangesAsync, code does:
_ = _email.SendOrderConfirmationAsync(order.Id);
return Ok(order);
Discuss reliability and safer designs.
Detailed answer
Risks:
- Scoped services (
DbContext,IEmailSenderwith scoped deps) may dispose when the request ends - Exceptions can become unobserved
- App recycle can kill in-flight sends
- No retry/visibility for ops
await notify.Writer.WriteAsync(new OrderNotify(order.Id), ct);
return Ok(order);
// BackgroundService: await foreach (var msg in channel.Reader.ReadAllAsync(stopping))
Better patterns: outbox + worker; Azure Service Bus / RabbitMQ if recycle cannot drop the message; bounded Channel + BackgroundService for in-process smoothing.
Interviewers love hearing “the request boundary is not a unit of business durability.”
Scenario 7: ConfigureAwait(false) debate
Definition: SynchronizationContext is the rule for which thread continuations run on. ASP.NET Core has none on the request path. How-to: ConfigureAwait(false) in libraries.
Prompt: A teammate pastes ConfigureAwait(false) on every await in an ASP.NET Core app. Necessary?
Detailed answer
// ASP.NET Core controller — skip; no context to skip
var order = await _orders.GetByIdAsync(id, ct);
// Shared library that WPF also calls
var bytes = await stream.ReadAsync(buffer, ct).ConfigureAwait(false);
In library code that may run on UI sync contexts, ConfigureAwait(false) avoids forcing continuations onto a captured context.
In ASP.NET Core application code, there is typically no UI sync context — the habit is often noise. Prefer clarity unless you are writing shared packages.
Don’t pretend it fixes .Result deadlocks you introduced yourself.
Scenario 8: ValueTask vs Task
Definition: ValueTask is TAP with less allocation when the result is often already complete (cache hit). Await a given instance once. How-to: Task vs Thread (ValueTask section).
Prompt: When do you choose ValueTask / ValueTask<T> over Task?
Detailed answer
public ValueTask<FeeSchedule?> GetCachedAsync(string tenantId)
{
if (_memory.TryGetValue(tenantId, out FeeSchedule? schedule))
return new ValueTask<FeeSchedule?>(schedule); // sync complete — no Task allocated
return new ValueTask<FeeSchedule?>(LoadAsync(tenantId));
}
When profiling shows allocation pressure on a hot path that often completes synchronously. Rules:
- Await a given
ValueTaskonce - Don’t store them casually
- Default to
Taskfor ordinary API code
Candidates who reach for ValueTask everywhere without numbers score lower than candidates who say “Task until evidence.”
Scenario 9: Sync over async in a “helper”
Definition: One .GetResult() helper on a hot path reintroduces starvation even if every controller is async Task.
Prompt: All controllers are async, but a helper does:
public string GetName(Guid id) =>
_users.GetNameAsync(id).GetAwaiter().GetResult();
Is the app “async enough”?
Detailed answer
No. One sync-over-async helper on a hot path reintroduces blocking. Interviewers look for async all the way down, including helpers, filters, and validators that touch I/O.
How to practice for real interviews
- Narrate a bug you fixed involving
.Resultor missing tokens (2 minutes) - Draw what happens to a thread when awaiting
ToListAsync - Review a PR: list three async hazards without insulting the author
- Explain why Angular cancel ≠ database cancel unless tokens flow