# C# Interview Questions for Experienced Developers: What Panels Are Really Testing

CareerCTO · 2026-08-04 · 14 min read · Interviews

Most C# interview prep is a list of questions with a paragraph of answer under
each one. That works for a junior screen. It falls apart the moment a panel
interviewing someone with five or more years of experience asks a follow-up
you did not rehearse.

Senior C# interviews are not testing whether you can define "boxing" or
recite the difference between `IEnumerable` and `IQueryable`. They are testing
whether you understand the mechanism well enough to predict what happens in a
case you have not seen before. The definition is the entry ticket. The
follow-up is the actual test.

This guide groups the questions you will meet by the signal behind them, not
by topic name. For each group we give the opening question, the follow-up
that exposes a shaky answer, and what a candidate who has actually debugged
this in production tends to say differently.

If you are also prepping the web
layer, the
[ASP.NET Core interview questions](/blog/asp-net-core-interview-questions)
guide covers the same style of drill for the pipeline and hosting model.

## Why the same question can pass or fail a candidate

Interviewers reuse the same handful of questions across candidates because
the questions are good at separating two groups: people who read the answer
once and people who have hit the behavior directly. The wording barely
changes. What changes is the follow-up.

A memorized answer describes what the documentation says. A real answer
describes what happens next, because the candidate has watched it happen -
in a debugger, in a production incident, or in code review. That is the gap
this guide is built to close.

## Signal one: do you understand the CLR memory model, not just "stack vs heap"

The stock question is "what is the difference between value types and
reference types?" Almost every candidate gets this right at the surface
level. The follow-up decides whether they understand it.

### The question that actually separates candidates

"If I put a `struct` inside a `List<T>` and mutate a field through a `foreach`
loop, what happens?" A candidate who only memorized "structs are value types"
will often guess wrong here, because the mutation silently fails or does not
compile, depending on how the loop variable is used.

```csharp
struct Point { public int X; }

var points = new List<Point> { new Point { X = 1 } };

foreach (var p in points)
{
    p.X = 99; // compiles, but is a no-op on a copy
}
```

The loop variable `p` is a copy of the struct pulled out of the list. Setting
`p.X` changes the copy, not the element inside `points`. A candidate who
understands this will say so unprompted and mention that `foreach` over a
`List<T>` of structs is a common source of "my change did not take" bugs.

### Why interviewers care

This is not trivia. It is a real failure mode in codebases that mix structs
and collections for performance reasons. A candidate who has hit it once
remembers it for good. One who has not will guess "it updates the list"
because that is the intuitive but wrong answer.

### The related question: garbage collection generations

"Why does the CLR have generations at all?" The weak answer is "to make GC
faster." The stronger answer explains the actual assumption: most objects die
young, so the collector only sweeps generation 0 most of the time and
promotes long-lived survivors upward, which avoids repeatedly scanning
objects that are still alive.

The good follow-up here is "what kind of allocation pattern defeats this
assumption?" Large temporary objects and objects that get promoted to
generation 2 and then die anyway (the "mid-life crisis" pattern) are the
answer a senior candidate should reach for.

### Boxing, and why it still matters with generics

"Does calling a generic method with a `struct` type argument still cause
boxing?" is a good filter, because the honest answer is "it depends." Generic
code compiled for a value-type argument is specialized by the JIT and avoids
boxing. The moment that struct is passed through a non-generic API expecting
`object`, or stored in a non-generic collection, boxing happens again.

A candidate who has profiled a hot path for allocations will usually mention
`Span<T>` or generic constraints as the fix, because both let you keep value
types on the stack instead of promoting them to the heap unnecessarily.

![Line drawing of a magnifying glass hovering over a tangled memory diagram with boxes and arrows](/blog/c-sharp-interview-questions-for-experienced-1.webp)

## Signal two: do you understand async/await as control flow, not as a keyword pair

"What does `async` and `await` do?" is asked in almost every senior .NET
interview. Nearly everyone can say "it lets you write asynchronous code that
looks synchronous." That sentence proves nothing.

### The follow-up that matters

"What actually happens to the calling thread when you hit an `await`?" The
correct answer: the method returns control to its caller immediately, and the
rest of the method runs as a continuation, scheduled by the awaiter, when the
awaited task completes. No thread is blocked while the awaited operation is
in flight.

A common wrong mental model is that `await` "pauses" the thread the way
`Thread.Sleep` would. Candidates who hold this model tend to also misuse
`.Result` and `.Wait()` to "make async code synchronous," which is exactly
the pattern that causes deadlocks in UI and older ASP.NET applications.

### The deadlock question

"Why does calling `.Result` on a task from a UI thread sometimes deadlock,
but the same code works fine in a console app?" This question is a strong
filter because it requires understanding `SynchronizationContext`.

| Environment | Has a `SynchronizationContext`? | Deadlock risk with `.Result` |
|---|---|---|
| WPF / WinForms UI thread | Yes, captures back to the UI thread | High |
| Classic ASP.NET request | Yes, captures back to the request context | High |
| Console app | No | Low |
| ASP.NET Core | No, by default | Low |

