42 C# Interview Questions for Experienced Developers (2026, with Proof)

Senior C# interviews in 2026 don’t ask you to define a class anymore. They hand you five lines of code and ask what it prints, then ask why.

The C# interview questions for experienced developers below are the ones I keep seeing in US loops: type system traps, garbage collection, async/await, LINQ, modern C# 14 features, performance, design, and behavioral questions, each with a short answer you can say out loud.

In this tutorial, I’ll group 42 questions by area, and for the eight trickiest ones I’ll show a small .NET 10 program with its real output, so you can prove the answer instead of reciting it.

Table of Contents

How Do Senior C# Interviews Work at US Companies?

Most US companies run the same four-stage loop for a senior .NET role. Knowing which stage tests what lets you study the right things.

StageLengthWhat they test
Recruiter or phone screen30-45 minRapid-fire C# and .NET questions, past projects, salary range
Live coding45-60 minA small problem in a shared editor; clean C#, tests, talking while you code
System design45-60 minAn API or service: data model, caching, async I/O, failure handling
Behavioral30-45 minOwnership, conflict, mentoring, a production incident

The questions in this guide map to the phone screen and the follow-ups in live coding. If the role is mostly web APIs, pair this list with my .NET Core interview questions and answers.

Live coding tasks are usually small: reverse a number, check a palindrome, print a Fibonacci series. I’ve written up the Fibonacci series program in C# and a palindrome number check if you want warm-up reps.

C# Language and Type System Interview Questions

1. What’s the difference between value types and reference types?

A value type variable holds the data itself, and assignment copies it. A reference type variable holds a reference to an object on the managed heap, and assignment copies only that reference.

Structs, enums, and the numeric types are value types. Classes, records, arrays, strings, and delegates are reference types. My data types in C# guide lists every built-in type.

Don’t say “value types live on the stack.” A struct field inside a class lives on the heap with the class. Interviewers love that follow-up.

2. When would you pick a struct over a class?

I pick a struct for small, immutable values that behave like a number: a point, a money amount, a date range. Microsoft’s guideline is roughly 16 bytes or less, short-lived, and rarely boxed.

The catch is copy semantics. This program proves what happens when you copy a struct, copy a class reference, and read a struct back out of a list.

var pointA = new PointStruct { X = 1 };
var pointB = pointA;          // copies the whole value
pointB.X = 99;
Console.WriteLine($"struct: a.X = {pointA.X}, b.X = {pointB.X}");

var refA = new PointClass { X = 1 };
var refB = refA;              // copies the reference
refB.X = 99;
Console.WriteLine($"class:  a.X = {refA.X}, b.X = {refB.X}");

var points = new List<PointStruct> { new() { X = 1 } };
var item = points[0];         // the indexer returns a copy
item.X = 50;
Console.WriteLine($"list item after edit: {points[0].X}");
// points[0].X = 50;  -> error CS1612: not a variable

struct PointStruct { public int X; }
class PointClass { public int X; }

Output:

struct: a.X = 1, b.X = 99
class:  a.X = 99, b.X = 99
list item after edit: 1
C# struct vs class copy semantics program and output in VS Code terminal
Changing the struct copy leaves the original alone, while both class variables see 99. The list indexer hands back a copy.

The last line trips up a lot of people. List<T>‘s indexer returns a copy of the struct, so editing item doesn’t touch the list.

Writing points[0].X = 50 won’t even compile. You’ll find more in my structures in C# tutorial.

3. What’s the difference between ==, Equals, and ReferenceEquals for strings?

For two string variables, == compares the text. ReferenceEquals asks if they’re the same object. The trap is == on variables typed as object: that’s a reference comparison, even when both hold strings.

string a = "Seattle";
string b = "Seattle";                          // literal: same interned object
string c = new string("Seattle".ToCharArray()); // built at run time

Console.WriteLine($"a == b               : {a == b}");
Console.WriteLine($"ReferenceEquals(a, b): {ReferenceEquals(a, b)}");
Console.WriteLine($"a == c               : {a == c}");
Console.WriteLine($"ReferenceEquals(a, c): {ReferenceEquals(a, c)}");

