25 questions · Updated

C# Interview Questions and Answers

C# interviews for .NET roles test how well you understand the type system and runtime, not just syntax. Expect questions on value types versus reference types, boxing, the difference between IEnumerable and IQueryable, how async and await work under the hood, deferred execution in LINQ, IDisposable and the garbage collector, and nullable reference types. Interviewers also like to hear that you know recent language features: records, pattern matching, init-only setters, required members, and C# 12 additions such as primary constructors on classes and collection expressions. This page collects 25 C# interview questions and answers grouped by level, with coding exercises at the end. Answers assume modern .NET (8 or later) and C# 12 or newer, and note older behaviour where it still trips people up. If the role is ASP.NET Core, you will also be asked about dependency injection lifetimes, middleware and Entity Framework Core, which are covered briefly here. Try answering each question aloud before you open it, and use the related links to revise SQL and system design for back-end roles.

Beginner C# interview questions

1.

What is the difference between value types and reference types?

Value types (int, double, bool, struct, enum) hold their data directly, and assignment copies the value. Reference types (class, string, array, delegate, record class) hold a reference to an object on the managed heap, so assignment copies the reference and both variables see the same object. Where a value type lives depends on context: locals are usually on the stack, but a value type field inside a class lives on the heap.

2.

What is boxing and unboxing?

Boxing wraps a value type in an object on the heap when it is converted to object or an interface; unboxing extracts it with an explicit cast. Each box is an allocation, so boxing in hot loops hurts performance. Generics such as List<int> avoid boxing, unlike the old non-generic ArrayList.

typesperformanceLink to this question
3.

What is the difference between class, struct and record?

A class is a reference type with reference equality by default. A struct is a value type, best for small, immutable data that is copied cheaply. A record (record class, or record struct) adds value-based equality, a readable ToString and non-destructive mutation with the with expression. Use records for data-centric types such as DTOs and events.

public record Person(string Name, int Age);
var a = new Person("Ana", 30);
var b = a with { Age = 31 };
Console.WriteLine(a == new Person("Ana", 30)); // True (value equality)
4.

What is the difference between const and readonly?

const values are compile-time constants, implicitly static, and are copied into calling assemblies, so changing one requires recompiling consumers. readonly fields are assigned at declaration or in a constructor and can hold runtime values, including references to mutable objects. static readonly is the usual choice for shared values that are not true constants.

5.

What are nullable reference types?

With nullable reference types enabled (the default in new projects), string means non-nullable and string? means it may be null. The compiler performs flow analysis and warns about possible null dereferences. It is a compile-time feature only, so values from external sources still need checks. The null-forgiving operator ! suppresses a warning and should be used sparingly.

6.

What is the difference between an interface and an abstract class?

A class can implement multiple interfaces but inherit from only one class. Interfaces define contracts and, since C# 8, can include default implementations and static members, but no instance fields. Abstract classes can hold state, constructors and protected members. Use interfaces for capabilities and testability, abstract classes for shared base behaviour.

7.

What are properties, and what are init and required?

