C# & .NET (C# 14, .NET 10 LTS) Module 15
Asynchronous programming interview questions
Interview questions on asynchronous programming: short warm-ups, then what each harder question tests, the first step of a good answer and its follow-ups.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
What does await do to the thread that is running the method?
Answered in the lesson async and await from first principles
-
Fresher
What does Task.WhenAll return, and in what order are its results?
Answered in the lesson Composing tasks: WhenAll, WhenAny and WhenEach
-
Fresher
What does it mean that cancellation in .NET is cooperative?
-
Fresher
What is the difference between IEnumerable<T> and IAsyncEnumerable<T>?
-
Fresher
What does ConfigureAwait(false) do?
Answered in the lesson SynchronizationContext, ConfigureAwait and deadlocks
-
Fresher
When should a method return ValueTask<T> instead of Task<T>?
When the method usually has its result at once, is called often enough for the allocation of a new task per call to show in measurements, and its callers simply await it. A cache lookup that hits most of the time is the typical case: returned as a struct, a ready result costs no heap allocation, while
Task<T>allocates for most values. When the method really waits, both types allocate about the same, so ValueTask gains nothing there. The price is a stricter contract: a ValueTask may be awaited only once and never by two callers, so code that keeps it or passes it toTask.WhenAllmust callAsTask(). Microsoft's guidance is to returnTask<T>by default.From the lesson ValueTask, pooling and async overhead
-
Fresher
What is thread-pool starvation?
Answered in the lesson Async pitfalls and thread-pool starvation
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
A dashboard endpoint calls four independent services, each answering in about 120 ms, and responds in about 500 ms. Each call is awaited on its own line before the next one is made. Why is it slow, how do you fix it, and what changes when one of the four calls fails?
- What it tests
- Starting independent awaits before awaiting them, where the time of an async method goes, and how exceptions of several awaited tasks reach the caller.
- First step
- Add up the waits: 4 × 120 = 480 ms is almost the whole response time, so the calls run one after another. Each await waits for its call before the next call starts. Start all four calls first, keep their tasks, then await them.
- Follow-ups
-
- How long should the fixed endpoint take, and what sets the floor?
- What happens to the other three calls when one of them throws?
- Would wrapping each call in Task.Run make the endpoint faster?
From the lesson async and await from first principles
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A background service crashes a few times a day with an unhandled InvalidOperationException from a method OnMessageReceived, declared async void. The team wrapped every call to it in try and catch, and nothing changed. Explain why, and fix it.
- What it tests
- Where the exception of an async void method goes, why a try around its call cannot catch it, and safe shapes for event handlers and background work.
- First step
- Look at what the call returns: an async void method returns at its first await, before it throws, and leaves no task to store the exception in. The try around the call has ended by then. Find out who calls the method and whether it must return void.
- Follow-ups
-
- What if the event's delegate type returns void and cannot change?
- Does handling AppDomain.UnhandledException keep the service alive?
- How do you make the fixed handler testable?
From the lesson async and await from first principles
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
An order service runs its inventory, pricing and fraud checks with await Task.WhenAll(...). During an incident two of the three fail, the logs show only one error, and support cannot explain why some orders were half processed. What is happening, and how should the code report and handle the failures?
- What it tests
- How WhenAll stores and rethrows exceptions, reading every inner exception, partial results after a failure, and what a failure does to the tasks that are still running.
- First step
- Recall that await on a faulted WhenAll throws only the first inner exception, while the combined task keeps them all. Check whether the code keeps the WhenAll task in a variable and reads its Exception, and what the successful checks had already changed.
- Follow-ups
-
- How do you log every failure with the check it came from?
- Why were some orders half processed, and how do you prevent it?
- When would you stop the other checks as soon as one fails?
From the lesson Composing tasks: WhenAll, WhenAny and WhenEach
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Product images are served by three mirrors with different speeds, and the fastest one sometimes answers with an error. You want the first good image. Write the logic with WhenAny, say what happens to the slower requests, and how you would stop wasting them.
- What it tests
- WhenAny returning a faulted winner, the loop that skips failed answers, the tasks left running after the winner, and cancelling them.
- First step
- Start the three requests and keep their tasks in a list. Then decide what the code does when the first task to finish is a failed one, since WhenAny hands back a failed winner like any other, and what becomes of the requests still running once a good image is in.
- Follow-ups
-
- What does the method do when all three mirrors fail?
- How do you cancel the requests that lost the race?
- Would WhenEach make this code simpler?
From the lesson Composing tasks: WhenAll, WhenAny and WhenEach
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
A team added .WaitAsync(TimeSpan.FromSeconds(2)) to a slow report query. The endpoint now answers quickly when the query is slow, but database load went up during the next peak. Explain why, and what a correct timeout for this endpoint looks like.
- What it tests
- What WaitAsync stops and what it leaves running, cancellation reaching the work, retries on top of abandoned work, and choosing the layer for a timeout.
- First step
- WaitAsync ends the waiting, not the query: the abandoned query runs on to its end on the database. Count what a timed-out request now costs, then check whether callers retry, which adds a new query while the old one still runs.
- Follow-ups
-
- How many queries can run at once for one user during a slow peak?
- Where should the 2-second limit live, and what must accept it?
- When is WaitAsync the right tool after all?
From the lesson Composing tasks: WhenAll, WhenAny and WhenEach
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
Users close a reporting page after a few seconds, but the 30-second report queries keep running and the database stays busy. The controller action is async and calls repository methods whose names end in Async. What is missing, and how do you fix it end to end?
- What it tests
- Cooperative cancellation, passing a token through every layer, the request's own token in a web app, and checking that the fix works.
- First step
- Being async frees the thread, not the work: nothing tells the query that nobody wants it. Look for a CancellationToken in the action, the repository methods and the database calls. The web framework already gives each request a token that is cancelled when the client goes away.
- Follow-ups
-
- Where does the token come from in an ASP.NET Core action?
- What should the code do when the cancel arrives?
- How do you test that the query really stops?
From the lesson Cancellation tokens and timeouts
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
During an incident a payment client logs "cancelled by the user" for thousands of requests, yet no user cancelled anything. The code links the request's token with a 5-second timeout and catches OperationCanceledException around the call. Find the bug and fix the reporting.
- What it tests
- Linked token sources, which token an OperationCanceledException carries, exception filters, and translating your own timeout for callers.
- First step
- Every stop through the linked token throws the same exception type, so the catch cannot tell the reasons apart by type. Check how the code decides "user": if it compares the exception's token with the timeout's, remember that the exception carries the linked token.
- Follow-ups
-
- Which condition really identifies the timeout?
- Should the method throw TimeoutException or OperationCanceledException on its own timeout?
- What should be logged and alerted on for each case?
From the lesson Cancellation tokens and timeouts
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
You are writing a public library method that reads a large file and calls a remote service. Design its cancellation contract: the parameters, what it throws, how it treats a timeout of its own, and what it must never do with the caller's token.
- What it tests
- API design for cancellation, token placement, exception types, linked sources and their disposal, and the rules a library owes its callers.
- First step
- Start from the signature: a CancellationToken as the last parameter, defaulting to default, passed to every call inside. Then decide what callers see for each way of stopping, and which resources the method creates and must dispose.
- Follow-ups
-
- Should the method also offer a timeout parameter?
- What happens if the caller's token is already cancelled when the method starts?
- How do you document and test the contract?
From the lesson Cancellation tokens and timeouts
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
An export endpoint loads 2 million order rows into a List<Order>, serialises the list and sends it. At peak the service runs out of memory. Rewrite its data path with async streams, and say what changes for memory, the time to the first byte and cancellation.
- What it tests
- Async iterators over a data source, streaming serialisation, where memory goes in each design, and passing the request's token through a stream.
- First step
- Find where all rows are held at once: the list, then the serialised text. Replace the list with an async iterator that yields rows as the reader returns them, and serialise the stream as it is read, so memory holds a buffer instead of 2 million objects.
- Follow-ups
-
- What stays in memory once the export streams?
- What happens when the client disconnects halfway?
- What do you lose compared with building the whole list first?
From the lesson Async streams with IAsyncEnumerable
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A dashboard stops reading a sensor stream with WithCancellation(token), but the sensor connection stays open and keeps polling. The async iterator takes a CancellationToken parameter and passes it to every call. What is wrong, how do you prove it, and how do you fix it?
- What it tests
- How a token reaches an async iterator, the EnumeratorCancellation attribute, warning CS8425, and checking that the fix stops the producer.
- First step
- Ask how the token of WithCancellation gets into the iterator's body: only through a parameter marked [EnumeratorCancellation]. Look for that attribute, and for warning CS8425 in the build output of the iterator's project.
- Follow-ups
-
- Why does passing the token as an argument work while WithCancellation does not?
- What happens when both kinds of token are given?
- How do you stop this from coming back?
From the lesson Async streams with IAsyncEnumerable
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
A worker reads messages from a queue faster than it can write them to a database. Would you connect the reading and the writing with an IAsyncEnumerable<T> or a Channel<T>? Explain back-pressure in both, and what happens when the writer fails.
- What it tests
- Pull-based async streams against buffered channels, bounded capacity, the pace of a producer that cannot wait, and failure handling between two loops.
- First step
- Decide whether the reading can wait for the writing. An async stream pulls, so the reader would fetch the next message only when the writer asks; a channel lets both run at their own pace, with a bounded buffer between them.
- Follow-ups
-
- What does a bounded channel do when it is full?
- How does the reader learn that the writer has failed?
- Which design loses messages on a crash, and how do you prevent it?
From the lesson Async streams with IAsyncEnumerable
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to mid
A WPF button handler calls customerClient.GetCustomerAsync(42).Result, and the window freezes for good on the first click. The same client works in a console tool and in ASP.NET Core. Explain step by step why, and fix it in the handler and in the library.
- What it tests
- The single-threaded context of a UI thread, what an await captures, why blocking that thread deadlocks, and the fixes for application code and for library code.
- First step
- Ask which thread must run the rest of GetCustomerAsync after its first await: the UI thread, because the await captured its context. Then ask what that thread is doing at that moment: waiting in .Result. That cycle is the deadlock.
- Follow-ups
-
- Why do the console tool and ASP.NET Core not hang?
- Would .Wait() or GetAwaiter().GetResult() fix it?
- The library is not yours to change. What now?
From the lesson SynchronizationContext, ConfigureAwait and deadlocks
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A teammate wants a rule that every await in the solution, including the WPF app's view models and click handlers, gets ConfigureAwait(false) to prevent deadlocks. Do you agree? Say where it helps, where it breaks code, and what you would put in the coding guidelines.
- What it tests
- Where ConfigureAwait(false) belongs, what it breaks in UI code, the cases where it does nothing, and enforcing a library rule without harming applications.
- First step
- Split the code by who calls it: library code that runs under any caller, and application code that knows its context. Then ask what the code after each await needs: a view model that updates bound properties needs the UI thread.
- Follow-ups
-
- What does CA2007 do, and where would you enable it?
- Does ConfigureAwait(false) matter in ASP.NET Core?
- Is ConfigureAwait(false) on the first await of a method enough?
From the lesson SynchronizationContext, ConfigureAwait and deadlocks
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
A plugin host calls your plugin through a synchronous interface method, string Load(string key), on its UI thread, and the only storage client you have is async. Design the safest Load, say what can still go wrong, and how you would test it before shipping.
- What it tests
- Sync-over-async when it cannot be avoided: moving the call off the context, timeouts, the cost of a blocked UI thread, starvation, and testing under several kinds of thread.
- First step
- Assume the worst caller: a UI thread whose context runs callbacks one at a time. Make sure nothing the client does after its awaits needs that thread, or start the whole call somewhere else, and decide how long the host may stay blocked.
- Follow-ups
-
- How long may Load block the host's UI thread?
- The client is a third-party package you cannot change. What then?
- How do you test it for a deadlock in CI?
From the lesson SynchronizationContext, ConfigureAwait and deadlocks
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A memory profile of a pricing service shows 70 MB a second of Task<decimal> objects, all created by GetPriceAsync, which answers 95 % of its calls from an in-memory cache. What do you measure first, what do you change, and what must callers of the method stop doing?
- What it tests
- Why a cached result still allocates a task, measuring per-path costs, ValueTask and split fast paths, pooling, and the consumption rules that a ValueTask imposes on callers.
- First step
- Turn the 70 MB into calls: at roughly 72 to 80 bytes a task, it is about a million calls a second. Check that rate in the metrics, then measure the bytes of a hit and of a miss separately before changing anything.
- Follow-ups
-
- Is the public signature worth changing?
- What if most calls miss the cache?
- How do you stop callers from misusing the new return type?
From the lesson ValueTask, pooling and async overhead
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
After a library upgrade, a socket reader sometimes returns data that belongs to the next message. The code stores the ValueTask<int> returned by ReadAsync, awaits it once to log the count and again to use it. Explain the bug, why tests never caught it, and fix it.
- What it tests
- The once-only rule of ValueTask, reused sources behind it, why the misuse passes until reuse, and the fixes: await once, AsTask, Preserve and analyzer rule CA2012.
- First step
- Count how many times each ValueTask is consumed: here twice. Then ask what object is behind it after the upgrade, and what that object is doing by the time the second await runs.
- Follow-ups
-
- Why did it work before the upgrade?
- How do you prove the cause with a test?
- Is Preserve() an alternative to AsTask()?
From the lesson ValueTask, pooling and async overhead
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
You are designing the public interface of a storage client that thousands of applications will use. For Get, Put and Enumerate, would you return Task<T>, ValueTask<T> or IAsyncEnumerable<T>? Defend each choice by what it costs and what it promises callers.
- What it tests
- Choosing return types from synchronous completion and call frequency, the contract cost of ValueTask for unseen callers, streaming results, and binary compatibility of public APIs.
- First step
- For each method, ask how often its result is ready without waiting, and what callers will do with the value: await it once, keep it, or combine it. Then weigh the allocation saved against a stricter contract for every caller.
- Follow-ups
-
- What happens if the choice turns out wrong?
- How do you protect callers of the ValueTask methods?
- Would you pool inside the client?
From the lesson ValueTask, pooling and async overhead
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
After a release, an ASP.NET Core API's latency under load rose from 50 ms to 20 seconds while CPU usage stayed near 15 %, and nothing logs an error. The release replaced an awaited repository call with a synchronous wrapper that calls .Result. Diagnose it with what you can see in production, and fix it.
- What it tests
- Recognising thread-pool starvation from sync-over-async, its production signature in the pool's counters and thread stacks, the real fix and the cost of stopgaps.
- First step
- Read the two numbers together: latency up a hundredfold while the processor idles means threads that wait, not work. Then look at what each request's thread waits for after the change: a task whose completion needs a pool thread of its own.
- Follow-ups
-
- Why is the CPU usage low?
- Why did the tests not catch it?
- Would wrapping the call in Task.Run help?
From the lesson Async pitfalls and thread-pool starvation
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
An order service writes an audit record with _ = auditClient.WriteAsync(order) so that the response is not delayed. Auditors find records missing after every deployment and after network blips, and the logs show no errors. Explain both losses and redesign the audit path.
- What it tests
- Unobserved task failures, work cut off at shutdown, tracked background queues with hosted services, bounded channels, and durability against crashes.
- First step
- Ask who owns the task after the handler returns: nobody. Then follow it through the two events: a failed write that nothing observes, and a process that stops while writes are still in flight.
- Follow-ups
-
- Why do the logs show no errors?
- Is a ContinueWith with OnlyOnFaulted enough?
- What happens when the queue is full?
From the lesson Async pitfalls and thread-pool starvation
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
You lead the review of a large codebase that is moving from synchronous to async code. Write the checklist you would enforce for async code, say which tool automates each item, and what still needs a human reviewer.
- What it tests
- The common async pitfalls, the analyzer rules that find some of them and their default settings, rollout on an existing codebase, and what only review or load testing can catch.
- First step
- List the shapes that fail: async void, blocking on tasks, unowned tasks, misused ValueTasks, missing ConfigureAwait in libraries, Task.Run wrappers. Then mark which ones an analyzer reports and which it cannot see.
- Follow-ups
-
- How do you roll the rules out on a large codebase?
- What does a load test add?
- Which item matters most in a server?
From the lesson Async pitfalls and thread-pool starvation
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
All C# & .NET (C# 14, .NET 10 LTS) interview questions · The C# & .NET (C# 14, .NET 10 LTS) track