The short answer: C# interviews check that you understand how the language and the runtime behave, beyond the syntax: value vs reference types, boxing, immutable strings, and the differences between classes, structs and records. Expect questions on LINQ (deferred execution, IEnumerable vs IQueryable), async and await (and why .Result is dangerous), memory (garbage collection, IDisposable), exceptions, thread safety, plus dependency injection and Entity Framework Core for .NET roles, and a live coding exercise. Interviewers increasingly ask what’s current. C# 14 with .NET 10 (November 2025) is the latest release, and .NET 11 is due on November 10, 2026. The answers below are checked against Microsoft’s documentation, with sources at the end, and all the code was compiled and run on .NET 10.
Interviewing for an ASP.NET Core role? Our .NET Core interview questions and web API interview questions cover the framework side.
C# and .NET versions in November 2026
A new .NET version ships every November. Even-numbered releases are long-term support (LTS), with three years of free support; odd-numbered releases are standard-term support (STS), with two. The quality is the same; only the support length differs.
| Version | C# | Type | Released | End of support |
|---|---|---|---|---|
| .NET 11 | C# 15 | STS | Release candidate; planned for November 10, 2026 | Planned for November 14, 2028 |
| .NET 10 | C# 14 | LTS | November 11, 2025 | November 14, 2028 |
| .NET 9 | C# 13 | STS | November 12, 2024 | November 10, 2026 |
| .NET 8 | C# 12 | LTS | November 14, 2023 | November 10, 2026 |
Dates are from Microsoft’s .NET support policy as of November 7, 2026. .NET 8 and .NET 9 both leave support on November 10, so if you’ve been working on either, be ready to talk about upgrading. Projects on .NET Framework default to C# 7.3, which is why older codebases can look so different.
Core C#
“Value types or reference types?”
A variable of a value type (int, bool, a struct) contains the instance itself, so assigning it copies the data. With reference types (classes, arrays, delegates, strings), two variables can point to the same object, so a change through one is visible through the other.
struct PointS { public int X; }
class PointC { public int X; }
var s1 = new PointS { X = 1 };
var s2 = s1; // copies the value
s2.X = 2; // s1.X is still 1
var c1 = new PointC { X = 1 };
var c2 = c1; // copies the reference
c2.X = 2; // c1.X is now 2
One trap: if a struct has a field of a reference type, copying the struct copies only the reference, so both copies share that object.
“What are boxing and unboxing?”
Boxing wraps a value type in an object and stores it on the managed heap. Unboxing extracts it again. Boxing is implicit, unboxing is explicit, and Microsoft calls both computationally expensive. Generic collections such as List<int> avoid it.
int i = 123;
object o = i; // boxing
int j = (int)o; // unboxing
“Why are strings immutable? When do you use StringBuilder?”
String objects can’t change after they’re created, so every “change”, including +=, makes a new string. That’s normally fine: Microsoft notes that string operations are highly optimized and rarely matter. In tight loops, where many new strings add up, use StringBuilder, a mutable buffer.
var sb = new StringBuilder();
foreach (var word in words)
{
sb.Append(word).Append(' ');
}
var sentence = sb.ToString().TrimEnd();
“Class, struct or record?”
| Type | Kind | Equality | Use it for |
|---|---|---|---|
class | Reference | By reference | Behavior and complex objects |
struct | Value | By value | Small, lightweight data |
record (or record class) | Reference | By value | Immutable data with inheritance |
record struct | Value | By value | Small value types with generated members |
Microsoft’s design guidelines say to avoid a struct unless it represents a single value, is under 16 bytes, is immutable and won’t be boxed often.
record is a modifier that makes the compiler generate value equality, a formatted ToString and nondestructive mutation with with:
public record Person(
string Name, int Age);
var a = new Person("Ana", 30);
var b = a with { Age = 31 };
Console.WriteLine(a == b);
Console.WriteLine(
a == new Person("Ana", 30));
This prints False, then True. Two details catch people out. Positional record class properties are init-only, but positional record struct properties are read-write by default. And with makes a shallow copy. Avoid records for EF Core entity types.
“Interface or abstract class?”
An interface defines a contract: it can’t contain instance fields, instance constructors or finalizers, and a class can implement many. An abstract class can only be a base class; it can hold state and shared code, but a class can inherit from only one. Interfaces can also have default implementations, mainly to add members safely to an interface already released. The trap: a default member is called through the interface type, not the class, so you must convert to the interface first.
“ref, out or in?”
C# passes arguments by value by default. For a class, the method gets a copy of the reference, so it can change the object but not the caller’s variable.
ref: the argument must be initialized first, and the method can change it.out: the method must assign a value, which is the pattern behindTryParse.in: the method can’t assign a new value. It suits large structs you don’t want to copy.
static void Twice(ref int n) =>
n *= 2;
static bool TryHalf(
int n, out int half)
{
half = n / 2;
return n % 2 == 0;
}
“const or readonly?”
A const is evaluated at compile time and copied into the code that uses it, so if a library changes one, the code using it must be recompiled. A readonly field is assigned at declaration or in a constructor, so it can differ per instance. A readonly field holding a reference still allows the object itself to change.
“What are nullable reference types?”
With them turned on, which recent .NET templates do, every reference type is non-nullable by default, and you mark the exceptions with ?. The compiler then warns about possible null dereferences. It’s entirely a compile-time feature: string and string? are both System.String at run time, so a null can still arrive from code that isn’t checked.
string? nickname = GetNickname();
// Warning CS8602 without the check
if (nickname is not null)
{
Console.WriteLine(nickname.Length);
}
var name = nickname ?? "Anonymous";
“What are generic constraints?”
Constraints tell the compiler what a type argument must be able to do, so you can call those members. Common ones are class, struct, notnull, new(), a base class and an interface.
static T Max<T>(T a, T b)
where T : IComparable<T> =>
a.CompareTo(b) >= 0 ? a : b;
“What is pattern matching?”
A pattern is a condition to test an expression against, used in is, switch statements and switch expressions. C# supports declaration, type, constant, relational, property, list, var and discard patterns:
static decimal Fee(object order) =>
order switch
{
Rush { Hours: < 4 } => 50m,
Rush => 20m,
Standard s when s.Weight > 10
=> 15m,
Standard => 5m,
null => throw new
ArgumentNullException(
nameof(order)),
_ => 0m,
};
“Delegate or event?”
A delegate is a type that references methods. Action is for methods that return void, and Func for methods that return a value, with the result as the last type parameter. An event is built on a delegate, with one important limit: only the class that declares it can raise it, while others can only subscribe or unsubscribe. Use events when listening is optional, and delegates when you need a return value.
Func<int, int, int> add =
(a, b) => a + b;
Action<string> log =
msg => Console.WriteLine(msg);
public class Button
{
public event EventHandler? Clicked;
public void Click() =>
Clicked?.Invoke(
this, EventArgs.Empty);
}
LINQ
“What is deferred execution?”
Creating a query doesn’t fetch any data. Methods that return a sequence, such as Where and Select, wait until you enumerate the result. Methods that return a single value, such as Count and Max, run immediately, and ToList or ToArray force execution and cache the results.
var numbers = new List<int> { 1, 2 };
var evens =
numbers.Where(n => n % 2 == 0);
numbers.Add(4);
// Runs now: prints 2 and 4
foreach (var n in evens)
{
Console.WriteLine(n);
}
“IEnumerable or IQueryable?”
IEnumerable<T> queries compile to delegates and run in memory. IQueryable<T> queries compile to expression trees, which a provider such as Entity Framework translates into SQL. So calling AsEnumerable or ToList too early moves the filtering into your application: all the data gets pulled into memory first.
“Group employees by department.”
var byDept = employees
.Where(e => e.Salary > 50_000)
.GroupBy(e => e.Department)
.Select(g => new
{
Department = g.Key,
Count = g.Count(),
Average = g.Average(e => e.Salary),
})
.OrderByDescending(x => x.Average)
.ToList();
Async and await
“How does async/await work?”
The compiler turns the method into a state machine. At each await, it suspends the method and yields control to the caller, freeing the thread while I/O is in progress. Marking a method async doesn’t run it on a background thread. For CPU-bound work, use Task.Run. Pass a CancellationToken through so callers can stop the work.
public async Task<string> GetAsync(
HttpClient client,
CancellationToken token)
{
using var response = await client
.GetAsync("/orders", token);
response.EnsureSuccessStatusCode();
return await response.Content
.ReadAsStringAsync(token);
}
Start independent operations first, then await them together:
var first = client.GetStringAsync(a);
var second = client.GetStringAsync(b);
string[] pages =
await Task.WhenAll(first, second);
“Why not use .Result or .Wait()?”
Blocking on a task can deadlock on a thread with a single-threaded SynchronizationContext, such as a UI thread: the task needs that thread to finish, and the thread is waiting for the task. The same code may work in a console app. In ASP.NET Core, blocking leads to thread pool starvation and slow responses. The fix is to use await all the way up.
“Task or ValueTask?”
Microsoft’s guidance: return Task by default, and use ValueTask only if performance analysis proves it worthwhile, typically when a method often completes synchronously. A ValueTask may only be awaited once.
“What does ConfigureAwait(false) do?”
It tells the awaiter not to resume on the captured context. It’s usually preferred in reusable library code. UI code often needs the context to update the interface, and ASP.NET Core has no SynchronizationContext by default, so app code there doesn’t need it.
“What’s wrong with async void?”
Callers can’t await it or catch its exceptions. Use it only for top-level event handlers in UI frameworks, and return Task everywhere else.
Memory and resources
“How does garbage collection work?”
The managed heap is divided into three generations. Generation 0 holds short-lived objects, generation 1 is a buffer between short-lived and long-lived objects, and generation 2 holds long-lived objects; a gen 2 collection is a full collection. Objects of 85,000 bytes or larger go on the large object heap, which is collected with gen 2 and usually not compacted, so heavy use of large temporary arrays can cause fragmentation.
“What is IDisposable? Does the garbage collector call Dispose?”
No: the GC doesn’t call Dispose. It exists to release unmanaged resources, such as file handles, promptly, and it should be safe to call twice. A using statement or declaration disposes the object even if an exception occurs. Use await using for IAsyncDisposable.
using var reader =
new StreamReader(path);
return reader.ReadToEnd();
“What about finalizers?”
You have no control over when a finalizer runs, and empty finalizers should not be used. They aren’t allowed on structs. To wrap an unmanaged resource, prefer SafeHandle over writing a finalizer. GC.SuppressFinalize tells the GC that cleanup has already happened.
“What is Span<T>?”
A type-safe view of a contiguous region of memory: an array, a string, stack memory or native memory. It lets you slice without copying or allocating. It’s a ref struct, so it can’t be a field in a class. When you need to store one, use Memory<T>.
ReadOnlySpan<char> line = "2026-11-07";
ReadOnlySpan<char> year = line[..4];
int value = int.Parse(year);
Exceptions
“throw or throw ex?”
throw; preserves the original stack trace. throw ex; restarts it at the current method, which hides where the error began.
try
{
Save();
}
catch (IOException ex)
{
logger.LogError(ex, "Save failed");
throw; // keeps the stack trace
}
Order catch blocks from the most derived type to the least derived. Exception filters let you catch only when a condition holds:
catch (HttpRequestException ex)
when (ex.StatusCode
== HttpStatusCode.NotFound)
{
return null;
}
Thread safety
“How do you protect shared state?”
lock lets at most one thread run its body and releases the lock even if an exception is thrown; you can’t await inside it. Since C# 13 and .NET 9, the best-performing option is to lock a dedicated System.Threading.Lock object:
private readonly Lock _gate = new();
private int _count;
public void Increment()
{
lock (_gate)
{
_count++;
}
}
For a simple counter, Interlocked.Increment is atomic and lighter, because count++ isn’t an atomic operation.
“Is ConcurrentDictionary fully thread-safe?”
Its operations are, but there’s a trap: the delegates passed to GetOrAdd and AddOrUpdate run outside the locks, so a factory can run more than once for the same key. Keep them free of side effects.
var counts = new
ConcurrentDictionary<string, int>();
counts.AddOrUpdate(
word, 1, (_, count) => count + 1);
.NET: dependency injection and EF Core
“Singleton, scoped or transient?”
- Transient: a new instance each time it’s requested.
- Scoped: one instance per client request in a web app.
DbContextis scoped by default. - Singleton: one instance for the whole application, so it must be thread-safe.
var builder =
WebApplication.CreateBuilder(args);
var services = builder.Services;
services.AddSingleton<IClock, Clock>();
services.AddScoped<IOrders, Orders>();
services.AddTransient<IMail, Smtp>();
The classic follow-up is the captive dependency: a singleton holding a scoped service keeps it alive, so it behaves like a singleton. Don’t resolve a scoped service directly from a singleton. Inject IServiceScopeFactory and create an explicit scope, which is also the pattern for hosted services.
“When do you use AsNoTracking?”
For read-only queries: EF Core doesn’t set up change tracking, so they’re usually faster. Include loads related data in the same query:
var orders = await context.Orders
.AsNoTracking()
.Include(o => o.Customer)
.Where(o => o.Total > 100)
.ToListAsync();
“What is the N+1 problem in EF Core?”
A loop triggers one extra query per item, which lazy loading makes very easy to do by accident. Load related data up front with Include, or use Select to fetch only the columns you need. Including several collections can multiply the rows returned (a “cartesian explosion”), and AsSplitQuery splits it into separate SQL queries, at the cost of consistency guarantees across them.
What’s new in C# 13, 14 and 15
| Version | What comes up in interviews |
|---|---|
| C# 15 (prerelease, with .NET 11) | Union types, closed hierarchies, collection expression arguments, labeled break and continue, extension indexers |
| C# 14 (.NET 10) | Extension members, the field keyword for properties, null-conditional assignment, more implicit Span conversions, modifiers on simple lambda parameters, partial events and constructors, directives for file-based apps |
| C# 13 (.NET 9) | params collections, the new Lock type, ref struct types can implement interfaces, partial properties |
Output puzzles (“what does this print?”)
Closures in a for loop
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.Write(i));
}
foreach (var a in actions) a();
Prints 333. All three lambdas capture the same variable i, which is 3 when they run. A foreach loop gets a fresh variable per iteration, so the equivalent prints 012.
Strings and references
string s = "hello";
string t = new string(
"hello".ToCharArray());
Console.WriteLine(s == t);
Console.WriteLine(
ReferenceEquals(s, t));
Prints True, then False. == on strings compares the contents; ReferenceEquals compares the objects.
Integer overflow
int max = int.MaxValue;
Console.WriteLine(max + 1);
Prints -2147483648. Arithmetic is unchecked by default, so the value wraps around. Inside checked, it throws an OverflowException.
double or decimal
Console.WriteLine(0.1 + 0.2 == 0.3);
Console.WriteLine(
0.1m + 0.2m == 0.3m);
Prints False, then True. double can’t store 0.1 exactly, while decimal is a base-10 type. Use decimal for money.
Changing a list inside foreach
var list = new List<int> { 1, 2, 3 };
foreach (var x in list)
{
if (x == 1) list.Remove(x);
}
Throws InvalidOperationException: “Collection was modified”. Use list.RemoveAll(x => x == 1) instead.
Coding exercises with solutions
Write each one, state its time and space cost, and test the edge cases: empty input, duplicates, no answer.
Two sum in one pass
static int[] TwoSum(
int[] nums, int target)
{
Dictionary<int, int> seen = [];
for (int i = 0; i < nums.Length; i++)
{
int need = target - nums[i];
if (seen.TryGetValue(
need, out int j))
{
return [j, i];
}
seen[nums[i]] = i;
}
return [];
}
One pass, O(n) time and space: for each number, check whether its complement was already seen.
First non-repeating character
static char? FirstUnique(string s)
{
var counts = s
.GroupBy(c => c)
.ToDictionary(
g => g.Key, g => g.Count());
foreach (char c in s)
{
if (counts[c] == 1) return c;
}
return null;
}
Returns w for "swiss" and null when every character repeats. O(n).
Balanced brackets
static bool Balanced(string s)
{
var stack = new Stack<char>();
foreach (char c in s)
{
switch (c)
{
case '(': stack.Push(')'); break;
case '[': stack.Push(']'); break;
case '{': stack.Push('}'); break;
case ')' or ']' or '}':
if (stack.Count == 0
|| stack.Pop() != c)
{
return false;
}
break;
}
}
return stack.Count == 0;
}
Push the closing bracket you expect; every closing bracket must match the top of the stack, and the stack must be empty at the end. O(n).
Group anagrams
static List<List<string>> Anagrams(
string[] words) =>
words
.GroupBy(w =>
string.Concat(w.Order()))
.Select(g => g.ToList())
.ToList();
Sorting each word’s letters gives anagrams the same key. Say what it costs: sorting each word dominates, and it’s a neat place to explain GroupBy.
Questions by level
- Junior: value vs reference types, classes and interfaces, strings,
constvsreadonly, collections, basic LINQ, exceptions,using. - Mid-level: records, nullable reference types, deferred execution,
IEnumerablevsIQueryable,async/await, DI lifetimes, EF Core tracking and N+1, unit testing. - Senior: deadlocks and thread pool starvation, GC and the large object heap,
Span<T>and allocations, thread safety, captive dependencies, upgrading from .NET Framework or .NET 8, API and service design.
Behavioral questions for C# developers
Use the STAR method with a real example:
- “Tell me about a production bug in a .NET service. How did you find the cause?”
- “Describe a time you made slow code faster. What did you measure?”
- “How did you upgrade a project to a newer .NET version? What broke?”
- “Tell me about code you wrote that someone else had to maintain. What did you do to make it easy?”
More: our software engineer interview questions, Java interview questions, SQL interview questions and backend developer interview questions.
Frequently asked questions
What C# questions are asked in interviews?
What is the latest version of C#?
What is the difference between a class and a struct in C#?
What is the difference between IEnumerable and IQueryable?
Why shouldn't I use .Result or .Wait()?
What is the difference between singleton, scoped and transient?
Can Huru help me practice C# interview questions?
Sources
- Microsoft, .NET and .NET Core support policy (versions, LTS and STS, dates), .NET 11 overview, .NET 11 release notes (planned dates) and C# language versioning. Read on 7 November 2026.
- Microsoft Learn, what’s new: C# 15, C# 14, C# 13 and version history. Read on 7 November 2026.
- Microsoft Learn, language: Value types, Boxing and unboxing, Strings, Records, Choosing between class and struct, Interfaces, Method parameters, const, readonly, Nullable reference types, Generic constraints, Patterns and Delegates and events. Read on 7 November 2026.
- Microsoft Learn, LINQ and async: LINQ overview, Introduction to LINQ queries, Asynchronous programming scenarios, Common async bugs, ValueTask and ASP.NET Core best practices. Read on 7 November 2026.
- Microsoft Learn, memory and threads: Garbage collection fundamentals, Large object heap, Implementing Dispose, Finalizers, Span, Exception-handling statements, lock, Interlocked and ConcurrentDictionary. Read on 7 November 2026.
- Microsoft Learn, .NET: Service lifetimes, Dependency injection guidelines, EF Core tracking, Efficient querying and Single vs split queries. Read on 7 November 2026.