0% found this document useful (0 votes)
5 views61 pages

Interview Questions & Answers

.NET Senior Interview Questions & Answers provides a comprehensive collection of 215 real-world interview questions and answers covering various topics such as C#, ASP.NET Core, EF Core, security, and system design. The document is organized into buckets that address different areas of .NET development, including types, caching, messaging, and architecture. Each question is accompanied by clear explanations and examples to aid understanding and preparation for interviews.

Uploaded by

saisteyn18
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views61 pages

Interview Questions & Answers

.NET Senior Interview Questions & Answers provides a comprehensive collection of 215 real-world interview questions and answers covering various topics such as C#, ASP.NET Core, EF Core, security, and system design. The document is organized into buckets that address different areas of .NET development, including types, caching, messaging, and architecture. Each question is accompanied by clear explanations and examples to aid understanding and preparation for interviews.

Uploaded by

saisteyn18
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

.

NET Senior
Interview Questions & Answers
215 Real-World Questions with Clear Answers

C# & .NET | [Link] Core | EF Core | Security | System Design

2025 Edition

.NET Senior Interview Q&A Page 1


Table of Contents
BUCKET 1 — C# AND .NET (Q1–Q75)
Types, generics, async, memory, GC, performance

BUCKET 2 — [Link] CORE AND EF CORE (Q76–Q145)


Controllers, Minimal APIs, filters, validation, EF Core

BUCKET 3 — CACHING, SCHEDULING, MESSAGING & SECURITY (Q146–Q185)


Auth, JWT, caching, Quartz, MediatR, MassTransit

BUCKET 4 — SYSTEM DESIGN AND ARCHITECTURE (Q186–Q215)


HttpClient, Polly, OpenTelemetry, Docker, Aspire

.NET Senior Interview Q&A Page 2


BUCKET 1 — C# AND .NET

Ch 1 — Types and Type System

Q1 What is the difference between a value type and a reference type in C#?

Answer

Value types live on the stack (or inline inside an object). Copying one gives you a completely independent copy —
changing the copy leaves the original untouched.
Reference types live on the heap. Assigning one to another variable just copies the pointer, so both variables point
at the same object. Mutating through one is visible through the other.
Common value types: int, bool, double, struct, enum. Common reference types: class, string, array, delegate.
Example
int a = 5;
int b = a; // independent copy
b = 10;
[Link](a); // still 5

var p1 = new Point { X = 1 };


var p2 = p1; // same heap object
p2.X = 99;
[Link](p1.X); // 99

Q2 What is the difference between float, double, and decimal?

Answer

float (32-bit) and double (64-bit) are binary floating-point — fast but imprecise for exact decimal fractions. decimal
(128-bit) uses base-10, so 0.1 + 0.2 is exactly 0.3.
Rule of thumb: scientific/graphics work use double. Money and financial figures use decimal. float is rarely needed
except for high-volume GPU work.
Example
double d = 0.1 + 0.2;
[Link](d); // 0.30000000000000004

decimal m = 0.1m + 0.2m;


[Link](m); // 0.3 (exact)

Q3 What is the difference between string and StringBuilder?

Answer

string is immutable — every concatenation creates a new heap object. For a few literals that is fine; inside a loop
with thousands of appends it causes a heap explosion.
StringBuilder keeps one mutable internal buffer and only allocates when it fills up. Call ToString() once at the end.
Example
// Bad in a tight loop
string s = "";
for (int i = 0; i < 10_000; i++) s += i; // 10,000 allocs

// Good
var sb = new StringBuilder();
for (int i = 0; i < 10_000; i++) [Link](i);
string result = [Link](); // 1 alloc

.NET Senior Interview Q&A Page 3


Q4 What is boxing and unboxing and what does it cost?

Answer

Boxing wraps a value type in a heap object so it can be passed as object. Unboxing casts it back. Each operation
involves a heap allocation plus a copy — expensive on hot paths.
Classic trap: ArrayList boxes every int you put in. Use List<int> instead.
Example

int x = 42;
object boxed = x; // boxing — heap alloc
int unboxed = (int)boxed; // unboxing

var list = new ArrayList();


[Link](42); // boxes — use List<int> instead

Q5 What is a ref struct and what restrictions does it have?

Answer

A ref struct is guaranteed to stay on the stack — the compiler prevents it escaping to the heap. That makes it safe
to wrap a raw memory pointer, which is exactly what Span<T> does.
Restrictions: can't be boxed, can't be a generic type argument, can't be a field on a regular class, can't be used in
async methods or capturing lambdas.
Example

ref struct StackBuffer


{
public Span<byte> Data;
}
// Span<T> is itself a ref struct — that's why you can't
// store a Span<T> as a field on a regular class.

Ch 2 — Operators, Statements, Expressions

Q6 What is the null-coalescing operator (??) and null-conditional operator (?.) ?

Answer

?? returns the left side if it isn't null, otherwise the right side — a safe default.
?. short-circuits the entire member-access chain the moment it hits null, returning null instead of throwing
NullReferenceException. Combine them for clean null-safe one-liners.
Example

string name = null;


string display = name ?? "Guest"; // "Guest"
int? len = name?.Length; // null, no exception

[Link](name?.ToUpper() ?? "No name"); // "No name"

Q7 What does a foreach loop compile to under the hood?

Answer

The compiler rewrites foreach into a while loop that calls GetEnumerator(), then loops calling MoveNext() and
reading Current. If the enumerator implements IDisposable, a try/finally disposes it even if the body throws.
Example

// foreach (var item in list) { ... }


// compiles roughly to:

.NET Senior Interview Q&A Page 4


var e = [Link]();
try {
while ([Link]()) {
var item = [Link];
// body
}
} finally { [Link](); }

Ch 3 — OOP: Classes, Inheritance, Polymorphism

Q8 What is the difference between an abstract class and an interface?

Answer

An abstract class can hold state (fields), constructors, and fully implemented methods. A class can only inherit from
one.
An interface since C# 8 can have default method bodies but no instance state. A class can implement any number
of interfaces.
Use abstract class for shared logic to inherit. Use interface for a capability contract.
Example
abstract class Shape {
public abstract double Area();
public void Print() => [Link](Area());
}
interface IResizable { void Resize(double factor); }

class Circle : Shape, IResizable {


public double Radius { get; private set; }
public Circle(double r) => Radius = r;
public override double Area() => [Link] * Radius * Radius;
public void Resize(double f) => Radius *= f;
}

Q9 What is polymorphism and how does C# support it via virtual/override?

Answer

Polymorphism means one variable behaves differently at runtime depending on the actual type of the object it
holds. Mark the base method virtual, override it in a subclass, and the runtime dispatches via the vtable — no
if/switch on the caller's side.
Example

class Animal {
public virtual string Sound() => "...";
}
class Dog : Animal {
public override string Sound() => "Woof";
}
Animal a = new Dog();
[Link]([Link]()); // "Woof" — runtime dispatch

