Huru
Technical Interviews

C# Interview Questions and Answers (2026): Core C#, LINQ, Async, Memory, .NET 10 and Coding Exercises

C# interview questions with answers and code, checked against Microsoft's documentation: value and reference types, boxing, strings, classes, structs and records, interfaces, ref and out, nullable reference types, LINQ and deferred execution, async and await, garbage collection, IDisposable, Span, exceptions, thread safety, dependency injection lifetimes, EF Core, what's new in C# 13, 14 and 15, output puzzles and coding exercises with solutions.

By Amine Boukioud Updated 15 min read
The Huru mascot fits a glowing violet gear into a row of interlocking glass gears and blocks that form a small machine, next to a laptop showing code

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.

VersionC#TypeReleasedEnd of support
.NET 11C# 15STSRelease candidate; planned for November 10, 2026Planned for November 14, 2028
.NET 10C# 14LTSNovember 11, 2025November 14, 2028
.NET 9C# 13STSNovember 12, 2024November 10, 2026
.NET 8C# 12LTSNovember 14, 2023November 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?”

TypeKindEqualityUse it for
classReferenceBy referenceBehavior and complex objects
structValueBy valueSmall, lightweight data
record (or record class)ReferenceBy valueImmutable data with inheritance
record structValueBy valueSmall 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 behind TryParse.
  • 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. DbContext is 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

VersionWhat 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, const vs readonly, collections, basic LINQ, exceptions, using.
  • Mid-level: records, nullable reference types, deferred execution, IEnumerable vs IQueryable, 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?
Value vs reference types, boxing, strings and StringBuilder, classes, structs and records, interfaces vs abstract classes, ref and out, nullable reference types, LINQ and deferred execution, async and await, garbage collection and IDisposable, exceptions, thread safety, dependency injection lifetimes, EF Core, and a live coding exercise.
What is the latest version of C#?
C# 14, released in November 2025 with .NET 10. C# 15 ships with .NET 11, which is a release candidate as of November 7, 2026, with general availability planned for November 10, 2026.
What is the difference between a class and a struct in C#?
A class is a reference type: variables can share one object. A struct is a value type: assignment copies the data. Microsoft advises using a struct only for small, immutable values under 16 bytes.
What is the difference between IEnumerable and IQueryable?
IEnumerable queries compile to delegates and run in memory. IQueryable queries compile to expression trees that a provider such as Entity Framework translates, for example into SQL, so filtering happens in the database.
Why shouldn't I use .Result or .Wait()?
Blocking on a task can deadlock on a thread with a single-threaded SynchronizationContext, such as a UI thread, and in ASP.NET Core it can cause thread pool starvation. Use await instead.
What is the difference between singleton, scoped and transient?
A transient service is created each time it's requested, a scoped service once per client request, and a singleton once for the whole application. Don't let a singleton hold a scoped service.
Can Huru help me practice C# interview questions?
Yes, for the speaking parts. You answer out loud, an AI interviewer follows up, and every answer is scored out of 100 with a stronger version. Huru doesn't run code, so practice the exercises in an editor.

Sources

Practice this with Huru AI

Turn what you just read into a rehearsal. Unlimited AI mock interviews with instant, specific feedback.

Amine Boukioud

Amine Boukioud is a co-founder of Beatview, Inc., the company behind Huru, and Head of Growth at Huru. He writes and reviews Huru’s interview guides; each one starts from primary sources such as employers’ own careers pages, official data and research, and follows Huru’s editorial policy.

Published

How this article was made: researched and drafted with AI assistance, then written, fact-checked and signed by Amine Boukioud on November 7, 2026. Sources are linked where they are used. How we work: editorial policy.