object objA = a, objC = c;
Console.WriteLine($"(object)a == (object)c: {objA == objC}");   // reference compare!
Console.WriteLine($"objA.Equals(objC)     : {objA.Equals(objC)}");
Console.WriteLine($"Interned c is a       : {ReferenceEquals(string.Intern(c), a)}");

Output:

a == b               : True
ReferenceEquals(a, b): True
a == c               : True
ReferenceEquals(a, c): False
(object)a == (object)c: False
objA.Equals(objC)     : True
Interned c is a       : True
C# string equality and string interning interview program with output in VS Code
Literals are interned, so a and b are the same object. A string built at run time isn’t, until you call string.Intern.

Two equal literals point to the same interned object, which is why ReferenceEquals(a, b) is true. objA == objC is false because the compiler picks object‘s reference equality. For case-insensitive compares, see comparing two strings in C#.

4. What are boxing and unboxing, and why do they matter?

Boxing wraps a value type in a heap object so it can be treated as object or an interface. Unboxing copies it back out, and it needs the exact type.

int count = 42;
object boxed = count;            // boxing: copies the value to the heap
count = 7;
Console.WriteLine($"count = {count}, boxed = {boxed}");

object x = 42, y = 42;
Console.WriteLine($"x == y      : {x == y}");        // two boxes, reference compare
Console.WriteLine($"x.Equals(y) : {x.Equals(y)}");

int unboxed = (int)boxed;        // unboxing: exact type required
Console.WriteLine($"unboxed     : {unboxed}");
try
{
    long wrong = (long)boxed;    // compiles, fails at run time
}
catch (InvalidCastException)
{
    Console.WriteLine("(long)boxed : InvalidCastException");
}

Output:

count = 7, boxed = 42
x == y      : False
x.Equals(y) : True
unboxed     : 42
(long)boxed : InvalidCastException

The box is a snapshot, so changing count later doesn’t change boxed. Two boxes of 42 aren’t ==, and unboxing an int as long throws at run time.

Generics killed most boxing, but it still sneaks in through ArrayList, non-generic interfaces, and string formatting of structs. The full story is in boxing and unboxing in C#.

5. What’s the difference between const and readonly?

const is a compile-time constant, implicitly static, and limited to primitives, strings, and null. Its value is copied into every assembly that uses it.

readonly is set at run time, in the declaration or a constructor, and holds any type. Change a public const in a library, and callers keep the old value until they recompile. That’s why I use static readonly for public values that might change.

6. How do ref, out, and in parameters differ?

All three pass by reference. ref needs an initialized variable and may change it, out doesn’t need initialization but must be assigned before the method returns, and in is read-only, which is handy for passing a large struct without copying it.

A classic live-coding warm-up is swapping two numbers in C#. A ref swap method or a tuple swap both make good talking points.

7. What do virtual, override, new, and sealed do?

virtual lets a derived class replace a method with override, and the call is resolved at run time. new hides the base method instead, so the version you get depends on the variable’s declared type.

sealed stops further inheritance or overriding. I cover the runtime dispatch rules in method overriding in C# and the basics in inheritance in C#.

8. Abstract class or interface: how do you choose?

Use an interface for a capability that unrelated types share, like IComparable. A class can implement many. Use an abstract class when related types share state or constructor logic, since a class has only one base.

Since C# 8, interfaces can carry default implementations, so the line is blurrier. I still reach for interfaces first because they’re easier to mock. Compare the two in abstract class in C# and interface in C#.

9. What’s the difference between var and dynamic?

var is static typing with inference; the compiler picks the type and checks every call. dynamic skips compile-time checks and binds members at run time, so typos become RuntimeBinderException.

I only use dynamic for COM interop or truly shapeless data. For JSON I’d rather deserialize into a record.

10. What do nullable reference types actually change?

With <Nullable>enable</Nullable>, string means “never null” and string? means “might be null.” The compiler warns when you dereference a maybe-null value.