The mechanism: `await` captures the current context by default and resumes
the continuation on it. If the UI thread is blocked waiting on `.Result`, and
the continuation needs that same thread to run, neither side can move. The
practical fix candidates should mention is `ConfigureAwait(false)` in library
code that does not need to resume on the original context.

### The question about exceptions

"If an `async void` method throws, where does the exception go?" This one
catches out even experienced developers who have only ever used `async Task`.

An exception from `async void` cannot be awaited or caught by the caller; it
is raised on the `SynchronizationContext` that was active when the method
started, which usually means it crashes the process or gets swallowed
depending on the host.

The follow-up worth having ready: "so when is
`async void` actually correct?" Answer: event handlers, because their
signature is fixed by the framework, and nowhere else.

## Signal three: do you understand LINQ's deferred execution, not just its syntax

Every candidate can write a `Where().Select()` chain. Far fewer can explain
when it actually runs.

### The core question

"When does this line actually query the database?"

```csharp
var query = dbContext.Users.Where(u => u.IsActive);
```

The correct answer is: not yet. `query` is an expression tree describing the
work, not a result. The database is hit only when the query is enumerated -
by a `foreach`, a `.ToList()`, a `.Count()`, or similar. This is deferred
execution, and it is one of the most-cited sources of subtle bugs and
performance problems in EF Core code.

### The follow-up that separates real understanding

"If I call `.Where()` twice on the same `IQueryable` in two different
methods, does the database get queried twice?" A candidate who understands
deferred execution says yes, once per enumeration, unless something
materializes the result in between with `.ToList()` or similar.

This matters
because a common bug pattern is passing an `IQueryable` around and
accidentally running the same query multiple times, or worse, running it
inside a loop.

### The N+1 question

"What is the N+1 query problem, and how would you spot it in a code review?"
This question tests whether the candidate has actually looked at generated
SQL, not just heard the term. A good answer describes lazy-loaded navigation
properties accessed inside a loop, each triggering its own round trip, and
names `.Include()` or explicit projection as the fix.

```csharp
// N+1: one query per order to fetch its customer
foreach (var order in orders)
{
    Console.WriteLine(order.Customer.Name);
}

// Fixed: one query, eager-loaded
var orders = dbContext.Orders.Include(o => o.Customer).ToList();
```

Candidates who have only used LINQ over in-memory collections sometimes
answer this question as if it were about CPU cost, not I/O round trips. That
mix-up is itself a useful signal for the interviewer.

### The IEnumerable vs IQueryable question

"You have an `IQueryable<User>` and you call `.Where(u => IsSenior(u))` where
`IsSenior` is a regular C# method, not an expression. What happens?" The
honest answer is that this throws at runtime, because `IsSenior` cannot be
translated into SQL - the provider needs an expression tree, not a compiled
method reference, to build the query.

The fix a senior candidate reaches for is either rewriting the condition as
an inline expression the provider can translate, or calling `.AsEnumerable()`
first to force the rest of the chain to run in memory against already
loaded objects.

Knowing which of those two options is appropriate, and why
switching to `.AsEnumerable()` too early can silently pull an entire table
into memory, is the real test behind this question.

## Signal four: do you understand DI lifetimes, not just the three words

"What is the difference between transient, scoped, and singleton?" is close
to universal in senior .NET interviews. The three definitions are easy to
memorize. The bug they cause is not.

| Lifetime | New instance per | Typical use |
|---|---|---|
| Transient | Every resolution | Lightweight, stateless services |
| Scoped | Per request (in ASP.NET Core) | Services that use a `DbContext` |
| Singleton | Once for the app lifetime | Configuration, caches, stateless clients |

### The question that actually matters

"What happens if a singleton service takes a scoped service as a constructor
dependency?" The correct answer is that the default container throws at
resolution time, because the singleton would otherwise hold on to the first
scope's instance forever, which is exactly the captive dependency problem the
validation is designed to catch.

A candidate who has not hit this describes the lifetimes correctly but has
no answer for what breaks when you mix them. A candidate who has hit it
describes the captive dependency by name and usually mentions the specific
symptom: a `DbContext` being reused across requests, causing stale data or
threading exceptions, because it got trapped inside a singleton.

### The disposal question

"Who disposes a scoped service, and when?" The container does, at the end of
the scope - the HTTP request, in a typical ASP.NET Core app. This is worth
knowing because it explains why manually caching a scoped service in a static
field is dangerous: you end up holding a reference to something the container
has already disposed.

### The factory pattern question

"How do you get a scoped or transient service from inside a singleton
safely?" The correct pattern is injecting `IServiceScopeFactory` into the
singleton and creating a new scope each time you need the dependency, rather
than injecting the scoped service directly. This gives the singleton a clean
way to reach into a short-lived dependency without becoming a captive itself.

A candidate who reaches for this pattern unprompted, instead of just saying
"you can't," shows they have actually had to solve this in a background
service or a hosted worker, both common places where a singleton legitimately
needs scoped data like a `DbContext`.

## Signal five: do you understand equality and comparison, not just override syntax

