⚙️

Top 70 C# / .NET Interview Questions & Answers (2026)

70 Questions 36 Beginner 22 Intermediate 12 Advanced

About C# / .NET

Top 100 C# and .NET interview questions covering language fundamentals, OOP, async programming, LINQ, memory management, ASP.NET Core, Entity Framework, and advanced patterns. Companies hiring for C# / .NET roles test this knowledge at every stage — from first-round screens that check the basics to final-round conversations about real-world trade-offs.

What to Expect in a C# / .NET Interview

Expect a mix of conceptual and practical C# / .NET questions: clear definitions and core-concept checks for junior roles, hands-on scenario questions for mid-level roles, and architecture or trade-off discussions for senior roles. Interviewers usually move from foundational topics toward the kind of problems you'd actually face on the job.

How to Use This Guide

Work through the C# / .NET questions in order — Beginner, then Intermediate, then Advanced — so each concept builds on the last. Every question has its own page; bookmark the ones that trip you up and revisit them the day before your interview.

Curated by Tech Baithak Editorial Team  ·  Last updated: June 2026

Beginner 36 questions

Core concepts every C# / .NET developer must know.

01

What is C#?

C# (pronounced "C sharp") is a modern, strongly typed, object-oriented programming language developed by Microsoft as part of the .NET platform. Created by Anders Hejlsberg and introduced in 2000, C# combines the power of C/C++ with the simplicity of Java. It supports multiple programming paradigms: object-oriented, functional, component-oriented, and generic. C# runs on the CLR (Common Language Runtime), which provides memory management, garbage collection, and type safety. It is used for: web development (ASP.NET Core), desktop apps (WPF, WinForms, MAUI), games (Unity), mobile apps (Xamarin/.NET MAUI), cloud services, and microservices. C# is maintained by Microsoft and the open-source community.

Open this question on its own page
02

What is the .NET platform?

.NET is a free, cross-platform, open-source developer platform for building various types of applications. It includes: CLR (Common Language Runtime): the execution engine that manages memory, handles exceptions, and provides security. BCL (Base Class Library): a large set of APIs for common tasks (collections, I/O, networking, strings, etc.). Languages: C#, F#, and VB.NET all compile to CIL (Common Intermediate Language), which is then JIT-compiled to native code by the CLR. Evolution: .NET Framework (Windows-only, 2002–present), .NET Core (cross-platform, 2016), unified as .NET 5+ (2020, current — .NET 8 LTS is current LTS release). Mono: the original cross-platform .NET runtime. The platform supports Windows, Linux, and macOS.

Open this question on its own page
03

What is the difference between value types and reference types in C#?

In C#, types are classified based on how they store and pass data. Value types store data directly on the stack (or inline in structures). Assigning a value type creates a copy — modifications don't affect the original. Examples: int, double, bool, char, struct, enum. Reference types store a reference (pointer) to data on the heap. Assigning copies the reference — both variables point to the same object; modifying one affects the other. Examples: class, string, array, interface, delegate. Boxing: converting a value type to object (reference type) — the value is copied to the heap. Unboxing: casting the object back to the value type. Boxing/unboxing is expensive and should be avoided in performance-critical code.

Open this question on its own page
04

What is the difference between a class and a struct in C#?

Class: a reference type — instances are allocated on the heap, and variables hold references. Supports inheritance, can have a finalizer (~ClassName()), default value is null. Best for: complex objects with identity, mutable state, and when inheritance is needed. Struct: a value type — instances are typically on the stack (or inline). Cannot inherit from other structs/classes (but can implement interfaces). No finalizer. Default value is all-zeros (not null). Best for: small, immutable data groupings where copying is cheap (Vector3, DateTime, Point). Key guidelines: use struct when: the object is ≤ 16 bytes, immutable, logically a value, and won't be boxed frequently. Record struct (C# 10+) provides value-semantic records. Mutable large structs are an antipattern — they get copied on every assignment.

Open this question on its own page
05

What are access modifiers in C#?

C# access modifiers control the visibility of types and members. public: accessible from anywhere. private: accessible only within the same class/struct (default for members). protected: accessible within the class and derived classes. internal: accessible within the same assembly (DLL/EXE). protected internal: accessible from the same assembly OR from derived classes in any assembly. private protected (C# 7.2+): accessible only from derived classes within the same assembly. file (C# 11+): accessible only within the same source file. Default for types (classes, enums) in a namespace is internal. Default for members in a class is private. Principle: use the most restrictive access modifier possible to maintain good encapsulation.

Open this question on its own page
06

What is inheritance in C#?

Inheritance is an OOP principle where a class (derived/child class) inherits members (fields, methods, properties) from another class (base/parent class), enabling code reuse and establishing an "is-a" relationship. In C#: class Dog : Animal { }. Key rules: (1) C# supports single inheritance for classes (a class can only have one base class). (2) Multiple inheritance is supported via interfaces. (3) Use base keyword to call the base class constructor or methods: base.Method(). (4) sealed class prevents further inheritance. (5) abstract class cannot be instantiated and may have abstract members that must be overridden. (6) virtual methods can be overridden in derived classes; override keyword replaces the implementation. Every class ultimately inherits from System.Object.

Open this question on its own page
07

What is polymorphism in C#?

Polymorphism (many forms) allows objects of different types to be treated as objects of a common type, with each type responding differently to the same message. In C#: Compile-time polymorphism (static dispatch): method overloading — multiple methods with the same name but different parameter lists. The compiler selects the correct one. Runtime polymorphism (dynamic dispatch): method overriding — a derived class overrides a virtual or abstract method. The correct method is selected at runtime based on the actual type. Example: Animal a = new Dog(); a.Speak(); — calls Dog.Speak() if overridden, even though the variable is typed as Animal. This requires virtual dispatch. Interface polymorphism: different types implementing the same interface can be used interchangeably. Polymorphism is the foundation of the Open/Closed Principle.

Open this question on its own page
08

What is an interface in C#?

An interface in C# defines a contract — a set of method, property, event, and indexer signatures that implementing types must provide. Interfaces cannot contain instance fields (but can have static fields and default implementations in C# 8+). Syntax: public interface IShape { double Area(); double Perimeter(); }. A class or struct implements interfaces with: class Circle : IShape { public double Area() => Math.PI * r * r; ... }. Key points: (1) C# supports multiple interface implementation: class Foo : IBar, IBaz. (2) Interfaces enable polymorphism and dependency injection. (3) Interface members are public by default. (4) C# 8+ allows default interface implementations (DIM) — base behavior without breaking existing implementors. (5) Prefer interfaces over abstract classes for defining contracts when no shared state is needed. Commonly named with I prefix (IEnumerable, IDisposable).

Open this question on its own page
09

What is the difference between abstract class and interface in C#?

Abstract class: can have implemented methods (behavior), instance fields, constructors, and abstract (unimplemented) methods. A class can inherit from only one abstract class. Use when: related classes share state or behavior, and you want to provide a base implementation. Interface: historically only defined a pure contract (no implementation). C# 8+ allows default implementations. A class can implement multiple interfaces. Use when: defining capabilities across unrelated class hierarchies. When to choose: choose abstract class when you're providing a partial implementation that subclasses complete (Template Method pattern). Choose interface when you want to define a capability that many unrelated classes can implement (e.g., IComparable, IDisposable). Modern C#: with default interface methods closing the gap, the distinction is smaller — but interfaces remain better for defining cross-cutting capabilities.

