C# Interview Questions and Answers (2026)
C# interview questions tend to cluster around a handful of recurring themes: how the type system actually works under the hood, how async/await really executes, and how modern C# features (records, pattern matching, nullable reference types) change the way idiomatic code looks. Here are the ones that come up most often, with answers that go past the one-line definition.
-
What's the difference between value types and reference types in C#?
Value types (struct, int, bool, enum, DateTime) store their data directly on the stack (or inline within containing objects), and assigning one variable to another copies the actual data. Reference types (class, string, array, delegate) store a reference on the stack that points to data on the managed heap; assignment copies the reference, so both variables point to the same object. This affects equality (value types compare by value by default, reference types by identity unless overridden), mutation (modifying a copy of a value type doesn't affect the original), and passing to methods (value types are passed by value unless you use
ref/out/in). Understanding this distinction matters for performance (avoiding heap allocations) and for reasoning about unexpected mutation bugs. -
What is boxing and unboxing, and why does it matter for performance?
Boxing is the implicit conversion of a value type to
object(or an interface it implements), which allocates a new object on the heap and copies the value into it. Unboxing is the reverse: extracting the value type back out of the boxed object, which requires an explicit cast and a type check. Both operations have a runtime cost — boxing allocates memory and adds GC pressure; unboxing does a type-safety check. This commonly happens accidentally, e.g. storingintvalues in a non-genericArrayList, or passing a struct to a method expectingobject. Generics (List<int>instead ofArrayList) avoid boxing entirely because the type is known at compile time, which is one of the main reasons generic collections outperform their non-generic predecessors. -
How does async/await actually work under the hood?
async/awaitis syntactic sugar over the compiler generating a state machine. When a method markedasynchits anawaiton an incompleteTask, the method returns control to its caller immediately (freeing the thread), and the compiler-generated state machine registers a continuation with the awaited task. When that task completes, the continuation resumes execution — by default back on the capturedSynchronizationContext(e.g., UI thread) unless you callConfigureAwait(false). This meansasyncdoesn't inherently create new threads; it's about not blocking the calling thread while waiting for I/O-bound work. For CPU-bound work, you still needTask.Runto offload to the thread pool.async Task<string> GetDataAsync() { var result = await httpClient.GetStringAsync(url); // yields thread here return result.ToUpper(); // resumes after completion } -
What is LINQ and what's the difference between deferred and immediate execution?
LINQ (Language Integrated Query) provides a unified, declarative syntax for querying collections, databases (via LINQ to SQL/EF), XML, etc., using extension methods like
Where,Select,OrderBy, or query syntax. Most LINQ operators use deferred (lazy) execution — the query isn't actually run until you enumerate it (viaforeach,.ToList(),.First(), etc.). This means a query variable holds a *definition*, not a result, and re-enumerating it re-executes the logic against the current state of the source data. Methods likeToList(),ToArray(),Count(), andSum()force immediate execution and materialize results. Deferred execution enables composing queries efficiently but can cause bugs if the underlying collection changes between definition and enumeration, or performance issues from repeated re-execution. -
How do generics work in C#, and what are constraints for?
Generics let you write type-safe, reusable code (classes, methods, interfaces) that operates on a type parameter specified at compile time, e.g.
List<T>orDictionary<TKey, TValue>. Unlike Java, C# generics are reified at runtime — the JIT generates specialized native code per value-type instantiation (avoiding boxing) while reference-type instantiations share a single compiled implementation. Constraints (where T : class,where T : struct,where T : IComparable<T>,where T : new()) restrict what types can be substituted and unlock operations the compiler otherwise can't guarantee are valid — e.g.,where T : new()lets you callnew T()inside the generic method.T Max<T>(T a, T b) where T : IComparable<T> => a.CompareTo(b) > 0 ? a : b; -
How should exception handling be structured in C#?
Use
try/catch/finallyto handle exceptional, unanticipated conditions — not for normal control flow, since exceptions are expensive (stack unwinding, stack trace capture). Catch specific exception types before general ones, and only catch what you can meaningfully handle or add context to; otherwise let it propagate. Usefinally(or better,using/using var) to guarantee cleanup of unmanaged resources regardless of whether an exception occurred. When rethrowing, usethrow;(notthrow ex;) to preserve the original stack trace. Custom exceptions should derive fromException(notApplicationException) and add meaningful named exceptions for domain-specific failure modes rather than throwing genericExceptioneverywhere, which makes precise catching impossible. -
What's the difference between properties and fields?
A field is a raw storage location declared directly in a class; a property is a member that exposes a field (or computed value) through
get/setaccessors, which compile down toget_X()/set_X()methods under the hood. Properties let you encapsulate validation, lazy computation, or change notification without changing the public API, and they're required for data binding (WPF, Blazor) and interfaces (fields can't be part of an interface contract). Fields should almost always beprivate; public fields break encapsulation since you can't later add logic without a breaking API change. Auto-implemented properties (public string Name { get; set; }) give you property syntax with compiler-generated backing fields when you don't need custom logic yet. -
What are records, and how do they differ from classes?
Records (C# 9+) are reference types (or
record structfor value semantics) designed for immutable, data-centric models. The compiler auto-generates value-based equality (Equals/GetHashCodecompare property values, not references), aToString()override, and non-destructive mutation viawithexpressions that create a copy with specified changes. Regular classes default to reference equality and require you to hand-write all of that. Records are ideal for DTOs, value objects, and immutable domain models where "two objects with the same data are equal" is the desired semantic; classes remain the right choice for entities with identity (e.g., an EF-tracked entity where two instances with the same Id but different in-memory state shouldn't be "equal").public record Point(int X, int Y); var p2 = p1 with { X = 5 }; // copy with X changed -
What problem do nullable reference types solve, and how do they work?
Nullable reference types (enabled via
<Nullable>enable</Nullable>or#nullable enable) are a compile-time-only static analysis feature, not a runtime change — reference types remain nullable at runtime regardless. With it enabled,stringmeans "should never be null" andstring?means "may be null," and the compiler emits warnings when you dereference a possibly-null reference without a null check, or assign null to a non-nullable type. This surfaces a huge class ofNullReferenceExceptionbugs at compile time instead of production. It's opt-in and warning-based (not enforced at runtime like Kotlin), so it doesn't guarantee safety against reflection, deserialization, or unannotated third-party libraries, but it's a strong discipline tool for new codebases. -
What do you need to know about .NET garbage collection basics?
The GC manages heap memory automatically using a generational, mark-and-sweep algorithm. Objects start in Gen 0; if they survive a collection they're promoted to Gen 1, then Gen 2, based on the "most objects die young" heuristic — Gen 0 collections are frequent and cheap, Gen 2 collections are rare and expensive. Large objects (≥85KB) go directly to the Large Object Heap, which is collected less often and can fragment. As a developer, you influence GC pressure mainly by minimizing allocations in hot paths (reusing buffers, using
Span<T>, avoiding unnecessary boxing/closures), implementingIDisposablecorrectly for unmanaged resources, and avoiding finalizers unless truly necessary since they force objects through an extra collection cycle. -
What are extension methods and when should you use them?
Extension methods let you "add" methods to an existing type without modifying its source or creating a subclass, by defining a
staticmethod in astaticclass where the first parameter is prefixed withthis. They compile down to ordinary static method calls — it's syntactic sugar, not real reopening of the type. They're most useful for adding fluent, chainable utility behavior to types you don't own (like LINQ extendingIEnumerable<T>) or for keeping domain logic out of core models. Overuse can hurt discoverability and testability since the logic isn't part of the type itself; instance methods should still be preferred when you own the type and the behavior is core to it.public static class StringExtensions { public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s); } -
How has pattern matching evolved in modern C#, and why is it useful?
Pattern matching lets you test a value's shape and extract data in one expression, going well beyond simple
is/astype checks. Modern C# supports type patterns, property patterns, positional (deconstruction) patterns, relational and logical patterns (>,and,or,not), and list patterns, all usable inswitchexpressions for concise, exhaustive branching. This reduces boilerplateif/elsechains and casting, and the compiler can warn about non-exhaustive switches over closed type hierarchies. It's especially powerful combined with records for modeling domain logic declaratively (e.g., discriminated-union-style handling).string Describe(object o) => o switch { int n when n < 0 => "negative", int n => $"positive int {n}", Point { X: 0, Y: 0 } => "origin", null => "nothing", _ => "unknown" };
Practicing for a real interview? Get live AI help with your answers, in the browser, free to start.
Start a free session