"How do you override equality for a class?" gets a mechanical answer:
override `Equals`, override `GetHashCode`, maybe implement `IEquatable<T>`.
The follow-up shows whether the candidate understands the contract, not just
the syntax.

### The contract question

"What breaks if `GetHashCode` is not consistent with `Equals`?" The specific
failure: objects that are `Equals` but have different hash codes can end up
in different buckets in a `Dictionary` or `HashSet`, so lookups silently fail
to find an entry that is logically present.

This is a bug that is very hard
to spot in a debugger because everything looks correct until you query the
collection.

### The records follow-up

"If C# records already generate value-based equality, why would you ever
still write a custom `Equals`?" A strong answer: when equality needs to
ignore certain fields (an audit timestamp, a cache key) or when the type has
reference-type fields, like collections, where you want deep comparison
instead of the default reference comparison records give you for those
members.

## Signal six: do you understand exceptions as control flow, not just try/catch syntax

"What is the difference between `throw ex;` and `throw;` inside a `catch`
block?" is a small question with a large gap between the memorized and real
answer. Both look identical when you run the code once and it works.

### Why the difference matters

`throw;` re-throws the current exception and preserves its original stack
trace, including every frame from where it actually happened. `throw ex;`
resets the stack trace to the current `catch` block, which throws away the
information you need to find the real source of the bug in production logs.

```csharp
try
{
    DoWork();
}
catch (InvalidOperationException ex)
{
    LogAndCleanup(ex);
    throw; // preserves the original stack trace
    // throw ex; would reset it to this line
}
```

A candidate who has debugged a production incident using a stack trace that
turned out to be useless will bring this up without being asked, because it
cost them real time once.

### The follow-up on exception filters

"When would you use `catch (Exception ex) when (condition)` instead of an
`if` check inside the catch block?" The strong answer: an exception filter
runs before the stack unwinds, so it does not disturb the original call
stack if the condition is false and the exception needs to propagate
further up.

An `if` check inside a plain `catch` has already unwound the
stack by the time you decide to re-throw, which is worse for diagnostics in
exactly the cases where you need them most.

![Line drawing of a stack trace unwinding through layered function call boxes toward an error icon](/blog/c-sharp-interview-questions-for-experienced-2.webp)

## How to actually prepare, not just read

Reading this guide once will not make these answers stick. The candidates who
do well are the ones who have caused, or debugged, at least one of these
behaviors directly. If you have not, the fastest way to build a real answer
is to write the ten-line reproduction yourself and watch it fail before you
read the explanation.

It also helps to know what the interview process on the other side actually
looks like. Our guide on
[how technical interview processes are designed](/blog/technical-interview-process-design)
explains why panels structure rounds the way they do, and why the same
question often shows up twice with different framing.

## Where this fits in a broader .NET interview

C# fundamentals are usually one round out of several. A typical senior .NET
loop also covers the web framework layer and the database layer, and skipping
either leaves a gap a panel will find. The
[.NET full stack developer roadmap](/blog/dotnet-full-stack-developer-roadmap)
lays out what a complete stack looks like if you want to check your prep
against the whole picture, not just the language.

Database questions come up constantly in these same loops, usually right
after the LINQ and EF Core section, because the two are hard to separate in
practice. If SQL is the weaker side of your prep, the
[SQL Server interview questions](/blog/sql-server-interview-questions) guide
uses the same grouped-by-signal approach for query plans, indexing, and
locking.

## What a strong answer sounds like, structurally

Across every group above, the pattern in a strong answer is the same. It
names the mechanism first, then the specific failure mode it causes, then a
concrete example the candidate has actually seen. A weak answer stops after
the definition and waits for the next question.

You do not need to have caused every failure mode above yourself. You do need
an honest answer when you have not - "I have not hit that directly, but based
on how the scheduler works, I would expect..." is a far better answer than a
confident guess, and most senior interviewers rate it accordingly.

![Line drawing of two developers at a table, one holding note cards and one holding a laptop mid-debug](/blog/c-sharp-interview-questions-for-experienced-3.webp)

## Where CareerCTO fits into this

CareerCTO does not run these interviews for employers, and a verified badge
on a profile does not mean someone has passed a C# interview - it means
Questpond's records confirm the person completed a specific cohort on a
specific date.

What it does give you is a shortcut past the resume-screening
stage: employers using the directory already trust that baseline, so the
conversation with them starts closer to a technical round like the one this
guide covers.

If you want to see what employers are actually asking for at the senior C#
level right now, browsing [reviewed job openings](/jobs) is a faster signal
than any list of interview questions, because the descriptions tell you which
of these five areas a given team cares about most.

## What to do next

Pick the one group above where your answer was closest to the "weak" version,
not the strongest one. Write the reproduction code yourself, run it, and
write down what actually happened before you move to the next group. That is
a better use of an evening than reading twenty more questions you will not
remember under pressure.

When you are ready to put this prep to use, [browse verified developer
profiles](/graduates) to see how other .NET developers present this kind of
depth on their own profile, or [build your profile](/build-a-profile) so a
completed cohort and a clear project history are the first thing an employer
sees before the interview even starts.

---

Source: https://careercto.dev/blog/c-sharp-interview-questions-for-experienced
