🧠 Core Language & Fundamentals- What is the difference between class / struct / record in C#?
When would you use them?
In C#, a class is a reference type, mutable by default, supports inheritance, and uses reference equality unless overridden. A struct is a value type, copied when passed, and typically used for small, performance-critical data. A record is mainly for immutable data with value-based equality, supports with expressions, deconstruction, and a generated ToString(). A record struct combines value-type semantics with value-based equality, and is mutable by default.
Usage: classes for complex or inheritable objects, structs for small values, and records for immutable data models or DTOs.
What happens if a record contains a property that is a class and you use a with expression? How does it affect equality and mutability?
- Explain the difference between interface and abstract class.
Both interfaces and abstract classes are used to define contracts for other classes.
An interface defines a contract that a class must implement. A class can implement multiple interfaces, which makes them useful for defining capabilities across different types.
An abstract class can provide both a contract and shared implementation. It supports constructors, fields, and implemented methods. However, a class can inherit from only one abstract class.
Since C# 8, interfaces can also contain default method implementations, but they still cannot have instance state like fields.
In practice, I use interfaces for defining behavior and abstraction, and abstract classes when I need to share common logic or state between related classes. Can a class implement two interfaces that have methods with the same signature but require different implementations? How can you call a specific implementation in that case?
- What is boxing and unboxing? When does it happen?
Boxing is the process of converting a value type (like int or struct) into a reference type (object or interface). Unboxing is the reverse process — extracting the value type from the object.
Boxing happens when a value type is assigned to an object or interface. Unboxing happens when you cast that object back to the original value type.
Boxing creates a new object on the heap, so it has a performance cost. Unboxing also requires casting and can throw an exception if the types don’t match. Why is boxing considered expensive, and how can you avoid it in high-performance code?
- What is the difference between
== and .Equals()?
The == operator and .Equals() are both used for comparison, but they behave differently.
The == operator compares references for reference types and values for value types by default, although it can be overloaded.
The .Equals() method is used for value-based comparison and can be overridden to define custom equality logic.
One important difference is that calling .Equals() on a null object will throw an exception, while == can safely compare null values. What is the difference between object.Equals(a, b) and a.Equals(b)?
- What are the access modifiers in C#?
In C#, there are six main access modifiers: public, private, protected, internal, protected internal, and private protected.
public – accessible from anywhere private – accessible only within the same class protected – accessible within the class and its derived classes internal – accessible within the same assembly protected internal – accessible from the same assembly or from derived classes private protected – accessible only within the same assembly and in derived classes
These modifiers can be applied to classes, methods, properties, fields, and other members to control visibility and encapsulation. What is the difference between protected internal and private protected?
⚡ Memory Management & Performance- How does garbage collection work in .NET? What are generations in GC?
In .NET, garbage collection automatically manages memory by reclaiming objects that are no longer in use. It works in a non-deterministic way and is triggered when the runtime decides it's necessary, typically when memory pressure increases. The GC uses a generational model with three generations: Gen 0, Gen 1, and Gen 2. - "Gen 0" is for short-lived objects
- "Gen 1" is a buffer between short- and long-lived objects
- "Gen 2" is for long-lived objects
Objects that survive collections are promoted to higher generations. This improves performance because most objects are short-lived and collected quickly. There is also a Large Object Heap for large allocations, which is collected less frequently. Although it's possible to force garbage collection using GC.Collect(), it is generally not recommended. What happens when an object survives multiple garbage collections?
- What is the Large Object Heap (LOH)?
The Large Object Heap (LOH) is a separate part of the managed heap used to store large objects, typically those larger than 85 KB. It is not a separate generation, but it is collected together with Gen 2. LOH is optimized for large allocations, so objects there are collected less frequently to reduce overhead. By default, the LOH is not compacted, which can lead to memory fragmentation over time. Why can the Large Object Heap lead to memory fragmentation, and how can it be mitigated?
- What is
Span<T> and when should you use it?
Span<T> is a lightweight value type that represents a contiguous region of memory. It provides a safe and efficient way to work with slices of arrays, stack memory, or unmanaged memory without additional allocations.
It contains a reference to the memory and its length, allowing direct access without copying data.
Span<T> is mainly used for performance-critical scenarios where avoiding allocations and copying is important, such as parsing or processing large data.
However, since it is a ref struct, it is restricted to the stack and cannot be used in async methods or stored in heap objects. Why can't Span<T> be used in async methods or stored as a class field?
- How can you reduce memory allocations in C#?
To reduce memory allocations in C#, I focus on minimizing unnecessary object creation.
Some common techniques include:
Preallocating collections to avoid resizing Avoiding boxing by using generics instead of object Using StringBuilder for string concatenation and Span<T> to avoid copying data Using structs for small, short-lived data to avoid heap allocations
In general, I try to reduce allocations in hot paths and reuse memory whenever possible. Why can using structs sometimes increase memory usage or hurt performance instead of improving it?
The Disposable Pattern is used to release unmanaged resources in a deterministic way.
It is implemented using the IDisposable interface and the Dispose() method, which allows developers to explicitly free resources when they are no longer needed.
If Dispose() is not called, the garbage collector will eventually clean up the object, but this is non-deterministic. A finalizer can be used as a fallback for unmanaged resources, but it is less efficient.
The pattern typically separates cleanup of managed and unmanaged resources, ensuring proper resource management. Why is it recommended to avoid finalizers when possible?
🔄 Async / Await & Multithreading- How does
async/await work under the hood?
- What is the difference between
Task and Thread?
- What is the difference between
Task and ValueTask?
- What happens if you don’t
await a task?
- What is a deadlock in async code? How can you avoid it?
- What is
ConfigureAwait(false) and when should you use it?
- Difference between
Parallel.ForEach and Task.WhenAll?
🧵 Threading & Concurrency- What is a race condition?
- What synchronization primitives do you know (
lock, Monitor, SemaphoreSlim, etc.)?
- What is the difference between
lock and Mutex?
- What is thread safety and how do you ensure it?
- What are concurrent collections?
🔗 LINQ & Collections- How does LINQ work internally?
- What is deferred execution?
- Difference between
IEnumerable and IQueryable and IAsyncEnumerable?
- When does LINQ execute a query?
- What are the performance implications of LINQ?
- Difference between
Select and SelectMany?
- What is the difference between ArrayList and List<T>?
🧩 Delegates, Events & Functional Features- What is a delegate?
- Difference between
Action, Func, and Predicate?
- What are events and how are they used?
- What are lambda expressions?
- What are closures in C#?
🏗️ OOP & Design- What are SOLID principles? (briefly explain each)
- What is dependency injection?
- What is inversion of control?
- What are common design patterns used in C#?
📦 Advanced Language Features- What are
yield return and iterators?
- What are extension methods?
- What is pattern matching in C#?
- What are nullable reference types?
- What is reflection and when would you use it?
🔍 Exception Handling- What is the difference between
throw and throw ex?
- When should you use custom exceptions?
- What are best practices for exception handling?
🧪 Practical / Real-world- How would you debug a memory leak in a .NET application?
- How do you handle high-load scenarios in C#?
- How do you profile performance in a .NET app?
- Can you describe a challenging bug you solved in C#?
Базові, але обов’язкові (часто валять кандидатів):
Архітектура + реальний досвід (Senior рівень) Describe a complex system you built What problems did you face and how did you solve them? How would you design a high-load API? How do you handle performance issues in a system?
🧩 tricky / conceptual (що часто «ламає» кандидатів) What causes StackOverflowException vs OutOfMemoryException?
🗄️ DB / EF (реальні питання) Can DbContext be a singleton? Explain SQL join types Design a database schema (Movies, Orders, etc.)
|