C# and .NET interview questions

The language questions, the framework questions, and the snippets that prove the answers.

Short answer

A C# interview moves from the type system to the framework. First come value against reference types, strings and equality, collections and LINQ. Then come async and Task, disposal, interfaces against abstract classes, and for anything web-facing the dependency injection container and its service lifetimes. Each snippet below compiled and ran on .NET 10, so the outputs in the comments are real.

Last reviewed October 13, 2026

How C# and .NET interviews are structured

The first question is often which .NET you have used. Say it precisely. The .NET Framework is the old Windows-only stack that many companies still maintain, while modern .NET is cross-platform and ships a new major version every year. If the role is a migration project, saying you know the difference matters more than any syntax question.

After that, three layers usually get covered. The language layer asks about the type system, LINQ and async. The runtime layer asks about garbage collection, disposal and memory in outline. The framework layer depends on the job: ASP.NET Core and Entity Framework Core for web work, WPF or WinForms for desktop, and testing with xUnit or NUnit almost everywhere.

Mid-level and senior rounds add a design conversation and code review. Junior rounds lean on predict-the-output snippets, which is good news, because those are the ones you can prepare by running them.

Value types, reference types and boxing

This is the foundation question and the C# reference states it in one line: a variable of a value type contains an instance of the type, which differs from a variable of a reference type, which contains a reference to an instance of the type. Assignment copies the variable’s value, so copying a struct copies the data while copying a class variable copies the reference.

PointStruct s1 = new PointStruct { X = 1 };
PointStruct s2 = s1;
s2.X = 99;
Console.WriteLine($"struct: {s1.X} {s2.X}");   // struct: 1 99

PointClass c1 = new PointClass { X = 1 };
PointClass c2 = c1;
c2.X = 99;
Console.WriteLine($"class: {c1.X} {c2.X}");    // class: 99 99

The follow-up worth volunteering is the mixed case: if a value type holds a member of a reference type, copying the value type copies only the reference, so both copies see the same list. That is documented on the same page and it surprises people in code review.

Then boxing. Assigning an int to an object wraps the value in a heap allocation, and casting it back unboxes it. The interview point is not the mechanism but the cost: boxing in a hot loop allocates, which is why generic collections such as List of int exist instead of the old non-generic ArrayList.

Structs come with their own guidance: keep them small and immutable, and reach for a class when the type has identity or grows. If asked where each lives, say that value types usually live inline, in the stack frame or inside the containing object, that reference type instances live on the heap, and that exact placement is a runtime detail.

Strings, equality and records

Strings are immutable, so every change produces a new string, and building text in a loop with the plus operator allocates on every pass. StringBuilder is the answer, and interviewers expect you to reach for it without being prompted.

Equality then catches people because C# behaves differently from Java here. The equality operator is overloaded for string and compares the characters, so you rarely need Equals for simple comparison, and ReferenceEquals is the way to ask the identity question:

string a = "hello";
string b = "hel" + "lo";
string c = new string("hello".ToCharArray());
Console.WriteLine($"{a == b} {a == c} {ReferenceEquals(a, c)} {a.Equals(c)}");
// True True False True

The comparison questions that follow are practical: use a culture-aware comparison for text you show to users, use the ordinal comparison for identifiers and file names, and never compare strings with ToLower when StringComparison.OrdinalIgnoreCase will do the job without allocating.

Records are the modern half of this topic. A record gets value-based equality generated for it, which is what makes the next snippet behave the way most people expect objects to behave:

var r1 = new Badge("A1");
var r2 = new Badge("A1");
Console.WriteLine($"{r1 == r2} {r1.Equals(r2)} {ReferenceEquals(r1, r2)}");  // True True False

var set = new HashSet<Badge> { new Badge("A1"), new Badge("A1") };
Console.WriteLine(set.Count);   // 1

For an ordinary class you get reference equality until you override Equals and GetHashCode together, and the rule to quote is that objects which compare equal must return the same hash code, or hash-based collections stop finding them.