Open this question on its own page
10

What is encapsulation in C#?

Encapsulation is the OOP principle of bundling data (fields) with the methods that operate on that data, and restricting direct access to internal state. In C#, achieved through: (1) Access modifiers: making fields private and exposing them via public properties or methods. (2) Properties: C# properties provide controlled access to private fields with get and set accessors, where validation or computation can be added. Example: private int _age; public int Age { get => _age; set { if (value < 0) throw new ArgumentException("Age must be non-negative"); _age = value; } }. Benefits: data integrity (validate on set), ability to change internal implementation without affecting callers, and clear separation of what is public API vs internal implementation. Auto-properties: public string Name { get; set; } (compiler generates the backing field).

Open this question on its own page
11

What is a property in C# and how does it differ from a field?

A field is a variable declared directly in a class: private int _age; — raw storage. A property is a member that provides a flexible mechanism to read, write, or compute the value of a private field. Properties look like fields to callers but are actually method pairs (get accessor / set accessor). Auto-property: public string Name { get; set; } — compiler auto-generates the backing field. Expression-bodied property: public string FullName => $"{FirstName} {LastName}";. Init-only setter (C# 9): public string Id { get; init; } — settable only in constructors and object initializers. Properties are preferred over public fields because: they support validation, can be abstract/virtual (for polymorphism), enable data binding in frameworks, and allow changing the implementation without breaking the API.

Open this question on its own page
12

What is a constructor in C#?

A constructor is a special method called when an object is created, used to initialize the object's state. It has the same name as the class and no return type. Default constructor: takes no parameters — auto-generated by the compiler if no constructor is defined. Parameterized constructor: public Person(string name, int age) { Name = name; Age = age; }. Constructor chaining: this(...) calls another constructor in the same class; base(...) calls the parent class constructor. Static constructor: static ClassName() { } — called once, before the first use of the class, for static initialization (setting static fields, loading resources). Primary constructors (C# 12): class Person(string name, int age) — concise syntax, parameters scoped to the whole class body. Object initializers (new Person { Name = "Alice" }) are different — they call the constructor then set properties.

Open this question on its own page
13

What is a delegate in C#?

A delegate is a type-safe function pointer — it holds a reference to a method with a specific signature. Delegates enable: passing methods as parameters, callbacks, and events. Declaration: public delegate int MathOperation(int a, int b);. Usage: MathOperation add = (a, b) => a + b; int result = add(3, 4);. Built-in delegates: Action (void return, 0-16 parameters), Func (non-void return, last type param is return type), Predicate<T> (bool return). Delegates are multicast — they can hold multiple method references: delegate += AnotherMethod; — all methods are called when the delegate is invoked. Delegates underpin events, LINQ (lambda expressions), and async callbacks. Modern C# prefers lambdas and built-in generic delegates over custom delegate declarations.

Open this question on its own page
14

What is an event in C#?

An event is a mechanism for communication between objects — allowing a class to notify other classes when something of interest occurs, following the publisher-subscriber pattern. Events are built on delegates. Declaration: public event EventHandler<ButtonClickedArgs> ButtonClicked;. Raising the event: ButtonClicked?.Invoke(this, new ButtonClickedArgs()); (null-conditional — safe if no subscribers). Subscribing: button.ButtonClicked += HandleClick;. Unsubscribing: button.ButtonClicked -= HandleClick;. Key difference from delegate: events can only be invoked from the class that declares them (not from outside) — enforcing the publisher pattern. EventHandler<TEventArgs> is the standard delegate type for events: (object sender, TEventArgs e). Always unsubscribe from events to prevent memory leaks (subscriber holds reference to publisher).

Open this question on its own page
15

What is LINQ in C#?

LINQ (Language Integrated Query) is a set of features in C# that provides a consistent, SQL-like syntax for querying and transforming data from various sources (collections, XML, databases, APIs) directly in C# code. Two syntax forms: Query syntax: var result = from p in people where p.Age > 18 orderby p.Name select p.Name;. Method syntax (fluent): var result = people.Where(p => p.Age > 18).OrderBy(p => p.Name).Select(p => p.Name);. Common LINQ operators: Where (filter), Select (transform/project), OrderBy/OrderByDescending, GroupBy, Join, First/FirstOrDefault, Any/All, Count, Sum/Average/Min/Max, Take/Skip, Distinct, SelectMany (flatten). LINQ uses deferred execution — queries are not evaluated until iterated. Call ToList() or ToArray() to force immediate execution.

Open this question on its own page
16

What is async/await in C#?

async/await is C#'s mechanism for writing asynchronous code that reads like synchronous code, built on the Task-based asynchronous pattern (TAP). async keyword marks a method as asynchronous — it can contain await expressions. await suspends the method at that point, releasing the thread to do other work while the awaited operation completes. Example: public async Task<string> GetDataAsync() { var response = await httpClient.GetAsync(url); return await response.Content.ReadAsStringAsync(); }. The method returns a Task (or Task<T>). Key rules: (1) Async all the way — don't mix sync and async (avoid .Result or .Wait() which can deadlock). (2) Use ConfigureAwait(false) in library code. (3) ValueTask<T> for hot paths where the result is often synchronously available. (4) Avoid async void (exceptions are unobservable) — except for event handlers.

Open this question on its own page
17

What is the difference between Task and Thread in C#?

A Thread is a low-level OS-managed execution unit — each thread has its own stack, takes 1MB+ of memory, and switching between threads (context switching) is expensive. Creating and managing threads manually is complex (synchronization, deadlocks). A Task is a higher-level abstraction built on the ThreadPool — it represents an asynchronous operation, managed by the runtime. Tasks are lightweight (no dedicated stack), pooled (threads are reused), and composable (continuations, Task.WhenAll, Task.WhenAny). Prefer Tasks over raw Threads for almost all scenarios. Use raw threads only when: you need control over thread priority, apartment state (COM interop), or very long-running dedicated work (even then, prefer Thread with IsBackground = true). Task.Run(() => ...) queues work to the ThreadPool. async/await is built on Tasks.

Open this question on its own page
18

What is garbage collection in C#?

The garbage collector (GC) is the CLR's automatic memory management system. It periodically identifies objects on the heap that are no longer reachable (no live references) and reclaims their memory — freeing developers from manual memory management (no free() like in C). The .NET GC uses a generational model with three generations: Gen 0: newly allocated, short-lived objects — collected most frequently, very fast. Gen 1: objects that survived Gen 0 collection — a buffer between Gen 0 and Gen 2. Gen 2: long-lived objects — collected infrequently, most expensive. Large Object Heap (LOH): objects ≥ 85KB, always Gen 2. GC pauses your application during collection (though .NET 5+ GC is increasingly concurrent). Tips: avoid unnecessary allocations, use Span<T> and ArrayPool<T> for hot paths, implement IDisposable for unmanaged resources.

Open this question on its own page
19

What is IDisposable and the using statement?

IDisposable is an interface with a single method void Dispose() used to release unmanaged resources (file handles, database connections, network sockets, COM objects) that the GC cannot automatically clean up. The using statement ensures Dispose() is called even if an exception is thrown: using (var conn = new SqlConnection(connStr)) { conn.Open(); /* ... */ } — equivalent to a try/finally block. Using declaration (C# 8+): using var stream = File.OpenRead("file.txt"); — disposed at the end of the enclosing scope. Implementation pattern: public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }. Include a finalizer as a safety net for unmanaged resources: ~MyClass() { Dispose(false); }. The Dispose Pattern distinguishes between managed and unmanaged cleanup. Always call Dispose() (or use using) on IDisposable objects.

Open this question on its own page
20

What is exception handling in C#?

C# uses structured exception handling with try/catch/finally blocks. try: code that might throw. catch: handles specific exceptions. finally: always executes (cleanup), whether or not an exception occurred. throw: throws an exception. Re-throw with context: throw; (preserves stack trace) vs throw ex; (resets stack trace — avoid). Best practices: (1) Catch specific exceptions, not the bare Exception (unless at the top level). (2) Never swallow exceptions silently (catch { }). (3) Use finally or using for cleanup — not catch. (4) Don't use exceptions for normal control flow — they're expensive. (5) Include meaningful messages: throw new ArgumentNullException(nameof(param), "Param cannot be null");. (6) Create custom exceptions for domain-specific errors by inheriting from Exception. (7) when clause for conditional catch: catch (HttpException ex) when (ex.StatusCode == 404).

Open this question on its own page
21

What are generics in C#?

Generics allow writing type-safe, reusable code where the type is a parameter. Instead of using object (which requires boxing and casting), generics maintain type safety at compile time. Syntax: public class Stack<T> { private List<T> items = new(); public void Push(T item) => items.Add(item); public T Pop() => items[^1]; }. Generic methods: public T Max<T>(T a, T b) where T : IComparable<T> => a.CompareTo(b) > 0 ? a : b;. Constraints: where T : class (reference type), where T : struct (value type), where T : new() (has parameterless constructor), where T : SomeBaseClass, where T : ISomeInterface. Built-in generic collections: List<T>, Dictionary<TKey,TValue>, Queue<T>, Stack<T>. Generics eliminate runtime casting errors and boxing overhead.

Open this question on its own page
22

What is the difference between == and .Equals() in C#?

== is an operator. For value types, it compares values (structural equality). For reference types, by default it compares references (identity — same object in memory), but can be overloaded. For string, == compares content (string interning + operator overload). .Equals() is a virtual method from System.Object. Value types override it for structural comparison. Reference types by default: reference equality (same as ==). Can be overridden to provide content comparison. Key scenarios: new string("hi") == new string("hi")true (string overloads ==). new object() == new object()false (different references). object.ReferenceEquals(a, b): always reference equality, no override. For collections, use SequenceEqual(). Override both == and Equals() consistently, and also override GetHashCode() when overriding Equals().

Open this question on its own page
23

What are extension methods in C#?

Extension methods allow adding methods to existing types without modifying them — even sealed classes or types from external libraries. They are defined as static methods in a static class, with the first parameter prefixed with this indicating the type being extended. Example: public static class StringExtensions { public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s); }. Usage: "hello".IsNullOrEmpty() — called as if it were an instance method. LINQ methods (Where, Select, OrderBy) are all extension methods on IEnumerable<T>. Rules: (1) Extension methods cannot access private members. (2) Instance methods always take precedence over extension methods with the same signature. (3) The namespace containing the extension class must be imported. Extension methods are syntactic sugar — the compiler translates them to static method calls.

Open this question on its own page
24

What is a lambda expression in C#?

A lambda expression is an anonymous function — an inline function definition. Syntax: (parameters) => expression (expression lambda) or (parameters) => { statements } (statement lambda). Examples: Func<int, int> square = x => x * x;. Func<int, int, int> add = (a, b) => a + b;. Action<string> print = msg => Console.WriteLine(msg);. Lambdas can capture variables from the surrounding scope (closure): int multiplier = 3; Func<int, int> triple = x => x * multiplier;. Lambda expressions are extensively used in LINQ: list.Where(x => x > 5).Select(x => x * 2). They compile to either a delegate or an Expression<T> (used by Entity Framework to translate to SQL). Lambdas with captured variables create closure objects on the heap — be mindful in hot paths.

Open this question on its own page
25

What is var in C#?

var is an implicitly typed local variable keyword. The compiler infers the type from the initialization expression at compile time — it is NOT dynamic typing. var x = 42; is identical to int x = 42;. Once inferred, the type is fixed and cannot change. Best practices: (1) Use var when the type is obvious from context: var list = new List<string>();. (2) Use explicit type when clarity matters: IEnumerable<string> items = GetItems(); — makes the intended type clear. (3) Especially useful with long generic types: var dict = new Dictionary<string, List<UserRole>>();. (4) Required for anonymous types: var person = new { Name = "Alice", Age = 30 }; — no other way to type this. dynamic is very different — it defers all type checking to runtime.

Open this question on its own page
26

What is the difference between String and StringBuilder in C#?

string (alias for System.String) is immutable — every operation that appears to modify a string (concatenation, replace, etc.) actually creates a new string object. Concatenating N strings in a loop creates N intermediate strings, leading to O(n²) memory allocations and GC pressure. StringBuilder is a mutable buffer that grows dynamically, performing in-place modifications — ideal for building strings incrementally. Example: var sb = new StringBuilder(); foreach (var item in items) { sb.Append(item).Append(", "); } string result = sb.ToString();. Use string: for simple concatenation of few strings, for string literals and constants. Use StringBuilder: when concatenating in loops, building strings with many parts, or in performance-critical code. string.Concat() and $"..." interpolation (which uses string.Format) are optimized for a few operands but still create new strings.

Open this question on its own page
27

What is the difference between Array and List&lt;T&gt; in C#?

An Array (T[]) is a fixed-size, contiguous block of memory for elements of the same type. Size is set at creation and cannot change. Direct index access is O(1) — the fastest possible. Arrays are the most memory-efficient collection (no overhead). Use when: the size is known and fixed, performance is critical. A List<T> is a dynamically resizable array (wraps a T[] internally). When capacity is exceeded, it allocates a new array (2× size) and copies elements. Supports adding, removing, inserting elements. Has LINQ extension methods and convenient APIs. Use when: the number of elements varies or is unknown. Performance: both offer O(1) indexed access. List adds, removes at the end are O(1) amortized. Insertion/removal in the middle is O(n). For frequent insertions/removals in the middle, consider LinkedList<T>. For sets and lookups, use HashSet<T> or Dictionary<K,V>.

Open this question on its own page
28

What is a namespace in C#?

A namespace in C# provides a way to organize code into logical groups and prevent naming conflicts. It acts as a container for related classes, interfaces, enums, structs, and delegates. Declaration: namespace MyCompany.MyProduct.Services { public class UserService { } }. Usage: using MyCompany.MyProduct.Services; then var service = new UserService();. Without using directive, use the fully qualified name: var service = new MyCompany.MyProduct.Services.UserService();. File-scoped namespaces (C# 10+): namespace MyApp.Services; at the top of a file — applies to the whole file, reduces nesting. Global using directives (C# 10+): global using System; in one file applies to the entire project. Namespaces don't affect access modifiers — they're purely organizational. The .NET BCL namespaces: System, System.Collections.Generic, System.IO, System.Linq, etc.

Open this question on its own page
29

What is the static keyword in C#?

The static keyword indicates that a member belongs to the type itself rather than any instance. Static members: (1) Static field: one copy shared across all instances of the class — static int Count;. (2) Static method: callable without an instance — Math.Abs(), string.IsNullOrEmpty(). (3) Static class: cannot be instantiated, can only have static members — used as containers for utility methods (extension methods must be in static classes). (4) Static constructor: runs once before first use, initializes static members. (5) Static property: shared state. (6) Static using: using static System.Math; — allows calling Abs(-1) instead of Math.Abs(-1). Static state can lead to concurrency bugs if multiple threads access it — use thread-safe patterns (Interlocked, lock, immutable static fields). Prefer injected dependencies over static state for testability.

Open this question on its own page
30

What is null safety in C#?

C# has evolved significantly in handling null. Before C# 8: all reference types could be null — NullReferenceException was the most common runtime error. Nullable Reference Types (C# 8+ — enabled with <Nullable>enable</Nullable> in .csproj): the compiler tracks nullability and warns when you dereference potentially null references. string name; → non-nullable (must not be null). string? name; → nullable (can be null, must check before use). The compiler emits warnings when you access a nullable value without a null check. Null operators: ?? (null coalescing): name ?? "default". ??= (null coalescing assignment): name ??= "default". ?. (null conditional): person?.Address?.Street — returns null instead of throwing if any part is null. ! (null-forgiving): tells compiler to trust you that it's not null (use sparingly).

Open this question on its own page
31

What is a record type in C#?

Records (C# 9+) are a special class type designed for immutable data models with value-based equality. By default, records are reference types (record class) where two records with the same property values are considered equal (unlike regular classes). record struct (C# 10+) is a value-type record. Declaration: public record Person(string FirstName, string LastName, int Age);. Features: (1) Value equality: new Person("Alice", "Smith", 30) == new Person("Alice", "Smith", 30)true. (2) with expressions: var updated = alice with { Age = 31 }; — creates a copy with specified properties changed. (3) Deconstruction: var (first, last, age) = alice;. (4) Auto-generated ToString() showing all properties. (5) Immutability: positional properties are init-only by default. Use records for DTOs, value objects, and any immutable data transfer scenarios.

Open this question on its own page
32

What is pattern matching in C#?

Pattern matching in C# enables concise conditional logic based on the shape or value of data. Evolved significantly from C# 7 to C# 11+. Type pattern: if (obj is string s) { Console.WriteLine(s.Length); }. Switch expression (C# 8+): string desc = shape switch { Circle c => $"Circle r={c.Radius}", Rectangle r => $"Rect {r.Width}x{r.Height}", _ => "Unknown" };. Property pattern: if (person is { Age: >= 18, Name: "Alice" }) { }. Tuple pattern: (a, b) switch { (0, _) => "a is zero", (_, 0) => "b is zero", _ => "neither" };. List pattern (C# 11+): if (arr is [1, 2, ..]) { }. Relational patterns: score switch { >= 90 => "A", >= 80 => "B", _ => "F" }. Pattern matching replaces complex if/else chains and switch statements with more readable, exhaustive checks.

Open this question on its own page
33

What is the difference between ref, out, and in parameters in C#?

These keywords modify how arguments are passed to methods by reference rather than by value. ref: passes by reference — the method can read and write the variable. The variable must be initialized before passing. void Double(ref int x) { x *= 2; } int n = 5; Double(ref n); // n is now 10. out: the method must assign the variable before returning — the caller doesn't need to initialize it. Used for methods returning multiple values: bool success = int.TryParse("42", out int result);. in (C# 7.2+): passes by read-only reference — the method cannot modify the variable. Used for performance when passing large structs without copying: void Print(in LargeStruct s) { }. ref and out both prevent copying; in adds immutability guarantees. Use out for TryParse patterns; use in for large struct performance; avoid ref unless truly necessary.

Open this question on its own page
34

What is the sealed keyword in C#?

The sealed keyword prevents further inheritance or overriding. Sealed class: cannot be used as a base class — no other class can inherit from it. public sealed class SqlConnection { }. Sealed override: in a derived class, prevents further overriding of a virtual method: public override sealed void Method() { }. Use cases: (1) Preventing unintended subclassing of classes with tight invariants. (2) Performance optimization — sealed classes allow the JIT to devirtualize method calls (since the type is final, virtual dispatch is unnecessary). (3) Security — preventing malicious subclassing to override behavior. (4) Immutability design — combined with readonly fields and init-only properties. The string class in .NET is sealed (you can't derive from it). Records are sealed by default if positional. Sealing a class is generally a good practice unless you explicitly design for extensibility.

Open this question on its own page
35

What are tuples in C#?

Tuples are lightweight structures for grouping multiple values without creating a dedicated class. C# 7+ introduced ValueTuple (struct-based, more efficient). Declaration: (string Name, int Age) person = ("Alice", 30); or with inference: var point = (X: 1, Y: 2);. Named elements: point.X, point.Y. Return from method: public (int Min, int Max) GetRange(int[] arr) => (arr.Min(), arr.Max());. Call: var (min, max) = GetRange(data); (deconstruction). Swap variables: (a, b) = (b, a);. The older System.Tuple<T1, T2> class (heap-allocated, access via .Item1, .Item2) is now discouraged. Use tuples for: returning multiple values, temporary groupings, pattern matching, and LINQ groupings. For complex or frequently used value groupings, prefer records or named classes.

Open this question on its own page
36

What is the IEnumerable interface?

IEnumerable<T> is the fundamental interface for sequences in .NET — it defines a single method: IEnumerator<T> GetEnumerator(). Any type implementing IEnumerable<T> can be iterated with foreach and used with LINQ. It represents a read-only, forward-only sequence — you can iterate it but not index into it directly, modify it, or know its count without enumerating. ICollection<T> adds Count and Add/Remove. IList<T> adds index access. Parameter typing rule: accept the most general type your method needs — if you only iterate, accept IEnumerable<T>. Arrays, List<T>, HashSet<T>, LINQ queries, and generator methods (yield return) all implement IEnumerable<T>. Generator methods: yield return creates a lazy state machine implementing IEnumerable<T>.

Open this question on its own page
Intermediate 22 questions

Practical knowledge for developers with hands-on experience.

01

What is ASP.NET Core and how does it differ from ASP.NET Framework?

ASP.NET Core is a cross-platform, high-performance, open-source framework for building web APIs, web apps, and microservices, running on .NET Core/.NET 5+. Key differences from ASP.NET Framework: (1) Cross-platform: ASP.NET Core runs on Linux, macOS, Windows; Framework is Windows-only. (2) Performance: ASP.NET Core is significantly faster (top-10 in TechEmpower benchmarks). (3) Modular: opt-in middleware pipeline; Framework included many things by default. (4) Unified: MVC and Web API are merged in Core; separate in Framework. (5) Built-in DI: native dependency injection container. (6) Kestrel: built-in cross-platform web server (not IIS-dependent). (7) Configuration: flexible JSON/env/secrets-based; Framework used web.config. (8) No OWIN dependency: built-in pipeline. Use ASP.NET Core for all new development; ASP.NET Framework only for maintaining legacy Windows apps.

Open this question on its own page
02

What is middleware in ASP.NET Core?

Middleware in ASP.NET Core are components that form the request processing pipeline. Each middleware component receives an HTTP request, can process it, and either passes it to the next middleware (next()) or short-circuits the pipeline (returning a response directly). Middleware is ordered — order matters. Configured in Program.cs (or Startup.Configure): app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();. Built-in middleware: authentication, authorization, routing, static files, response caching, exception handling, CORS, session. Custom middleware: app.Use(async (context, next) => { // before; await next(); // after; }); or as a class with InvokeAsync(HttpContext, RequestDelegate). The pipeline is bidirectional — code before next() runs on the way in; code after runs on the way out. Short-circuiting: not calling next() stops pipeline processing.

Open this question on its own page
03

What is dependency injection in ASP.NET Core?

Dependency Injection (DI) is a design pattern where dependencies are provided to a class rather than the class creating them — promotes loose coupling and testability. ASP.NET Core has a built-in DI container. Register services in Program.cs: builder.Services.AddScoped<IUserService, UserService>();. Service lifetimes: Singleton: one instance per application (stateless services, cache). Scoped: one instance per HTTP request (DB contexts, unit-of-work). Transient: new instance every time requested (lightweight, stateless). Inject via constructor: public class UsersController(IUserService userService) { } (primary constructor) or traditional constructor injection. Benefits: (1) Testability — inject mock implementations in tests. (2) Loose coupling — depend on interfaces, not implementations. (3) Lifecycle management — container handles creation and disposal. (4) Configurability — swap implementations via configuration. For complex scenarios, third-party containers (Autofac, Castle Windsor) can replace the built-in container.

Open this question on its own page
04

What is Entity Framework Core?

Entity Framework Core (EF Core) is Microsoft's official ORM (Object-Relational Mapper) for .NET. It enables working with databases using .NET objects, eliminating most data-access boilerplate. Core concepts: DbContext: the main class coordinating EF Core functionality — represents a session with the database. DbSet<T>: represents a table. Migrations: code-based database schema versioning (dotnet ef migrations add, dotnet ef database update). LINQ queries: translated to SQL — context.Users.Where(u => u.Age > 18).ToListAsync(). Two approaches: Code First (define C# models → generate DB), Database First (scaffold models from existing DB). Supports: SQL Server, PostgreSQL, MySQL, SQLite, Cosmos DB. Key patterns: repository pattern, unit of work (built-in via DbContext.SaveChanges()). Avoid N+1 with eager loading: .Include(u => u.Orders).

Open this question on its own page
05

What is the difference between eager, lazy, and explicit loading in EF Core?

These control how related entities are loaded in EF Core. Eager loading: loads related data as part of the initial query using Include(). var users = context.Users.Include(u => u.Orders).ThenInclude(o => o.Items).ToList(); — one query with JOINs. Best for: known navigations needed by most callers. Lazy loading: related data is automatically loaded when you access a navigation property (on first access). Requires virtual navigation properties and the Microsoft.EntityFrameworkCore.Proxies package. Danger: causes N+1 queries if not careful — each access triggers a separate query. Explicit loading: manually load related data when needed: context.Entry(user).Collection(u => u.Orders).LoadAsync();. Best for: conditionally loading navigations based on business logic. Recommendation: prefer eager loading for known relationships; avoid lazy loading in production (N+1); use explicit loading for conditional scenarios.

Open this question on its own page
06

What is the Task Parallel Library (TPL) in C#?

The Task Parallel Library (TPL) is a set of APIs in the System.Threading.Tasks namespace that simplifies parallel and asynchronous programming. Key components: Task: represents an asynchronous operation. Task<T>: returns a value. Task.Run(): queues work to the ThreadPool. Task.WhenAll(): await multiple tasks in parallel. Task.WhenAny(): complete when first task finishes. Parallel.For/ForEach: data parallelism with automatic partitioning across cores: Parallel.ForEach(items, item => Process(item));. PLINQ: parallel LINQ — items.AsParallel().Where(x => Expensive(x)).ToList(). CancellationToken: cooperative cancellation — pass to async methods and check in loops. IProgress<T>: report progress back to the UI thread. SemaphoreSlim: async-friendly throttling. The TPL abstracts thread management, work stealing, and load balancing.

Open this question on its own page
07

What is a CancellationToken in C#?

A CancellationToken is the standard mechanism for cooperative cancellation of asynchronous and long-running operations in .NET. CancellationTokenSource creates and controls the token: var cts = new CancellationTokenSource();. Cancel: cts.Cancel();. Get token: var token = cts.Token;. Cancellation with timeout: var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));. In a long-running method: pass the token, periodically check token.IsCancellationRequested or call token.ThrowIfCancellationRequested(). All async I/O methods accept a CancellationToken parameter: await httpClient.GetAsync(url, cancellationToken);. Best practices: (1) Thread all async methods with CancellationToken — name it ct or cancellationToken. (2) In ASP.NET Core, use HttpContext.RequestAborted — cancels when the client disconnects. (3) Always dispose CancellationTokenSource. (4) Link tokens: CancellationTokenSource.CreateLinkedTokenSource(ct1, ct2).

Open this question on its own page
08

What are memory-related performance features in modern C#?

Modern C# provides several features to avoid unnecessary heap allocations. Span<T>: a stack-allocated view over a contiguous region of memory (array, stack-allocated memory, or unmanaged). No heap allocation — zero-copy slicing: ReadOnlySpan<char> slice = "Hello World".AsSpan(0, 5);. Cannot be stored in fields (stack-only). Memory<T>: like Span but can be stored and used asynchronously (heap-based). ArrayPool<T>: rent and return array buffers to avoid GC pressure: var arr = ArrayPool<int>.Shared.Rent(100); try { ... } finally { ArrayPool<int>.Shared.Return(arr); }. stackalloc: allocate arrays on the stack (C# 7.3+, usable with Span): Span<int> buffer = stackalloc int[128];. ref structs: stack-only structs (cannot be boxed or stored on heap). String interpolation handlers (C# 10+): avoid intermediate strings. These features power ASP.NET Core's high throughput.

Open this question on its own page
09

What is reflection in C#?

Reflection is the ability to inspect and manipulate types, members, and objects at runtime. The System.Reflection namespace provides APIs to examine assembly metadata. Common uses: typeof(MyClass).GetProperties() (inspect properties), obj.GetType().GetMethod("Run").Invoke(obj, args) (invoke method dynamically), Activator.CreateInstance(type) (create instance from type). Used by: ORMs (EF Core maps columns to properties), serializers (JSON.NET, System.Text.Json), DI containers, test frameworks, attribute processing, and code generation. Drawbacks: (1) Performance: significantly slower than direct calls. (2) Type safety: errors are runtime, not compile-time. (3) AOT incompatibility: reflection can break in Native AOT scenarios. Alternatives: source generators (C# 9+) — generate code at compile time, avoiding runtime reflection. Expression trees: compile-time-safe dynamic code execution.

Open this question on its own page
10

What are attributes in C#?

Attributes are metadata annotations added to code elements (classes, methods, properties, parameters) using [AttributeName] syntax. They are inspected at runtime via reflection. Built-in attributes: [Obsolete("Use NewMethod instead")], [Serializable], [DllImport("user32.dll")], [HttpGet] / [Route] (ASP.NET Core routing), [Required] / [MaxLength(100)] (data annotations validation), [JsonPropertyName("first_name")] (JSON serialization). Custom attributes: inherit from System.Attribute: [AttributeUsage(AttributeTargets.Method)] public class LogAttribute : Attribute { public string Level { get; } public LogAttribute(string level) => Level = level; }. Read attributes: typeof(MyClass).GetCustomAttributes<MyAttribute>(). Attributes power many framework features: ASP.NET Core routing/model binding, EF Core schema configuration, test frameworks (NUnit's [Test], xUnit's [Fact]).

Open this question on its own page
11

What is the difference between IQueryable and IEnumerable in LINQ?

IEnumerable<T>: executes queries in memory (client-side) — all data is loaded into memory first, then filtered/transformed in C#. IQueryable<T>: executes queries at the data source (server-side) — the query is represented as an expression tree and translated to the data source's query language (SQL for EF Core). Key difference: context.Users.Where(u => u.Age > 18) with IQueryable → SQL: SELECT * FROM Users WHERE Age > 18. If cast to IEnumerable first: context.Users.AsEnumerable().Where(u => u.Age > 18) → loads ALL users into memory, then filters in C#. Always keep queries as IQueryable as long as possible (add filters, projections) before materializing with ToList()/ToListAsync(). IEnumerable is appropriate for in-memory collections (LINQ to Objects). IQueryable is for data sources (LINQ to SQL, EF Core).

Open this question on its own page
12

What is a ConcurrentDictionary and when should you use it?

ConcurrentDictionary<TKey, TValue> is a thread-safe dictionary in System.Collections.Concurrent that allows multiple threads to read and write simultaneously without external locking. Uses fine-grained locking (striped locks) for better throughput than a single lock on a regular dictionary. Key methods: GetOrAdd(key, valueFactory) — atomically add if missing. AddOrUpdate(key, addValue, updateFactory) — atomically add or update. TryGetValue(key, out value), TryRemove(key, out value). Use when: multiple threads need to read/write shared state concurrently (cache, counters, request tracking). Caution: GetOrAdd with a factory is not atomically guaranteed — the factory might be called multiple times (only one value is stored, but the factory runs on multiple threads). For true atomic create-once semantics, use Lazy<T>. For simple counters, prefer Interlocked.Increment. Do not use regular Dictionary in multi-threaded contexts — it will corrupt internal state.

Open this question on its own page
13

What is the Observer pattern and how is it implemented in C#?

The Observer pattern defines a one-to-many dependency — when one object (subject/observable) changes state, all its dependents (observers) are automatically notified. In C#, implemented via: (1) Events and delegates: the most idiomatic C# implementation — the subject exposes an event; observers subscribe with event handlers. button.Clicked += OnClicked;. (2) IObservable<T> / IObserver<T>: .NET's formal observer interfaces, forming the basis of Reactive Extensions (Rx.NET). IObservable<T> represents a push-based sequence; observers subscribe and receive OnNext, OnError, OnCompleted callbacks. (3) Reactive Extensions (Rx.NET): powerful library for composing asynchronous event streams with LINQ-style operators. Used in UI frameworks, real-time data processing. The observer pattern is fundamental for decoupled event-driven architectures.

Open this question on its own page
14

What is the Repository pattern in C#?

The Repository pattern abstracts data access logic behind an interface, decoupling the domain layer from data persistence concerns. The repository acts as a collection-like interface for accessing domain objects. Example: public interface IUserRepository { Task<User> GetByIdAsync(int id); Task<IEnumerable<User>> GetAllAsync(); Task AddAsync(User user); Task UpdateAsync(User user); Task DeleteAsync(int id); }. Implementation: public class EfUserRepository : IUserRepository { private readonly AppDbContext _ctx; ... }. Benefits: (1) Testability — inject a mock repository in unit tests. (2) Swap data sources without changing domain logic. (3) Centralized query logic. Debate: EF Core's DbContext is itself a Unit of Work + Repository, making an additional repository layer sometimes redundant (the "generic repository antipattern"). Use repositories when: testing requires it, data source might change, or query complexity benefits from centralization. Keep repositories thin — business logic belongs in domain/service layers.

Open this question on its own page
15

What is the CQRS pattern in C#?

CQRS (Command Query Responsibility Segregation) separates read operations (Queries) from write operations (Commands) into distinct models and handlers. Commands: represent intent to change state — no return value (or minimal acknowledgment). Queries: return data — no side effects. In C# with MediatR library: public record GetUserQuery(int Id) : IRequest<UserDto>;. Handler: public class GetUserHandler : IRequestHandler<GetUserQuery, UserDto> { public async Task<UserDto> Handle(GetUserQuery q, CancellationToken ct) { return await _repo.GetDtoByIdAsync(q.Id); } }. Controller: return Ok(await _mediator.Send(new GetUserQuery(id)));. Benefits: (1) Separate read/write models optimized for their purpose. (2) Scale reads and writes independently. (3) Enables event sourcing. (4) Clear separation of concerns. Drawbacks: complexity — don't apply CQRS to simple CRUD applications without justification. Best suited for complex domain logic with high read/write ratio differences.

Open this question on its own page
16

What is SignalR?

SignalR is an ASP.NET Core library that simplifies adding real-time, bidirectional communication between server and clients. It abstracts the transport mechanism — automatically selecting WebSockets (preferred), Server-Sent Events, or Long Polling based on client/server capabilities. Server hub: public class ChatHub : Hub { public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } }. Client (JavaScript): const connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build(); connection.on("ReceiveMessage", (user, msg) => { ... }); await connection.start(); await connection.invoke("SendMessage", "Alice", "Hello!");. Client groups, user-specific messaging, and authentication are all supported. Scale-out with Redis backplane for multi-server deployments. Use cases: chat, live dashboards, collaborative editing, real-time notifications, gaming.

Open this question on its own page
17

What is middleware authentication vs authorization in ASP.NET Core?

In ASP.NET Core's pipeline, authentication and authorization are separate middleware. Authentication middleware (app.UseAuthentication()): reads the request (cookies, JWT header, API key) and populates HttpContext.User with the identity if valid — answering "who is this?" Configured with schemes: builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(...);. Authorization middleware (app.UseAuthorization() — must come after authentication): checks if the authenticated user has permission to perform the requested action. Uses policies, roles, and claims. [Authorize(Policy = "AdminOnly")] on controllers/endpoints. Policies: builder.Services.AddAuthorization(opts => opts.AddPolicy("AdminOnly", p => p.RequireRole("Admin")));. Order matters: UseRouting → UseAuthentication → UseAuthorization → MapControllers. Authentication must always run before authorization so the user identity is established first.

Open this question on its own page
18

What is a hosted service in ASP.NET Core?

A hosted service is a background long-running service that runs alongside your ASP.NET Core application, managed by the host. Implements IHostedService or inherits from BackgroundService (simpler). BackgroundService pattern: public class MyBackgroundJob : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await DoWorkAsync(); await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); } } }. Register: builder.Services.AddHostedService<MyBackgroundJob>();. Use cases: polling an external service, processing a message queue (RabbitMQ consumer, Azure Service Bus), scheduled tasks (replace cron jobs), sending scheduled emails, cache warming. For CPU-intensive work, offload to a dedicated background queue (IBackgroundTaskQueue pattern). Quartz.NET is a popular library for complex scheduling scenarios (cron expressions). Worker Service template creates a standalone background process without the HTTP server overhead.