It’s compile-time analysis only, so nothing changes at run time. You still need guards at public boundaries, like the ones in checking for null values in C#.

Memory Management, GC, and IDisposable Interview Questions

11. How does the .NET garbage collector work?

The GC is generational. New objects go into generation 0, survivors get promoted to 1 and then 2, and most collections only scan gen 0 because most objects die young.

Objects of 85,000 bytes or more go to the large object heap, which is collected with gen 2. The GC finds live objects by walking from roots: statics, locals on the stack, and CPU registers.

A good senior answer also mentions workstation vs server GC. ASP.NET Core apps use server GC by default, with a heap per core.

12. What’s the difference between Dispose and a finalizer?

Dispose is deterministic cleanup that you call, usually through using. A finalizer runs on the GC’s finalizer thread at some unknown time, and only when the object is collected.

You need a finalizer only when you own a raw unmanaged handle, and in modern code SafeHandle does that for you. The full pattern, with GC.SuppressFinalize, is in dispose and finalize in C#.

13. What does the using statement compile to?

A try/finally that calls Dispose() in the finally block. The C# 8 using declaration, using var file = ...;, disposes at the end of the enclosing scope.

For async resources, await using calls DisposeAsync() from IAsyncDisposable. Database connections and streams both support it.

14. How do memory leaks happen in managed C# code?

Something long-lived keeps a reference to something that should’ve died. The usual suspects are event handlers that are never removed, static caches that only grow, timers, and closures that capture a big object.

I find them with dotnet-counters to watch heap size, then dotnet-gcdump and a diff of two snapshots. Saying you’ve done this in production is worth more than any definition.

Async/Await and Task Interview Questions

15. What does await actually do?

The compiler turns an async method into a state machine. At an await on an incomplete task, the method returns to its caller and the rest runs as a continuation when the task finishes.

Async isn’t about threads. Awaiting a network call holds no thread at all while it waits, which is why async I/O scales so well in ASP.NET Core.

16. Why is async void dangerous?

An async Task method stores its exception in the returned task, and await rethrows it. An async void method has nothing to return, so its exception goes straight to the thread pool. That crashes the process.

try { await SaveOrderAsync(); }              // async Task: the exception lives in the Task
catch (InvalidOperationException ex) { Console.WriteLine($"Caught from Task: {ex.Message}"); }

try
{
    SaveOrderVoid();                          // async void: nothing to await
    Console.WriteLine("async void returned, try block finished");
}
catch (InvalidOperationException) { Console.WriteLine("This line never runs"); }

await Task.Delay(500);                        // the async void exception crashes us here
Console.WriteLine("Never printed");

static async Task SaveOrderAsync()
{
    await Task.Delay(50);
    throw new InvalidOperationException("Order 1001 failed (Task)");
}

static async void SaveOrderVoid()
{
    await Task.Delay(50);
    throw new InvalidOperationException("Order 1002 failed (async void)");
}

Output (exit code is non-zero):

Caught from Task: Order 1001 failed (Task)
async void returned, try block finished
Unhandled exception. System.InvalidOperationException: Order 1002 failed (async void)
   at Program.<<Main>$>g__SaveOrderVoid|0_1() in C:\DotNetDemo\CsInterviewDemo\AsyncVoidCrash.cs:line 23
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__124_1(Object state)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
   at System.Threading.Thread.StartCallback()
C# async void vs async Task exception handling interview example in VS Code terminal
The Task exception is caught. The async void exception skips the try/catch and terminates the app with an unhandled exception.

Look at the second try block: it finished cleanly, and the crash came later during Task.Delay. Microsoft’s guidance is to use async void only for event handlers.

If you need fire-and-forget, return a Task anyway and log its failures. I cover the catch-and-log side in try-catch in C#.

17. Why can .Result or .Wait() deadlock?

In an app with a single-threaded synchronization context, like WinForms, WPF, or classic ASP.NET, .Result blocks the UI thread. The continuation needs that same thread to finish, so both wait forever.

ASP.NET Core has no synchronization context, so it doesn’t deadlock there, but blocking still burns thread-pool threads. The fix is “async all the way” and ConfigureAwait(false) in library code.

