← Home
.NETAsynchronous programmingReported by candidates

How does async/await work, and why use it in a web API?

Reported across Deloitte, IBM, Infosys, TCS and other .NET interviews. `ConfigureAwait(false)` asks an await not to resume through a captured context; interviewers also ask how await pauses a method.

Say this first · about 30 seconds
Make it yours · UDC Finance disclosure API (invented example)
The code, simplified

Invented .NET 8 API code. A dealer posts application details; ASP.NET binds them into LoanData. Dependency injection supplies LoanService, its typed DocumentClient, and the document API wrapper over HttpClient. ct comes from RequestAborted; the remote API returns PDF bytes.

UDC.sln · where these 3 files sit
  • src/Api/
    • Controllers/
    • Services/
    • Clients/
Diagram · step 1The dealer posts details; ASP.NET passes the model and cancellation token to the controller.
DisclosureController.cs · line 1 · 1 of 5ASP.NET calls this action; IActionResult represents its HTTP response.

Fix later in your repo Invented example based on the UDC thread-pool-starvation hook; composed, not a claim about a real event.

Say it out loud, then check

Show the 30-second answer
  • An API request often waits on a database or another service. That is I/O-bound work: the app waits for another system. With synchronous code, the request's thread sits blocked during that wait.
  • With and , the method returns a that represents work finishing later. When an awaited I/O call is still pending, the method saves what it needs and returns; the can serve another request.
  • When the I/O finishes, the continuation (the saved remainder of the method) is scheduled and execution resumes, possibly on a different thread. The task for this operation completes with this request's result, which flows back to the controller.
  • That lets the API handle more concurrent (in-flight at once) I/O-bound requests (waiting on another system) with the same thread pool. It can improve throughput (completed requests per unit of time); it does not make the database query faster, create a thread per request, or make CPU-heavy (processor-work) code faster.
Check your answer: tick what you said

You said 0 of 6

Then add your example: UDC Finance disclosure API (invented example)
  • Situation: during UDC's wider rollout, the API generated disclosure PDFs while dealers waited for the response.
  • Problem: an older helper called RenderAsync(...).Result. A burst of requests blocked thread-pool threads; even health checks queued while the document service was slow.
  • What I changed: I used through the controller and service calls. We later moved bulk PDF generation to a background , because a request should not keep dealers waiting for a long document job.
  • Result: at twice the rollout peak, p95 stayed under a second. I added a load test and a build rule against .Result / .Wait() in API request code.
  • What I'd say: async improved how many waiting requests the API could serve; moving long work out of the request reduced user wait time. They solve different parts of the problem.

See it

1/6A request reaches the controller, which starts the service call for loan 42.

In plain words

The senior answer · about 2 minutes
What the keywords do
  • I explain as compiler support, not a command to start a thread. The compiler turns the method into a state machine (generated code that remembers where to resume) which returns a or .
  • At each , if the operation is incomplete, the method stores its continuation (the remaining work) and returns control. If it has already completed, the method continues without suspending.
  • The task carries completion, a result, cancellation or an exception back to the caller. I await it instead of reading .Result or calling .Wait().
Why an API uses it
  • A request spends time waiting on EF Core (the .NET data-access library), (the .NET HTTP client), or a file. Asynchronous I/O does not keep a thread occupied just to wait, so the thread pool can run other requests.
  • This improves throughput when many requests are waiting at once. It does not make one slow dependency faster; I still measure latency and fix the dependency if it is the bottleneck.
  • I keep the whole call chain asynchronous: controller, service, and EF Core or API. A synchronous wait in the middle blocks a thread and can cause thread-pool starvation under load.
What I watch in production
  • I pass through to database and HTTP calls that accept a cancellation token, so work can stop after the client disconnects. Cancellation is cooperative; every callee must observe the token, and a database provider may not honor it immediately.
  • I don't use to wrap asynchronous I/O. For genuinely CPU-heavy work, I measure the cost and consider a bounded background if it does not need to finish inside the request.
  • I don't run parallel EF Core operations on the same ; async does not make that object safe for concurrent use. See .

One request, three layers

Follow loan 42 from the HTTP request to SQL and back. Every layer returns its task to the caller.

1/3The request passes its token into the service call.

The story

  1. Before

    Before: a .NET API called SQL synchronously. While SQL was working, the request thread stayed blocked. At low traffic this looked fine.

    1/1The request thread waits in place while SQL runs.

  2. Why it had to change

    Problem: at the rollout peak, a burst of slow document calls left too many threads blocked. Even health checks queued while CPU stayed low.

    1/1Blocked threads pile up while the dependency is still working.

  3. What happens next

    Fix: make the I/O call asynchronous end to end. When it is pending, the thread returns to the pool instead of waiting on .Result.

    1/1The method pauses; SQL continues, and the thread can serve another request.

  4. How it works now

    How it works: the compiler saves the method's continuation at ; when the task completes, execution resumes and the result returns to the caller.

    1/1The completed task carries the result back, then the saved method continues.

In the lending API

ASP.NET calls the controller. Dependency injection supplies its service and scoped database context. The awaited EF Core query returns a loan or null; the controller answers 200 or 404.

Only the key lines are shown; Deep walks the whole file.

Lending.sln · where this file sits
  • src/Api/Controllers/
⋯ 1 line in Deep
LoansController.cs · line 9 · 1 of 2Passes the route id and client-disconnect token to the service, then awaits its result.

The code, file by file

