Skip to Content
Course content

248: Common C# Interview Questions on Threading and Async

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you're heading into a C# interview, you're almost certainly going to get grilled on async and await. Most candidates can recite the syntax, but few actually understand what's happening under the hood. I've sat on both sides of the table, and the quickest way to spot someone who doesn't actually "get" threading is when they fall for the most common myth in the .NET ecosystem.

"Async always runs on a background thread" is a lie

I see this constantly. A developer tells me that adding async to a method signature automatically pushes the work onto a different thread to keep the UI responsive. That is fundamentally wrong. async and await are about concurrency, not necessarily parallelism.

Look at this snippet:

public async Task<string> GetWelcomeMessageAsync()
{
    return "Hello there!";
}

In this case, no new thread is ever created. The method runs synchronously on the calling thread because there is no actual asynchronous operation to await. Even when you do use await, like awaiting a network call via HttpClient, there isn't necessarily a thread "sitting there" waiting for the bytes to come back from the server. Instead, the runtime uses an I/O completion port. The thread is returned to the pool to do other work, and when the network card signals the OS that the data has arrived, the runtime schedules the remainder of your method to resume.

The takeaway? async is about efficiency—doing more with fewer threads—not just throwing more threads at a problem.

Stop using Task.Run to "fix" synchronous methods

Interviewer: "How do you make a slow, CPU-bound method asynchronous?"
Bad Answer: "I just wrap it in Task.Run(() => SlowMethod()) and await it."

I can't tell you how many times I've seen this. This is called "Async over Sync," and it's an anti-pattern. All you're doing is stealing a thread from the ThreadPool to block it while another thread waits for it. You haven't made the code more scalable; you've actually added the overhead of a context switch for no gain. If a method is CPU-bound, it's synchronous. Period. The Task.Run should happen at the caller level if the caller specifically needs to offload work to keep a UI thread free, not inside the library method itself.

Why ConfigureAwait(false) is the "Senior" Answer

You'll likely be asked about ConfigureAwait(false). In modern .NET (Core/5/6+), it's less of a deal-breaker than it was in the old .NET Framework days, but it's still a critical concept for library authors. By default, when you await something, .NET tries to marshal the continuation back to the original SynchronizationContext (like the UI thread in WPF or the request context in legacy ASP.NET).

If you're writing a generic utility library that calculates hashes or parses JSON, you don't care about the UI thread. By using await someTask.ConfigureAwait(false);, you're telling the runtime: "I don't need to go back to the original thread; just finish this on whatever thread is available." This prevents potential deadlocks and slightly boosts performance by avoiding unnecessary context switches.

The "Async Void" Landmine

If you tell an interviewer that async void is just a shorthand for async Task, the interview is basically over. async void should be used almost exclusively for event handlers.

  • You can't await it: The caller has no way to know when the method finished.
  • Crash and Burn: If an exception is thrown inside an async void method, it cannot be caught by a surrounding try-catch block. It will bubble up to the SynchronizationContext and, in many cases, crash the entire process.

Always use async Task if you want the method to be awaitable and exceptions to be handled properly. If you truly want a "fire and forget" pattern, use async Task and explicitly assign the result to a discarded variable: _ = DoSomethingAsync();.




📋 Practical Task

Refactoring the Blocking API Aggregator for True Concurrency

You've inherited a piece of code that is supposed to aggregate data from three different services. However, the current implementation is "fake async"—it blocks threads and will likely deadlock in a UI environment.

Your Task: Refactor the following DataAggregator class. You must:

  1. Remove the "Async over Sync" pattern (the Task.Run inside the method).
  2. Ensure the FetchAllDataAsync method initiates all three requests concurrently rather than sequentially.
  3. Apply ConfigureAwait(false) to the internal calls to ensure the library doesn't capture the synchronization context.
public class DataAggregator 
{
    private readonly IApiService _apiService = new ApiService();

    // FIX THIS: This is currently sequential and uses "Async over Sync"
    public async Task<AggregatedResult> FetchAllDataAsync()
    {
        // This is a bad pattern: wrapping sync work in Task.Run inside the method
        var profile = await Task.Run(() => _apiService.GetProfile()); 
        var posts = await Task.Run(() => _apiService.GetPosts());
        var settings = await Task.Run(() => _apiService.GetSettings());

        return new AggregatedResult(profile, posts, settings);
    }
}

// Assume these are provided and cannot be changed:
public class ApiService 
{
    public UserProfile GetProfile() { /* Simulates heavy I/O */ return new UserProfile(); }
    public List<Post> GetPosts() { /* Simulates heavy I/O */ return new List<Post>(); }
    public UserSettings GetSettings() { /* Simulates heavy I/O */ return new UserSettings(); }
}

Hint: Think about Task.WhenAll to achieve true concurrency.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.