18. How do you run tasks in parallel and handle their errors?

Start the tasks first, then await Task.WhenAll(tasks). Awaiting rethrows only the first exception; the task’s Exception property holds an AggregateException with all of them.

For CPU-bound loops I’d use Parallel.ForEachAsync with a MaxDegreeOfParallelism, so you don’t hammer a downstream API.

19. How does cancellation work with CancellationToken?

It’s cooperative. The caller owns a CancellationTokenSource, passes its token down, and the callee checks ThrowIfCancellationRequested() or passes the token into every async API it calls.

In ASP.NET Core, accept a CancellationToken in the endpoint and it fires when the client disconnects. Forgetting to pass it through is a very common code review comment.

20. When should you return ValueTask instead of Task?

When a hot method usually completes synchronously, like a cache hit. ValueTask<T> is a struct, so it avoids allocating a Task on that path.

The rules: await it once, never await it twice, and don’t call .Result before it completes. If you’re not measuring a hot path, plain Task is the right default.

21. How do you make shared state thread-safe?

Prefer not sharing it. When you must, use lock for compound updates, Interlocked for single counters, and ConcurrentDictionary for shared lookups.

.NET 9 added the System.Threading.Lock type, and C# 13’s lock statement uses it automatically. volatile only guarantees visibility of single reads and writes, never atomic increments.

LINQ and Collections Interview Questions

22. What is deferred execution in LINQ?

Most LINQ operators build a query, they don’t run it. The query runs each time you enumerate it, which means it sees later changes to the source and repeats its work.

var orders = new List<decimal> { 120m, 45m, 300m };
int filterCalls = 0;

var bigOrders = orders.Where(o => { filterCalls++; return o > 100m; });
Console.WriteLine($"After defining query: filter ran {filterCalls} times");

orders.Add(500m);                       // added AFTER the query was written
Console.WriteLine($"Count: {bigOrders.Count()}");   // runs now, sees 500
Console.WriteLine($"Sum:   {bigOrders.Sum()}");     // runs AGAIN
Console.WriteLine($"Filter ran {filterCalls} times for 4 items");

var snapshot = bigOrders.ToList();       // materialize once
orders.Add(900m);
Console.WriteLine($"snapshot: {snapshot.Count}, live query: {bigOrders.Count()}");

Output:

After defining query: filter ran 0 times
Count: 3
Sum:   920
Filter ran 8 times for 4 items
snapshot: 3, live query: 4
C# LINQ deferred execution interview example showing the filter running 8 times in VS Code
The filter didn’t run when the query was defined. Count() and Sum() each ran it over all four orders, and ToList() froze a snapshot.

The 500 order was added after the query, yet Count() returned 3. And the filter ran 8 times for 4 items. With EF Core, that double enumeration means two database round trips.

Call ToList() or ToArray() once when you’ll reuse the result. The C# List tutorial covers what you get back.

23. What’s the difference between IEnumerable and IQueryable?

IEnumerable<T> runs LINQ in memory with delegates. IQueryable<T> builds an expression tree that a provider, such as EF Core, translates into SQL.

The classic bug: calling AsEnumerable() or ToList() too early, which pulls a whole table into memory before the Where runs.

24. How does yield return work?

The compiler turns the method into an iterator that produces one item per MoveNext() call and keeps its local state between calls. It’s lazy, just like LINQ.

Two gotchas: argument checks in an iterator don’t run until the first MoveNext(), and a finally block runs only when enumeration ends or the enumerator is disposed. A plain foreach loop in C# disposes it for you.

25. Which collection would you pick, and what’s its lookup cost?

NeedCollectionLookup
Ordered list, index accessList<T>O(1) by index, O(n) by value
Lookup by keyDictionary<TKey,TValue>O(1) average
Unique items, fast ContainsHashSet<T>O(1) average
Sorted by keySortedDictionary<TKey,TValue>O(log n)
FIFO / LIFO workQueue<T>, Stack<T>O(1) at the ends
Shared across threadsConcurrentDictionary<TKey,TValue>O(1) average