Collections, IEnumerable and LINQ deferred execution

The collection question is usually a choice. List of T for an ordered sequence with index access, Dictionary for key lookup, HashSet for uniqueness and membership tests, Queue or Stack when the order of processing is the point. For a read-only API surface, return IReadOnlyList or IEnumerable rather than the concrete list.

That leads to the difference between IEnumerable and a materialised collection, which is the deferred execution question. Microsoft’s documentation is blunt about it: deferred execution means the operation isn’t performed at the point in the code where the query is declared, it is performed only when the query variable is enumerated, and the results depend on the contents of the data source when the query is executed rather than when it is defined. So a query re-reads its source every time you iterate it:

var numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(n => n > 1);   // nothing has run yet
numbers.Add(4);
Console.WriteLine(string.Join(",", query));      // 2,3,4

var snapshot = numbers.Where(n => n > 1).ToList();
numbers.Add(5);
Console.WriteLine(string.Join(",", snapshot));   // 2,3,4
Console.WriteLine(string.Join(",", query));      // 2,3,4,5

The same documentation gives you the second half of the answer: you can force any query to execute immediately with ToList or ToArray, and operators that return a single value such as Count, Max and First execute immediately by their nature. Say that out loud and the interviewer will usually ask the real question behind it: with Entity Framework, where does the query run. The answer is that an IQueryable is translated into SQL and executed on the database, and that calling ToList before you filter drags the whole table into memory, which is one of the most common performance bugs in .NET web code.

If you are asked to name LINQ operators, group them by job: Where and Select for filtering and projection, OrderBy and ThenBy for sorting, GroupBy and Join for shaping, Any, All and First for questions, Select with SelectMany for flattening.

Async, await and Task

Start by separating concurrency from parallelism. A Task represents work that will complete, which may be a network call that uses no thread while it waits, and await releases the current thread until the work finishes. A thread is a worker. Most async questions are really asking whether you know that awaiting input and output work does not consume a thread.

The practical skill is starting independent work together. Awaiting one call after another costs the sum of the waits; starting both and awaiting once costs roughly the longer one:

var first = Delay(200, "a");
var second = Delay(200, "b");
var both = await Task.WhenAll(first, second);   // about 200 ms measured

var one = await Delay(200, "a");
var two = await Delay(200, "b");               // about 400 ms measured

Those two numbers came from running the code, not from theory, and quoting the shape of the result (one wait against two) is what the question is for.

The traps to name before you are asked: async void, which exists only for event handlers because nothing can await it and an exception inside it is unrecoverable, so use async Task instead. Calling .Result or .Wait() on a task, which can deadlock where a synchronization context is in play. And forgetting to pass a CancellationToken when the caller may give up.

Finish with error handling: an exception in an awaited task surfaces at the await, so try and catch around the await works as you expect, while Task.WhenAll collects exceptions and rethrows the first one, with the rest available on the aggregate.

Interfaces, abstract classes and disposal

Interface against abstract class is asked in every C# interview. An interface is a contract, a type can implement many, and since C# 8 an interface may supply a default implementation for a member. An abstract class can hold fields, constructors and shared logic, and a class may inherit only one. Choose the interface for a capability that unrelated types can offer, choose the abstract class when the subtypes really do share state and behaviour.

interface IGreeter
{
    string Greet(string name);
    string Shout(string name) => Greet(name).ToUpperInvariant();   // default member
}

class Greeter : IGreeter
{
    public string Greet(string name) => $"hi {name}";
}

Disposal is the other staple. The garbage collector reclaims managed memory on its own schedule, so it does not help with a file handle, a socket or a database connection. Types that hold those implement IDisposable, and a using statement or declaration calls Dispose when the scope ends, including when an exception is thrown:

using (var res = new Resource("db"))
{
    Console.WriteLine("inside using");
}
// prints: open db / inside using / dispose db