Open this question on its own page
19

What is the difference between AddSingleton, AddScoped, and AddTransient?

These are the three service lifetime options in ASP.NET Core's DI container. AddSingleton: one instance for the entire application lifetime — created once on first request, shared across all requests and threads. Use for: stateless services, configuration, in-memory caches, HttpClient (via IHttpClientFactory). Thread-safety required. AddScoped: one instance per HTTP request (scope). All components within the same request share the same instance. Disposed at the end of the request. Use for: DbContext (critical — one transaction per request), UnitOfWork, business services. AddTransient: new instance every time the service is requested from the container. Use for: lightweight, stateless services with no shared state. Captive dependency problem: injecting a scoped service into a singleton causes the scoped service to behave like a singleton (lives for the application lifetime) — the container throws an exception in development. Never inject scoped or transient services into singletons.

Open this question on its own page
20

What are source generators in C#?

Source generators (C# 9+) are compile-time code generation tools that run as part of the compilation process and add additional C# source files to the compilation. They analyze existing code (via Roslyn APIs) and generate new code based on it — eliminating runtime reflection and boilerplate. Examples of source generators in .NET: System.Text.Json source gen (generates fast serialization code, AOT-compatible), Regex source gen ([GeneratedRegex] — compiles regex at build time), LoggerMessage source gen (efficient structured logging), AutoMapper source gen, Dapper.AOT, MediatR.SourceGenerator. Build your own: implement IIncrementalGenerator, read syntax trees, emit C# code as strings. Benefits over reflection: (1) Zero runtime overhead. (2) Compile-time errors. (3) AOT/trimming compatible. (4) Better IDE support. Source generators have largely replaced T4 templates and some uses of reflection.

Open this question on its own page
21

What is the ILogger interface in ASP.NET Core?

ILogger<T> is ASP.NET Core's built-in structured logging abstraction. Injected via DI: public class UserService(ILogger<UserService> logger) { }. Log levels (ascending severity): Trace, Debug, Information, Warning, Error, Critical. Log calls: logger.LogInformation("User {UserId} logged in at {Time}", userId, DateTime.UtcNow);. Structured logging: named placeholders {UserId} are captured as separate properties (not just string-formatted), enabling filtering and analysis in log aggregation systems. Configurable via appsettings.json: "Logging": { "LogLevel": { "Default": "Information" } }. Built-in providers: Console, Debug, EventSource. Third-party providers: Serilog, NLog, Application Insights — plug in via ILoggingBuilder. LoggerMessage: source-generated high-performance logging to avoid allocation on hot paths. Avoid string interpolation in log calls (logger.LogInformation($"User {id}")) — use structured logging placeholders.

Open this question on its own page
22

What is options pattern in ASP.NET Core?

The Options pattern provides a type-safe way to access configuration settings in ASP.NET Core. Define a strongly typed settings class: public class EmailSettings { public string SmtpServer { get; set; } = ""; public int Port { get; set; } }. Bind in Program.cs: builder.Services.Configure<EmailSettings>(builder.Configuration.GetSection("Email"));. Inject: public EmailService(IOptions<EmailSettings> options) { var settings = options.Value; }. Three interfaces: IOptions<T>: singleton, reads config once at startup — not updated if config changes. IOptionsSnapshot<T>: scoped, re-reads config per request (hot reload in dev). IOptionsMonitor<T>: singleton with change notification — use in long-running services. Validation: use ValidateDataAnnotations() or implement IValidateOptions<T>. The options pattern is strongly preferred over injecting IConfiguration directly — provides type safety, testability, and validation.

Open this question on its own page
Advanced 12 questions

Deep expertise questions for senior and lead roles.

01

What are expression trees in C#?

Expression trees represent code as a data structure (an AST — Abstract Syntax Tree) that can be examined, modified, and compiled at runtime. They are Expression<TDelegate> — lambda expressions that are not compiled to IL but to an object graph representing the code. Example: Expression<Func<int, bool>> expr = x => x > 5;expr is a tree, not a delegate. Inspect: expr.Body (BinaryExpression), expr.Parameters[0]. Compile to a delegate: var func = expr.Compile();. EF Core uses expression trees extensively — LINQ queries are represented as expression trees and translated to SQL. Useful for: (1) Building dynamic queries. (2) Creating fast property accessors without reflection overhead. (3) Dynamic code generation. (4) LINQ providers (LINQ to SQL, EF Core). Limitations: expression trees don't support statement lambdas, async, or patterns — only single-expression lambdas can be expression trees.

Open this question on its own page
02

What is the difference between value tasks and tasks in C#?

Task<T> is a heap-allocated reference type — even for synchronous results, a Task object is created. In hot paths called millions of times per second (e.g., cache lookups), this causes significant GC pressure. ValueTask<T> (C# 7+) is a struct that can be either synchronous (no allocation) or backed by a real Task when truly asynchronous. If the operation completes synchronously (common in cached scenarios), ValueTask<T> avoids the heap allocation entirely. When to use: Use Task<T> by default. Switch to ValueTask<T> when: (1) the method is called in a very hot path, (2) the synchronous path is the common case, and (3) profiling shows allocation pressure. Important constraints: ValueTask must be awaited only once — do not cache, pass around, or await multiple times. For shared awaiting, call .AsTask() first. IValueTaskSource<T> enables pooling-based zero-allocation async operations (used by .NET runtime internally for socket I/O).

Open this question on its own page
03

What is Native AOT compilation in .NET?

Native AOT (Ahead-of-Time compilation) compiles .NET applications to a native self-contained executable with no JIT (Just-In-Time) compilation at runtime. Published with: <PublishAot>true</PublishAot>. Benefits: (1) Faster startup: no JIT warm-up — starts in milliseconds (critical for serverless/Lambda). (2) Lower memory: no JIT engine, smaller footprint. (3) Self-contained: no .NET runtime required on the target machine. (4) Security: harder to reverse-engineer. Limitations: (1) No runtime reflection (unless annotated with source generators). (2) No dynamic code generation. (3) Trimming required: unused code is stripped — some libraries aren't trim-compatible. (4) Longer build times. (5) Platform-specific binaries (no portable DLLs). AOT-incompatible features: arbitrary reflection, Activator.CreateInstance without registration, dynamic loading. Use AOT for: Azure Functions, CLI tools, high-density container deployments, Lambda functions.

Open this question on its own page
04

What are channels in C# and how do they work?

Channels (System.Threading.Channels) are an asynchronous, thread-safe producer-consumer communication primitive — the .NET equivalent of Go's channels. Create: var channel = Channel.CreateUnbounded<int>(); or Channel.CreateBounded<int>(100) (backpressure when full). Writer: await channel.Writer.WriteAsync(item);. Reader: await foreach (var item in channel.Reader.ReadAllAsync(cancellationToken)) { Process(item); }. Signal completion: channel.Writer.Complete();. Use cases: (1) Producer-consumer pipelines. (2) Batching work items. (3) Rate-limited processing. (4) Fan-out (one writer, multiple readers). Advantages over BlockingCollection<T>: fully async (no thread blocking), better performance, supports IAsyncEnumerable. Compared to SignalR/queues: channels are in-process only. For multi-process pipelines, use a message broker (RabbitMQ, Azure Service Bus).

Open this question on its own page
05

What is the IAsyncEnumerable interface and how is it used?

IAsyncEnumerable<T> (C# 8+) is the async counterpart to IEnumerable<T> — represents an asynchronous stream that yields items one at a time without loading everything into memory. Producer (generator method): public async IAsyncEnumerable<User> GetUsersAsync([EnumeratorCancellation] CancellationToken ct = default) { var page = 1; while (true) { var users = await FetchPageAsync(page++, ct); if (!users.Any()) yield break; foreach (var u in users) yield return u; } }. Consumer: await foreach (var user in GetUsersAsync(cancellationToken)) { await ProcessAsync(user); }. Benefits: (1) Streaming — process items as they arrive, not all at once. (2) Low memory — no need to buffer the entire result set. (3) Backpressure — naturally flows control. (4) EF Core 3+: await foreach (var u in context.Users.AsAsyncEnumerable()) — stream DB results without loading all rows. Use in ASP.NET Core for streaming API responses. Avoid ToListAsync() when you can stream.

Open this question on its own page
06

What are covariance and contravariance in C# generics?

Variance describes how generic types relate when their type arguments are related by inheritance. Covariance (out): allows using a more derived type than originally specified. IEnumerable<Dog> can be assigned to IEnumerable<Animal> (because IEnumerable<T> is out T — read-only, producers). interface ICovariant<out T> { T GetValue(); }T only appears in output positions. Contravariance (in): allows using a less derived type. Action<Animal> can be assigned to Action<Dog> (because an action that handles any Animal can handle a Dog). interface IContravariant<in T> { void Process(T item); }T only appears in input positions. Built-in examples: IEnumerable<out T>, IReadOnlyList<out T> (covariant), Action<in T>, IComparer<in T> (contravariant), Func<in T, out TResult> (both). Only interfaces and delegates support variance annotations (not classes/structs).

Open this question on its own page
07

What is the Roslyn compiler API?

Roslyn is Microsoft's open-source C# and VB.NET compiler platform — it's a "compiler as a service," exposing rich APIs for analyzing and generating code. Key APIs: Syntax API: parse C# code into a syntax tree (SyntaxTree, SyntaxNode) and analyze its structure. Semantic API: understand the meaning of code — type information, symbol resolution, data flow. Workspace API: open and modify Visual Studio solutions and projects programmatically. Use cases: (1) Analyzers: custom code quality rules that show warnings/errors in the IDE and CI. (2) Code fixes: automatic fixes for analyzer findings. (3) Source generators: generate code at compile time based on existing code. (4) Refactoring tools: IDE extensions. (5) Scripting: evaluate C# scripts at runtime. Roslyn analyzers are distributed as NuGet packages and integrate seamlessly into the build pipeline. Tools like Roslynator, StyleCop, and many framework-specific analyzers are built on Roslyn.

Open this question on its own page
08

What is the actor model in C# with Akka.NET or Orleans?

The Actor Model is a concurrency paradigm where the basic unit of computation is an "actor" — an isolated entity that communicates exclusively via asynchronous messages, maintains private state, and can create child actors. No shared mutable state between actors eliminates many concurrency issues. Akka.NET: a port of the Scala Akka framework. Actors inherit from ReceiveActor and define message handlers: Receive<string>(msg => sender.Tell($"Echo: {msg}"));. Support for supervision strategies (fault tolerance), routing, clustering, persistence. Microsoft Orleans: Microsoft's virtual actor framework (originally for Halo). Actors are called "grains" — virtual, addressable by ID, automatically activated on demand. Grains use persistent state and are distributed across a cluster transparently. Used by Skype, Xbox, and Azure services. Actor model excels at: distributed systems, game backends, IoT telemetry, state machines. Compared to async/await: better for long-lived stateful entities and when explicit concurrency control is needed.

Open this question on its own page
09

What is GC pressure and how do you diagnose and fix it?

GC pressure occurs when an application allocates objects faster than the GC can collect them, or allocates many short-lived objects that trigger frequent Gen 0 collections, impacting throughput and adding latency spikes. Diagnosis: (1) dotnet-counters: dotnet-counters monitor -p PID — watch gen-0-gc-count, gen-1-gc-count, gen-2-gc-count. (2) dotnet-trace: collect GC events. (3) Memory profiler: JetBrains dotMemory, Visual Studio Diagnostic Tools — identify allocation hot spots. (4) EventSource GC events. Common causes: (1) String concatenation in loops. (2) LINQ creating intermediate collections. (3) Boxing value types. (4) Closures capturing large objects. (5) Oversized arrays. (6) Large object heap fragmentation. Fixes: Span<T>, ArrayPool<T>, StringBuilder, struct instead of class, object pooling (ObjectPool<T>), RecyclableMemoryStream, avoid LINQ in hot paths, reduce closure captures.

Open this question on its own page
10

What is the difference between process and AppDomain in .NET?

A Process is an OS-level isolation boundary — its own memory space, handles, and security context. Starting a separate process provides complete isolation. A AppDomain (Application Domain) was a .NET Framework feature providing a lightweight isolation boundary within a single process — allowing multiple independent "mini-applications" to run in one process with isolated state and different permissions, enabling unloading of code. Uses: hosting environments (IIS, older plugin systems), isolated test runs. In .NET Core / .NET 5+: AppDomains are removed (except for the default AppDomain which exists for compatibility). Code that creates new AppDomains throws PlatformNotSupportedException. The replacement for runtime code isolation in .NET Core is: (1) AssemblyLoadContext (ALC): load and unload assemblies into isolated contexts — the new plugin system. (2) Separate processes. (3) Isolated containers/processes. AssemblyLoadContext.Collectible = true enables unloading of the loaded assemblies.

Open this question on its own page
11

What is System.Text.Json and how does it compare to Newtonsoft.Json?

System.Text.Json is .NET's built-in, high-performance JSON library (added in .NET Core 3.0) designed to be allocation-efficient with Span-based parsing. Newtonsoft.Json (Json.NET) is the long-standing third-party library with a much richer feature set. Comparison: Performance: System.Text.Json is 2-3× faster and uses far less memory. AOT/trimming: System.Text.Json has full source generator support for AOT; Newtonsoft relies on reflection. Features: Newtonsoft has more features (property name remapping case-insensitive by default, reference loops, constructor-based deserialization, LINQ to JSON, JSON path). System.Text.Json is strict by default (case-sensitive, throws on unknown properties). Migration gotchas: null handling, enums, datetime formats, property casing differ. When to use each: new .NET 6+ projects → System.Text.Json. Complex scenarios needing rich JSON manipulation, custom converters, or Newtonsoft-specific behaviors → Newtonsoft. ASP.NET Core uses System.Text.Json by default.

Open this question on its own page
12

How do you implement caching in ASP.NET Core?

ASP.NET Core offers several caching layers. In-memory cache: builder.Services.AddMemoryCache();. Inject IMemoryCache: if (!_cache.TryGetValue("key", out MyData data)) { data = await FetchDataAsync(); _cache.Set("key", data, TimeSpan.FromMinutes(5)); }. Node-local only — data lost on restart/scale-out. Distributed cache: interface IDistributedCache — shared across multiple server instances. Implementations: Redis (AddStackExchangeRedisCache), SQL Server (AddDistributedSqlServerCache). Stores byte arrays — use JSON serialization. Output caching (ASP.NET Core 7+): cache HTTP responses at the middleware level: app.UseOutputCache(); [OutputCache(Duration = 60)]. Response caching: sends Cache-Control headers to CDNs and browsers. HybridCache (.NET 9+): L1 (in-memory) + L2 (distributed) in one API with stampede protection. Cache-aside pattern is most common. Always consider: cache invalidation strategy, cache stampede (use locking or HybridCache), and memory pressure.

Open this question on its own page
Back to All Topics 70 questions total