If you call list.Contains inside a loop, a HashSet usually turns O(n²) into O(n). I go deeper on keyed lookups in Dictionary in C#, and on the legacy Hashtable in C#.

26. First, FirstOrDefault, or Single?

First throws on an empty sequence, FirstOrDefault returns the default value, and Single also throws when there’s more than one match.

I use Single when duplicates would mean bad data, such as a lookup by primary key, because it fails loudly instead of hiding the problem.

Generics, Delegates, and Events Interview Questions

27. What does this loop of lambdas print?

This is my favorite whiteboard question. A lambda captures the variable, not its value, and a for loop has one i shared by every iteration.

var actions = new List<Action>();

for (int i = 0; i < 3; i++)
    actions.Add(() => Console.Write($"{i} "));       // captures ONE variable i

Console.Write("for loop:     ");
foreach (var act in actions) act();

actions.Clear();
foreach (var city in new[] { "Austin", "Denver", "Boston" })
    actions.Add(() => Console.Write($"{city} "));    // fresh variable per iteration

Console.Write("\nforeach loop: ");
foreach (var act in actions) act();

actions.Clear();
for (int i = 0; i < 3; i++)
{
    int copy = i;                                    // the fix: copy inside the loop
    actions.Add(() => Console.Write($"{copy} "));
}
Console.Write("\nfixed for:    ");
foreach (var act in actions) act();
Console.WriteLine();

Output:

for loop:     3 3 3 
foreach loop: Austin Denver Boston 
fixed for:    0 1 2
C# closure capture in a for loop vs foreach loop interview question with output in VS Code
Every lambda in the for loop sees the final i, which is 3. foreach gets a fresh variable per iteration since C# 5, and copying i fixes the for loop.

By the time the lambdas run, the loop has finished and i is 3. Copying it into a local inside the loop body gives each lambda its own variable. Older lambda syntax has the same rule, as shown in anonymous methods in C#.

28. What’s the difference between a delegate and an event?

A delegate is a type-safe reference to one or more methods. An event wraps a delegate field so outside code can only += and -=; it can’t invoke it or wipe the list with =.

That restriction is the whole point: only the owning class raises the event. See delegates in C# and events in C# for the standard EventHandler<T> pattern.

29. What are Func, Action, and Predicate?

Built-in generic delegates. Func<T, TResult> returns a value, Action<T> returns void, and Predicate<T> returns bool.

Modern code rarely declares custom delegate types. The exception is when you need ref or out parameters, or a name that documents intent.

30. What are generic constraints and variance?

Constraints such as where T : class, new(), notnull, or a base type let the compiler allow members on T. Variance applies only to interfaces and delegates.

out T is covariant, so an IEnumerable<string> is an IEnumerable<object>. in T is contravariant, so an Action<object> works as an Action<string>. List<T> is invariant.

31. How do extension methods work, and what changed in C# 14?

An extension method is a static method whose first parameter has this, so you can call it like an instance method. It can’t touch private members.

C# 14 adds extension blocks, which also allow extension properties and static extension members. Instance members always win over extensions with the same signature.

Modern C# 12, 13, and 14 Interview Questions

32. How is record equality different from class equality?

Records get compiler-generated value equality: two records are equal when their types match and every property is equal. Classes compare references unless you override Equals.

var r1 = new CustomerRecord("Jenna Moore", "Seattle");
var r2 = new CustomerRecord("Jenna Moore", "Seattle");
var c1 = new CustomerClass("Jenna Moore", "Seattle");
var c2 = new CustomerClass("Jenna Moore", "Seattle");

Console.WriteLine($"record == record : {r1 == r2}");
Console.WriteLine($"class  == class  : {c1 == c2}");

var moved = r1 with { City = "Denver" };     // non-destructive copy
Console.WriteLine($"with             : {moved}");
Console.WriteLine($"original         : {r1}");

var tags1 = new TaggedRecord("Ryan", ["vip"]);
var tags2 = new TaggedRecord("Ryan", ["vip"]);
Console.WriteLine($"record with list : {tags1 == tags2}");   // shallow equality