Expect two more questions in this area. Finalisers against Dispose: a finaliser is a last resort, it runs at an unpredictable time, and you avoid writing one unless you own an unmanaged handle. And exceptions: catch what you can handle, prefer specific exception types, use filters when you want to inspect before catching, and never swallow an exception silently. If asked about custom exceptions, say you derive from Exception and add only the context a caller can act on.

ASP.NET Core, dependency injection and the live round

For any web role, the container is the heart of the questions. .NET has dependency injection built in, services are registered at start-up and injected through constructors, and the documented lifetimes are three. A transient service is created each time it is requested from the service container. A scoped service, in a web application, is created once per client request and disposed at the end of it. A singleton is created the first time it is requested and every subsequent request uses the same instance, which is why the docs add that singleton services must be thread safe.

The follow-up separates the prepared candidates: what happens if you inject a scoped service into a singleton. The documentation is explicit that you should not, because the scoped service then behaves like a singleton and can carry state into later requests, and that in development resolving a service from one with a longer lifetime throws. The fix is to inject IServiceScopeFactory and create a scope for each unit of work. Entity Framework Core registers its DbContext as scoped by default, which is exactly why this comes up.

Around that, expect the middleware pipeline and the order of its steps, model binding and validation, status codes from a controller, configuration and logging. For Entity Framework, expect change tracking, eager against lazy loading, and migrations.

In the live round, read the snippet aloud, name the types, and say which part is deferred, asynchronous or disposable, because those three words cover most of the surprises in C#. When a question has a documented rule behind it, quote the rule: lifetimes, the hash-code contract and deferred execution are all places where precision beats paraphrase.

Practice it now

Answer out loud, as if the interviewer just asked. Then check yourself against the list.

“A colleague's controller calls a repository method that returns IEnumerable of Order, then filters it with Where and calls Count. Explain what actually happens against Entity Framework Core, what you would change, and what you would check about the service lifetimes involved.”

Aim for 10 minutes
  • You said whether the filter runs in the database or in memory, and why the return type decides it.
  • You named the change: filter on IQueryable, or project to what the caller needs before materialising.
  • You mentioned ToList or Count forcing immediate execution and where you would put it.
  • You identified DbContext as a scoped service and what goes wrong if it is captured by a singleton.
  • You proposed one way to verify the fix, such as logging the generated SQL.
  • You kept the answer to the question asked instead of redesigning the whole data layer.

Frequently asked questions

What are the most common C# interview questions?

Value against reference types, struct against class, boxing, string immutability and comparison, IEnumerable against List with deferred execution, async and await with Task, interface against abstract class, IDisposable with using, and the dependency injection lifetimes for anything ASP.NET Core.

How much do I need to know about garbage collection?

For most roles: that it reclaims managed memory automatically, that generations exist so short-lived objects are collected cheaply, that you do not call it manually, and that it does not release unmanaged resources, which is what IDisposable is for. Deeper tuning only matters for performance-focused roles.

Do interviewers still ask about .NET Framework?

In companies with older products, yes, usually as a migration question. Be ready to say which parts of your experience are Framework and which are modern .NET, and to name one difference that affects a port, such as hosting or configuration.

What should I say about design patterns?

Name the ones you have actually used and where: repository or unit of work around data access, factory for creation, strategy for swapping behaviour, and dependency injection as the pattern the framework already applies for you. Invented examples are easy for an interviewer to spot.

How do I prepare the live coding part for a C# role?

Write and run small console programs: a LINQ query over a list you mutate afterwards, two awaits against Task.WhenAll with a stopwatch, a class with and without an Equals override in a HashSet. Seeing those outputs once makes your explanation concrete.

Sources

  1. Microsoft Learn: Value types (C# reference)
  2. Microsoft Learn: Introduction to LINQ queries, immediate and deferred execution
  3. Microsoft Learn: Service lifetimes (dependency injection)
  4. Microsoft Learn: Dependency injection in .NET, scope validation