ASP.NET Core invokes the controller with the route id and RequestAborted token (cancelled if the client disconnects). Dependency injection supplies LoanService and its request-scoped LoansDbContext (EF Core's object for querying the database and tracking entities). EF Core is the .NET library that turns C# queries into SQL returns the database Loan or null; the controller sends 200 or 404.

Lending.sln · where these 2 files sit
  • src/Api/
    • Controllers/
    • Services/
LoansController.cs · line 1 · 1 of 7This field holds the service the controller receives.

What they ask next

The follow-ups interviews really ask. Answer out loud, then tap to check.

Follow-up tree

Answer, then open the next level. That is the order an interviewer digs in.

Reporteda candidate was asked Intervieweran interviewer asks it

Production bugs, and how to tell them

Each one: what you see, why, the fix in code, and the line to say.

Production incident: The whole API stalled during the UDC rollout

P1 · outage or data leakUDC Financeonly under loadDisclosure API and document service

Composed, not a real event at UDC Finance. Reuses the async/thread-pool hook from incident #28; the dates, numbers and names below are invented.

The story in 5 lines
  1. During a UDC rollout peak, every API endpoint, including health checks, took 10–20 seconds. The incident was , the highest-priority severity in this story.
  2. showed low CPU but rising thread count and queued requests.
  3. A shared PDF helper called an async document API with .Result, holding a thread for each request.
  4. We disabled on-request PDFs, changed the call chain to async and moved bulk generation to a queue.
  5. A 2× load test now holds p95 below one second; a build rule prevents .Result and .Wait() in API request code.
Who's who (4 people)
You
.NET developer on the vendor team; the disclosure flow was yours.
Sushmita
Backend lead; led triage and approved the mitigation.
Nitin
DevOps; scaled the App Service and gathered counters.
UDC incident manager
Chaired the bridge and coordinated dealer updates.

Tap a step. The map lights the place where it happens.

1/8 Detect

Dealers report a frozen portal; shows p95 at 14 seconds, even for health checks.

Tue 28 Apr 2026, 10:30 NZST = 04:00 ISTUDC dealers; you on call

  • Dealers report the portal freezing during the rollout (a release reaching users). , Microsoft's request-telemetry service, shows p95 (the time 95% of requests finish within) rising from about 700 ms to 14 seconds; health checks (automated probes that test whether the API responds) fail too.
  • The alert pages you at 04:05 IST. The incident manager opens the and you bring in Sushmita.
  • The issue spans endpoints, not only disclosures, during a burst of roughly 40 bulk document requests. QA reproduces it in (the system-integration test environment).
App Insights: request p95
requests
| where timestamp > ago(30m)
| summarize p95=percentile(duration,95)
    by bin(timestamp,1m), name
| render timechart
In theory

Low CPU with rising thread count and queued requests points to blocked , not a CPU bottleneck.

In practice

In this composed incident, the first symptom was low CPU with a growing request queue.

AI assist · GitHub Copilot Chat, if UDC policy allows it

Drafted an chart query in Query Language () from the request-p95 question.

You checked: The table, duration column and time window against the schema and known deployment time.

Try it yourself · 15 minutes, locally, no cloud
  1. Create a small .NET 8 console project and run the proof program in app/qa/writer-test2/proof-luna.
  2. Run it once with a deliberately blocking wait, then with ; compare the marker order and thread IDs.
  3. For the API shape, replace the blocking helper in a local controller/service/repository example with async methods and pass the cancellation token.
You should see

Both versions return the same value. The awaited version does not occupy the caller thread while the fake I/O delay is pending.

The senior move

When the UDC disclosure flow was under load, our shared helper blocked request threads on .Result while a document service was slow. I helped reproduce it with a concurrent load in and .NET runtime counters, then made the request chain async and moved bulk generation to a queue. At twice peak, p95 stayed below a second. Async freed threads during I/O; the queue kept long document work out of the dealer's request.

Sources Sourced hook: docs/work-story/06-incidents/00-index.md #28 and the UDC incident file. Composed: this card's incident dates, names, metrics and code. Runtime behavior for async I/O and thread-pool use is checked in the proof program and Microsoft Learn references on the card.

Traps

Traps

What should you say instead? Tap to check.

Live coding

Easy first, then harder. Set a timer and type it before you look.

Task 1

Question index: Mustmedium · 20 minutes

Medium · Make the blocking controller → service → EF Core call chain asynchronous end to end; remove .Result / .Wait() and pass the request cancellation token.

Microsoft Learn: asynchronous programming scenarios
You are given

Broken three-file request path. ASP.NET invokes the controller; dependency injection supplies service, repository and scoped DbContext. filters by id; copies row values into LoanDto; SingleOrDefaultAsync runs the database query and returns one row or null.

Lending.sln · where these 3 files sit
  • Controllers/
  • Services/
  • Data/
Stuck? Hints
  1. Start with the EF Core call and return Task<Loan?> from the repository.
  2. Make the service return Task<LoanDto?> and await the repository.
  3. Make the controller return Task<ActionResult<LoanDto>> and await the service; pass RequestAborted as ct.
Show a solution
Lending.sln · where these 3 files sit
  • Controllers/
  • Services/
  • Data/
Diagram · step 1A request reaches the controller, which starts the service call for loan 42.
LoansController.cs · line 1 · 1 of 2The action returns a task for the eventual HTTP response.
What the interviewer checks
  • No .Result, .Wait() or GetAwaiter().GetResult() remains in the request path.
  • The controller → service → repository signatures all return Task / Task<T>.
  • The database call uses SingleOrDefaultAsync(ct), and the same token reaches it.
  • Loan not found still returns 404; a row still returns 200.
  • Do not add Task.Run around EF Core I/O.
With AI tools

AI coding tools often suggest .Result as the smallest patch or wrap EF Core in . Review the whole request path, verify every signature and cancellation token, and run a load test; the compiler alone will not reveal thread-pool starvation.

Glossary: every name, one line each →

My notes

Paste the NOTES block from your doubt chat here. The grader never reads notes; the coach reads "Still unclear".

Read moreWhere it was asked