Q10 What is a primary constructor (C# 12)?

Answer

A primary constructor puts parameters directly on the class or struct declaration. Those parameters are in scope
everywhere in the type body — no separate constructor block needed.
Example

.NET Senior Interview Q&A Page 5


class Point(double x, double y)
{
public double X => x;
public double Y => y;
public double Distance() => [Link](x*x + y*y);
}

Q11 What are init-only setters and what problem do they solve?

Answer

init setters let you assign a property during object initialiser syntax but block any further change afterwards. This
gives immutability without forcing everything through a constructor — ideal for DTOs.
Example

class Order {
public int Id { get; init; }
public string Product { get; init; }
}
var o = new Order { Id = 1, Product = "Book" }; // ok
[Link] = 2; // compile error — init-only

Q12 How does the runtime dispatch a virtual call vs. an interface call?

Answer

Virtual call: the object header points to its Method Table. The vtable slot for that method is at a fixed offset — one
pointer indirection, very fast.
Interface call: the runtime looks up the interface map inside the Method Table to find the slot — slightly more work,
though the JIT often inlines or caches it.
Example

Animal a = new Dog();


[Link](); // virtual — one vtable slot lookup (fast)

IResizable r = new Circle(5);


[Link](2); // interface — interface map lookup (slightly slower)

Ch 4 — Records, Anonymous Types, Tuples

Q13 What is a record in C# and how does it differ from a class?

Answer

A record is a reference type with compiler-generated value semantics: Equals, GetHashCode, == and != all
compare by property values rather than object identity. You also get a with-expression for non-destructive
mutation.
Example

record Person(string Name, int Age);

var p1 = new Person("Alice", 30);


var p2 = p1 with { Age = 31 }; // new object, Name copied
[Link](p1 == p2); // False
[Link]([Link] == [Link]); // True

Q14 How does equality work for records compared to classes?

Answer

.NET Senior Interview Q&A Page 6


Classes compare by identity by default — two variables are equal only if they point to the exact same heap object.
Records compare by value — two records with the same property values are equal even if they are different
instances.
Example

var a = new Person("Bob", 25);


var b = new Person("Bob", 25);
[Link](a == b); // True for record, False for class

Q15 When would you prefer a record struct over a record class?

Answer

When the object is small, short-lived, and in a hot path. record struct allocates on the stack — zero GC pressure.
Coordinate pairs, colour values, and price ranges are good candidates. You still get value equality and
with-expressions.
Example

record struct Point(double X, double Y);

var p = new Point(1, 2);


var shifted = p with { X = 5 }; // new stack value

Ch 5 — Generics and Variance

Q16 What are generic constraints and what kinds are available?

Answer

Constraints tell the compiler what operations are legal on a type parameter.
where T : class -- reference type where T : struct -- value type where T : new() -- has parameterless constructor
where T : SomeBase -- inherits SomeBase where T : IFoo -- implements IFoo where T : notnull -- non-nullable
Example
T CreateAndLog<T>(ILogger log)
where T : class, new()
{
var obj = new T();
[Link]([Link]());
return obj;
}

Q17 Explain covariance and contravariance with in and out.

Answer

out (covariance): type only flows OUT. IEnumerable<Dog> is assignable to IEnumerable<Animal> because you
only read from it.
in (contravariance): type only flows IN. Action<Animal> is assignable to Action<Dog> because a handler that
accepts any Animal can certainly handle a Dog.
Example

IEnumerable<Dog> dogs = new List<Dog>();


IEnumerable<Animal> animals = dogs; // covariant -- ok

Action<Animal> feedAnimal = a => [Link]("fed");


Action<Dog> feedDog = feedAnimal; // contravariant -- ok

.NET Senior Interview Q&A Page 7


Q18 How does the JIT specialise generic code for value types vs. reference types?

Answer

For reference types the JIT generates ONE shared native code path (all references are the same size). For value
types it generates a separate native code path per type — so List<int> and List<double> get different machine
code, avoiding boxing entirely.
Example

List<int> ints = new(); [Link](42); // no boxing


List<object> objs = new(); [Link](42); // boxing — int wrapped in object

Ch 6 — Collections

Q19 What is the difference between IEnumerable<T> and ICollection<T>?

Answer

IEnumerable<T> is the bare minimum: forward-only iteration. ICollection<T> adds Count, Add, Remove, Contains,
and Clear. Use IEnumerable when you only need to loop; ICollection when the caller needs to modify or measure.
Example
void PrintAll(IEnumerable<int> items) {
foreach (var x in items) [Link](x);
}
void AddIfMissing(ICollection<int> items, int val) {
if (![Link](val)) [Link](val);
}

Q20 What are the concurrent collections in .NET?

Answer

ConcurrentDictionary -- thread-safe key-value store with atomic AddOrUpdate/GetOrAdd. ConcurrentQueue --


lock-free FIFO, great for producer-consumer. ConcurrentBag -- unordered, best when adder and remover are the
same thread. BlockingCollection -- wraps any concurrent collection, adds blocking Take() with optional capacity.
Example
var dict = new ConcurrentDictionary<string, int>();
[Link]("hits", 1, (k, old) => old + 1);

var q = new ConcurrentQueue<string>();


[Link]("job1");
[Link](out var job);

Q21 What is FrozenDictionary and when does it beat a regular dictionary?

Answer

FrozenDictionary is a read-only dictionary optimised at construction time. Because it never changes, the runtime
can pick a perfect hash layout. For read-heavy lookup tables like routing tables or config maps — built once, read
millions of times — it is measurably faster.
Example
var config = new Dictionary<string,string> {
["host"] = "localhost", ["port"] = "5432"
}.ToFrozenDictionary();

var host = config["host"]; // fast repeated reads

.NET Senior Interview Q&A Page 8


Q22 How is Dictionary<TKey,TValue> implemented internally?

Answer

It uses an array of buckets. The key's hash code selects a bucket. Collisions are handled by chaining. Average
lookup is O(1). A poor GetHashCode that returns the same value for many keys degrades to O(n) linear scan.
Example

record Point(int X, int Y)


{
// Good spread prevents O(n) degradation
public override int GetHashCode() =>
[Link](X, Y);
}

Q23 How does ConcurrentDictionary handle concurrent updates?

Answer

It splits the bucket array into stripes and assigns one lock per stripe. Reads are mostly lock-free via volatile reads.
Writes lock only the stripe that the key hashes into, so two threads writing to different keys rarely contend.
Example
var cd = new ConcurrentDictionary<int, int>();
// Thread-safe increment — no external lock needed
[Link](1, addValue: 1,
updateValueFactory: (k, v) => v + 1);

Ch 7 — Delegates, Events, Lambdas

Q24 What is the difference between a delegate and an event?

Answer

A delegate is a type-safe function pointer — anyone holding it can invoke it directly. An event wraps that delegate
and restricts outside code to only += and -=. Only the declaring class can fire it. Events enforce the
publisher/subscriber pattern without letting subscribers call each other.
Example
class Button {
public event EventHandler Clicked;
public void SimulateClick() => Clicked?.Invoke(this, [Link]);
}
// Outside: [Link] += handler; -- ok
// Outside: [Link](this, e); -- compile error

Q25 What is a closure and what are the captured-variable rules?

Answer

A closure is a lambda that captures a variable from its enclosing scope. The compiler creates a hidden class to
hold it. That variable is shared — changes inside the lambda are visible outside. Classic bug: capturing a loop
variable directly means all lambdas share the same final value.
Example
var funcs = new List<Func<int>>();
for (int i = 0; i < 3; i++) {
int copy = i; // capture a fresh copy each iteration
[Link](() => copy);
}
// Without copy: all three lambdas return 3 (loop's final i)

.NET Senior Interview Q&A Page 9


Q26 What is an expression tree and how does it differ from a delegate?

Answer

A delegate is compiled native code you can call but not inspect. An expression tree is an object graph describing
the code — you can inspect every node, translate it (EF Core translates LINQ trees to SQL), or compile it to a
delegate at runtime.
Example

Expression<Func<int, bool>> expr = x => x > 5;

var binary = (BinaryExpression)[Link];


[Link]([Link]); // GreaterThan

var func = [Link]();


[Link](func(10)); // True

Q27 What is a multicast delegate and what happens when one handler throws?

Answer

A multicast delegate holds a list of methods and calls them in registration order. If one handler throws the
exception propagates immediately and remaining handlers never run. To guarantee all handlers run, iterate
GetInvocationList() manually with try/catch per call.
Example

Action notify = () => [Link]("A");


notify += () => throw new Exception("B failed");
notify += () => [Link]("C"); // never runs if B throws

// Safe:
foreach (Action h in [Link]())
try { h(); } catch { /* log */ }

Ch 8 — LINQ

Q28 What is the difference between IEnumerable<T> and IQueryable<T> in LINQ?

Answer

IEnumerable runs the query in memory — data is fetched first, filtered in C#. IQueryable holds an expression tree;
the provider (EF Core) translates the whole query to SQL before any data crosses the wire. Always use IQueryable
against a database.
Example

// IQueryable -- translated to SQL, only matching rows fetched


var adults = [Link](u => [Link] > 18).ToList();

// IEnumerable -- loads ALL rows first, then filters in C#


var adults2 = [Link]().Where(u => [Link] > 18).ToList();

Q29 What is deferred execution in LINQ?

Answer

A LINQ query doesn't run when you write it — it runs the moment you iterate it (ToList, First, foreach, etc.).
Changes to the source made after the query is written but before iteration are visible. Running the query twice hits
the source twice.
Example

var nums = new List<int> { 1, 2, 3 };


var q = [Link](n => n > 1); // nothing runs yet

.NET Senior Interview Q&A Page 10


[Link](4);
foreach (var n in q) // executes NOW -- sees 4
[Link](n + " "); // 2 3 4

Q30 What is IAsyncEnumerable<T> and how do you consume it?

Answer

IAsyncEnumerable<T> streams items one at a time from an async source without buffering everything in memory
first — think reading rows page by page. Consume with await foreach.
Example

async IAsyncEnumerable<int> GetNumbers() {


for (int i = 0; i < 5; i++) {
await [Link](100);
yield return i;
}
}
await foreach (var n in GetNumbers())
[Link](n); // arrives one at a time

Q31 How does the EF Core LINQ provider translate expression trees to SQL?

Answer

When you query a DbSet, EF Core receives an IQueryable with an expression tree. The query provider walks the
tree, maps C# constructs to SQL, and sends one SQL statement to the database. No filtering happens in C#.
Example

var orders = await [Link]


.Where(o => [Link] > 100 && [Link] == "Paid")
.OrderBy(o => [Link])
.ToListAsync();
// SELECT * FROM Orders WHERE Total > 100 AND Status='Paid'
// ORDER BY CreatedAt

Q32 Why is Count() == 0 worse than Any() on an IEnumerable?

Answer

Count() scans every element to get the total. Any() stops at the first — O(1) for the question 'is there anything
here?'. On a million-item sequence Any() returns immediately; Count() scans the lot. Exception: List<T>.Count and
[Link] are O(1) properties.
Example
var items = GetMillionItems();
bool slow = [Link]() > 0; // scans everything
bool fast = [Link](); // stops at first element

Ch 9 — Pattern Matching

Q33 What is a list pattern (C# 11)?

Answer

List patterns let you match the shape and content of an array or list in a switch expression. Pin specific positions,
use _ as a single-element wildcard, and .. for any-length slices.
Example

int[] nums = { 1, 2, 3, 4 };
var desc = nums switch {

.NET Senior Interview Q&A Page 11


[1, 2, ..] => "starts with 1,2",
[.., 4] => "ends with 4",
[] => "empty",
_ => "other"
}; // "starts with 1,2"

Ch 10 — Nullable Reference Types

Q34 Is the nullable reference type annotation enforced at runtime?

Answer

No — it is purely a compile-time hint. At runtime string? and string are identical types. The annotations let the
compiler warn about potential null dereferences but insert no runtime checks. You still need explicit null guards for
runtime safety.
Example
#nullable enable
string? name = null;
[Link]([Link]); // compiler warning, but compiles
// -> NullReferenceException at runtime

if (name is null) throw new ArgumentNullException(nameof(name));

Ch 11 — C# Error Handling

Q35 What is the difference between throw ex and throw inside a catch block?

Answer

throw ex resets the stack trace — you lose where the exception actually originated. Bare throw re-throws with the
original stack trace intact. Always use bare throw when you just want to rethrow after logging.
Example

catch (Exception ex)


{
_log.LogError(ex, "failed");
throw; // correct -- original stack trace kept
// throw ex; // wrong -- stack trace reset to this line
}

Q36 What is the cost of throwing exceptions in .NET?

Answer

Throwing is expensive: the runtime walks every stack frame to build the trace, allocates the exception object, and
unwinds the stack. On a hot path this is thousands of times slower than returning a result. Use exceptions for truly
unexpected failure, not control flow.
Example

// Bad -- exception for ordinary control flow


try { user = FindUser(id); }
catch (NotFoundException) { user = null; }

// Good -- return null / Result type instead


user = FindUserOrDefault(id);

Q37 How does the CLR unwind the stack when an exception is thrown?

Answer

.NET Senior Interview Q&A Page 12


The CLR scans exception-handling tables baked into each JIT method, walks up the call stack for a matching
catch, and runs all finally blocks along the way. Zero-cost tables mean no overhead unless an exception is actually
thrown.
Example

void A() { B(); }


void B() { throw new Exception("boom"); }

// Unwind: B checks -- no catch.


// A checks -- no catch.
// Keeps going until match or app crash.

Ch 12 — Async/Await and Tasks

Q38 What is the difference between Task and ValueTask?

Answer

Task always allocates a heap object. ValueTask is a struct — when the result is already ready (cache hit) it can
return with zero allocation. Use ValueTask on methods that are frequently synchronous. Rule: never await a
ValueTask more than once.
Example

ValueTask<int> GetCachedAsync(int key)


{
if (_cache.TryGetValue(key, out var v))
return new ValueTask<int>(v); // zero alloc
return new ValueTask<int>(LoadFromDbAsync(key));
}

Q39 What is ConfigureAwait(false) and when should you use it?

Answer

By default await resumes on the captured SynchronizationContext (e.g., UI thread). ConfigureAwait(false) says
resume on any thread-pool thread. In library code this is a small win and prevents certain deadlocks. In UI event
handlers where you need to touch controls after the await, don't use it.
Example

public async Task<string> FetchDataAsync(string url)


{
using var client = new HttpClient();
return await [Link](url)
.ConfigureAwait(false);
}

Q40 What state machine does the compiler generate for an async method?

Answer

The compiler rewrites every async method into a struct implementing IAsyncStateMachine. Each await point
becomes a numbered state. When an await suspends, locals are saved and the method returns. When the awaited
task completes MoveNext() resumes from the saved state.
Example
// You write:
async Task<int> AddAsync(int a, int b) {
await [Link](1);
return a + b;
}
// Compiler generates a hidden struct:
// State 0 -> start delay, save a+b, return

.NET Senior Interview Q&A Page 13


// State 1 -> resume, compute a+b, complete task

Q41 What is SynchronizationContext and how does it affect await resumption?

Answer

SynchronizationContext controls how continuations are scheduled back to a specific thread. [Link] Core has no
SynchronizationContext, so await resumes on a free thread-pool thread.
Important: even in [Link] Core, blocking with .GetResult() or .Wait() wastes a thread-pool thread and can cause
starvation under load. Never do it in production.
Example

// [Link] Core -- no SynchronizationContext.


// The classic UI-thread deadlock can't occur,
// but blocking STILL starves the thread pool:
var result = SomeAsync().GetAwaiter().GetResult(); // avoid

// In WinForms/WPF -- .Result WILL deadlock!

Q42 What is the difference between async void and async Task?

Answer

async Task lets callers await and catch exceptions. async void cannot be awaited — exceptions thrown inside
crash the process. The only accepted use of async void is event handlers where the signature is forced on you.
Example

async void DoWork() { throw new Exception(); } // crashes process


async Task DoWorkAsync() { throw new Exception(); } // caller can catch

// Event handler -- signature forced, acceptable:


[Link] += async (s, e) => { await LoadAsync(); };

Q43 Why does calling .Result on a Task cause a deadlock in some contexts?

Answer

The async method schedules its continuation to resume on the current SynchronizationContext thread. But .Result
blocks that same thread waiting for the Task. The continuation can never run — deadlock.
Example
// WinForms button click:
private void btn_Click(object s, EventArgs e)
{
var data = FetchDataAsync().Result; // deadlock!
// FetchDataAsync wants the UI thread back,
// but the UI thread is blocked right here.
}
// Fix: make the handler async and await

Ch 13 — Threading and Synchronization

Q44 What is lock and what does it compile to?

Answer

lock is syntax sugar for [Link] and [Link] wrapped in try/finally, ensuring the lock is always released.
Always lock on a private readonly object — never on this or a public type.
Example

.NET Senior Interview Q&A Page 14


private readonly object _sync = new();
lock (_sync) { _count++; }

// Compiles to:
bool taken = false;
[Link](_sync, ref taken);
try { _count++; }
finally { if (taken) [Link](_sync); }

Q45 What is the new Lock type in C# 13?

Answer

[Link] is a dedicated lock type. The compiler recognises it and emits more optimised primitives
than generic [Link]/Exit. You still use the same lock keyword.
Example

private readonly Lock _lock = new();


lock (_lock) // compiler emits optimised EnterScope
{
_count++;
}

Q46 What is Channel<T> and how does it compare to BlockingCollection?

Answer

Channel<T> is async-first — both sides use WriteAsync/ReadAsync, no thread is ever blocked. BlockingCollection
blocks a real thread while waiting, wasting thread-pool resources. In [Link] Core background services always
prefer Channel<T>.
Example

var ch = [Link]<string>();

// Producer
await [Link]("hello");

// Consumer
await foreach (var msg in [Link]())
[Link](msg);

Q47 What happens when the thread pool gets starved?

Answer

When too many threads are blocked (usually from .Result on async code), the pool queue fills but has no free
threads to drain it. The pool injects new threads slowly — one per second — causing latency spikes. Diagnose with
dotnet-counters watching [Link] length.
Example

// Classic cause: blocking an async call


public IActionResult Get()
{
var result = _service.GetDataAsync().Result; // blocks thread
return Ok(result);
}
// Fix: make the action async and await

Ch 14 — Memory Management and GC

.NET Senior Interview Q&A Page 15


Q48 Explain GC generations 0, 1, and 2.

Answer

Gen 0 is collected most often and holds new short-lived objects. Survivors are promoted to Gen 1, then to Gen 2
which is collected rarely and holds long-lived objects like static caches. The insight: most objects die young, so
collecting Gen 0 alone clears most garbage cheaply.
Example

var temp = new byte[100]; // short-lived, Gen 0

// Long-lived -- eventually reaches Gen 2


private static readonly Dictionary<string, Config> _cache = new();

Q49 What is the Large Object Heap (LOH) and why does it matter?

Answer

Objects >= 85,000 bytes go straight to the LOH. The LOH is only collected during Gen 2 collections (rare) and isn't
compacted by default, so it fragments. Fix: rent large buffers from ArrayPool instead of allocating fresh each time.
Example
// Bad -- 100 KB on the LOH every call
byte[] buf = new byte[100_000];

// Good -- borrow from pool, no LOH pressure


byte[] buf = ArrayPool<byte>.[Link](100_000);
try { /* use buf */ }
finally { ArrayPool<byte>.[Link](buf); }

Q50 What is the difference between workstation GC and server GC?

Answer

Workstation GC (default on desktop) runs on the application thread, keeping pauses short. Server GC (default in
[Link] Core) creates a separate heap and GC thread per logical CPU, giving much higher throughput on
multi-core machines at the cost of more memory.
Example

<!-- .csproj -->


<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

Q51 What is the dispose pattern and why does Dispose(bool) have a parameter?

Answer

The parameter separates managed cleanup (run only from Dispose) from unmanaged cleanup (run from both
Dispose and finalizer). When true you were called from Dispose() — safe to release managed objects too. When
false you were called from the finalizer — only release unmanaged handles.
Example
class ResourceHolder : IDisposable {
private bool _disposed;
protected virtual void Dispose(bool disposing) {
if (_disposed) return;
if (disposing) _managed?.Dispose(); // managed
_nativeHandle.Free(); // unmanaged
_disposed = true;
}
public void Dispose() { Dispose(true); [Link](this); }
~ResourceHolder() { Dispose(false); }

.NET Senior Interview Q&A Page 16


}

Q52 How do you diagnose a memory leak in a .NET application?

Answer

1. Watch process memory with dotnet-counters or Prometheus. 2. Capture a dump with dotnet-dump or ProcDump
when memory is high. 3. Analyse with dotnet-dump analyze, Visual Studio, or WinDbg+sos. 4. Run dumpheap
-stat to spot types with unexpectedly high instance counts.
Common culprits: static collections growing forever, event subscriptions never removed, IDisposable not disposed.
Example

// Classic leak: subscription never removed


Publisher pub = GetPublisher();
[Link] += Handler; // pub holds a reference back
// Fix: [Link] -= Handler; when done

Ch 15 — Reflection and Dynamic

Q53 What is the performance cost of reflection and how do you reduce it?

Answer

Reflection bypasses JIT optimisations and does dynamic dispatch — 10-100x slower than direct calls. Cache
MethodInfo/PropertyInfo at startup so you pay the lookup cost once. Better: compile the reflected call to a delegate
once — then invocation is near-native.
Example

var method = typeof(MyClass).GetMethod("DoWork");

// Compile once -- near-native invocation thereafter


var action = (Action<MyClass>)[Link](
typeof(Action<MyClass>), method);
action(instance);

Q54 How do source generators compare to reflection?

Answer

Source generators run at compile time and emit C# code. The result is strongly typed, AOT-compatible, and has
zero runtime reflection. [Link] source-gen mode and Mapperly both use this. Trade-off: more complex
build setup, but huge runtime gains.
Example

[JsonSerializable(typeof(Order))]
public partial class AppJsonContext : JsonSerializerContext { }

// Reflection-free at runtime:
var json = [Link](order, [Link]);

Ch 16 — Span, Memory, Pipelines

Q55 What is Span<T> and what problem does it solve?

Answer

Span<T> is a stack-only struct that references a contiguous memory block — array slice, stack memory, or
unmanaged memory — without copying. Parsing, slicing, and in-place processing become zero-allocation
operations.

.NET Senior Interview Q&A Page 17


Example

string text = "Hello, World";


ReadOnlySpan<char> hello = [Link](0, 5);
[Link]([Link]()); // "Hello" -- no heap alloc

Q56 Why can't Span<T> be stored as a field on a class?

Answer

Span<T> is a ref struct — it must live on the stack. A class lives on the heap. Storing a Span inside a class would
let it outlive the stack memory it points to — a use-after-free. Use Memory<T> instead: it lives on the heap and
converts to Span on demand.
Example

class Buffer {
// Span<byte> _data; // compile error
Memory<byte> _data; // heap-safe
public Span<byte> AsSpan() => _data.Span;
}

Q57 What is ArrayPool and when should you use it?

Answer

ArrayPool<T>.Shared rents arrays from a pre-allocated pool. Return them when done and the memory is reused.
Use it whenever you need a large temporary buffer — network reads, serialisation — to avoid LOH allocations.
Example

byte[] buf = ArrayPool<byte>.[Link](4096);


try {
int read = await [Link]([Link]());
Process([Link](0, read));
}
finally { ArrayPool<byte>.[Link](buf); }

Ch 17 — Serialization

Q58 What is source-generator mode in [Link]?

Answer

It generates all serialisation/deserialisation code at compile time — no runtime reflection. Use it for Native AOT
(where reflection-based serialisation doesn't work) or high-throughput hot paths.
Example

[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
public partial class MyContext : JsonSerializerContext { }

var json = [Link](order, [Link]);

Q59 Why was BinaryFormatter deprecated?

Answer

BinaryFormatter can execute arbitrary code during deserialisation — a well-known attack vector. It's also brittle for
versioning as it couples serialised bytes to internal type layout. Use [Link] for JSON, MessagePack for
binary, or Protobuf for cross-language binary.
Example

// Throws in .NET 9+
// [Link](stream, obj);

.NET Senior Interview Q&A Page 18


var json = [Link](obj);
var bytes = [Link](obj);

Ch 18 — C# Version Features

Q60 What is the field keyword in C# 14?

Answer

field gives access to the compiler-generated backing field inside a property accessor without declaring the field
yourself. Useful for semi-auto properties — e.g., trim on get, null-check on set.
Example

public string Name


{
get => [Link]();
set => field = value ?? throw new ArgumentNullException();
}

Q61 What is CallerArgumentExpression and how is it useful?

Answer

CallerArgumentExpression captures the source-code text of an argument as a string at compile time.


[Link] uses this so the message automatically names which parameter was null.
Example

static void ThrowIfEmpty(


string value,
[CallerArgumentExpression(nameof(value))] string? argName = null)
{
if ([Link](value))
throw new ArgumentException("Must not be empty", argName);
}
ThrowIfEmpty(name); // message: "Must not be empty (name)"

Ch 19 — .NET Runtime, CLR, JIT, AOT

Q62 What is tiered compilation in .NET?

Answer

On first call a method is JIT-compiled at Tier 0 — fast compile, fewer optimisations. After enough calls the runtime
recompiles it at Tier 1 with full optimisations. Fast startup (cheap Tier 0) plus fast steady-state (optimised Tier 1)
with no code changes needed.
Example

// On by default — no changes needed.


// Disable for benchmarking:
// DOTNET_TieredCompilation=0

Q63 What is Native AOT and what are the trade-offs?

Answer

Native AOT compiles the whole app to a standalone native binary — no JIT, no CLR bundled. Benefits: tiny
startup, lower memory, single-file. Costs: no runtime code generation, limited dynamic features, longer build times,
trimming can break libraries that use reflection.

.NET Senior Interview Q&A Page 19


Example

<!-- .csproj -->


<PublishAot>true</PublishAot>

// dotnet publish -r linux-x64 -c Release


// -> standalone native binary

Q64 What is PGO (profile-guided optimisation) and dynamic PGO?

Answer

PGO feeds runtime profiling data back into the JIT — which branches are taken, which types appear — to produce
better-optimised code. Dynamic PGO does this automatically at runtime. On by default since .NET 8; gives
meaningful throughput gains with zero code changes.
Example

// Dynamic PGO is on by default in .NET 8+.


// Check with: DOTNET_TieredPGO=1
// Observe with JitStats in BenchmarkDotNet

Ch 20 — Performance and Benchmarking

Q65 Why shouldn't you use Stopwatch for microbenchmarks?

Answer

Stopwatch doesn't account for JIT warm-up, CPU frequency scaling, GC pauses, or process noise. Your first
measurement includes JIT compilation time. BenchmarkDotNet handles warm-up, statistical analysis, multiple
iterations, GC pressure, and hardware counters.
Example

[MemoryDiagnoser]
public class MyBench {
[Benchmark] public string Concat() => "Hello" + " " + "World";
[Benchmark] public string Interpolate() => $"Hello World";
}
[Link]<MyBench>();

Q66 What does MemoryDiagnoser show and how do you read Gen columns?

Answer

Adds Allocated (bytes per op) and Gen0/Gen1/Gen2 (collections per 1,000 ops). Gen0=1 means one Gen 0
collection per 1,000 ops — usually fine. Any non-zero Gen2 means long-lived allocations in the expensive heap —
investigate.
Example

// BenchmarkDotNet output:
// | Method | Mean | Gen0 | Allocated |
// |--------|--------|--------|-----------|
// | Alloc | 120 ns | 0.0234 | 96 B |
// ~23 Gen0 collections per 1,000 ops

Ch 21 — BCL and IO

Q67 What is the difference between Stream, MemoryStream, and FileStream?

Answer

.NET Senior Interview Q&A Page 20


Stream is the abstract base. MemoryStream is backed by an in-memory byte array — no I/O, great for testing.
FileStream reads/writes a file on disk. Both implement Stream so code written against Stream works with either.
Example

async Task ProcessAsync(Stream input) {


byte[] buf = new byte[1024];
int n = await [Link](buf);
// works with FileStream, MemoryStream, NetworkStream...
}

Q68 What is RandomAccess (introduced in .NET 6)?

Answer

RandomAccess provides static methods for reading/writing files at specific byte offsets without seeking. Multiple
threads can safely read different parts of a large file concurrently — something FileStream can't do safely with its
shared seek position.
Example

using var handle = [Link]("[Link]");


byte[] buf = new byte[512];
await [Link](handle, buf, fileOffset: 4096);
// safe to call from multiple threads at different offsets

Ch 22 — NuGet

Q69 What is Central Package Management and [Link]?

Answer

Central Package Management lets you declare every package version in one file at the repo root. Individual .csproj
files reference packages without specifying a version — the central file supplies it, eliminating version drift across a
large monorepo.
Example
<!-- [Link] -->
<Project>
<ItemGroup>
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Dapper" Version="2.1.28" />
</ItemGroup>
</Project>

<!-- In .csproj -- no version needed -->


<PackageReference Include="Serilog" />

Ch 23 — MSBuild and csproj

Q70 What is [Link] and what is it for?

Answer

MSBuild automatically imports this file from any ancestor directory. Put shared settings — Nullable,
TreatWarningsAsErrors, TargetFramework — here once and they apply to every project under that folder. No more
copying the same boilerplate into each .csproj.
Example
<!-- [Link] at repo root -->
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>

.NET Senior Interview Q&A Page 21


<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>

Q71 What is multi-targeting and when would you use it?

Answer

Multi-targeting builds one project against multiple framework versions in one pass, producing separate binaries for
each. Use it in library projects that need to support both net8.0 and net9.0, or where you want to use newer APIs
on newer runtimes with a fallback.
Example

<TargetFrameworks>net8.0;net9.0</TargetFrameworks>

#if NET9_0
// use net9-only API
#else
// fallback
#endif

Ch 24 — .NET CLI

Q72 What is the difference between dotnet build and dotnet publish?

Answer

dotnet build compiles code and resolves NuGet references — output is not self-contained. dotnet publish produces
a ready-to-deploy folder with runtime files, static content, and any trim/single-file options you configure.
Example

dotnet build # compile, refs from local NuGet cache

dotnet publish -c Release -r linux-x64 --self-contained


# Full deployable folder with everything needed

Q73 What is [Link]?

Answer

[Link] pins the exact .NET SDK version for a directory tree. Without it, dotnet commands use the latest
installed SDK — which silently breaks builds when teammates or CI have different versions.
Example

{
"sdk": {
"version": "9.0.100",
"rollForward": "latestPatch"
}
}

Q74 What is TaskCompletionSource<T> and when do you use it?

Answer

TaskCompletionSource<T> creates a Task you control manually — you decide when to set the result, throw an
exception, or cancel it. Classic use: wrapping an old callback-based API into an awaitable Task.
Example

.NET Senior Interview Q&A Page 22


public Task<string> WaitForMessageAsync()
{
var tcs = new TaskCompletionSource<string>();
_socket.OnMessage += msg => [Link](msg);
_socket.OnError += ex => [Link](ex);
return [Link];
}
var msg = await WaitForMessageAsync();

Q75 What is the difference between [Link] and [Link]?

Answer

[Link] waits for ALL tasks to finish and collects all results. If any faults, the combined task faults.
[Link] completes as soon as the FIRST task finishes — useful for timeout patterns or racing two sources.
Example
// WhenAll -- parallel fetch, correct syntax
var usersTask = GetUsersAsync();
var ordersTask = GetOrdersAsync();
await [Link](usersTask, ordersTask);
var users = await usersTask; // already complete
var orders = await ordersTask;

// WhenAny -- timeout pattern


var fetchTask = FetchAsync();
var done = await [Link](fetchTask, [Link](3000));
if (done != fetchTask) throw new TimeoutException();

.NET Senior Interview Q&A Page 23


BUCKET 2 — [Link] CORE AND EF CORE

Ch 25 — Types of Web Projects

Q76 What are the main [Link] Core project types?

Answer

Empty Web -- bare canvas, no scaffolding. Web API (controller-based) -- MVC controllers returning JSON. Minimal
API -- direct endpoint registration, no controllers. MVC with Views -- server-rendered HTML via Razor views. Razor
Pages -- one page-model file per page. Blazor -- component-based UI (Server or WebAssembly). gRPC Service --
Protobuf-based typed RPC.
Example

// Minimal API -- entire app in a few lines


var app = [Link](args);
[Link]("/ping", () => "pong");
[Link]();

Q77 When would you choose Minimal APIs over controller-based APIs?

Answer

Minimal APIs shine for microservices, small focused services, and serverless functions where you want the least
ceremony possible. Controller-based APIs are better for larger teams wanting conventions, area support, a full filter
pipeline, and easy discoverability across a big codebase.
Example

[Link]("/orders", async (CreateOrderDto dto, IOrderService svc) =>


[Link]($"/orders/{[Link]}", await [Link](dto)));

Ch 26 — Application Host

Q78 What is WebApplicationBuilder?

Answer

WebApplicationBuilder merges HostBuilder and WebHostBuilder into one fluent API. Register services on
[Link], configure the host on [Link], call [Link]() to get the WebApplication where you
add middleware and map routes.
Example

var builder = [Link](args);


[Link]<IOrderService, OrderService>();
[Link]<AppDb>(o => [Link](cs));
var app = [Link]();
[Link]();
[Link]();
[Link]();

Q79 What is IHostedService and what is a typical use case?

Answer

IHostedService ties background work to the application lifetime. StartAsync fires at host start; StopAsync on
graceful shutdown. Typical uses: message queue consumers, periodic cleanup jobs, warming up caches, seeding
data.
Example

.NET Senior Interview Q&A Page 24


public class OutboxProcessor : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken ct) {
while (![Link]) {
await ProcessPendingMessages(ct);
await [Link]([Link](5), ct);
}
}
}

Q80 How does [Link] signal graceful shutdown?

Answer

StopAsync cancels the host lifetime CancellationToken. Every registered [Link] is called with
a deadline (default 5 seconds). After that the host terminates everything regardless.
Example

// Honour the token in your background loop:


while (![Link])
{
await DoWorkAsync(stoppingToken); // pass the token down
}

Ch 27 — Controllers

Q81 What is model binding in [Link] Core?

Answer

Model binding reads incoming data from route segments, query string, body, form fields, and headers, and maps
them to action method parameters. The framework tries each source in a default order unless you pin one with an
attribute.
Example
[HttpGet("{id}")]
public IActionResult Get(
int id, // from route
string filter, // from query string
[FromHeader] string token) // from header
{ ... }

Q82 Explain [FromBody], [FromQuery], [FromRoute], and [FromForm].

Answer

[FromBody] -- deserialises the request body (JSON/XML), read once. [FromQuery] -- reads a query-string
parameter (?key=value). [FromRoute] -- reads a route template segment ({id}). [FromForm] -- reads a
multipart/form-data or urlencoded form field.
Example

[HttpPost("upload")]
public IActionResult Upload(
[FromRoute] int tenantId,
[FromQuery] string tag,
[FromForm] IFormFile file)
{ ... }

Q83 What is the purpose of IActionResult?

Answer

.NET Senior Interview Q&A Page 25


IActionResult lets one action return different HTTP responses — Ok(), NotFound(), BadRequest(), Created() —
depending on what happened. Returning a concrete type forces 200 OK always. Use ActionResult<T> when you
want both type safety and the flexibility to return non-200 responses.
Example

public ActionResult<Order> Get(int id)


{
var order = _repo.Find(id);
if (order is null) return NotFound();
return Ok(order);
}

Q84 What is the lifecycle of a controller?

Answer

Controllers are Transient by default — created fresh per request with all dependencies injected, then discarded
when the request ends. You can change this by registering controllers as services, but Transient is the safe
default.
Example
public class OrdersController : ControllerBase {
private readonly IOrderService _svc;
public OrdersController(IOrderService svc) => _svc = svc;
// Fresh _svc instance every request
}

Q85 How do you handle exceptions globally for all controllers?

Answer

Three main options: 1. UseExceptionHandler middleware -- catches anything unhandled, redirects to error
endpoint. 2. IExceptionHandler (.NET 8+) -- clean interface, chain multiple handlers, first match wins. 3. Exception
filter -- runs inside the MVC pipeline, has access to action context.
Example
public class GlobalExceptionHandler : IExceptionHandler {
public async ValueTask<bool> TryHandleAsync(
HttpContext ctx, Exception ex, CancellationToken ct)
{
[Link] = 500;
await [Link](new { error = [Link] });
return true;
}
}
[Link]<GlobalExceptionHandler>();
[Link]();

Ch 28 — Minimal APIs

Q86 How does a Minimal API differ from a controller-based API?

Answer

Minimal APIs register endpoints directly in [Link] with MapGet/MapPost etc. No controller class, no
[ApiController], no action conventions. They use the same underlying pipeline but with far less ceremony.
Example
[Link]("/users/{id}", async (int id, IUserService svc) => {
var user = await [Link](id);
return user is null ? [Link]() : [Link](user);
});

.NET Senior Interview Q&A Page 26


Q87 How do you secure Minimal API endpoints?

Answer

Add UseAuthentication() and UseAuthorization() to the pipeline, then chain RequireAuthorization() onto individual
endpoints or groups.
Example

[Link]();
[Link]();

[Link]("/admin", () => "secret").RequireAuthorization("AdminPolicy");


[Link]("/public", () => "open");

Q88 How do you add API versioning to Minimal APIs?

Answer

Use the [Link] package. Create an ApiVersionSet and attach endpoints to specific versions using
WithApiVersionSet and MapToApiVersion.
Example
var vs = [Link]()
.HasApiVersion(new ApiVersion(1))
.HasApiVersion(new ApiVersion(2))
.Build();

[Link]("/orders", GetV1).WithApiVersionSet(vs).MapToApiVersion(1);
[Link]("/orders", GetV2).WithApiVersionSet(vs).MapToApiVersion(2);

Q89 What filters are supported by Minimal APIs?

Answer

Minimal APIs support IEndpointFilter added with AddEndpointFilter. Filters form a pipeline around the endpoint
handler — useful for logging, validation, and auth checks. Traditional MVC action/resource filters don't apply here.
Example

[Link]("/orders", CreateOrder)
.AddEndpointFilter(async (ctx, next) => {
// before handler
var result = await next(ctx);
// after handler
return result;
});

Q90 What are the trade-offs of Minimal APIs in large enterprise apps?

Answer

Pros: less boilerplate, faster startup, great for microservices and vertical-slice architecture. Cons: no
convention-based routing, no area support, harder to discover endpoints across a big codebase, simpler filter
pipeline than full MVC.
Example
// Keep large Minimal API apps organized:
public static class OrderEndpoints {
public static void Map(IEndpointRouteBuilder app) {
[Link]("/orders", GetAll);
[Link]("/orders", Create);
}
}

.NET Senior Interview Q&A Page 27


Ch 29 — Middlewares

Q91 What is the order of middleware execution and why does it matter?

Answer

Middleware runs in registration order for the request and in reverse order for the response. Order matters because
each middleware decides whether to call next(). UseAuthentication must come before UseAuthorization —
establish identity before checking permissions.
Example

[Link](); // first -- wraps everything


[Link]();
[Link](); // identity before permissions
[Link]();
[Link]();

Q92 What are all the ways to create middleware in [Link] Core?

Answer

1. Inline lambda: [Link]((ctx, next) => ...) 2. Convention-based class with InvokeAsync (singleton lifecycle) 3.
Factory-based: implement IMiddleware, created per request via DI 4. Terminal: [Link]() -- no next call 5.
Registration: [Link]<T>()
Example

public class TimingMiddleware(RequestDelegate next) {


public async Task InvokeAsync(HttpContext ctx) {
var sw = [Link]();
await next(ctx);
[Link]($"Took {[Link]}ms");
}
}
[Link]<TimingMiddleware>();

Q93 What is the difference between convention-based and factory-based middleware?

Answer

Convention-based is instantiated once at startup — effectively a singleton. Don't inject scoped services via its
constructor. Factory-based (IMiddleware) is resolved from DI on every request, so injecting scoped services in the
constructor is safe.
Example

public class AuditMiddleware : IMiddleware {


// Scoped service is fine -- created per request
public AuditMiddleware(IAuditService audit) { ... }
public Task InvokeAsync(HttpContext ctx, RequestDelegate next) { ... }
}
[Link]<AuditMiddleware>();

Q94 How do you handle exceptions globally using middleware?

Answer

Register UseExceptionHandler before everything else. It catches any unhandled exception, resets the response,
and runs an error handler that writes the error response.
Example
[Link](errApp => {
[Link](async ctx => {
var ex = [Link]<IExceptionHandlerFeature>()?.Error;

.NET Senior Interview Q&A Page 28


[Link] = 500;
await [Link](new { error = ex?.Message });
});
});

Ch 30 — Filters

Q95 What are the main filter types in [Link] Core?

Answer

Authorization filters -- run first; short-circuit if not allowed. Resource filters -- run after auth but before model
binding; can return cached response early. Action filters -- run around the action method after model binding.
Exception filters -- catch exceptions from action methods. Result filters -- run around IActionResult execution.
Example

public class LogActionFilter : IActionFilter {


public void OnActionExecuting(ActionExecutingContext ctx)
=> [Link]($"Executing {[Link]}");
public void OnActionExecuted(ActionExecutedContext ctx)
=> [Link]("Done");
}

Q96 How do you control filter execution order?

Answer

Implement IOrderedFilter and set the Order property. Lower numbers run earlier in the before-phase and later in
the after-phase (outermost wrapper). You can also set order when registering global filters.
Example

public class FirstFilter : IActionFilter, IOrderedFilter {


public int Order => -1000; // runs before all default filters
}

Q97 What is the difference between resource filters and action filters?

Answer

Resource filters fire before model binding — return a cached response without the framework ever reading the
body. Action filters fire after model binding — arguments are already populated, so you can inspect or mutate
them.
Example

public class CacheFilter : IResourceFilter {


public void OnResourceExecuting(ResourceExecutingContext ctx) {
if (_cache.TryGet([Link], out var cached))
[Link] = new ContentResult { Content = cached };
}
public void OnResourceExecuted(ResourceExecutedContext ctx) { }
}

Q98 What are filter scopes and how is precedence resolved?

Answer

Global filters apply to every action. Controller-level apply to all actions in that controller. Action-level apply to one
action. When Order values tie, global runs outermost, then controller, then action.
Example

.NET Senior Interview Q&A Page 29


// Global
[Link](o => [Link]<LogFilter>());
// Controller-level
[ServiceFilter(typeof(ValidateTenantFilter))]
public class OrdersController : ControllerBase { }
// Action-level
[HttpPost, ValidateModel]
public IActionResult Create(OrderDto dto) { ... }

Ch 31 — REST Fundamentals

Q99 What is the Richardson Maturity Model?

Answer

Level 0 -- single URL, single verb (RPC over HTTP). Level 1 -- separate URLs per resource. Level 2 -- correct
HTTP verbs and proper status codes. Level 3 -- HATEOAS: responses include links to next possible actions. Most
real-world REST APIs target Level 2.
Example
// Level 2:
GET /orders -> 200 list
GET /orders/1 -> 200 single
POST /orders -> 201 created
PUT /orders/1 -> 200 updated
DELETE /orders/1 -> 204 no content

Q100 What are the six architectural constraints of REST?

Answer

1. Client-Server -- independent evolution of client and server. 2. Stateless -- each request contains all context the
server needs; no server-side session. 3. Cacheable -- responses declare whether they can be cached. 4. Uniform
Interface -- resource identified by URI, self-descriptive messages. 5. Layered System -- client can't tell if talking
directly to server or proxy. 6. Code on Demand (optional) -- server can send executable code to client.
Example
// Stateless in practice:
// Bad: store user context in server-side session
// Good: send JWT in every request

Q101 What is the difference between PUT and PATCH?

Answer

PUT replaces the entire resource — any omitted field gets null or its default. PATCH applies a partial update —
only the fields you send are changed.
Example

// PUT -- send the whole replacement


PUT /orders/1
{ "product": "Book", "quantity": 2, "status": "Paid" }

// PATCH -- only what changed


PATCH /orders/1
{ "status": "Shipped" }

Q102 What is idempotency and which HTTP methods are idempotent?

Answer

.NET Senior Interview Q&A Page 30


An operation is idempotent if calling it once or many times leaves the server in the same state. GET, PUT,
DELETE, HEAD, OPTIONS are idempotent. POST is not — each call may create a new resource.
Example

DELETE /orders/1 // first: deletes. Second: 404.


// Server state is the same both times -> idempotent

POST /orders // each call creates a new order -> NOT idempotent

Q103 What is the difference between 401 and 403?

Answer

401 means no valid credentials — you aren't authenticated yet. 403 means credentials are valid but you don't have
permission for this resource — authenticated but not authorised.
Example
// 401: missing or expired token
GET /admin/reports
Authorization: Bearer expired_token -> 401

// 403: valid token, wrong role


GET /admin/reports
Authorization: Bearer valid_user_token -> 403

Q104 What is HATEOAS?

Answer

HATEOAS (Hypermedia As The Engine Of Application State) means responses include links describing what
actions the client can take next. The client discovers the API dynamically — no hard-coded URLs. This is REST
Level 3.
Example

{
"id": 1, "status": "Pending",
"_links": {
"pay": { "href": "/orders/1/pay", "method": "POST" },
"cancel": { "href": "/orders/1/cancel", "method": "DELETE" }
}
}

Q105 What are common pagination strategies in REST APIs?

Answer

Offset pagination (?page=2&pageSize=20) is simple but slow for large offsets. Cursor/keyset pagination
(?after=lastId&pageSize=20) uses an index seek — fast and stable for live data. Link header pagination includes
prev/next URLs in response headers (GitHub style).
Example

GET /orders?after=550&pageSize=20
{
"data": [...],
"nextCursor": "570",
"hasMore": true
}

Q106 What is data shaping in REST APIs?

Answer

.NET Senior Interview Q&A Page 31


Data shaping lets the client request only needed fields via ?fields=id,name,email. Reduces payload, cuts
bandwidth, and can be faster if also projected at the DB level so unused columns are never selected.
Example

GET /users?fields=id,email
[
{ "id": 1, "email": "alice@[Link]" },
{ "id": 2, "email": "bob@[Link]" }
]

Ch 32 — API Versioning

Q107 What are the four API versioning strategies?

Answer

URL (/api/v1/orders) -- most visible, easy to test in a browser. Query string (?api-version=1.0) -- clean URL, version
is optional. Header (api-version: 1.0) -- URL stays clean, needs tool support to discover. Content negotiation
(Accept: application/json;v=1.0) -- fully RESTful but complex.
Example
[Link](o => {
[Link] = [Link](
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("api-version"));
});

Q108 How do you mark an API version as deprecated?

Answer

Call HasDeprecatedApiVersion() when building the ApiVersionSet. The framework adds a Deprecation response
header and marks the version as deprecated in generated OpenAPI docs.
Example

var vs = [Link]()
.HasApiVersion(new ApiVersion(2))
.HasDeprecatedApiVersion(new ApiVersion(1))
.Build();

Q109 How do you maintain backward compatibility across API versions?

Answer

Never remove or rename fields in an existing version. Only add new fields in new versions. Keep old endpoints live
until all consumers have migrated. Use Sunset headers to give advance notice of retirement.
Example

// V1 -- frozen forever
{ "id": 1, "name": "Alice" }

// V2 -- additive only
{ "id": 1, "name": "Alice", "email": "alice@[Link]" }

Ch 33 — Validation

Q110 What is FluentValidation and why use it over DataAnnotations?

Answer

.NET Senior Interview Q&A Page 32


FluentValidation lets you write validation rules in code rather than decorating models with attributes. Rules live in a
separate validator class — proper separation of concerns. It supports conditional rules, async validation, and
service injection, none of which DataAnnotations does cleanly.
Example

public class CreateOrderValidator : AbstractValidator<CreateOrderDto> {


public CreateOrderValidator() {
RuleFor(x => [Link]).GreaterThan(0);
RuleFor(x => [Link]).InclusiveBetween(1, 100);
RuleFor(x => [Link]).NotEmpty().EmailAddress();
}
}

Q111 How does FluentValidation handle nested objects and collections?

Answer

Use SetValidator to delegate to a child validator for a nested object. Use RuleForEach to run a validator against
every item in a collection.
Example

RuleFor(x => [Link]).SetValidator(new AddressValidator());


RuleForEach(x => [Link]).SetValidator(new OrderItemValidator());

Q112 How do you perform async validation in FluentValidation?

Answer

Use MustAsync in the rule builder. Always call ValidateAsync on the validator — the synchronous Validate() simply
skips async rules.
Example

RuleFor(x => [Link])


.MustAsync(async (email, ct) =>
!await _db.[Link](u => [Link] == email, ct))
.WithMessage("Email already taken");

var result = await _validator.ValidateAsync(dto);

Q113 How do you inject services into a FluentValidation validator?

Answer

Register the validator in DI and accept services through its constructor. Use AddFluentValidationAutoValidation()
so [Link] Core runs the validator automatically on model binding.
Example

public class UniqueEmailValidator : AbstractValidator<RegisterDto> {


public UniqueEmailValidator(AppDbContext db) {
RuleFor(x => [Link])
.MustAsync(async (e, ct) =>
!await [Link](u => [Link] == e, ct));
}
}

Q114 How do you return FluentValidation errors as Problem Details?

Answer

Register AddFluentValidationAutoValidation(). When validation fails, [Link] Core returns a


ValidationProblemDetails JSON response (RFC 9457 format) with field errors grouped by property name.
Example

.NET Senior Interview Q&A Page 33


[Link]();
[Link]<IValidator<CreateOrderDto>, CreateOrderValidator>();
// On failure:
// { "title": "One or more validation errors occurred.",
// "errors": { "Email": ["Email already taken"] } }

Ch 34 — Mapping

Q115 What is AutoMapper and what are common pitfalls?

Answer

AutoMapper maps properties by convention at runtime using reflection. Common problems: silent mismatches
when property names differ, hard-to-trace global configuration, N+1 queries when mapping inside LINQ instead of
projecting at the DB, and reflection overhead on hot paths.
Example

var config = new MapperConfiguration(cfg =>


[Link]<Order, OrderDto>());
var mapper = [Link]();
OrderDto dto = [Link]<OrderDto>(order);

Q116 What is Mapperly?

Answer

Mapperly is a source-generator mapper — it generates the full mapping code at compile time from a partial class
you annotate. Zero runtime reflection, AOT-safe, and the generated code is human-readable.
Example

[Mapper]
public partial class OrderMapper {
public partial OrderDto ToDto(Order order);
}
// Compile-time generated:
// public partial OrderDto ToDto(Order o)
// => new OrderDto { Id = [Link], ... };

Q117 Mapperly vs AutoMapper -- what are the trade-offs?

Answer

Mapperly: compile-time errors if a mapping is wrong, no reflection, AOT-safe, faster. AutoMapper: more flexible
conventions, easier for large legacy codebases, but runtime-only errors and reflection-based.
Example

// Mapperly: compile error if a property is unmapped


// AutoMapper: silently ignores unmapped properties unless you call
// AssertConfigurationIsValid()

Q118 When is manual mapping better than a mapping library?

Answer

When the mapping has non-trivial business logic — conditional fields, computed values, lookups. Manual mapping
via extension methods is explicit, zero-dependency, and instantly debuggable.
Example
public static OrderDto ToDto(this Order o) => new() {
Id = [Link],
Total = [Link](i => [Link] * [Link]),
StatusLabel = [Link] == [Link] ? "Paid" : "Pending"

.NET Senior Interview Q&A Page 34


};

Ch 35 — Configuration and Options Pattern

Q119 What is the options pattern and why prefer it over IConfiguration?

Answer

The options pattern binds a config section to a typed POCO. You inject IOptions<T>, IOptionsSnapshot<T>, or
IOptionsMonitor<T> instead of raw IConfiguration strings. Benefits: strongly typed, validated at startup, reloadable,
and easy to unit test.
Example
public class SmtpOptions {
public string Host { get; set; } = "";
public int Port { get; set; } = 587;
}
[Link]<SmtpOptions>(
[Link]("Smtp"));

public class MailService(IOptions<SmtpOptions> opts) {


private readonly SmtpOptions _smtp = [Link];
}

Q120 What is the difference between IOptions, IOptionsSnapshot, and IOptionsMonitor?

Answer

IOptions<T> -- singleton; values fixed at startup. Simplest option. IOptionsSnapshot<T> -- scoped; re-reads config
once per HTTP request. IOptionsMonitor<T> -- singleton with OnChange callback; notified immediately when
config changes. Use in background services.
Example

public class Worker(IOptionsMonitor<FeatureFlags> monitor) {


public Worker() =>
[Link](flags => [Link]("Flags reloaded"));
}

Q121 How do you validate options at startup with DataAnnotations?

Answer

Call ValidateDataAnnotations() and ValidateOnStart(). The framework validates the bound POCO at startup and
throws immediately if any annotations are violated — fail fast.
Example

public class SmtpOptions {


[Required] public string Host { get; set; } = "";
[Range(1, 65535)] public int Port { get; set; }
}
[Link]<SmtpOptions>()
.BindConfiguration("Smtp")
.ValidateDataAnnotations()
.ValidateOnStart();

Q122 How do you keep secrets out of appsettings?

Answer

Dev: User Secrets (dotnet user-secrets) -- stored outside the repo. Production: environment variables, Azure Key
Vault, AWS Secrets Manager, or HashiCorp Vault. The config system layers multiple providers, so secrets override

.NET Senior Interview Q&A Page 35


appsettings.
Example

// Azure Key Vault


[Link](
new Uri([Link]["KeyVault:Uri"]!),
new DefaultAzureCredential());

Ch 36 — Error Handling

Q123 What is UseExceptionHandler middleware?

Answer

UseExceptionHandler wraps the rest of the pipeline in a try/catch. When an unhandled exception escapes it resets
the response and runs an error handler. Register it first so it catches errors from all other middleware.
Example

[Link]("/error");
[Link]("/error", (HttpContext ctx) => {
var ex = [Link]<IExceptionHandlerFeature>()?.Error;
return [Link](detail: ex?.Message, statusCode: 500);
});

Q124 What is IExceptionHandler in .NET 8?

Answer

A clean interface — implement TryHandleAsync, return true if you handled it, false to pass to the next handler.
Chain multiple handlers for different exception types. Integrates nicely with Problem Details.
Example
public class NotFoundHandler : IExceptionHandler {
public async ValueTask<bool> TryHandleAsync(
HttpContext ctx, Exception ex, CancellationToken ct)
{
if (ex is not NotFoundException) return false;
[Link] = 404;
await [Link](
new ProblemDetails { Title = "Not found", Status = 404 });
return true;
}
}

Q125 How do you return Problem Details (RFC 9457) in [Link] Core?

Answer

Call AddProblemDetails() and UseExceptionHandler(). [Link] Core returns ProblemDetails JSON for 4xx/5xx
responses automatically. Return [Link]() from Minimal APIs or Problem() from controllers for manual
control.
Example
[Link]();

// Manual:
return [Link](
title: "Validation failed",
statusCode: 422,
detail: "Quantity must be positive");

.NET Senior Interview Q&A Page 36


Q126 How do you map custom exceptions to HTTP status codes globally?

Answer

Use IExceptionHandler with a switch expression to map known domain exception types to status codes.
Example

public class DomainExceptionHandler : IExceptionHandler {


public async ValueTask<bool> TryHandleAsync(
HttpContext ctx, Exception ex, CancellationToken ct)
{
var status = ex switch {
NotFoundException => 404,
ValidationException => 422,
ConflictException => 409,
_ => 0
};
if (status == 0) return false;
[Link] = status;
await [Link](
new ProblemDetails { Status = status, Detail = [Link] });
return true;
}
}

Ch 37 — Logging

Q127 What is structured logging and why does it matter?

Answer

Structured logging stores log entries as key-value pairs rather than plain strings. When you pass a named
placeholder like {OrderId}, the logging framework saves the value as a separate field. Log aggregators like Seq or
Elasticsearch can then filter on orderId == 1234 instead of doing regex on a text blob.
Example
// Structured -- OrderId stored as searchable field
_logger.LogInformation("Order {OrderId} shipped to {City}",
[Link], [Link]);

// Unstructured -- just a string, can't filter without regex


_logger.LogInformation($"Order {[Link]} shipped to {[Link]}");

Q128 What are logging scopes and when do you use them?

Answer

A logging scope attaches extra key-value pairs to all log entries made within a using block. It is the right way to add
RequestId, UserId, or TenantId to every line inside an operation without passing those values to every method.
Example

using (_logger.BeginScope(new Dictionary<string, object> {


["RequestId"] = [Link],
["UserId"] = userId
}))
{
_logger.LogInformation("Processing order"); // both fields appear
await ProcessAsync();
}

.NET Senior Interview Q&A Page 37


Q129 How do you enable SQL logging in EF Core?

Answer

Set the EF Core command log level to Information in your logging config. For development also call
EnableSensitiveDataLogging() to include parameter values — never use that in production.
Example

{
"Logging": { "LogLevel": {
"[Link]": "Information"
}}
}
// Dev only:
[Link]().LogTo([Link]);

Q130 How do you set up centralised log aggregation in production?

Answer

Typical stack: Serilog as the library, structured JSON sinks (Seq locally, Elasticsearch/Loki/CloudWatch in cloud),
correlation IDs on every entry, and alerts in Grafana or Kibana on elevated error rates.
Example
[Link] = new LoggerConfiguration()
.[Link]()
.[Link]()
.[Link](new JsonFormatter())
.[Link]("[Link]
.CreateLogger();
[Link]();

Q131 How do you configure log file rotation in Serilog?

Answer

Use [Link] with rollingInterval and fileSizeLimitBytes. retainedFileCountLimit controls how many old files to
keep before Serilog deletes the oldest.
Example
[Link] = new LoggerConfiguration()
.[Link]("logs/[Link]",
rollingInterval: [Link],
fileSizeLimitBytes: 50_000_000,
retainedFileCountLimit: 14,
rollOnFileSizeLimit: true)
.CreateLogger();

Ch 38 — Health Checks

Q132 What is the difference between a liveness probe and a readiness probe?

Answer

Liveness: is the process alive? If not, Kubernetes restarts the container. Readiness: is the app ready to serve
traffic? If not, Kubernetes removes the pod from load balancer rotation but does not restart it. A starting app should
fail readiness but pass liveness.
Example
// Liveness -- just 'am I alive?'
[Link]("/health/live", new() { Predicate = _ => false });
// Readiness -- run all registered checks

.NET Senior Interview Q&A Page 38


[Link]("/health/ready", new() { });

Q133 How do you implement a custom health check?

Answer

Implement IHealthCheck and return Healthy, Degraded, or Unhealthy. Register with


AddHealthChecks().AddCheck<T>().
Example

public class RedisHealthCheck : IHealthCheck {


private readonly IDatabase _db;
public RedisHealthCheck(IConnectionMultiplexer mux)
=> _db = [Link]();
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext ctx, CancellationToken ct)
{
var ok = await _db.PingAsync() < [Link](1);
return ok ? [Link]()
: [Link]("Redis slow");
}
}
[Link]().AddCheck<RedisHealthCheck>("redis");

Ch 39 — Dependency Injection

Q134 What are the three DI lifetimes in [Link] Core?

Answer

Transient -- new instance every time requested. Use for stateless services. Scoped -- one instance per HTTP
request (or per DI scope). DbContext is the classic example. Singleton -- one instance for the entire app lifetime.
Use for caches, configuration, HttpClientFactory.
Example

[Link]<IEmailSender, SmtpEmailSender>();
[Link]<IOrderRepository, EfOrderRepository>();
[Link]<ICache, MemoryCache>();

Q135 What is a captive dependency?

Answer

A captive dependency happens when a singleton captures a scoped service in its constructor. The scoped service
is supposed to be created and discarded per request, but being held by a singleton means it lives forever.
[Link] Core throws InvalidOperationException at startup when it detects this.
Example

// Bad -- will throw at startup


[Link]<ReportGenerator>();
// ReportGenerator depends on scoped IOrderRepository

// Fix -- use IServiceScopeFactory to create a scope when needed


public class ReportGenerator(IServiceScopeFactory factory) { ... }

Q136 How do you use a scoped service inside a background task?

Answer

Inject IServiceScopeFactory into the singleton or background service. Create a new scope around each unit of
work and resolve the scoped service from that scope.

.NET Senior Interview Q&A Page 39


Example

public class OutboxWorker(IServiceScopeFactory factory) : BackgroundService {


protected override async Task ExecuteAsync(CancellationToken ct) {
while (![Link]) {
using var scope = [Link]();
var repo = [Link]
.GetRequiredService<IOutboxRepo>();
await [Link](ct);
await [Link](5000, ct);
}
}
}

Q137 How do you fix circular dependencies in DI?

Answer

Circular dependencies usually signal a design problem. Cleanest fix: extract a third class both can depend on, or
use events/mediator to decouple them. Quick fix: Lazy<T> breaks the cycle by deferring resolution until first use.
Example
public class ServiceA {
private readonly Lazy<IServiceB> _b;
public ServiceA(Lazy<IServiceB> b) => _b = b;
public void DoWork() => _b.[Link]();
}

Q138 How do you conditionally resolve different implementations?

Answer

Register all implementations, inject IEnumerable<T>, and pick by a key property at runtime. Alternatively, use a
factory delegate.
Example

[Link]<IPaymentGateway, StripeGateway>();
[Link]<IPaymentGateway, PayPalGateway>();

public class Checkout(IEnumerable<IPaymentGateway> gateways) {


public Task Pay(string provider) =>
[Link](g => [Link] == provider).ChargeAsync();
}

Ch 43 — Entity Framework Core

Q139 What is the difference between DbContext and DbSet<T>?

Answer

DbContext is the unit of work: tracks changes, manages identity map, coordinates SaveChanges. DbSet<T>
represents one table. You write LINQ against DbSet; DbContext translates and executes them.
Example

public class AppDbContext : DbContext {


public DbSet<Order> Orders { get; set; }
public DbSet<Product> Products { get; set; }
}
var paid = await [Link]
.Where(o => [Link] > 100).ToListAsync();

.NET Senior Interview Q&A Page 40


Q140 Explain eager, lazy, and explicit loading.

Answer

Eager (Include) -- loads related entities in the same query via JOIN. No extra round-trips. Lazy -- related entities
load automatically when you access the navigation property (requires proxies). Risk: N+1 queries. Explicit -- you
call LoadAsync manually when you decide you need the related data.
Example

// Eager
var orders = await [Link](o => [Link]).ToListAsync();

// Explicit -- you choose when to load


var order = await [Link](id);
await [Link](order).Collection(o => [Link]).LoadAsync();

Q141 When and why should you use AsNoTracking()?

Answer

Use it for read-only queries — GET endpoints, reports — where you won't update the returned entities. EF Core
skips adding them to the change tracker, reducing both memory and CPU overhead.
Example

var orders = await [Link]


.AsNoTracking()
.Where(o => [Link] == "Paid")
.ToListAsync();

Q142 What is AsSplitQuery and what does it solve?

Answer

When you Include multiple collection navigations EF Core generates a cartesian-product JOIN. The result set
explodes: 1 order x 100 items x 50 tags = 5,000 rows. AsSplitQuery fires a separate SQL query per collection,
avoiding the explosion at the cost of extra round-trips.
Example

var orders = await [Link]


.Include(o => [Link])
.Include(o => [Link])
.AsSplitQuery()
.ToListAsync();

Q143 What is DbContext pooling?

Answer

AddDbContextPool reuses DbContext instances across requests instead of creating and GC-ing one per request.
Between uses the context is reset — change tracker cleared. This cuts GC pressure significantly on
high-throughput services.
Example

[Link]<AppDbContext>(
o => [Link](connectionString),
poolSize: 128);

Q144 How do you implement optimistic concurrency with EF Core?

Answer

.NET Senior Interview Q&A Page 41


Add a RowVersion byte[] property and mark it [Timestamp]. EF Core includes the token in UPDATE WHERE
clauses. If another process changed the row first, the UPDATE matches 0 rows and EF throws
DbUpdateConcurrencyException — catch it and retry.
Example

public class Order {


public int Id { get; set; }
[Timestamp] public byte[] RowVersion { get; set; }
}
try { await [Link](); }
catch (DbUpdateConcurrencyException) { /* reload and retry */ }

Q145 How do you do bulk update/delete without loading entities?

Answer

EF Core 7 added ExecuteUpdateAsync and ExecuteDeleteAsync. These emit a single UPDATE or DELETE SQL
statement directly — no entities loaded, no change tracking, no SaveChanges needed.
Example
await [Link]
.Where(o => [Link] == "Cancelled"
&& [Link] < [Link](-1))
.ExecuteDeleteAsync();

await [Link]
.Where(p => [Link] == "Electronics")
.ExecuteUpdateAsync(s => [Link](p => [Link], 0.1m));

.NET Senior Interview Q&A Page 42


BUCKET 3 — CACHING, SCHEDULING, MESSAGING & SECURITY

Ch 40 — Authentication and Authorization

Q146 What is the difference between authentication and authorization?

Answer

Authentication answers WHO ARE YOU -- verifying identity (JWT, cookie, API key). Authorization answers WHAT
CAN YOU DO -- checking what the authenticated identity is allowed to do. The two middleware must be registered
in that order: authenticate first, then authorise.
Example

[Link](); // who are you?


[Link](); // what can you do?

[Authorize(Roles = "Admin")]
public IActionResult DeleteUser(int id) { ... }

Q147 What is the difference between cookie auth and JWT bearer?

Answer

Cookie auth stores the session token in an HTTP-only cookie -- the browser attaches it automatically. Ideal for
server-rendered web apps. JWT bearer puts the token in the Authorization header -- the client must manage it.
Ideal for SPAs, mobile, and service-to-service calls.
Example

// Cookie
[Link](
[Link]).AddCookie();

// JWT Bearer
[Link](
[Link])
.AddJwtBearer(o => [Link] = "[Link]

Q148 What are refresh tokens and how does the JWT refresh flow work?

Answer

Access tokens are short-lived (minutes). Refresh tokens are long-lived (days/weeks). Flow: 1. Login -> server
returns access token + refresh token. 2. Access token expires -> client POSTs refresh token to /auth/refresh. 3.
Server validates and returns new tokens. 4. On logout -> server revokes the refresh token.
Example

POST /auth/login -> { accessToken (5min), refreshToken (7d) }


POST /auth/refresh -> body: { refreshToken }
-> { new accessToken, new refreshToken }

Q149 How do you implement policy-based authorization?

Answer

Define a named policy with AddAuthorization, specify what claims or roles are required, apply the policy name with
[Authorize(Policy='name')].
Example

[Link](o =>
[Link]("CanApproveOrders",

.NET Senior Interview Q&A Page 43


p => [Link]("permission", "orders:approve")));

[Authorize(Policy = "CanApproveOrders")]
public IActionResult Approve(int id) { ... }

Q150 What is an authorization Requirement?

Answer

A Requirement is a plain marker class carrying data the authorization decision needs. A separate
AuthorizationHandler reads it and calls [Link] or [Link].
Example
public class MinimumAgeRequirement : IAuthorizationRequirement {
public int MinAge { get; }
public MinimumAgeRequirement(int min) => MinAge = min;
}
public class AgeHandler : AuthorizationHandler<MinimumAgeRequirement> {
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext ctx, MinimumAgeRequirement req)
{
var dob = [Link]("dob");
if (dob != null && CalculateAge([Link]) >= [Link])
[Link](req);
return [Link];
}
}

Q151 How do you create a custom authorization handler?

Answer

Inherit AuthorizationHandler<TRequirement>, override HandleRequirementAsync, call [Link] when satisfied


or [Link] to hard-fail. Register the handler as a service.
Example

[Link]<IAuthorizationHandler, AgeHandler>();
[Link](o =>
[Link]("Over18",
p => [Link](new MinimumAgeRequirement(18))));

Q152 When is claims transformation necessary and how do you implement it?

Answer

JWT tokens carry static claims baked in at login. If the user's role changes after login the token doesn't reflect it
until expiry. IClaimsTransformation lets you augment claims on every request from the database.
Example
public class PermissionsTransformer : IClaimsTransformation {
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal p) {
var userId = [Link]("sub")?.Value;
var perms = await _db.GetPermissionsAsync(userId);
var id = new ClaimsIdentity();
foreach (var perm in perms)
[Link](new Claim("permission", perm));
[Link](id);
return p;
}
}

Ch 41 — [Link] Core Identity

.NET Senior Interview Q&A Page 44


Q153 How do you configure password requirements in Identity?

Answer

Set the [Link] properties in the AddIdentity lambda.


Example

[Link]<AppUser, IdentityRole>(o => {


[Link] = 12;
[Link] = true;
[Link] = true;
[Link] = true;
});

Q154 How do you implement two-factor authentication with Identity?

Answer

Enable TwoFactorEnabled on the user, configure a token provider, use GenerateTwoFactorTokenAsync to create
a code, and TwoFactorSignInAsync to validate it.
Example
var token = await _userManager
.GenerateTwoFactorTokenAsync(user, "Email");
await _emailSender.SendAsync([Link], "Your code", token);

var result = await _signInManager.TwoFactorSignInAsync(


"Email", code, isPersistent: false, rememberClient: false);

Q155 How do you lock out users after failed login attempts?

Answer

Enable lockout in IdentityOptions. Every failed PasswordSignInAsync call increments the failure counter. After
MaxFailedAccessAttempts the account is locked for DefaultLockoutTimeSpan.
Example

[Link] = 5;
[Link] = [Link](15);
[Link] = true;
// PasswordSignInAsync handles lockout automatically

Q156 How do you integrate Identity with JWT authentication?

Answer

After a successful Identity login, generate a JWT containing the user's claims with JwtSecurityTokenHandler.
Return it to the client. Configure AddJwtBearer on the API to validate incoming tokens.
Example

var claims = new[] {


new Claim([Link], [Link]),
new Claim([Link], [Link])
};
var key = new SymmetricSecurityKey([Link](_jwtKey));
var token = new JwtSecurityToken(
issuer: "myapp", audience: "myapp", claims: claims,
expires: [Link](15),
signingCredentials: new SigningCredentials(
key, SecurityAlgorithms.HmacSha256));

.NET Senior Interview Q&A Page 45


Q157 How do you create a custom user store for a NoSQL database?

Answer

Implement IUserStore<T> plus whichever optional interfaces you need (IUserPasswordStore, IUserRoleStore).
Register with AddIdentityCore().AddUserStore<T>(). UserManager delegates all persistence to your store.
Example

public class MongoUserStore :


IUserStore<AppUser>, IUserPasswordStore<AppUser>
{
private readonly IMongoCollection<AppUser> _col;
public MongoUserStore(IMongoDatabase db)
=> _col = [Link]<AppUser>("users");
public Task<AppUser> FindByIdAsync(string id, CancellationToken ct)
=> _col.Find(u => [Link] == id).FirstOrDefaultAsync(ct);
// implement remaining interface members...
}

Ch 42 — [Link] Core Security

Q158 What is CORS and what is the risk of a permissive policy?

Answer

CORS controls which origins browsers allow to call your API. Allowing * combined with cookie auth is dangerous:
any website can make credentialed requests on behalf of your users. Always restrict origins, methods, and
headers to what you need.
Example

[Link](o => [Link]("FrontEnd", p =>


[Link]("[Link]
.WithMethods("GET", "POST")
.AllowCredentials()));
[Link]("FrontEnd");

Q159 How do preflight OPTIONS requests work?

Answer

Before a cross-origin request with custom headers or non-simple verbs, the browser sends an OPTIONS preflight.
The server must reply with Access-Control-Allow-* headers. If the preflight fails the browser blocks the real
request. [Link] Core handles this automatically when CORS middleware is configured.
Example

OPTIONS /api/orders
Origin: [Link]
Access-Control-Request-Method: POST
// Server replies:
Access-Control-Allow-Origin: [Link]
Access-Control-Allow-Methods: POST

Q160 How do you validate a JWT's signature and claims?

Answer

Configure TokenValidationParameters in AddJwtBearer. The middleware validates signature, issuer, audience,


and expiry on every incoming request automatically.
Example

[Link]([Link])
.AddJwtBearer(o => {

.NET Senior Interview Q&A Page 46


[Link] = new() {
ValidateIssuer = true, ValidIssuer = "myapp",
ValidateAudience = true, ValidAudience = "myapp",
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
[Link](secret))
};
});

Q161 What are the security risks of refresh tokens?

Answer

If stolen, the attacker gets long-lived access. Mitigations: store only a hashed copy in the database, rotate on every
use, bind to device fingerprint, set expiry, and revoke all tokens for a user on logout or password change.
Example

// Refresh token rotation:


// 1. Client sends old refresh token
// 2. Server validates, issues new access + refresh token
// 3. Server marks old token as revoked
// 4. If old token replayed -> revoke entire chain (replay attack)

Q162 How do you revoke refresh tokens on logout?

Answer

Store refresh tokens in the database with an IsRevoked flag. On logout or password change, mark all of that user's
tokens as revoked. JWT access tokens can't be individually invalidated before expiry — the server-side store is the
only way.
Example

await _db.RefreshTokens
.Where(t => [Link] == userId)
.ExecuteUpdateAsync(s => [Link](t => [Link], true));

if ([Link]) return Unauthorized();

Q163 What is OAuth 2.0?

Answer

OAuth 2.0 is a delegation framework. A user grants a client app limited access to their resources on a server,
mediated by an Authorization Server, without handing the client their credentials. The client gets a scoped access
token.
Example

// Authorization Code flow:


// 1. Redirect to /authorize?client_id=...&scope=orders:read
// 2. User authenticates and consents
// 3. Auth server redirects back with authorization code
// 4. App exchanges code for access token at /token
// 5. App calls API: Authorization: Bearer <token>

Q164 What is OpenID Connect and how does it differ from OAuth 2.0?

Answer

OAuth 2.0 is about authorization (access tokens). OpenID Connect adds authentication on top -- it returns an ID
token (JWT) saying WHO the user is (name, email, subject). Use OAuth 2.0 for API access delegation. Use OIDC

.NET Senior Interview Q&A Page 47


for user login and SSO.
Example

{
"access_token": "...",
"id_token": "<JWT: sub, email, name>",
"token_type": "Bearer"
}
// access_token -> what you can do
// id_token -> who you are

Ch 44 — Caching

Q165 What is IDistributedCache and how does it differ from IMemoryCache?

Answer

IMemoryCache is in-process -- fast but private to one instance and lost on restart. IDistributedCache is backed by
Redis, SQL Server, etc. -- shared across all instances and survives restarts. Use memory cache for
single-instance; distributed cache for multi-instance deployments.
Example
// In-memory only
_memCache.Set("key", value, [Link](5));

// Shared across pods


await _distCache.SetStringAsync("key", json, new() {
AbsoluteExpirationRelativeToNow = [Link](5)
});

Q166 How do you configure Redis cache expiration and eviction?

Answer

Set TTL in DistributedCacheEntryOptions when writing. Configure Redis maxmemory-policy at the server level
(allkeys-lru is a sensible default) for what gets evicted when memory fills up.
Example
await _cache.SetStringAsync("product:1", json,
new DistributedCacheEntryOptions {
AbsoluteExpirationRelativeToNow = [Link](1),
SlidingExpiration = [Link](20)
});

Q167 What is OutputCache and how do you use it?

Answer

OutputCache stores the complete HTTP response for an endpoint and serves it directly to subsequent matching
requests -- the action never runs again until the entry expires. Register AddOutputCache and UseOutputCache,
annotate endpoints with CacheOutput.
Example

[Link]();
[Link]();

[Link]("/products", GetProducts)
.CacheOutput(p => [Link]([Link](10)));

.NET Senior Interview Q&A Page 48


Q168 How do you vary OutputCache by user or query parameter?

Answer

Use VaryByQuery, VaryByHeader, or VaryByValue in the cache policy. This creates a separate cache entry per
unique combination of those values.
Example

[Link](o =>
[Link]("ByUser", p =>
[Link](ctx =>
[Link]("sub")?.Value ?? "anon")
.Expire([Link](30))));

Q169 What is HybridCache in .NET 9?

Answer

HybridCache is a two-level cache: L1 is in-process memory for ultra-fast reads; L2 is a distributed cache (Redis).
On a hit it serves from L1. On a miss it checks L2, populates L1, and returns. Write-through keeps both in sync.
Example
[Link]();

var value = await _hybridCache.GetOrCreateAsync(


"product:1",
async ct => await _db.[Link](1, ct),
new() { Expiration = [Link](5) });

Q170 How do you wire HybridCache with Redis as the L2 backend?

Answer

Register AddStackExchangeRedisCache for L2 and AddHybridCache for the orchestration. HybridCache


automatically detects and uses the registered IDistributedCache (Redis) as its L2.
Example

[Link](
o => [Link] = "redis:6379");
[Link]();
// in-memory = L1, Redis = L2 automatically

Q171 What is FusionCache and what does it add over HybridCache?

Answer

FusionCache adds two extras HybridCache currently lacks: 1. Cache stampede protection -- only one request
fetches from source when cold; others wait. 2. Soft expiry / stale-while-revalidate -- serve a slightly stale value
while refreshing in the background. It also adds a circuit breaker for the distributed cache layer.
Example

[Link]()
.WithDefaultEntryOptions(o => o
.SetDuration([Link](5))
.SetFailSafe(true)); // serve stale if downstream is down

var product = await _fusionCache.GetOrSetAsync(


"product:1",
async ct => await _db.[Link](1, ct));

Ch 49 — Task Scheduling

.NET Senior Interview Q&A Page 49


Q172 What is a BackgroundService in [Link] Core?

Answer

BackgroundService is an abstract base class implementing IHostedService. Override ExecuteAsync and write your
work loop there. The host starts it on app start and cancels its CancellationToken on graceful shutdown.
Example

public class CleanupService : BackgroundService {


protected override async Task ExecuteAsync(CancellationToken ct) {
while (![Link]) {
await DeleteOldRecordsAsync(ct);
await [Link]([Link](1), ct);
}
}
}

Q173 What is the difference between BackgroundService and IHostedService?

Answer

IHostedService is the low-level interface with StartAsync and StopAsync -- you manage everything.
BackgroundService is a convenience wrapper: it runs your ExecuteAsync in a Task and handles cancellation
wiring. Most of the time BackgroundService is all you need.
Example

// Raw -- manage the task yourself


public class MyService : IHostedService {
public Task StartAsync(CancellationToken ct) { ... }
public Task StopAsync(CancellationToken ct) { ... }
}
// Convenience -- just write the loop
public class MyService : BackgroundService {
protected override Task ExecuteAsync(CancellationToken ct) { ... }
}

Q174 What happens if a BackgroundService throws an unhandled exception?

Answer

By default (.NET 6+) the service stops but the host keeps running. Set BackgroundServiceExceptionBehavior to
StopHost to crash the whole app and let your process supervisor (Kubernetes, systemd) restart it.
Example
[Link]<HostOptions>(o =>
[Link] =
[Link]);

Q175 How do you schedule recurring jobs with [Link] and cron?

Answer

Create a class implementing IJob, build a JobDetail and a CronTrigger, schedule them via IScheduler.
Example
public class ReportJob : IJob {
public Task Execute(IJobExecutionContext ctx) =>
_report.GenerateAsync([Link]);
}
var job = [Link]<ReportJob>().Build();
var trigger = [Link]()
.WithCronSchedule("0 0 8 * * ?") // every day at 8 AM
.Build();

.NET Senior Interview Q&A Page 50


await [Link](job, trigger);

Q176 How do you inject services into [Link] jobs?

Answer

Use [Link] and call UseMicrosoftDependencyInjectionJobFactory. Quartz


resolves each job from the DI container so constructor injection works normally.
Example

[Link](q => {
[Link]();
[Link]<ReportJob>(opts => [Link]("report"));
[Link](opts => opts
.ForJob("report")
.WithCronSchedule("0 0 8 * * ?"));
});
[Link](
q => [Link] = true);

Q177 How do you prevent the same job running on multiple instances simultaneously?

Answer

Four options: 1. [Link] clustered AdoJobStore (shared PostgreSQL) -- cluster elects one runner per job. 2.
Distributed lock (Redis Redlock or Azure Blob lease) -- first instance to acquire wins. 3. Hangfire with shared
database backend -- enqueuing is idempotent. 4. Outbox record with UNIQUE constraint -- only one instance can
insert the 'job started' record.
Example

var acquired = await _redlock.TryAcquireAsync(


"job:nightly-report", [Link](10));
if (!acquired) return; // another instance has it
try { await RunReportAsync(); }
finally { await _redlock.ReleaseAsync(); }

Q178 How would you architect distributed scheduling across multiple servers?

Answer

Option A (clustered Quartz): shared AdoJobStore in PostgreSQL, cluster coordinates which node runs each job.
Automatic failover if leader goes down. Option B (message-driven): a single lightweight scheduler enqueues a
message (RabbitMQ, SQS). Worker pods consume and process. Scale workers independently; scheduler stays
simple.
Example

// Message-driven (recommended for scale):


[Link](new ProcessExpiredOrdersCommand());

// Any number of worker pods consumes:


[Link] = async msg =>
await _orderService.ExpireAsync(msg);

Ch 50 — Event Messaging

Q179 What is MediatR and what pattern does it implement?

Answer

.NET Senior Interview Q&A Page 51


MediatR implements the Mediator and CQRS patterns in-process. A sender posts a request (command or query)
to the mediator, which finds and calls the right handler. The sender has no direct dependency on the handler.
Example

public record CreateOrderCommand(string Product, int Qty) : IRequest<int>;


public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, int> {
public Task<int> Handle(CreateOrderCommand cmd, CancellationToken ct)
=> _repo.CreateAsync([Link], [Link], ct);
}
var orderId = await _mediator.Send(new CreateOrderCommand("Book", 2));

Q180 How do you publish notifications with MediatR?

Answer

Use INotification instead of IRequest and call [Link](). Unlike Send, Publish calls ALL registered
handlers -- useful for domain events where multiple things need to react.
Example
public record OrderCreated(int OrderId) : INotification;
public class SendConfirmationEmail : INotificationHandler<OrderCreated> {
public Task Handle(OrderCreated n, CancellationToken ct)
=> _mailer.SendAsync([Link], ct);
}
await _mediator.Publish(new OrderCreated(orderId));

Q181 What are the downsides of overusing MediatR?

Answer

Every method call becomes invisible -- F12 takes you to MediatR's Send, not your handler. Pipeline behaviors add
indirection. For simple CRUD endpoints it is just extra ceremony. Use it where the decoupling genuinely adds
value -- complex CQRS flows, domain events.
Example

// Good fit: complex domain with many handlers and cross-cutting concerns
// Overkill: a simple CRUD action that just calls one service method

Q182 What is MassTransit?

Answer

MassTransit is a .NET message bus abstraction. Write message contracts and consumer classes; MassTransit
handles transport (RabbitMQ, Azure Service Bus, SQS, Kafka), serialisation, consumer routing, retry policies,
sagas, and observability.
Example

[Link](x => {
[Link]<OrderCreatedConsumer>();
[Link]((ctx, cfg) => {
[Link]("rabbitmq://localhost");
[Link](ctx);
});
});

Q183 How do you configure MassTransit with RabbitMQ?

Answer

Call UsingRabbitMq inside AddMassTransit. ConfigureEndpoints reads your registered consumers and creates the
right queues and exchanges automatically.
Example

.NET Senior Interview Q&A Page 52


[Link]((ctx, cfg) => {
[Link](new Uri("amqp://guest:guest@rabbitmq:5672"));
[Link](ctx);
});

Q184 How do you define and consume messages with MassTransit?

Answer

Declare message contracts as records. Implement IConsumer<TMessage>. MassTransit routes to the right
consumer based on the message type name.
Example

public record OrderPlaced(Guid OrderId, decimal Amount);


public class OrderPlacedConsumer : IConsumer<OrderPlaced> {
public async Task Consume(ConsumeContext<OrderPlaced> ctx) {
var msg = [Link];
await _warehouse.ReserveAsync([Link]);
}
}

Q185 What advanced features does MassTransit offer?

Answer

Sagas: state machines that persist state across multiple messages -- for long-running workflows. Retry policies:
exponential back-off on consumer exceptions, configurable per endpoint. Outbox: writes outgoing message inside
the same DB transaction as your domain changes -- guarantees exactly-once publishing.
Example

[Link]("order-placed", e => {
[Link](r => [Link](
5,
[Link](1),
[Link](5),
[Link](5)));
[Link]<OrderPlacedConsumer>(ctx);
});

.NET Senior Interview Q&A Page 53


BUCKET 4 — SYSTEM DESIGN AND ARCHITECTURE

Ch 51 — APIs, SDKs and Resilience

Q186 What happens if you create a new HttpClient for every API call?

Answer

Each new HttpClient opens its own socket. Disposing it doesn't immediately close the socket -- it lingers in
TIME_WAIT for up to 4 minutes. Under load you quickly exhaust available ports (socket exhaustion). DNS changes
also go undetected because each client has its own DNS cache.
Example

// Bad -- socket exhaustion under load


for (int i = 0; i < 1000; i++) {
using var client = new HttpClient();
await [Link]("[Link]
}
// Good -- reuse via IHttpClientFactory
public class MyService(HttpClient client) { }

Q187 What is HttpClientFactory and why was it introduced?

Answer

HttpClientFactory manages a pool of HttpMessageHandler instances (default 2-minute lifetime). Handlers are
recycled to respect DNS changes while sockets are reused. Integrates cleanly with DI and Polly.
Example

[Link]("PaymentsApi", c =>
[Link] = new Uri("[Link]

Q188 What is a typed HttpClient and how does it differ from a named client?

Answer

Named client: inject IHttpClientFactory, call CreateClient("name") -- loose coupling but magic strings. Typed client:
inject a class that wraps HttpClient -- strongly typed, mockable in tests, no magic strings.
Example

public class PaymentsClient {


private readonly HttpClient _http;
public PaymentsClient(HttpClient http) => _http = http;
public Task<PaymentResult> ChargeAsync(ChargeDto dto)
=> _http.PostAsJsonAsync("charges", dto).ContinueWith(/* parse */);
}
[Link]<PaymentsClient>(c =>
[Link] = new Uri("[Link]

Q189 What is Refit?

Answer

Refit generates HttpClient implementations from annotated C# interfaces. You define the API contract with
attributes; Refit generates the HTTP calls, URL building, and serialisation.
Example
public interface IPaymentsApi {
[Post("/charges")]
Task<PaymentResult> ChargeAsync([Body] ChargeDto dto);

.NET Senior Interview Q&A Page 54


[Get("/charges/{id}")]
Task<PaymentResult> GetAsync(string id);
}
[Link]<IPaymentsApi>()
.ConfigureHttpClient(c =>
[Link] = new Uri("[Link]

Q190 What is a DelegatingHandler and what is it used for?

Answer

DelegatingHandler is middleware for the HttpClient pipeline. Override SendAsync, modify the request or response,
call the inner handler. Common uses: injecting auth tokens, logging, adding correlation IDs, implementing retry
logic.
Example
public class AuthHandler : DelegatingHandler {
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage req, CancellationToken ct)
{
[Link] = new AuthenticationHeaderValue(
"Bearer", await GetTokenAsync());
return await [Link](req, ct);
}
}

Q191 What is Polly and how do you use it with HttpClientFactory?

Answer

Polly is a resilience library with policies for retry, circuit breaker, timeout, bulkhead, and fallback. Hook it into
HttpClientFactory with AddPolicyHandler -- the policy wraps every outgoing request from that client.
Example

[Link]<PaymentsClient>()
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.RetryAsync(3));

Q192 How do you add an exponential back-off retry policy with Polly?

Answer

Use HandleTransientHttpError (covers 5xx and network errors) and WaitAndRetryAsync with a back-off formula.
Example

var retry = HttpPolicyExtensions


.HandleTransientHttpError()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt =>
[Link]([Link](2, attempt)));

[Link]<OrdersClient>()
.AddPolicyHandler(retry);

Q193 How do you implement a circuit breaker with Polly?

Answer

CircuitBreakerAsync opens the circuit after N consecutive failures, fast-failing all requests for a break duration.
After the break it lets one probe request through (half-open). Prevents hammering a failing downstream service.

.NET Senior Interview Q&A Page 55


Example

var cb = HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: [Link](30));

[Link]<OrdersClient>()
.AddPolicyHandler(cb);

Q194 How do you implement a fallback strategy with Polly?

Answer

FallbackAsync catches a failure and returns a pre-defined fallback response. Combine with circuit breaker and
retry for a full resilience stack.
Example
var fallback = Policy<HttpResponseMessage>
.HandleResult(r => ![Link])
.FallbackAsync(_ => {
var res = new HttpResponseMessage([Link]);
[Link] = new StringContent("{"source":"cache"}");
return [Link](res);
});

Q195 How do you handle token refresh for outgoing HTTP requests?

Answer

Use a DelegatingHandler that fetches a cached token and adds it as a Bearer header. On a 401 response,
invalidate the cache, get a fresh token, and retry once.
Example
public class BearerTokenHandler : DelegatingHandler {
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage req, CancellationToken ct)
{
var token = await _cache.GetOrRefreshAsync(ct);
[Link] = new("Bearer", token);
var resp = await [Link](req, ct);
if ([Link] == [Link]) {
_cache.Invalidate();
token = await _cache.GetOrRefreshAsync(ct);
[Link] = new("Bearer", token);
resp = await [Link](req, ct);
}
return resp;
}
}

Ch 52 — OpenTelemetry & Observability

Q196 What are the three pillars of observability in OpenTelemetry?

Answer

Traces -- distributed request flows across services, composed of spans linked by a trace ID. Metrics -- numeric
measurements over time: request rate, latency percentiles, error rate. Logs -- structured event records correlated
to traces via the trace ID.
Example

.NET Senior Interview Q&A Page 56


[Link]()
.WithTracing(t =>
[Link]().AddOtlpExporter())
.WithMetrics(m =>
[Link]().AddOtlpExporter())
.WithLogging(l => [Link]());

Q197 What is the OpenTelemetry Collector?

Answer

The Collector is a vendor-neutral agent that sits between your apps and your observability backends. Apps send
telemetry in standard OTLP format. The Collector filters, enriches, and fans out to Jaeger, Prometheus, Datadog,
etc. -- without your app code needing to know which backends you use.
Example

// App sends to Collector:


[Link] = new Uri("[Link]

// [Link]:
// receivers: [otlp]
// exporters: [jaeger, prometheus]
// pipelines: traces->[jaeger], metrics->[prometheus]

Q198 What is a span and what is a trace?

Answer

A trace is the complete end-to-end journey of one request through all services, identified by a trace ID. A span is
one unit of work within that trace -- an HTTP call, a DB query. Spans have a start time, duration, parent span ID,
and arbitrary attributes, forming a tree.
Example

using var activity = _tracer.StartActivity("ProcessOrder");


activity?.SetTag("[Link]", orderId);
// Any async calls inside create child spans automatically

Q199 How does OTel propagate context between microservices?

Answer

[Link] Core's OTel instrumentation automatically propagates W3C TraceContext headers (traceparent,
tracestate) on outgoing HttpClient calls and reads them from incoming requests. Baggage carries custom
key-value data along the same chain.
Example
[Link]("tenantId", tenantId);
// Included in all downstream HttpClient calls automatically
var tid = [Link]("tenantId");

Q200 What are OTel sampling strategies?

Answer

AlwaysOn -- record everything. Only feasible for low-traffic. TraceIdRatioBased -- record a fixed percentage (e.g.,
10%). ParentBased -- inherit parent's sampling decision, keeping complete traces. Tail sampling -- decide after the
fact, keeping all error traces. Requires the Collector.
Example
[Link]()
.WithTracing(t => t
.SetSampler(new TraceIdRatioBasedSampler(0.1))

.NET Senior Interview Q&A Page 57


.AddAspNetCoreInstrumentation());

Q201 How do you create custom metrics in .NET?

Answer

Use [Link] to create counters, histograms, and gauges. OTel picks them up via
AddMeter when you configure the metrics pipeline.
Example

public class OrderMetrics {


private readonly Counter<int> _created;
private readonly Histogram<double> _total;
public OrderMetrics(IMeterFactory factory) {
var m = [Link]("Orders");
_created = [Link]<int>("[Link]");
_total = [Link]<double>("[Link]", "USD");
}
public void RecordOrder(decimal total) {
_created.Add(1);
_total.Record((double)total);
}
}

Q202 What are trace links in OTel and when are they useful?

Answer

Trace links associate a span with spans from other traces. Classic use: a batch job links to each individual
request's trace it is processing, showing causal relationships without merging them into one giant trace.
Example

var links = incomingMessages


.Select(m => new ActivityLink([Link]))
.ToList();
using var activity = [Link](
"ProcessBatch", [Link],
parentContext: default, links: links);

Q203 How do you handle high-cardinality data in metrics?

Answer

High cardinality (userId, request path with IDs as tags) creates a new time series per unique value -- can crash
your metrics backend. Use Views to allowlist only low-cardinality tags you actually need.
Example

[Link]()
.WithMetrics(m => m
.AddView("[Link]",
new MetricStreamConfiguration {
TagKeys = new[] { "region", "status" }
// userId and [Link] dropped
}));

Ch 54 — Build, Deploy & Distributed Systems

Q204 What is the difference between framework-dependent and self-contained deployment?

Answer

.NET Senior Interview Q&A Page 58


Framework-dependent: the .NET runtime must be installed on the target machine. Small output, easy to patch the
runtime separately. Self-contained: runtime is bundled with the app. Larger but runs anywhere with no .NET
pre-installed.
Example

dotnet publish -c Release # framework-dependent


dotnet publish -c Release -r linux-x64 --self-contained # self-contained

Q205 How do you publish for a specific platform?

Answer

Use -r with a Runtime Identifier (RID). Common: win-x64, linux-x64, linux-arm64, osx-arm64.
Example

dotnet publish -c Release -r linux-x64 --self-contained -o ./publish

# Single-file:
dotnet publish -c Release -r linux-x64 -p:PublishSingleFile=true --self-contained

Q206 How do you manage environment-specific config at deployment?

Answer

[Link] Core loads [Link] then overlays appsettings.{ASPNETCORE_ENVIRONMENT}.json. Set the


environment variable at deploy time. Sensitive values should come from environment variables or a secrets
manager -- never appsettings files committed to source control.
Example

[Link] # base
[Link] # prod overrides
[Link] # staging overrides
# Set at runtime:
# ASPNETCORE_ENVIRONMENT=Production

Q207 How do you write a Dockerfile for an [Link] Core app?

Answer

Use a multi-stage build: compile in the SDK image, copy only the published output into the lean runtime image.
Final image has no SDK, no source, no build tools.
Example
FROM [Link]/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

FROM [Link]/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "[Link]"]

Q208 What is a multi-stage Docker build and why use it?

Answer

Multiple FROM instructions. Compile in a large SDK image, copy only the output into a lean runtime image. Final
image has no compiler, no source, no NuGet cache -- typically 3-5x smaller and much smaller attack surface.
Example

# Stage 1 -- build
FROM [Link]/dotnet/sdk:9.0 AS build

.NET Senior Interview Q&A Page 59


RUN dotnet publish -c Release -o /app

# Stage 2 -- runtime only


FROM [Link]/dotnet/aspnet:9.0
COPY --from=build /app /app
ENTRYPOINT ["dotnet", "/app/[Link]"]

Q209 How do you minimise Docker image size for .NET apps?

Answer

Multi-stage build + runtime base image. Enable PublishTrimmed to remove unused assemblies. PublishSingleFile
packs everything into one binary. Native AOT produces the smallest result of all -- a standalone native binary.
Example

dotnet publish -c Release -r linux-x64 \


-p:PublishTrimmed=true \
-p:PublishSingleFile=true \
--self-contained

Q210 How do you pass config and secrets to a Docker container?

Answer

Use -e flags on docker run, the environment section in Docker Compose, or ENV in the Dockerfile for non-secret
defaults. For secrets use Docker Secrets or mount from a secrets manager -- never bake them into the image.
Example

docker run \
-e ASPNETCORE_ENVIRONMENT=Production \
-e ConnectionStrings__DefaultConnection="Host=db;" \
myapp

Q211 How do you write a Docker Compose file for [Link] Core + PostgreSQL + Redis?

Answer

Define three services. Use depends_on so the app doesn't start before the database. Pass connection strings as
environment variables.
Example
services:
app:
image: myapp
ports: ["8080:8080"]
environment:
- ConnectionStrings__Db=Host=postgres;Database=app;Username=app;Password=secret
- ConnectionStrings__Redis=redis:6379
depends_on: [postgres, redis]
postgres:
image: postgres:16
environment: { POSTGRES_PASSWORD: secret }
redis:
image: redis:7

Q212 How do you use health checks in Docker Compose?

Answer

Add a healthcheck block to the service. Docker polls it and marks the container healthy or unhealthy. Use
condition: service_healthy in depends_on to make the app wait until the DB is actually ready.
Example

.NET Senior Interview Q&A Page 60


postgres:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 5
app:
depends_on:
postgres:
condition: service_healthy

Q213 What is .NET Aspire?

Answer

.NET Aspire is an opinionated stack for cloud-native apps. Instead of writing Docker Compose or Helm charts, you
declare your services, databases, caches, and message buses in a C# AppHost project. Aspire starts all
containers locally, provides a built-in dashboard with distributed traces and metrics, and handles service discovery
wiring.
Example
var builder = [Link](args);
var db = [Link]("db");
var cache = [Link]("cache");
[Link]<[Link]>("orders-api")
.WithReference(db)
.WithReference(cache);
[Link]().Run();

Q214 How do you deploy an Aspire application to Docker Compose?

Answer

Run the Aspire publish command to generate a [Link] from the AppHost definition, then deploy
normally.
Example

dotnet aspire publish --format docker-compose


docker compose -f [Link] up -d

Q215 How do you configure OpenTelemetry in an Aspire application?

Answer

Call [Link]() in each service project. Aspire injects OTEL_EXPORTER_OTLP_ENDPOINT


automatically. ServiceDefaults wires up traces, metrics, and logs pointing at Aspire's built-in dashboard.
Example

// In each service's [Link]:


[Link]();
// registers OTel, health checks, service discovery
// Aspire sets OTEL_EXPORTER_OTLP_ENDPOINT automatically

.NET Senior Interview Q&A Page 61

You might also like