C# Interview Questions and Answers
The .NET runtime, LINQ, async/await, generics and object-oriented design.
Whether you are preparing for entry-level C# interview questions for freshers or senior software engineer interview questions addressing concurrency, scalability, and system architecture, this track provides peer-reviewed model answers with syntax walkthroughs, edge cases, and practical interview tips.
1 What is the difference between value types and reference types? Easy
Value types store their data directly and are copied on assignment. Reference types store a reference to heap data, and copying copies the reference.
- Value types: numeric types,
bool,char,enum,structand tuples. Usually on the stack or inline in a containing object. - Reference types:
class,interface,delegate,string, arrays and records (unless declaredrecord struct).
struct Point { public int X, Y; }
class Shape { public int X, Y; }
var p1 = new Point { X = 1 };
var p2 = p1; // independent copy
p2.X = 99; // p1.X stays 1
var s1 = new Shape { X = 1 };
var s2 = s1; // same object
s2.X = 99; // s1.X is now 99
Boxing wraps a value type in an object and is a common performance pitfall in tight loops. string is a reference type but behaves like a value because it is immutable. Prefer structs for small, short-lived, immutable data and classes for objects with identity.
2 When should you use StringBuilder instead of string concatenation? Easy
string is immutable: every operation such as concatenation or Replace allocates a new string. StringBuilder is a mutable buffer backed by a resizable array, so appends modify the buffer in place.
string s = "";
for (int i = 0; i < 10000; i++) s += i; // O(n^2), many allocations
var sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.Append(i);
string result = sb.ToString(); // O(n)
Use string for a fixed number of concatenations; the compiler and string.Concat handle those efficiently. Use StringBuilder inside loops and when building large text from many pieces. StringBuilder is not thread-safe.
For joining collections, string.Join is usually the clearest option. Interpolation with $"..." compiles to string.Format or concatenation and is fine for small cases. Immutability of string is also why it is safe to share across threads.
3 What is a property and how does it differ from a field? Easy
A field is a variable declared directly in a class. A property is a member with get and set accessors that looks like a field to callers but runs code, enabling validation, computed values, lazy loading and encapsulation.
public class Person {
private int _age; // field
public string Name { get; set; } // auto-property
public int Age { // full property
get => _age;
set => _age = value >= 0
? value
: throw new ArgumentOutOfRangeException();
}
public string Display => $"{Name} ({Age})"; // get-only
}
Auto-properties let the compiler generate the backing field. Prefer properties in public APIs so you can change the implementation later without breaking binary compatibility or callers.
init accessors allow assignment only during object initialisation, supporting immutable objects. Expression-bodied get-only properties are concise for computed values. Properties can also have different access levels, such as public string Id { get; private set; }.
4 How does async/await work in C#? Medium
async/await enables asynchronous programming without blocking a thread. Marking a method async lets you await a Task or Task<T>, and the compiler rewrites the method into a state machine. When the awaited operation is incomplete, control returns to the caller and resumes via a continuation when it completes.
public async Task<string> FetchAsync(HttpClient client, string url) {
string body = await client.GetStringAsync(url);
return body.Trim();
}
var text = await FetchAsync(client, url);
Key points: async void is only for event handlers, because callers cannot catch its exceptions or await it. Return Task when there is no value. Do not block on async code with .Result or .Wait(), which can deadlock. Use ConfigureAwait(false) in library code to avoid capturing the synchronisation context. I/O-bound work benefits most; CPU-bound work belongs on Task.Run.
5 What is deferred execution in LINQ and why does it matter? Medium
LINQ queries over IEnumerable<T> are lazy: the query is a description that runs only when enumerated by foreach, ToList, Count, First or similar. Each enumeration re-executes the query.
var query = people.Where(p => p.Age > 18).Select(p => p.Name);
foreach (var n in query) Console.WriteLine(n); // executes now
var list = query.ToList(); // executes again
This enables composition and streaming, but it causes pitfalls: querying a database executes SQL on each enumeration; capturing a mutable variable in a lambda can give surprising results; and a query over a List re-reads current contents each time. Materialise with ToList or ToArray when you need a snapshot or to avoid repeated work.
IQueryable<T> translates expression trees to provider-specific queries such as EF Core SQL, so keep it composable and avoid forcing client-side evaluation.
6 How do generics and constraints work in C#? Medium
Generics let you write type-safe, reusable code without boxing or casts. The type parameter is resolved at compile time, and the JIT specialises for value types while sharing code for reference types.
public T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) > 0 ? a : b;
public class Repo<T> where T : class, new() { }
Constraints include where T : class, struct, new(), a base class, one or more interfaces, notnull and unmanaged. Multiple constraints are separated by commas and new() must be last. Without a constraint you can only use members of object, or use EqualityComparer<T>.Default.
Covariance (out) and contravariance (in) on generic interfaces and delegates let IEnumerable<string> be used as IEnumerable<object>. Generics improve performance by avoiding boxing and improve safety by removing casts.
7 Explain IDisposable, the using statement and finalizers. Medium
IDisposable provides deterministic release of unmanaged resources through Dispose(). The using statement or declaration guarantees Dispose runs even if an exception is thrown.
using var stream = File.OpenRead("data.bin"); // disposed at scope end
using (var conn = new SqlConnection(cs)) {
conn.Open();
}
A finalizer (~Type()) is a non-deterministic safety net run by the GC for types that directly own unmanaged handles. It runs on the finalizer thread and delays collection, so most code should not write one; use SafeHandle instead.
The full pattern uses Dispose(bool disposing) to release managed resources only during explicit disposal, and calls GC.SuppressFinalize(this). IAsyncDisposable with await using covers asynchronous cleanup. Never rely on the GC for files, sockets or locks, because it does not know about them.
8 When would you choose an interface over an abstract class? Medium
Both define contracts that other types implement, but they differ in intent and mechanics.
- An interface declares a contract with no state (traditionally) and supports multiple implementation. Members are public by default, and C# 8+ allows default implementations. A class can implement many interfaces.
- An abstract class can hold state (fields), constructors, and both abstract and concrete methods, but a class can inherit only one. It expresses an "is-a" relationship.
public interface IShape { double Area(); }
public abstract class Shape {
public string Name { get; init; } = "";
public abstract double Area();
public override string ToString() => $"{Name}: {Area()}";
}
Prefer interfaces for capability-style contracts: they are multiple, decoupled and easy to mock in tests. Use an abstract class when implementations share code or state, or when you need to evolve the base with protected members over time.
9 How does the .NET garbage collector work, including generations and the LOH? Hard
.NET uses a generational, compacting, tracing collector. New objects go to generation 0. Survivors are promoted to gen 1 then gen 2. Gen 0 collections are frequent and cheap because most objects die young; gen 2 collections are expensive and mark the whole heap.
Large objects of 85KB or more, such as big arrays, go to the Large Object Heap. It is not compacted by default to avoid moving costs, so it can fragment. ArrayPool<T> avoids repeated large allocations.
var buffer = ArrayPool<byte>.Shared.Rent(1_000_000);
try {
// use buffer
} finally {
ArrayPool<byte>.Shared.Return(buffer);
}
The GC is non-deterministic, so it does not replace IDisposable. Reduce pressure with pooling, Span<T> and structs, and measure with dotnet-counters or GC ETW events before optimising.
10 What causes async deadlocks and how does ConfigureAwait help? Hard
A classic deadlock happens in UI applications and classic ASP.NET when you block synchronously on async code with .Result or .Wait().
The awaited task wants to resume on the captured synchronisation context, but that context's single thread is blocked waiting for the task, so neither can proceed.
var data = GetDataAsync().Result; // deadlock in a UI/ASP.NET context
Fixes: go async all the way up and return Task instead of blocking, or use ConfigureAwait(false) in library code so continuations do not require the original context. ASP.NET Core has no synchronisation context, so this exact deadlock is less common there, but blocking still wastes threads and can starve the pool.
Other pitfalls include async void, unobserved task exceptions and not passing CancellationToken. Use Task.WhenAll for concurrency and IAsyncEnumerable<T> for asynchronous streams.
Frequently Asked Questions About C# Interviews
What do hiring managers evaluate in C# technical rounds?
Technical interviewers look for foundational fluency, idiomatic syntax, clarity when communicating complex logic, and awareness of performance trade-offs (e.g. memory footprint, render performance, and network latency) in production environments.
What are the best interview tips for practicing C# questions?
Use active recall: summarize each answer in your own words before revealing the model solution. Focus on explaining why a certain approach is chosen rather than just memorizing code syntax.