This covers the C#-language-level ground an interview loop draws from — data types, the CLR's memory model, delegates/events/generics, collections, async internals, and concurrency primitives. A few questions here overlap with OOP principles or Dependency Injection specifically — where that's the case, this gives the C# angle and points to the dedicated 52 OOP interview questions and 35 Dependency Injection interview questions posts for the deeper design-level treatment.
C# Fundamentals
Q1. What is C# and what are its key features?
C# is Microsoft's statically-typed, object-oriented language running on .NET — type-safe, garbage-collected, and compiled to intermediate language (IL) that the CLR JIT-compiles to native code at runtime. Key features: full OOP support, generics, LINQ, async/await, pattern matching, and (since C# 8) nullable reference types for compile-time null-safety checking.
Q2. Explain the basic structure of a C# program.
// Modern C# (top-level statements, .NET 6+) — no explicit Main() needed
Console.WriteLine("Hello, world!");
// Classic structure:
namespace MyApp;
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, world!");
}
}Q3. What are the different types of data types available in C#?
| Category | Examples |
|---|---|
| Value types | int, double, bool, char, struct, enum |
| Reference types | class, string, array, interface, delegate |
| Nullable value types | int?, DateTime? — via Nullable<T> |
Q4. What is the difference between value types and reference types?
struct Point { public int X, Y; }
Point p1 = new Point { X = 1 };
Point p2 = p1; // COPIES the whole struct
p2.X = 99; // p1.X is still 1
class Box { public int X; }
Box b1 = new Box { X = 1 };
Box b2 = b1; // COPIES the reference, not the object
b2.X = 99; // b1.X is now 99 too — same object| Value type | Reference type | |
|---|---|---|
| Storage | Typically the stack (or inline within a containing object) | The heap, referenced by a pointer stored on the stack |
| Assignment | Copies the entire value | Copies the reference — both variables point to the same object |
| Default | struct, int, bool, enum | class, string, array, delegate |
Q5. What are nullable types in C#?
int? age = null; // Nullable<int>
if (age.HasValue) Console.WriteLine(age.Value);
int actual = age ?? 0; // null-coalescing defaultValue types can't natively hold null (a plain int always has some numeric value) — Nullable<T> (int? is shorthand for Nullable<int>) wraps a value type to add "no value" as a legitimate state, which is essential for things like an optional database column mapped to a C# property.
Q6. Can you describe what namespaces are and how they are used in C#?
namespace MyCompany.Orders;
using System.Collections.Generic; // brings another namespace's types into scopeA namespace organizes types hierarchically and prevents naming collisions — two classes named Order can coexist as MyCompany.Orders.Order and ThirdParty.Sdk.Order without conflict, and a using directive avoids having to fully qualify every reference to a type from another namespace.
Q7. Explain the concept of boxing and unboxing in C#.
int i = 42;
object boxed = i; // boxing — the value type is wrapped in a heap object
int unboxed = (int)boxed; // unboxing — copied back out into a value typeBoxing wraps a value type in a heap-allocated object so it can be treated as object (or an interface it implements) — necessary because value types don't naturally live on the heap, but it costs an allocation and a copy. Unboxing reverses it. Both are a real, sometimes-overlooked performance cost when they happen inside a hot loop (a common source of unexpected allocations pre-generics, e.g. adding ints to an ArrayList).
Q8. What is Type Casting and what are its types in C#?
| Cast type | Example | Fails how |
|---|---|---|
| Implicit | int i = 5; long l = i; | Never — only allowed when no data can be lost |
| Explicit | double d = 5.7; int i = (int)d; | May lose data (truncates 5.7 to 5) silently |
| as | obj as string | Returns null on failure instead of throwing |
| is | if (obj is string s) | Pattern-matches and safely extracts the typed value |
Q9. What are operators in C# and can you provide examples?
| Category | Examples |
|---|---|
| Arithmetic | +, -, *, /, % |
| Comparison | ==, !=, <, >, <=, >= |
| Logical | &&, ||, ! |
| Null-related | ??, ??=, ?., ! |
| Bitwise | &, |, ^, ~, <<, >> |
Q10. What is the difference between == operator and .Equals() method?
string a = "hello";
string b = "hel" + "lo";
Console.WriteLine(a == b); // true — string overrides == for value comparison
Console.WriteLine(a.Equals(b)); // true — same reason
object x = new object();
object y = new object();
Console.WriteLine(x == y); // false — default == is reference comparison for objectFor reference types, == defaults to reference equality unless the type overloads it (string and records both do, for value-based comparison) — .Equals() is virtual and can be overridden per type for genuine content-based equality, which is why user-defined classes should override both consistently if value equality is intended.
Q11. What is the purpose of the var keyword in C#?
var count = 5; // inferred as int
var customers = new List<Customer>(); // inferred as List<Customer>var lets the compiler infer a variable's type from its initializer — it's still statically typed (not dynamic), just with the type written implicitly instead of explicitly. Best used when the type is obvious from the right-hand side; explicit typing is often clearer when it isn't.
Q12. What are the differences between const and readonly keywords?
| const | readonly | |
|---|---|---|
| Set at | Compile time — must be a literal/constant expression | Runtime — can be set in a constructor |
| Applies to | Value types and strings only | Any type |
| Per instance? | Implicitly static — one value for the type | Can differ per instance if set in an instance constructor |
Q13. How does checked and unchecked context affect arithmetic operations?
int max = int.MaxValue;
int overflowed = unchecked(max + 1); // silently wraps around, no exception
checked
{
int willThrow = max + 1; // throws OverflowException
}By default, integer arithmetic overflow silently wraps around (unchecked). Wrapping code in a checked block (or compiling with /checked+) makes overflow throw an OverflowException instead — useful when silent wraparound would be a genuine bug you'd rather catch immediately.
Q14. What are the different ways to handle errors in C#?
try
{
Process();
}
catch (InvalidOperationException ex)
{
// handle a specific exception type
}
finally
{
Cleanup(); // always runs
}try/catch/finally for exceptions, exception filters (catch (Ex ex) when (condition)) for conditional handling, and — for expected, non-exceptional failure — return types like nullable values, Result<T>-style wrapper types, or the TryParse pattern (bool TryX(out T result)) that avoid the cost and control-flow disruption of throwing at all.
Q15. Explain the role of the garbage collector in .NET.
The GC automatically reclaims heap memory no longer reachable from any live reference, so you don't manually free() every object — it organizes the heap into generations (0, 1, 2) based on object age, collecting the short-lived Gen 0 far more often and cheaply than the long-lived Gen 2, on the empirical assumption that most objects die young.
Object-Oriented Programming in C#
Q16. Define Object-Oriented Programming and its principles.
Covered in depth in the 52 OOP interview questions post — encapsulation, inheritance, polymorphism, and abstraction, the four pillars C# is built around at the language level (private/public access modifiers, class inheritance and interfaces, virtual/override, abstract classes).
Q17. What is a class and how is it different from a struct?
| class | struct | |
|---|---|---|
| Type category | Reference type | Value type |
| Storage | Heap | Stack (or inline), unless boxed |
| Inheritance | Supports inheritance | Cannot inherit from another struct/class (only implement interfaces) |
| Default choice for | Most types, especially anything with identity/mutable state | Small, immutable, value-semantic data (a Point, a Money amount) |
Q18. Explain the concept of inheritance and its use in C#.
public class Animal { public virtual void Speak() => Console.WriteLine("..."); }
public class Dog : Animal { public override void Speak() => Console.WriteLine("Woof"); }C# supports single inheritance for classes (one base class) and multiple interface implementation — see the OOP post's diamond-problem question for why multiple class inheritance specifically isn't allowed.
Q19. What is polymorphism, and can you give a C# example?
Animal a = new Dog();
a.Speak(); // "Woof" — runs Dog's override, even through an Animal-typed referenceQ20. What is encapsulation and how is it implemented in C#?
public class Account
{
private decimal _balance;
public decimal Balance => _balance; // read-only from outside
}Via access modifiers (private, protected, public, internal) and properties — a property can expose a computed or validated view of private state without exposing the field itself for direct, unchecked mutation.
Q21. What are abstract classes and interfaces, and when do you use each?
Covered in depth in the OOP post's interface-vs-abstract-class question — in short, an interface for a pure capability contract implementable by unrelated classes, an abstract class when you want to share actual implementation among closely related subclasses.
Q22. Can you explain what a virtual method is in C#?
public virtual void Render() { ... } // can be overridden by a subclass
public void Save() { ... } // cannot — no virtual keywordvirtual marks a method as overridable — without it, a method is sealed to its declaring class's implementation regardless of the runtime type, and "overriding" it in a subclass would actually just hide it (a separate, distinct, and generally confusing behavior called method hiding via new).
Q23. What is method overloading and method overriding?
Covered in the OOP post's dedicated question — overloading is same name, different parameters, resolved at compile time; overriding is a subclass replacing a virtual base method's implementation, resolved at runtime.
Q24. Can you describe the base keyword?
public class Dog : Animal
{
public override void Speak()
{
base.Speak(); // calls Animal's version first
Console.WriteLine("Woof");
}
public Dog() : base("generic name") { } // calls a specific base constructor
}base accesses the immediate base class's members explicitly — most commonly to call the base implementation of an overridden method (extending rather than fully replacing it), or to call a specific base constructor overload.
Q25. What is an access modifier and what are the different types of access modifiers?
Covered in the OOP post's access-specifiers question — public, private, protected, internal, and protected internal (and, since C# 9, the more restrictive private protected).
C# Advanced Concepts
Q26. What are indexers in C#?
public class Matrix
{
private double[,] _data = new double[10, 10];
public double this[int row, int col]
{
get => _data[row, col];
set => _data[row, col] = value;
}
}
var m = new Matrix();
m[0, 0] = 5.0; // uses the indexer, reads like array syntaxAn indexer lets a custom class support array-like [ ] syntax — defined via a this[...] property, useful whenever a type conceptually represents a collection or grid of values, even if it isn't literally backed by a plain array.
Q27. Explain the concept of delegates in C#.
public delegate int MathOp(int a, int b);
MathOp op = (a, b) => a + b;
int result = op(2, 3); // 5
// Built-in generic delegate types cover most needs directly:
Func<int, int, int> add = (a, b) => a + b;
Action<string> log = msg => Console.WriteLine(msg);A delegate is a type-safe reference to a method (or several, since delegates support multicast) — the foundation events, LINQ's lambda parameters, and callback-based APIs are all built on. Func<T> and Action<T> are the built-in generic delegate types covering the vast majority of cases without declaring a custom delegate type.
Q28. What are events and how are they different from delegates?
public class Button
{
public event EventHandler Clicked;
public void SimulateClick() => Clicked?.Invoke(this, EventArgs.Empty);
}
var button = new Button();
button.Clicked += (sender, e) => Console.WriteLine("Clicked!");| Delegate | Event | |
|---|---|---|
| Invocation | Callable directly by anyone with a reference | Only the declaring class can raise it (invoke it) |
| Subscription | Assignment (=) replaces all subscribers | Only += / -= allowed from outside the declaring class |
An event is a delegate with restricted access — outside code can subscribe/unsubscribe (+=/-=) but can't invoke it directly or overwrite other subscribers with =, which is exactly the encapsulation event adds specifically to protect the publish/subscribe contract.
Q29. What are Lambda expressions and where would you use them?
Covered in the LINQ post's lambda question — an inline, unnamed function, used constantly as arguments to LINQ operators, event handlers, and anywhere a short delegate is more convenient than a named method.
Q30. Can you explain what extension methods are and how to use them?
public static class StringExtensions
{
public static bool IsValidEmail(this string s) => s.Contains("@");
}
bool ok = "a@b.com".IsValidEmail(); // called as if it were an instance methodAn extension method is a static method with this before its first parameter, which lets it be called using instance-method syntax on the extended type — this is the exact mechanism that makes Where()/Select() and every other LINQ operator callable on any IEnumerable<T>, without modifying that interface itself.
Q31. What are generics and how do they provide type safety?
Covered in the OOP post's generics question — a class or method parameterized by a type argument, checked at compile time, avoiding both the runtime cost of boxing and the risk of a bad cast that a pre-generics object-based design would carry.
Q32. Define LINQ and mention its advantages.
Covered exhaustively in the 100 LINQ interview questions post — in short, a unified, declarative query syntax over any IEnumerable<T>/IQueryable<T> source, more concise and composable than hand-written loops.
Q33. What is the difference between IEnumerable and IQueryable?
Covered in depth in the LINQ post — IEnumerable<T> executes in-memory as compiled code; IQueryable<T> builds an expression tree a provider (like EF Core) translates into a different query language (SQL) and executes remotely.
Q34. What are async and await keywords and how do they work?
public async Task<string> GetDataAsync()
{
var response = await httpClient.GetAsync(url); // thread returns to the pool while awaiting
return await response.Content.ReadAsStringAsync();
}async marks a method as containing asynchronous logic; await suspends execution at that point until the awaited Task completes, without blocking the calling thread — the compiler transforms the method into a state machine that resumes from the right point once the awaited operation finishes, which is what lets a small thread pool serve huge numbers of concurrent I/O-bound operations.
Q35. What is the purpose of the using statement?
using (var file = new StreamReader("data.txt"))
{
var content = file.ReadToEnd();
} // file.Dispose() called automatically here, even if an exception is thrown
// C# 8+ using declaration — disposed at the end of the enclosing scope:
using var file = new StreamReader("data.txt");using guarantees Dispose() is called on an IDisposable object once it goes out of scope, even if an exception is thrown partway through — the deterministic alternative to relying on the garbage collector's non-deterministic finalization for releasing unmanaged resources (file handles, database connections, network sockets).
C# Collections and Data Structures
Q36. What are collections in C#?
Types (in System.Collections.Generic, mostly) for storing and managing groups of related objects — List<T>, Dictionary<TKey, TValue>, Queue<T>, Stack<T>, HashSet<T>, and more, each optimized for a different access pattern.
Q37. What is the difference between arrays and collections?
| Array | Collection (e.g. List<T>) | |
|---|---|---|
| Size | Fixed at creation | Grows/shrinks dynamically |
| Type safety pre-generics | Always type-safe (T[]) | Object-based (ArrayList) unless generic |
| Rich operations | Minimal — length, indexing | Add/Remove/Insert/Sort/Find and much more |
Q38. Explain the different types of collections in .NET.
| Collection | Characteristics |
|---|---|
| List<T> | Ordered, index-accessible, dynamically resizable |
| Dictionary<TKey,TValue> | Key-value lookup, average O(1) access by key |
| HashSet<T> | Unique elements, fast membership checks |
| Queue<T> | FIFO — first in, first out |
| Stack<T> | LIFO — last in, first out |
| LinkedList<T> | Doubly-linked nodes, efficient insertion/removal anywhere |
Q39. What is the difference between List and LinkedList?
| List<T> | LinkedList<T> | |
|---|---|---|
| Backing structure | A resizable array | Doubly-linked nodes |
| Random access by index | O(1) | O(n) — must walk the list |
| Insert/remove in the middle | O(n) — shifts elements | O(1) once you have the node reference |
Q40. Can you discuss the IDictionary interface and its implementation?
IDictionary<string, int> ages = new Dictionary<string, int>();
ages["Ada"] = 36;
if (ages.TryGetValue("Ada", out int age)) { ... }IDictionary<TKey, TValue> defines the key-value contract (indexer, Keys/Values, TryGetValue, Add/Remove) — Dictionary<TKey, TValue> is the standard hash-table-based implementation; SortedDictionary<TKey, TValue> is a tree-based alternative that keeps keys ordered at the cost of slower average-case operations.
Q41. What are HashTable and Dictionary and how do they differ?
| Hashtable (legacy) | Dictionary<TKey,TValue> | |
|---|---|---|
| Typing | Object-based — boxing for value types, casts needed | Generic — type-safe, no boxing |
| Status | Legacy, from before generics existed | The modern standard choice |
Q42. How does a C# HashSet work and what are its benefits?
var seen = new HashSet<int>();
if (seen.Add(5)) { /* wasn't already present */ }HashSet<T> stores unique elements backed by a hash table, giving average O(1) Add/Contains/Remove — its main benefit is exactly that: fast membership testing and automatic deduplication, without the O(n) linear scan a List<T>.Contains() would need.
Q43. What are Enumerable and Queryable collections?
These map directly to IEnumerable<T> and IQueryable<T>, covered above and in depth in the LINQ post — "Enumerable" collections execute queries in-memory, "Queryable" ones defer to a provider that translates the query into another language entirely.
Q44. When would you use a Queue vs a Stack?
var queue = new Queue<string>();
queue.Enqueue("first"); queue.Enqueue("second");
queue.Dequeue(); // "first" — FIFO
var stack = new Stack<string>();
stack.Push("first"); stack.Push("second");
stack.Pop(); // "second" — LIFOQueue for processing items in the order they arrived (task/message processing, breadth-first traversal); Stack for undo history, backtracking, or depth-first traversal — the choice follows directly from whether "oldest first" or "newest first" matches the problem's actual semantics.
Q45. How do you sort elements in a collection?
list.Sort(); // in-place, natural ordering
list.Sort((a, b) => b.CompareTo(a)); // in-place, custom comparison
var sorted = list.OrderBy(x => x.Name).ToList(); // LINQ — returns a new sequenceC# Exception Handling
Q46. What is exception handling and why is it necessary?
Exception handling is the mechanism for detecting and responding to runtime errors (a failed network call, invalid input, a missing file) without the whole application crashing — try/catch isolates the failure, lets you recover, retry, or fail gracefully with a useful message instead of an unhandled crash.
Q47. What are the common exception types in C#?
| Exception | Common cause |
|---|---|
| NullReferenceException | Calling a member on a null reference |
| ArgumentException / ArgumentNullException | Invalid method arguments |
| InvalidOperationException | A method called when the object isn't in a valid state for it |
| IndexOutOfRangeException | Accessing an array/collection index that doesn't exist |
| FormatException | Parsing a string into a type it doesn't match (e.g. int.Parse("abc")) |
Q48. How do you create custom exceptions in C#?
public class InsufficientFundsException : Exception
{
public decimal ShortfallAmount { get; }
public InsufficientFundsException(decimal shortfall)
: base($"Short by {shortfall:C}")
{
ShortfallAmount = shortfall;
}
}Derive from Exception (or a more specific existing exception type), add any extra context as properties, and call the base constructor with a meaningful message — custom exceptions are worth creating when callers genuinely need to catch and handle that specific failure differently from a generic one.
Q49. What is the use of the finally block?
FileStream file = null;
try
{
file = File.OpenRead("data.txt");
Process(file);
}
finally
{
file?.Dispose(); // runs whether Process() succeeded, threw, or the method returned early
}finally always runs — whether the try block completed normally, threw an exception, or hit a return statement — making it the right place for cleanup that must happen no matter what (though using is generally preferred specifically for IDisposable cleanup, since it's more concise and less error-prone).
Q50. Can you explain exception filters introduced in C# 6?
try
{
CallApi();
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests)
{
// only catches this specific case — anything else propagates normally
}The when clause adds a condition to a catch block — the exception is only caught if both the type matches and the condition is true, letting you handle one specific scenario of an exception type differently without a generic catch-and-rethrow-if-not-matching pattern.
C# Asynchronous Programming
Q51. What is the Task Parallel Library (TPL)?
The TPL is the API surface (Task, Task<T>, Parallel class, and async/await's underlying implementation) for writing asynchronous and parallel code in .NET — Task represents an operation that will complete in the future, and it's the foundation both async/await and explicit parallel programming are built on.
Q52. Explain the difference between synchronous and asynchronous operations.
A synchronous call blocks the calling thread until it completes; an asynchronous call returns immediately (typically as a Task representing the eventual result), letting the calling thread do other work while the operation completes in the background — critical for I/O-bound work specifically, where the thread would otherwise sit idle waiting on a network/disk response.
Q53. How do you cancel an asynchronous operation?
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
try
{
await LongRunningOperationAsync(cts.Token);
}
catch (OperationCanceledException)
{
// handled the cancellation
}CancellationToken is passed into an async method, which periodically checks (ThrowIfCancellationRequested()) or passes it into another cancellable API (like HttpClient) — cancellation is cooperative, not forcible: the method itself has to check the token and actually stop, .NET doesn't forcibly kill a running Task.
Q54. What is the difference between Task and Thread?
| Thread | Task | |
|---|---|---|
| Represents | An actual OS thread | An abstraction over asynchronous work — may or may not use a dedicated thread |
| Overhead | Heavier — each thread costs real OS resources | Lighter — often runs on the thread pool, reused across many tasks |
| Composability | Manual coordination (Join, etc.) | await, ContinueWith, Task.WhenAll/WhenAny — much richer composition |
Q55. Discuss the use of the Parallel class in C#.
Parallel.For(0, 1000, i => Process(i));
Parallel.ForEach(items, item => Process(item));Parallel.For/ForEach distributes CPU-bound iterations across multiple threads automatically — appropriate for independent, parallelizable work over a large enough dataset that the parallelization overhead pays for itself; the wrong tool for I/O-bound work, where async/await (not more threads) is the right fit.
C# File I/O and Serialization
Q56. How do you read from and write to a text file in C#?
string content = File.ReadAllText("data.txt");
File.WriteAllText("output.txt", "some content");
// For large files, stream instead of loading everything into memory:
foreach (var line in File.ReadLines("large.txt")) { Process(line); }Q57. What are the file handling classes in C#?
| Class | Purpose |
|---|---|
| File / Directory | Static helpers for common file/folder operations |
| FileStream | Low-level byte-oriented file access |
| StreamReader / StreamWriter | Text-oriented reading/writing over a stream |
| BinaryReader / BinaryWriter | Reading/writing primitive types in binary form |
Q58. Explain serialization and deserialization in the context of C#.
var json = JsonSerializer.Serialize(order); // object → JSON string
var order = JsonSerializer.Deserialize<Order>(json); // JSON string → objectSerialization converts an in-memory object into a storable/transmittable format (JSON, XML, binary); deserialization reverses it — essential for persisting objects to disk, sending them over a network, or caching them, since raw C# objects can't be written to a file or an HTTP response directly.
Q59. What is the difference between XML Serialization and JSON Serialization?
| XML | JSON | |
|---|---|---|
| Verbosity | More verbose (opening/closing tags) | More compact |
| Ecosystem fit | Common in older/enterprise SOAP-based systems | The default for modern REST APIs |
| .NET API | XmlSerializer / DataContractSerializer | System.Text.Json (or Newtonsoft.Json) |
Q60. How do you use streams in C#?
using var input = File.OpenRead("source.txt");
using var output = File.Create("destination.txt");
await input.CopyToAsync(output);A Stream represents a sequence of bytes from some source/destination (a file, network connection, memory buffer) — the abstraction that lets the same code read/write/copy data regardless of whether it's backed by a file, an HTTP response body, or an in-memory MemoryStream.
C# Attributes and Reflection
Q61. What are attributes in C#?
[Obsolete("Use NewMethod instead")]
public void OldMethod() { }
[Route("api/products")]
public class ProductsController { }Attributes attach declarative metadata to code elements (classes, methods, properties) that other code — the compiler, a framework, or your own reflection-based logic — can read and act on, without that metadata affecting the code's normal runtime behavior on its own.
Q62. How do you define a custom attribute?
[AttributeUsage(AttributeTargets.Property)]
public class MaxLengthAttribute : Attribute
{
public int Length { get; }
public MaxLengthAttribute(int length) => Length = length;
}
public class User
{
[MaxLength(50)]
public string Name { get; set; }
}Q63. What is reflection and why is it useful?
Reflection lets code inspect and interact with types, methods, and attributes at runtime — reading an assembly's metadata, discovering a class's members dynamically, or creating an instance without knowing its concrete type at compile time. It's what powers serializers, DI containers (constructing types based on registered mappings), and ORMs mapping properties to database columns.
Q64. Explain how to use reflection to inspect an assembly's metadata.
Assembly assembly = typeof(Order).Assembly;
foreach (Type type in assembly.GetTypes())
{
Console.WriteLine(type.Name);
foreach (var prop in type.GetProperties())
Console.WriteLine($" {prop.Name}: {prop.PropertyType}");
}Q65. How do you use reflection to create an instance of a class at runtime?
Type type = Type.GetType("MyApp.OrderService");
object instance = Activator.CreateInstance(type);Activator.CreateInstance() constructs an object from a Type reference discovered at runtime rather than a compile-time new SomeType() — exactly what a DI container does internally when resolving a registered service type from its interface mapping.
C# Memory Management
Q66. Describe the stack and heap in .NET's memory management.
| Stack | Heap | |
|---|---|---|
| Holds | Value types, method call frames, local variable references | Reference type instances (objects), arrays |
| Lifetime | Tied to the method call — freed when it returns | Managed by the garbage collector, freed once unreachable |
| Speed | Very fast — simple pointer increment/decrement | Slower — involves allocation bookkeeping and GC |
Q67. What are the finalizers in C#?
public class ResourceHolder
{
~ResourceHolder() // finalizer syntax
{
// last-resort cleanup — timing is non-deterministic
}
}A finalizer runs before the GC reclaims an object's memory, at a non-deterministic time — it's a safety net for releasing unmanaged resources if Dispose() was never called, but it shouldn't be relied on as the primary cleanup mechanism, which is exactly what the IDisposable + using pattern exists to provide deterministically instead.
Q68. How do you force a garbage collection?
GC.Collect();
GC.WaitForPendingFinalizers();GC.Collect() forces an immediate collection — almost never a good idea in production code, since the GC's own heuristics are generally better-tuned than a manual trigger, and forcing collections can actually hurt performance by disrupting the generational assumptions the collector relies on. It's mostly useful in benchmarking/diagnostic scenarios where you deliberately want a clean measurement baseline.
Q69. Explain the IDisposable interface and the Dispose pattern.
public class DatabaseConnection : IDisposable
{
private bool _disposed;
public void Dispose()
{
if (_disposed) return;
// release unmanaged resources here
_disposed = true;
}
}IDisposable defines a single Dispose() method for deterministic cleanup of unmanaged resources — implementing it (and guarding against being called more than once, as above) is the standard contract that lets consumers use using to guarantee cleanup runs, exception or not.
Q70. What is a memory leak in .NET and how can it be prevented?
Even with a garbage collector, a "leak" happens when objects stay reachable (and therefore un-collectible) longer than intended — the usual causes are event handlers subscribed but never unsubscribed (the publisher keeps the subscriber alive), static collections that only ever grow, and undisposed IDisposable resources. Prevent it by unsubscribing from events, disposing resources via using, and being deliberate about anything held in a static field.
C# Debugging and Diagnostic
Q71. How do you debug a C# application?
Visual Studio/VS Code/Rider's built-in debugger — breakpoints, step-through execution, watch windows for inspecting variable state, and the call stack for tracing how execution reached the current point. For production issues you can't reproduce locally, structured logging and tools like dotnet-dump/dotnet-trace take over instead.
Q72. What are breakpoints and how are they used?
A breakpoint pauses execution at a specific line when hit, letting you inspect the current state (local variables, the call stack) before deciding whether to step through line by line, step into/over a method call, or continue running — the fundamental interactive debugging primitive.
Q73. Explain the use of the Debug and Trace classes.
Debug.WriteLine("Only compiled into Debug builds");
Trace.WriteLine("Compiled into both Debug and Release builds");Debug.WriteLine calls are stripped out of Release builds entirely (via the DEBUG conditional compilation symbol); Trace.WriteLine calls remain in both — useful when you need production-environment diagnostic output that Debug-only logging wouldn't provide.
Q74. Discuss the techniques to analyze a memory dump.
Capture a dump with dotnet-dump (or ProcDump/Task Manager on Windows), then analyze it with dotnet-dump analyze, WinDbg with SOS, or Visual Studio's own dump analysis — inspecting live object counts by type, the largest objects on the heap, and reference chains keeping unexpectedly-alive objects rooted, to pin down exactly what's consuming memory.
Q75. How can you profile a C# application to identify performance bottlenecks?
dotnet-trace for CPU sampling, dotnet-counters for live GC/thread-pool/CPU metrics, BenchmarkDotNet for rigorous micro-benchmarking of specific methods, or the Visual Studio Profiler for an integrated IDE experience — the same core toolset covered in the .NET Core interview post's performance section, since it's really a platform-level (not C#-language-level) concern.
C# Concurrency and Parallelism
Q76. What is a deadlock and how can it be prevented?
// Deadlock risk: two locks acquired in different orders on different threads
lock (lockA) { lock (lockB) { ... } } // thread 1
lock (lockB) { lock (lockA) { ... } } // thread 2 — can deadlock with thread 1A deadlock happens when two or more threads each hold a lock the other needs, so none can proceed. Prevent it by always acquiring locks in a consistent, agreed-upon order across every code path that needs more than one, minimizing how much code runs while holding a lock, and preferring lock-free/higher-level constructs (Interlocked, concurrent collections) where they suffice instead of hand-rolled locking.
Q77. Discuss the reader-writer lock pattern in C#.
private readonly ReaderWriterLockSlim _lock = new();
public int Read()
{
_lock.EnterReadLock();
try { return _value; } finally { _lock.ExitReadLock(); }
}
public void Write(int value)
{
_lock.EnterWriteLock();
try { _value = value; } finally { _lock.ExitWriteLock(); }
}ReaderWriterLockSlim allows many concurrent readers, but only one writer at a time (and no readers while a write is in progress) — a meaningful improvement over a plain lock for read-heavy workloads, where a full mutual-exclusion lock would needlessly serialize reads that don't actually conflict with each other.
Q78. Explain how the lock keyword ensures thread safety.
private readonly object _sync = new();
private int _counter;
public void Increment()
{
lock (_sync) { _counter++; } // only one thread executes this block at a time
}lock is syntactic sugar over Monitor.Enter/Exit — it ensures only one thread at a time can execute the guarded block for a given lock object, preventing race conditions on shared mutable state. The lock object should be a private, dedicated object (not a public one, or a value like a string that might be interned/shared unexpectedly elsewhere).
Q79. What are Mutexes, Semaphores, and Monitors?
| Primitive | Scope | Notes |
|---|---|---|
| Monitor (lock) | Within one process | The most common, lightweight in-process mutual exclusion |
| Mutex | Can span processes (a system-wide named mutex) | Heavier than Monitor; needed for cross-process synchronization |
| Semaphore / SemaphoreSlim | Within (or across) a process | Limits concurrent access to N holders, not just one — good for throttling |
Q80. How do you achieve parallelism using PLINQ?
Covered in depth in the LINQ post's PLINQ question — call .AsParallel() on a LINQ to Objects query to have it partition the source across multiple cores automatically, worthwhile for CPU-bound work over a large enough dataset to justify the coordination overhead.
C# Unit Testing and Test Driven Development (TDD)
Q81. What is unit testing and what frameworks do you use for it in C#?
| Framework | Notes |
|---|---|
| xUnit | Most common in modern .NET projects — [Fact]/[Theory] |
| NUnit | Long-established, [Test]/[TestCase] |
| MSTest | Ships from Microsoft, [TestMethod] |
Q82. Explain the concept of Test-Driven Development (TDD).
Write a failing test first (red), write the minimum code to pass it (green), then refactor without changing behavior — repeat. Covered in more depth in the .NET Core interview post's TDD question.
Q83. How do you mock objects in C# unit tests?
var mockRepo = new Mock<IOrderRepository>();
mockRepo.Setup(r => r.GetById(1)).Returns(new Order { Id = 1 });
var service = new OrderService(mockRepo.Object);Moq (or NSubstitute) generates a fake implementation of an interface at test time, letting you configure exactly what it returns for a given call and verify it was called as expected — this only works cleanly because the class under test depends on an interface via Dependency Injection, not a concrete hardcoded implementation.
Q84. What are the common attributes used in a test method?
| xUnit attribute | Purpose |
|---|---|
| [Fact] | A test with no parameters |
| [Theory] | A parameterized test, run once per [InlineData] |
| [InlineData(...)] | Supplies one set of parameters for a [Theory] |
Q85. How do you test asynchronous code in C#?
[Fact]
public async Task GetOrderAsync_ReturnsOrder_WhenExists()
{
var service = new OrderService(mockRepo.Object);
var order = await service.GetOrderAsync(1);
Assert.NotNull(order);
}Make the test method itself async Task and await the method under test — xUnit (and NUnit/MSTest) natively support async test methods, running and awaiting them correctly without any special ceremony beyond the normal async/await syntax.
C# Best Practices and Design Patterns
Q86. Why are SOLID principles important in C#?
Covered in depth across the OOP and Dependency Injection posts — SOLID guides toward loosely coupled, independently testable, extensible classes, which matters especially in a statically-typed, interface-rich language like C# that's specifically built to support exactly this style of design.
Q87. Can you describe some common design patterns and their applications in C#?
Covered with C# code examples in the OOP post's "Practical Use and Patterns" section — Factory Method, Builder, Adapter, Decorator, Observer, Command, and Strategy, each addressing a different structural or behavioral design problem.
Q88. How do you ensure your C# code is maintainable and readable?
- Consistent naming conventions (PascalCase for types/public members, camelCase for locals/private fields)
- Small, focused methods and classes — high cohesion, low coupling
- Meaningful names over comments explaining what code does; comments reserved for non-obvious why
- Nullable reference types enabled, so the compiler flags likely null-reference bugs at build time
Q89. What strategies do you use for error handling and exception management?
Catch only exceptions you can meaningfully handle at that point; let others propagate to a level that can (or to a global exception handler for logging/response formatting); use exception filters for conditional handling; and reserve exceptions for genuinely exceptional conditions, not routine expected outcomes better modeled with a return value or the TryX pattern.
Q90. Discuss the concept of dependency injection and how it's used in C#.
Covered exhaustively in the 35 Dependency Injection interview questions post — in the C# ecosystem specifically, Microsoft.Extensions.DependencyInjection is built directly into ASP.NET Core's hosting model, so constructor injection is the default, idiomatic way virtually every ASP.NET Core class receives its dependencies.
Q91. What are the new features introduced in the latest version of C#?
The exact list depends on which C# version is being asked about, since the language ships roughly yearly alongside .NET — the honest answer names the general direction (less boilerplate, better pattern matching, stronger null-safety) plus a couple of concrete recent examples, rather than reciting a changelog from memory.
Q92. How has pattern matching evolved in recent C# versions?
// C# 7 — type pattern
if (obj is Circle c) { ... }
// C# 8 — switch expressions, property patterns
string Describe(Shape s) => s switch
{
Circle { Radius: > 10 } => "big circle",
Circle => "circle",
Square => "square",
_ => "unknown",
};
// C# 9+ — relational and logical patterns
bool IsMidRange(int x) => x is >= 10 and <= 100;Pattern matching started with simple type checks (is Circle c) and expanded through switch expressions, property patterns (matching on a nested property's value), and relational/logical pattern combinators (and/or/not, and comparisons like >= 10) — increasingly letting you express complex conditional logic declaratively rather than as nested if statements.
Q93. Explain how C# 8 nullable reference types work.
#nullable enable
string nonNull = "always has a value"; // compiler-enforced
string? maybeNull = null; // explicitly nullable
Console.WriteLine(nonNull.Length); // fine
Console.WriteLine(maybeNull.Length); // compiler WARNING — possible null dereferenceCovered in the .NET Core post's dedicated question — nullable reference types don't change runtime behavior at all, they let the compiler statically flag code paths where a possibly-null reference flows somewhere that assumes it isn't, catching a large class of NullReferenceException bugs before the code ever runs.
Q94. What is the switch expression and how does it differ from the switch statement?
// Switch statement — imperative, each case is a block
switch (day)
{
case DayOfWeek.Saturday:
case DayOfWeek.Sunday:
isWeekend = true;
break;
default:
isWeekend = false;
break;
}
// Switch expression (C# 8+) — an expression that evaluates to a value
bool isWeekend = day switch
{
DayOfWeek.Saturday or DayOfWeek.Sunday => true,
_ => false,
};A switch statement executes a block of statements per case; a switch expression evaluates directly to a value, more concise for the common "assign a value based on a condition" pattern, and the compiler enforces exhaustiveness (or requires a _ discard case) more strictly than a statement's optional default.
Q95. How do you take advantage of tuples in C#?
(string Name, int Age) GetPerson() => ("Ada", 36);
var person = GetPerson();
Console.WriteLine($"{person.Name} is {person.Age}");
// Deconstruction:
var (name, age) = GetPerson();Tuples let a method return multiple values without declaring a dedicated class/struct just for that one purpose — named tuple elements (Name, Age above) make the result self-documenting, and deconstruction lets the caller unpack them directly into separate variables.
C# Interoperability
Q96. How can you call unmanaged code using C#?
[DllImport("user32.dll")]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, int type);
MessageBox(IntPtr.Zero, "Hello", "Title", 0);P/Invoke (Platform Invoke) lets managed C# code call functions in native/unmanaged libraries (a Windows DLL, a native shared library) directly — declaring the native function's signature with DllImport, and the CLR marshals data between managed and unmanaged representations automatically.
Q97. What is the role of P/Invoke in C#?
It's the specific mechanism enabling the interop above — handling the calling convention, marshaling of parameters/return values between .NET types and native types (a C# string to a native char*, for instance), and locating the native function in the specified library.
Q98. How do you interface with COM objects in C#?
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
excel.Visible = true;Via COM interop — either compile-time generated interop assemblies (from a COM type library) for strongly-typed access, or dynamic (as above) for a looser, runtime-resolved binding. This is a Windows-specific capability — .NET Core added more limited COM support than .NET Framework had, and it doesn't extend to Linux/macOS at all, since COM itself is a Windows technology.
Q99. Discuss C# and .NET Core inter-platform capabilities.
Covered in the .NET Core interview post's cross-platform question — the same C# code, compiled once, runs on Windows, Linux, and macOS via .NET Core's cross-platform CoreCLR runtime, with platform-specific interop (P/Invoke, COM) being the main place platform differences actually leak through.
C# and .NET Framework/Core Future
Q100. How is C# evolving with .NET 5 and beyond?
C# ships on the same yearly release train as .NET itself (covered in the .NET Core post's LTS/STS question) — recent language direction has consistently leaned toward less boilerplate (top-level statements, minimal APIs, primary constructors), stronger compile-time safety (nullable reference types, more exhaustive pattern matching), and performance-oriented features (Span<T>, Native AOT support) rather than radical syntax changes.
Test yourself
A quick mock test covering the ground above — the rapid-fire round that often opens a C# interview before the deeper follow-ups.
Question 1 of 8
Score: 0 / 0
1. What's the fundamental difference between a value type and a reference type in C#?