GAP·MAP
← all posts
July 11, 2026questions

"C# interview questions: CLR, async/await, LINQ"

Most .NET loops circle the same three areas: how the CLR stores your types, what async/await actually compiles into, and when a LINQ query runs. The questions repeat, and interviewers grade the mechanism behind your answer, not the vocabulary. This list comes from the rubrics behind our C# modules and real loop reports, and it routes you into the free notes for each area.

At a glance, where each trap lives:

AreaTypical questionThe lesson
CLR memoryboxing, mutable struct in a List<T>, where value types liveCLR memory & types
async/awaitthe sync-over-async deadlock, ConfigureAwait, Task vs ValueTaskC# async/await
LINQdeferred execution, multiple enumeration, IQueryable to SQLLINQ & collections
Concurrencythe deadlock underneath the async one, races, locksConcurrency

Value types and boxing

Where do value types live?

The textbook answer is "value types on the stack, reference types on the heap." Say that as an absolute rule and a senior interviewer pushes back. A struct field of a class lives inline on the heap inside the object. A local captured by a closure or an async state machine lives on the heap too. Storage location is a JIT implementation detail, not a property of the type. The honest answer names the exceptions before the interviewer does.

The boxing trap

Trap boxing copies the value, so mutating through the box is lost.

struct Counter { public int N; public void Inc() => N++; }

object boxed = new Counter { N = 0 };  // heap allocation + copy
((Counter)boxed).Inc();                // WRONG: unbox copies out, Inc mutates the copy
// boxed still holds N = 0

Follow-up "name three places boxing happens by accident." The strong answer: non-generic collections (ArrayList, Hashtable), casting a struct to an interface, and composite string formatting of value types. Generic collections like List<T> avoid it entirely, which is the real reason to prefer them over ArrayList.

The same copy semantics bite without a box. A mutable struct stored in a List<T> cannot be mutated in place because the indexer returns a copy, while an array element is a real variable. This is why the guidance is to make structs immutable.

Study CLR memory & types


async/await and the deadlock

What does await compile into?

The compiler rewrites the method into a state machine. It runs synchronously up to the first await on an operation that is not already complete, then returns its Task to the caller and schedules the rest as a continuation. No new thread appears. For I/O there is no thread at all while the operation is in flight.

The sync-over-async deadlock

Trap blocking on a Task that needs a captured context back deadlocks both.

// In a WPF/WinForms handler or legacy ASP.NET request:
public string Get() => GetAsync().Result;   // WRONG: blocks the one context thread

async Task<string> GetAsync() {
    var data = await httpClient.GetStringAsync(url);  // wants to resume on that same context
    return data;
}

The context runs one continuation at a time. The caller is blocking it with .Result, and the awaited method wants to post its continuation back onto that occupied context. Both wait forever. Fix it by going async all the way up, or in library code by not capturing the context in the first place:

var data = await httpClient.GetStringAsync(url).ConfigureAwait(false);  // resume on the pool

Red flags claiming await spins up a thread, or that "ConfigureAwait(false) is needed in ASP.NET Core to avoid deadlocks." ASP.NET Core has no SynchronizationContext, so this deadlock is a UI-app and library problem now. The same blocking code runs (badly) there instead of hanging.

Follow-up "Task vs ValueTask?" ValueTask<T> is a struct that wraps a result or a Task<T>, so a method that usually completes synchronously (cache hit, buffered read) returns with no heap allocation. The catch: await it once, never read .Result before completion, do not hand it to Task.WhenAll. Default to Task and reach for ValueTask only when profiling says the allocation matters.

Study C# async/await


LINQ: when does the query run?

Deferred execution

A LINQ query is a description, not a result. It runs on enumeration: foreach, ToList(), Count(). That laziness comes from the iterator state machine yield return compiles into. It also means the query captures the variable, not its value at definition time.

int threshold = 5;
var q = numbers.Where(n => n > threshold);
threshold = 100;
var result = q.ToList();   // filters against 100, not 5: reads the variable at enumeration

The multiple-enumeration trap

Trap iterating the same IEnumerable twice re-runs the whole pipeline. It does not cache.

IEnumerable<Order> orders = db.Orders.Where(o => o.Total > 100);
var count = orders.Count();          // round-trip 1
foreach (var o in orders) { ... }    // round-trip 2, and results can differ if data changed
// RIGHT: materialize once
var list = orders.ToList();

Under EF Core each enumeration is a fresh SQL round-trip. The fix is a single ToList()/ToArray() to force execution once and cache.

Follow-up "IEnumerable vs IQueryable?" IEnumerable<T> runs Func<> delegates in memory. IQueryable<T> builds an expression tree the provider translates to SQL, so your Where becomes a WHERE clause instead of pulling the table into the process. When an expression cannot be translated, EF Core 3.0+ throws at runtime rather than silently evaluating on the client. Knowing what pushes down to the database is a SQL fundamentals question wearing a C# hat.

Study LINQ & collections


Where these connect

The async deadlock is a specific case of a general one. The four Coffman conditions, why a lock ordering prevents a cycle, and how optimistic versus pessimistic locking trade off all live in Concurrency, and senior interviewers move between the two freely.

Cancellation is the other bridge. A CancellationToken is cooperative: pass it down, check it, let OperationCanceledException surface. Nobody checks it and it does nothing. In a web service the same token often carries the request timeout, which is where async design meets the request contract questions in API design.

Red flags across the loop: treating "the stack" as a rule for value types, blocking on async and calling it fixed, assuming a re-enumerated IEnumerable is cached, and passing a CancellationToken no code ever checks. Any one caps the score no matter how fluent the rest sounds.

The free notes for each area run from the middle-band floor to the senior signals. Start with the one your last interview stalled on: CLR memory, async/await, or LINQ.

was this useful?

beta

The interviewer part is in the works.

The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.