record CustomerRecord(string Name, string City);
record TaggedRecord(string Name, List<string> Tags);
class CustomerClass(string name, string city)
{
    public string Name { get; } = name;
    public string City { get; } = city;
}

Output:

record == record : True
class  == class  : False
with             : CustomerRecord { Name = Jenna Moore, City = Denver }
original         : CustomerRecord { Name = Jenna Moore, City = Seattle }
record with list : False
C# record equality vs class equality and with expression interview example in VS Code
Two records with the same data are equal and two classes aren’t. The with expression copies, and a List property makes equality shallow.

The last line is the senior-level follow-up. Equality compares a List by reference, so two records with identical tags aren’t equal. with is also a shallow copy, so both records would share the list.

33. What did C# 12 add that you actually use?

Primary constructors on classes and structs, as in class OrderService(IOrderRepository repo), and collection expressions like int[] zips = [78701, 98101];.

Watch the primary constructor detail: parameters are captured, not turned into properties, and they’re mutable. I explain regular constructors in constructors in C#.

34. What’s new in C# 13?

params collections, so params ReadOnlySpan<T> works; the new Lock type; partial properties and indexers; ref struct types implementing interfaces; and the \e escape. C# 13 shipped with .NET 9.

Partial properties matter mostly to source generators. If partial types are new to you, start with the partial class in C# tutorial.

35. What’s new in C# 14 and .NET 10?

C# 14 ships with .NET 10, an LTS release supported for three years. The features you’ll be asked about are the field keyword, extension members, null-conditional assignment, implicit Span<T> conversions, and nameof(List<>).

customer?.Order = GetCurrentOrder(); now assigns only when customer isn’t null, and it doesn’t call the method at all when it is. Partial constructors and events and user-defined compound assignment round out the list.

36. How do you use pattern matching in real code?

Switch expressions with property and relational patterns replace long if/else chains: order switch { { Total: > 500m } => "review", { State: "TX" } => "local", _ => "standard" }.

List patterns, added in C# 11, match arrays and lists by shape, like [var first, .., var last]. The older switch case in C# statement still works, but expressions read better.

C# Performance Interview Questions: Span<T> and Allocations

37. What is Span<T>, and why does it cut allocations?

Span<T> is a ref struct view over contiguous memory: an array, a string, stack memory, or native memory. Slicing it creates no new object.

This program measures the bytes each approach allocates with GC.GetAllocatedBytesForCurrentThread(), after a warm-up call so first-run setup doesn’t skew the numbers.

using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");

string csv = "TX,Austin,78701";
_ = csv.Substring(3, 6); _ = int.Parse(csv.AsSpan(10));   // warm up (first-call setup)

long before = GC.GetAllocatedBytesForCurrentThread();
string city = csv.Substring(3, 6);                     // new string on the heap
long substringBytes = GC.GetAllocatedBytesForCurrentThread() - before;

before = GC.GetAllocatedBytesForCurrentThread();
ReadOnlySpan<char> citySpan = csv.AsSpan(3, 6);        // a view over the same memory
int zip = int.Parse(csv.AsSpan(10));                   // parse without a substring
long spanBytes = GC.GetAllocatedBytesForCurrentThread() - before;

Console.WriteLine($"Substring: {city} allocated {substringBytes} bytes");
Console.WriteLine($"AsSpan:    {citySpan} + zip {zip} allocated {spanBytes} bytes");

Output:

Substring: Austin allocated 40 bytes
AsSpan:    Austin + zip 78701 allocated 0 bytes

The Substring call allocated a 40-byte string. Slicing with AsSpan and parsing the ZIP code straight from the span allocated nothing.

Because it’s a ref struct, a span can’t be a field in a class, be boxed, or be captured in a lambda. Use Memory<T> when you need to store it.

38. How do you find and prove a performance problem?

Measure first. I use BenchmarkDotNet with [MemoryDiagnoser] for micro-benchmarks, dotnet-counters for live GC and thread-pool stats, and dotnet-trace for CPU hot paths.