Properties expose fields through get and set accessors and can include logic. An init accessor allows setting a property only during object initialisation, giving immutability with object-initialiser syntax. The required modifier (C# 11) forces callers to set the member during initialisation, with a compile error otherwise.

public class Order
{
    public required string Id { get; init; }
    public decimal Total { get; init; }
}
var o = new Order { Id = "A1", Total = 9.99m };

Intermediate C# interview questions

8.

How do async and await work?

An async method is compiled into a state machine. When it awaits an incomplete Task, it returns to the caller and registers a continuation, freeing the thread rather than blocking it. When the task completes, execution resumes, possibly on a different thread. async void should be used only for event handlers, because exceptions cannot be awaited or caught by callers.

9.

Why is calling .Result or .Wait() on a Task dangerous?

Blocking on a task wastes a thread and can deadlock in environments with a synchronization context, such as UI apps or legacy ASP.NET, because the continuation needs the very thread that is blocked. It also wraps exceptions in AggregateException. The rule is async all the way down. In library code, ConfigureAwait(false) avoids capturing the context.

asyncdeadlocksLink to this question
10.

What is deferred execution in LINQ?

Most LINQ operators such as Where and Select build a query that runs only when it is enumerated, for example by foreach, ToList, Count or First. Enumerating the same query twice runs it twice, which can mean two database calls. Materialise with ToList or ToArray when you need a stable snapshot or will iterate repeatedly.

11.

What is the difference between IEnumerable and IQueryable?

IEnumerable<T> runs LINQ operators in memory using delegates. IQueryable<T> builds expression trees that a provider such as Entity Framework Core translates into SQL, so filtering happens in the database. Calling AsEnumerable or ToList too early pulls all rows into memory and filters there, a common performance bug.

12.

What is IDisposable and the using statement?

IDisposable lets a type release unmanaged resources such as file handles or connections deterministically through Dispose. The using statement, or the using declaration, calls Dispose automatically at the end of the scope, even if an exception is thrown. IAsyncDisposable with await using covers asynchronous cleanup. The garbage collector does not call Dispose.

await using var conn = new SqlConnection(connectionString);
await conn.OpenAsync();
// conn is disposed at the end of the enclosing scope
resourcesmemoryLink to this question
13.

What are delegates, events, Func and Action?

A delegate is a type-safe reference to a method. Func<T, TResult> and Action<T> are built-in generic delegates for methods that return a value or return void. An event wraps a delegate so outside code can only subscribe and unsubscribe, not invoke or overwrite it. Forgetting to unsubscribe from long-lived publishers causes memory leaks.

delegateseventsLink to this question
14.

What are the dependency injection lifetimes in ASP.NET Core?

Transient services are created every time they are requested. Scoped services are created once per request (per scope). Singletons are created once for the application's lifetime. Injecting a scoped service such as a DbContext into a singleton is a captive dependency bug, which the default container detects in development.

asp.net corediLink to this question
15.

How does pattern matching work in C#?

C# supports type patterns, property patterns, relational patterns (< 0, >= 100), logical patterns (and, or, not) and list patterns (C# 11). Switch expressions combine them into concise, expression-based branching, and the compiler warns when a switch expression is not exhaustive.

string Classify(object o) => o switch
{
    int n when n < 0 => "negative",
    int n => n is >= 0 and < 10 ? "small" : "large",
    string { Length: 0 } => "empty string",
    int[] [var first, ..] => $"array starting with {first}",
    null => "null",
    _ => "other"
};
pattern matchingLink to this question

Advanced C# interview questions

16.

How does .NET garbage collection work?

The .NET GC is generational: new objects start in generation 0, survivors are promoted to generation 1 and then 2, because most objects die young. Objects of 85,000 bytes or more go to the Large Object Heap, which is collected with generation 2 and not compacted by default. Workstation and server modes, and background GC, trade latency for throughput. Finalizers delay collection and should be rare.

17.

What are Span<T> and Memory<T>?

Span<T> is a stack-only, ref struct view over contiguous memory — an array, a slice of it, stack-allocated memory or native memory — that lets you slice and parse without allocating. Because it is stack-only, it cannot be stored in fields of classes or used across await. Memory<T> is the heap-storable counterpart for asynchronous code.

performancememoryLink to this question
18.

What C# 12 features should you know?

C# 12, shipped with .NET 8, added primary constructors for all classes and structs, collection expressions such as int[] xs = [1, 2, 3] with the spread element .., default values for lambda parameters, alias any type with using, and inline arrays. Primary constructor parameters are captured like fields but are not automatically properties, which is a common follow-up question.

public class OrderService(IOrderRepository repo, ILogger<OrderService> log)
{
    public Task<Order?> Get(int id)
    {
        log.LogInformation("Fetching {Id}", id);
        return repo.FindAsync(id);
    }
}
int[] all = [..first, ..second];
c# 12modern c#Link to this question
19.

What is the difference between Task and ValueTask?

Task is a reference type, so every asynchronous result allocates unless cached. ValueTask is a struct that avoids the allocation when the result is often available synchronously, such as a cache hit. ValueTask has restrictions: await it only once and do not await it concurrently. Default to Task and use ValueTask in measured hot paths.

asyncperformanceLink to this question
20.

How do you handle thread safety in C#?

Prefer immutability and avoiding shared state. When state must be shared, use lock (backed by the new System.Threading.Lock type in .NET 9), Interlocked for atomic counters, and concurrent collections such as ConcurrentDictionary. In async code use SemaphoreSlim, since you cannot await inside a lock block. Channels are a good fit for producer-consumer pipelines.

C# coding interview questions

21.

Group words by their first letter with LINQ.

GroupBy with a key selector returns IGrouping objects; project them into a dictionary or anonymous types. Normalise case so A and a group together.

var groups = words
    .GroupBy(w => char.ToLowerInvariant(w[0]))
    .ToDictionary(g => g.Key, g => g.ToList());
22.

Run several HTTP calls concurrently and collect the results.

Start all tasks first, then await Task.WhenAll so they run concurrently. Reuse a single HttpClient (or IHttpClientFactory) rather than creating one per request, which can exhaust sockets. For large batches, limit concurrency with Parallel.ForEachAsync or SemaphoreSlim.

var tasks = urls.Select(u => http.GetStringAsync(u));
string[] pages = await Task.WhenAll(tasks);
23.

Implement FizzBuzz with a switch expression.

Switch on a tuple of the two divisibility checks. It is a quick check that you know modern syntax as well as the logic.

for (int i = 1; i <= 100; i++)
{
    Console.WriteLine((i % 3, i % 5) switch
    {
        (0, 0) => "FizzBuzz",
        (0, _) => "Fizz",
        (_, 0) => "Buzz",
        _ => i.ToString()
    });
}
pattern matchingcodingLink to this question
24.

Check whether two strings are anagrams.

Count letters of the first string up and the second down in an array or dictionary, then check that every count is zero. This is O(n), better than sorting both strings at O(n log n).

bool IsAnagram(string a, string b)
{
    if (a.Length != b.Length) return false;
    var counts = new Dictionary<char, int>();
    foreach (var c in a) counts[c] = counts.GetValueOrDefault(c) + 1;
    foreach (var c in b)
    {
        if (!counts.TryGetValue(c, out var n) || n == 0) return false;
        counts[c] = n - 1;
    }
    return true;
}
25.

Write a generic, thread-safe memoizer.

Use ConcurrentDictionary.GetOrAdd with a Lazy<T> value so the expensive function runs at most once per key, even if several threads request the same key at the same time.

Func<TKey, TValue> Memoize<TKey, TValue>(Func<TKey, TValue> fn) where TKey : notnull
{
    var cache = new ConcurrentDictionary<TKey, Lazy<TValue>>();
    return key => cache.GetOrAdd(key, k => new Lazy<TValue>(() => fn(k))).Value;
}
concurrencygenericscodingLink to this question

Preparing for a C# interview

Common questions about getting ready

How do I prepare for a C# interview?

Make sure you can explain value versus reference types, async and await, LINQ deferred execution, IDisposable and the garbage collector, and nullable reference types. Then learn the recent language features (records, pattern matching, required members, primary constructors, collection expressions). For web roles, add ASP.NET Core dependency injection, middleware and EF Core.

Which .NET version should I know?

Know the current long-term-support release and the one before it. Most interview questions are stable across versions, but mentioning features from C# 12 and later shows you are current.

Are C# interviews mostly about ASP.NET Core?

For web back-end roles, a large share are. Desktop, game (Unity) and cloud roles shift the focus. Core language and runtime questions appear in all of them.

How are C# and Java interviews different?

The OOP and collections questions overlap heavily. C# interviews add async/await internals, LINQ, value types and structs, properties and IDisposable, while Java interviews add JVM, GC tuning and virtual threads.

Related resources

More interview questions