The usual wins are fewer allocations in hot loops, StringBuilder for many concatenations, ArrayPool<T> for big buffers, and a dictionary instead of a list scan. I compare the string options in string vs StringBuilder in C#.

Design, Architecture, and Testing Interview Questions

39. Explain the DI lifetimes and the captive dependency bug.

ASP.NET Core’s container has three lifetimes. Transient creates an instance every time, scoped creates one per request, and singleton creates one for the whole app.

A captive dependency is a singleton that takes a scoped service, such as a DbContext, and keeps it forever. The default host validates scopes in Development and throws at startup, so mention that.

Tie it to the OOP concepts in C#: constructor injection plus interfaces is dependency inversion, the D in SOLID, and it’s what makes code testable.

40. How would you write a thread-safe singleton?

In an ASP.NET Core app, I’d register the class with AddSingleton and let the container own it. Outside DI, a static readonly Lazy<T> is thread-safe by default and creates the instance on first use.

Then say the trade-off out loud: a hand-rolled static singleton is global state, and global state is hard to test.

41. How do you unit test a class that calls a database or an API?

Put the dependency behind an interface, inject it, and pass a fake or a mock in the test. I use xUnit with NSubstitute or Moq, and name tests Method_State_ExpectedResult.

For data access, I add a few integration tests against a real database in a container. Mocked EF Core queries don’t prove the SQL works.

42. What’s your error-handling strategy, and is throw ex wrong?

Yes, throw ex; resets the stack trace to the current line. Use throw; to rethrow, or wrap it with new OrderException("...", ex) to add context.

My strategy: catch only what you can handle, use exception filters with when, log once at the boundary, and return ProblemDetails from APIs. Custom exceptions are worth it when callers need to react to a specific failure.

Behavioral Interview Questions for Experienced C# Developers

Senior loops weigh these as heavily as the technical rounds. Answer with STAR: situation, task, action, result. Keep it to two minutes with a number in the result.

QuestionWhat they’re really checking
Tell me about a production incident you owned.Calm debugging, root cause, what you changed so it can’t recur
Tell me about a time you disagreed on a design.You argue with data, then commit to the team’s decision
Tell me about a performance problem you fixed.You measured before and after, with real numbers
How do you mentor junior developers?Code reviews that teach, pairing, and patience
Tell me about a migration you led.Planning, risk control, and incremental rollout

Prepare three stories you can bend to fit most of these. For example, one .NET Framework to .NET 10 migration story usually covers ownership, risk, and a measurable result.

Which C# Interview Topics Should You Study First?

If you’ve got a week, spend it on async/await, LINQ deferred execution, and the type system. They come up in nearly every phone screen and in live coding follow-ups.

Then run the programs above yourself and change one line at a time. Explaining why the output changed is exactly what a senior interviewer wants to hear.

Interviewing for an early-career role, or brushing up on the basics behind these answers? Start with these:

Frequently Asked Questions

How many C# interview questions should a senior developer prepare?

Around 40 to 50 well-understood questions beat 200 memorized ones. Interviewers follow up on your answer, so be ready to explain why and show it in code.

Which C# version should I mention in a 2026 interview?

C# 14 with .NET 10, the current LTS release. Know the field keyword, extension members, and null-conditional assignment from What’s new in C# 14 on Microsoft Learn.

Do US companies still ask C# coding questions in live interviews?

Yes. Expect a 45 to 60 minute live coding round with a small problem, such as string or collection manipulation, plus questions about the complexity and tests for your solution.

Is async/await the most asked senior C# topic?

It’s one of the top three, along with LINQ and the type system. Expect questions on async void, deadlocks from .Result, Task.WhenAll, and CancellationToken.

Should I learn .NET Framework or .NET 10 for interviews?

Focus on .NET 10 and ASP.NET Core. Many US teams still maintain .NET Framework 4.8 apps, so be ready to talk about migrating one.

What’s the difference between C# and .NET?

C# is the programming language. .NET is the runtime, libraries, and SDK that compile and run it, along with F# and Visual Basic.