DEVBOOK

Home

Questions

Questions

Edit this pageReport / suggest

Total questions: 1096

Programming131
AllNET131
Computer Science301
AllAlgorithms186Data Structures109
Data Persistence44
AllNoSQL13ORMs2SQL22
Networks36
AllArchitecture & Ops8Protocols16Transport & Sockets8
Architecture142
AllApplication Architecture9Distributed Systems33Patterns86System Architecture13
Software Architecture144
AllApplication Architecture11Distributed Systems34Patterns85System Architecture13
Development Practices31
AllParadigms18Principles12
Software Design34
AllParadigms12Principles12Testing9
AI & ML172
AllLLM133Machine Learning19Tooling16
Security34
AllAuthentication12
Cloud3
All
DevOps19
AllDeployment Strategies3Version Control Systems2
SDLC5
All

Programming

NET

When should you still target netstandard?

  • Target netstandard2.0 when you must support .NET Framework or older ecosystem consumers that cannot load modern netX assemblies.
  • Prefer adding it as part of multi-targeting (net8.0;netstandard2.0) instead of making it your only target for new libraries.
  • If all known consumers are modern .NET, target modern netX directly.
NET Standart

What are the three layers of the .NET platform, and why does that distinction matter?

Runtime (CLR), language (C#/F#), and framework libraries (ASP.NET Core, EF Core, extensions). It matters because most production issues cross layers: a performance problem might involve language-level allocations (C#), runtime GC behavior (CLR), and framework middleware configuration (ASP.NET Core). Understanding the boundaries helps you diagnose root causes instead of applying surface-level fixes.

NET

ASP.NET Web API

Where should authentication and authorization live in an ASP.NET Core API?

Put authentication and authorization in the pipeline so endpoints can assume an authenticated principal. Use middleware for auth and use endpoint metadata and policies to decide access per endpoint.

ASP.NET Web API

Why can't you revoke a JWT before its expiry?

JWTs are self-contained and the server stores no record of issued tokens. Revocation requires a server-side blacklist (typically Redis) or very short expiry (15–60 min) combined with long-lived refresh tokens stored server-side and revocable on logout.

Authentication

When would you choose cookie authentication over JWT Bearer in an ASP.NET Core API?

For browser-based web applications (Razor Pages/MVC) where the browser manages cookie transmission automatically, where session revocation (logout everywhere) is a hard requirement, and where CSRF risk is manageable via ASP.NET Core’s built-in anti-forgery tokens.

Authentication

What does ValidateIssuerSigningKey = true actually enforce?

It forces the JWT middleware to verify the token’s signature against your known signing key. Without it, a token signed by a different (attacker-controlled) key could be accepted — breaking the entire authentication model.

Authentication

When should you return 403 Forbidden vs 404 Not Found for an unauthorized resource access?

Return 403 when the resource exists but the user lacks permission, and the resource’s existence is not sensitive. Return 404 consistently when leaking the resource’s existence is a security risk (e.g., private financial or health records).

Authorization

What is the difference between context.Succeed() and context.Fail() in an authorization handler?

context.Succeed(requirement) marks that requirement as satisfied. context.Fail() explicitly forces authorization failure regardless of other handlers’ decisions — it cannot be overridden by a subsequent Succeed. Use Fail only when you have a definitive security reason to block access.

Authorization

How do you implement OR logic across two authorization policies on a single endpoint?

Multiple [Authorize] attributes stack with AND semantics — all policies must pass. For OR logic, implement a single custom IAuthorizationRequirement that internally checks whether any of the conditions is met, then apply that single requirement via one policy.

Authorization

Why must app.UseCors() come before authentication middleware?

Preflight OPTIONS requests do not carry authentication credentials. If authentication middleware runs first, it rejects the preflight with 401 before CORS headers are added to the response. The browser then sees a failed preflight and blocks the actual request. Placing UseCors() before UseAuthentication() ensures preflight responses include the correct CORS headers and return 200, allowing the browser to proceed with the actual authenticated request.

CORS

What is a captive dependency and why is it dangerous?

A captive dependency occurs when a long-lived service (Singleton) captures a shorter-lived service (Scoped or Transient) at construction time. The shorter-lived service then lives as long as the Singleton, violating its intended lifetime. For DbContext, this causes a single database context to be shared across all requests, leading to data corruption and race conditions.

Dependency Injection

How do you use a Scoped service inside a Singleton without a captive dependency?

Inject IServiceScopeFactory into the Singleton and call scopeFactory.CreateScope() at the point of use. Resolve the Scoped service from the new scope and dispose the scope when done. This ensures the Scoped service lives within a controlled scope, not captured inside the Singleton.

Dependency Injection

What is the difference between GetService<T> and GetRequiredService<T>?

GetService<T> returns null if the service is not registered; GetRequiredService<T> throws InvalidOperationException. Use GetRequiredService<T> in production code where a missing registration is a programming error that should fail loudly at startup rather than silently return null at call time.

Dependency Injection

Explain when to choose middleware over an MVC action filter.

Expected answer:

  • Middleware when concern is app-wide and independent of controller/action internals.
  • Action filter when concern needs ActionExecutingContext, action arguments, or action result wrapping.
  • Middleware runs earlier in pipeline and can affect all endpoints.
  • Filters are more granular for controller/action scope. Why this matters: this is a common architecture tradeoff in API design interviews.
Filters

What is the execution order of ASP.NET Core filter types?

Expected answer:

  • Authorization -> Resource -> Action -> Exception -> Result (with before/after phases where applicable).
  • Scope affects order: global, then controller, then action.
  • IOrderedFilter can override default order. Why this matters: ordering mistakes cause hidden bugs in validation, caching, and error handling.
Filters

How do you inject services into a filter safely?

Expected answer:

  • Register filter/service in DI container.
  • Use ServiceFilter/TypeFilter or options.Filters.AddService<TFilter>().
  • Prefer constructor injection and async interfaces.
  • Avoid RequestServices.GetService inside filter bodies unless absolutely necessary. Why this matters: DI misuse in filters causes brittle code and testing pain.
Filters

Action filter vs middleware: what is the difference?

Middleware is pipeline-level and can apply to all requests (before routing/MVC, around endpoint execution). Action filters are MVC-level and run only for MVC actions, with access to action context, model binding, and results; they are a better fit for cross-cutting concerns that are specific to controller actions.

Middlewares

How can you log execution time for all requests?

Use a middleware that measures elapsed time around next() and logs it. An action filter works too, but only for MVC actions.

Middlewares

How can you centrally catch errors for all requests?

Add a global exception-handling middleware. In ASP.NET Core this is commonly done with app.UseExceptionHandler(...) (and app.UseDeveloperExceptionPage() in development). The handler can log the exception and return a consistent error response (for example, RFC 7807 Problem Details).

Middlewares

What is the ASP.NET request processing pipeline?

A request is received by the host (for example, Kestrel) and then flows through an ordered chain of middleware. Middleware can add features (routing, authN/authZ, CORS, compression, etc.), select an endpoint, and finally execute the endpoint (MVC action, Minimal API handler, etc.). On the way back out, the middleware chain unwinds, allowing post-processing of the response.

Middlewares

CSharp

What makes C# a multi-paradigm language, and why does that matter for API design?

C# supports object-oriented (classes, interfaces, inheritance), functional (LINQ, pattern matching, immutable records, expression-bodied members), and procedural styles. This matters because you can choose the paradigm that fits the problem: OOP for domain modeling, functional for data transformation pipelines, procedural for scripts and glue code. In API design, mixing paradigms intentionally (e.g., immutable records for DTOs, interfaces for extensibility, LINQ for queries) produces cleaner, more testable code than forcing everything into one style.

CSharp

How does C#'s type system help prevent bugs at compile time?

  • Nullable reference types flag potential null dereferences before runtime.
  • Generic constraints enforce type relationships without casting.
  • Pattern matching with exhaustiveness checks catch missing cases in switch expressions.
  • Strong typing prevents accidental type confusion that would be runtime errors in dynamic languages. The practical impact: many bugs that surface as runtime exceptions in Python or JavaScript are caught during compilation in C#.
CSharp
Concurrency and Parallelism

How is asynchrony different from multithreading?

Asynchrony is about not blocking while waiting (especially for I/O). An async method can release the current thread while awaiting, and continue later — potentially on the same thread. Multithreading is about executing work on multiple threads concurrently (for CPU-bound work). Async code can be single-threaded and still be asynchronous. The key cost difference: async I/O uses no thread while waiting; multithreading always occupies a thread.

Async Await

What is the difference between await and Task.Result?

await waits asynchronously: it does not block the current thread, it unwraps exceptions as Exception (not AggregateException), and it respects SynchronizationContext. Task.Result waits synchronously: it blocks the current thread, wraps exceptions in AggregateException, and can deadlock under a SynchronizationContext. Cost: .Result in a context-aware environment is a deadlock waiting to happen; await is the safe default.

Async Await

When should you use ConfigureAwait(false)?

In library code that does not need to resume on a specific context. It avoids context-capture overhead and eliminates the deadlock risk when library code is called from a context-aware environment. Do not use it in application-layer code (controllers, view models) where you need to resume on the original context to update UI or access HttpContext.

Async Await

If async does not always use extra threads, why does it improve scalability?

Because waiting time is no longer paid by tying up worker threads. Released threads can process other requests while I/O is pending. A server with 100 threads can handle thousands of concurrent I/O-bound requests if each thread is released during the wait.

Async Await

When should you use Task.Run with async code?

For CPU-bound work that you intentionally offload to a pool thread (e.g., image processing, heavy computation). Do not use it to wrap already-async I/O APIs — that wastes a thread for no benefit.

Async Await

When is it reasonable not to cancel immediately after the token is signaled?

When a tiny critical section must complete to keep state consistent — for example, finishing a single idempotent write or releasing resource ownership. The key is that the section is short and bounded. Never hold a lock or do I/O while ignoring a cancellation signal for an extended period.

CancellationToken

Why is cooperative cancellation safer than Thread.Abort?

Cooperative cancellation stops at known safe points under your control. Thread.Abort (removed in .NET 5+) could interrupt arbitrary code paths — including finally blocks and lock releases — leaving resources or invariants in bad state. Cooperative cancellation keeps control flow explicit and cleanup reliable.

CancellationToken

How do you propagate cancellation across a service boundary (e.g., HTTP call)?

Pass the token to HttpClient.GetAsync(url, cancellationToken) or equivalent. The HTTP client registers a cancellation callback that aborts the underlying socket. For gRPC, pass the token to the call options. For message queues, check the token between message processing iterations. Cost: if the downstream service has already started processing, canceling the HTTP call does not cancel the server-side work — only the client-side wait.

CancellationToken

What does a bounded Channel<T> give you that SemaphoreSlim does not?

FIFO ordering and a buffer. SemaphoreSlim throttles concurrent entrants with no fairness guarantee; a channel queues the work, hands it out in arrival order, and pushes back on producers when full.

Channels

Why is Channel.CreateUnbounded<T>() a risky default?

It removes backpressure. Writes always succeed, so a consumer that falls behind causes unbounded memory growth. A capacity forces you to decide what happens under overload.

Channels

When is BoundedChannelFullMode.DropOldest acceptable?

When newer data supersedes older and losing items is cheaper than stalling the producer: live metrics, a progress feed, sensor samples. Never for events with business meaning, and always with a counter on the drops.

Channels

What is the difference between concurrency and parallelism in practice?

Concurrency is structure — a program composed of independently executing tasks that can be dealt with in overlapping time periods. Parallelism is execution — those tasks actually running in the same instant, which takes multiple cores. A single-core machine is perfectly capable of concurrency (it interleaves) and incapable of parallelism. In practice the structural choice buys responsiveness and non-blocking progress for I/O-bound work, and separately enables the throughput gain that parallelism delivers for CPU-bound work. Concurrent design permits parallelism; it does not require it.

Concurrency and Parallelism

Why do many production outages in .NET systems look like "performance" but are actually concurrency bugs?

Because thread starvation, deadlocks, lock contention, and unbounded fan-out all manifest as latency spikes and timeouts before obvious crashes.

Concurrency and Parallelism

What is the first decision before choosing a primitive ( Task, lock, Parallel, Channel)?

Classify the workload as I/O-bound vs CPU-bound and define cancellation/error boundaries. Primitive choice follows that classification.

Concurrency and Parallelism

Why is unbounded Task.WhenAll often a bad first choice when calling around 300 external APIs in one request?

Because it can overload downstream dependencies, saturate connection pools, and create timeout storms. Bounded fan-out keeps latency gains while preserving system stability.

Concurrency and Parallelism

For one requirement ("update shared state safely"), when do you choose lock vs SemaphoreSlim vs Channel<T>?

Use lock for short synchronous sections, SemaphoreSlim for async flows that need awaiting, and Channel<T> when you also need queueing, ordering, or backpressure.

Concurrency and Parallelism

What are the four Coffman conditions and which is easiest to break in practice?

Mutual exclusion, hold-and-wait, no preemption, circular wait. Circular wait is easiest to break: enforce a global lock acquisition order across all code paths. This requires discipline but no runtime overhead. The cost of consistent ordering: you must document and enforce the order, which adds coordination overhead in large codebases.

Deadlocks

Why does calling .Result on a Task deadlock in a UI app but not in a console app?

UI apps have a SynchronizationContext that marshals continuations back to the UI thread. .Result blocks that thread; the continuation needs it to resume — circular wait. Console apps and ASP.NET Core have no SynchronizationContext, so continuations resume on any pool thread and .Result merely blocks the calling thread without creating a classic cycle. But ASP.NET Core is not safe from sync-over-async — see the ThreadPool-starvation note below. The absence of a SynchronizationContext removes the single-thread cycle, not the risk of hanging the whole app.

Deadlocks

How do you diagnose a deadlock in a production .NET service?

Capture a process dump with dotnet-dump collect or procdump. Analyze with dotnet-dump analyze and clrthreads/syncblk commands to find threads blocked on monitors. For async deadlocks, look for threads blocked in .Result or .Wait() while holding a SynchronizationContext. Cost: dump capture briefly pauses the process; plan for a maintenance window or use a non-blocking snapshot tool.

Deadlocks

What does lock (obj) { ... } compile to?

Monitor.Enter(obj, ref lockTaken) followed by try { ... } finally { if (lockTaken) Monitor.Exit(obj); }. The lockTaken flag ensures Exit runs only if the lock was actually acquired, even if Enter throws.

Locking

Why can't a lock block contain await?

Monitor has thread affinity: the thread that releases must be the one that acquired. A continuation after await can resume on a different thread, which would exit a lock it doesn’t own. The compiler rejects it (CS1996); use SemaphoreSlim.WaitAsync for async mutual exclusion.

Locking

Why prefer System.Threading.Lock over locking on a plain object in .NET 9+?

It’s purpose-built: you can’t accidentally lock on a string, a boxed value, or this; EnterScope makes intent explicit; and it’s slightly faster. The lock statement recognizes a Lock-typed operand and calls the new API for you.

Locking

Is lock/Monitor reentrant, and is SemaphoreSlim?

lock/Monitor is reentrant — the owning thread can re-acquire and must exit the same number of times. SemaphoreSlim is not; a recursive acquire of a 1-permit semaphore self-deadlocks.

Locking

When is a named Mutex the right tool in .NET?

When you need to coordinate access across multiple processes on the same machine (for example single-writer protection for shared file/database artifacts).

Mutex

Why is Mutex often a poor default for web request hot paths?

It is OS-backed and blocking, so heavy contention can increase latency. In-process patterns (lock, SemaphoreSlim, or a Channel<T>) are usually more efficient.

Mutex

What does AbandonedMutexException signal?

A previous owner exited without releasing the mutex, which means exclusive ownership was recovered but shared state may be inconsistent and must be validated.

Mutex

Why can adding more parallel workers reduce performance?

Because of contention, synchronization overhead, cache misses, and context switching once you exceed useful core-level parallelism.

Parallelism

How do you decide MaxDegreeOfParallelism?

Start near Environment.ProcessorCount, then tune with workload benchmarks and production telemetry rather than assumptions.

Parallelism

When should you avoid PLINQ?

When correctness depends on strict ordering, side-effect sequencing, or when query-level debugging clarity matters more than terse syntax.

Parallelism

Why can a parallel query be slower than sequential for small inputs?

Partitioning, scheduling, and result merge overhead can dominate when per-element CPU work is too small.

Parallelism

When should you choose SemaphoreSlim over lock?

Choose SemaphoreSlim when critical sections include await and you need asynchronous waiting. lock cannot contain await safely.

Semaphore

Why is a semaphore useful for fan-out HTTP calls?

It limits in-flight requests, protecting downstream dependencies and your own resources from overload while preserving concurrency.

Semaphore

What bug pattern most often breaks semaphore-based code?

Missing or unbalanced Release calls. The safe pattern is acquire then try/finally release in the same method scope.

Semaphore

Why is Task not equivalent to a thread?

Task models completion and scheduling, while threads are execution resources. Many async tasks complete I/O without holding a thread during waiting. A single thread can drive thousands of concurrent I/O tasks by processing their continuations sequentially. Cost of confusing them: over-allocating threads (via Task.Run for I/O) wastes memory and increases context-switching overhead.

Tasks

When should Task.Run be used in ASP.NET Core?

Rarely for request I/O — async I/O APIs already don’t block threads. Use Task.Run for CPU-bound work that must be isolated from the request thread (e.g., image processing, heavy computation), ideally with bounded concurrency via SemaphoreSlim.

Tasks

Why is Task.WhenAll usually better than sequential await for independent calls?

Sequential await runs operations one after another — total latency is the sum. Task.WhenAll runs them concurrently — total latency is the slowest. For three 100ms calls: sequential = 300ms, concurrent = ~100ms. Cost: concurrent fan-out can overwhelm downstream services; bound concurrency when calling external APIs.

Tasks

When should you use ValueTask instead of Task?

Only when profiling shows allocation pressure from Task on a hot path where the result is frequently synchronously available (e.g., a cache hit). ValueTask has strict consumption rules and is harder to use correctly. Default to Task; switch to ValueTask only with measurement evidence.

Tasks

What is ThreadPool starvation and how does it usually start?

Starvation means queued work cannot get workers quickly enough. It starts when threads block synchronously (.Result, .Wait(), Thread.Sleep) instead of releasing back to the pool. The hill-climbing algorithm injects new threads slowly (~1 per 500ms), so starvation can persist for seconds under burst load. Cost of fixing: increasing SetMinThreads reduces ramp-up latency but increases baseline memory usage.

ThreadPool

Why can a fully async app still suffer ThreadPool issues?

Because only part of the call graph may be async. Any synchronous segment on a pool thread — a blocking filter, a sync-over-async call in a library, a Thread.Sleep — holds that thread and reduces pool capacity. Even one blocking call per request can starve the pool under high concurrency.

ThreadPool

When is it appropriate to call ThreadPool.SetMinThreads?

When you have measured sustained queue depth under burst load and confirmed the bottleneck is thread injection latency (not CPU saturation or I/O). Increase min threads to match your expected burst concurrency. Do not set it higher than needed — each thread costs ~1MB of stack and adds context-switching overhead.

ThreadPool

What is the difference between Task.Run and ThreadPool.QueueUserWorkItem?

Task.Run returns a Task that supports await, cancellation, and exception propagation. QueueUserWorkItem is a lower-level fire-and-forget API with no built-in result or error handling. Prefer Task.Run in all modern code; QueueUserWorkItem is a legacy API.

ThreadPool
Fundamentals

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

throw; preserves the original stack trace, while throw ex; resets it to the current method. In practice, this means throw; keeps the real failure path for debugging and observability.

Exception Handling

When should you wrap an exception instead of rethrowing it directly?

Wrap when you need to add domain context or translate infrastructure exceptions at a boundary (for example, repository to application service). Keep the original error in InnerException so root cause details remain available.

Exception Handling

Why is throwing from finally considered dangerous?

A throw in finally can replace the original exception and hide root cause information. The safer pattern is cleanup-only logic in finally, with error handling done in catch or at higher boundaries.

Exception Handling

When might finally not execute?

When the process terminates abruptly and normal stack unwinding does not happen (for example, crash/kill, Environment.FailFast(), or StackOverflowException).

Exception Handling

What types can you use in foreach?

Any type that implements IEnumerable / IEnumerable<T>, or any type that provides the enumerator pattern (GetEnumerator() + Current + MoveNext()).

Foreach

How is foreach implemented under the hood?

The compiler lowers it to enumerator code: call GetEnumerator(), loop MoveNext(), read Current.

Foreach

What is yield and how does it work?

It creates an iterator: each yield return produces a value and pauses the method; the method resumes on the next iteration request.

Foreach

Why and when should you use yield return instead of returning a materialized collection like List<T>?

Use yield return for deferred execution and streaming when consumers may stop early or the sequence is large, because it lowers peak memory usage. Materialize (ToList() / ToArray()) when you need a snapshot, random access or Count, or repeated enumeration without rerunning expensive or side-effectful generation logic.

Foreach

Why does IEnumerable<string> assign to IEnumerable<object>, but List<string> does not assign to List<object>?

IEnumerable<out T> is covariant, so it is safe to upcast because it only produces T values. List<T> is invariant because it both reads and writes T; if List<string> were assignable to List<object>, code could add a non-string object and break type safety. In practice, expose covariant interfaces (IEnumerable<T>, IReadOnlyList<T>) at API boundaries and keep mutable concrete collections internal.

Generics

When should you mark a generic interface type parameter as out or in?

Use out when the type parameter is output-only (returned values), and in when it is input-only (method arguments). If a parameter must be both consumed and produced, keep it invariant because variance would allow unsafe assignments. This design choice improves API flexibility without sacrificing compile-time safety.

Generics

A generic method uses default(T) as a fallback value. Why can this be dangerous in production code?

default(T) can silently map to meaningful domain values (0, DateTime.MinValue, null), so failures look like valid data instead of explicit errors. Repeated fallback usage can spread bad state across caches, persistence, or downstream services before detection. Prefer explicit failure paths (TryXxx, exceptions, discriminated result types) and validate invariants at boundaries.

Generics

Why might you need ref for reference types if reference types are already passed by reference?

Reference type values (the reference) are passed by value. ref is needed when you want the callee to replace the caller’s reference (rebind it to a different object).

Methods

What is an in parameter used for?

To pass an argument by readonly reference: avoid copies for large structs and communicate that the method should not modify the argument.

Methods

What are optional parameters in methods?

Parameters with default values that callers can omit; the default is substituted at compile time at the call site.

Methods

In a hierarchy where Animal a = new Dog();, how can you make a.Category() return the derived value, and what does that imply for API design?

Mark the base member as virtual and the derived member as override. That enables polymorphic dispatch by runtime type. It also means the base API explicitly supports extensibility and behavioral substitution.

Methods

When should you prefer new over override?

Prefer new only when polymorphism is not desired and you intentionally want different behavior based on the compile-time reference type (for compatibility or specialized APIs). In most extensible designs, override is the safer default.

Methods

A base method is not marked virtual, but you need derived-specific behavior. What are your options?

Option 1: Change the base API to virtual (best if you own the base type and want polymorphism). Option 2: Hide with new (works, but no polymorphism and can confuse callers). Option 3: Redesign with composition/strategy if inheritance is not a good fit.

Methods

When should you prefer file-scoped namespaces over block-scoped namespaces?

Prefer file-scoped namespaces when a file contains one namespace and regular type declarations, because it reduces indentation and visual noise. Use block-scoped form when a file needs multiple namespace blocks or unusual nesting patterns.

Namespaces

Why is reflection often a bad default in performance-critical code?

Reflection shifts work from compile time to runtime: member discovery, argument handling, and dynamic dispatch all add overhead compared to direct calls. The cost is usually acceptable for startup/configuration paths, but on hot paths it compounds quickly. Practical rule: cache metadata and use compiled delegates when repeated invocation is required.

Reflection

What is an attribute and why is reflection central to attribute-driven frameworks?

An attribute is metadata attached to code elements (types, methods, properties, parameters). Frameworks inspect these attributes at runtime (or generation time) to apply conventions like routing, validation, serialization, and test discovery. Without reflection (or generated equivalents), this metadata would remain passive and unused.

Reflection

When should you choose reflection versus alternatives like interfaces, generics, or source generators?

Choose reflection when the shape of types/members is unknown until runtime (plugin ecosystems, late-bound tooling, extensibility points). Prefer interfaces/generics when contracts are known at compile time because they provide stronger safety and better performance. Prefer source generators in reflection-heavy infrastructure when you need predictable startup, high throughput, or trim/AOT compatibility.

Reflection
Types

What is the difference between abstract class and interface with default interface methods (C# 8+)? When would you still choose an abstract class?

Both can define contracts with shared implementation. Key differences:

  • State: abstract classes can have instance fields and constructors; interfaces cannot hold instance state (only static fields).
  • Inheritance: a class can implement many interfaces but inherit from only one class.
  • Access modifiers: abstract classes support protected/internal members; interface members are implicitly public (C# 8+ allows explicit modifiers but no protected instance state).
  • Performance: virtual dispatch on abstract class methods is a single vtable lookup; default interface methods may involve additional dispatch overhead.
  • Use Case: The default implementation for interface methods have different goal compared to the abstract class implemented methods. While in class, semantically methods providing the basic shared functionality for the delivered classes, that don’t have to be overriden. While default implementation exist for making extending the interfaces without breaking the inheritors.
Classes

Can a static class implement an interface? Why or why not?

No. A static class compiles to an abstract sealed class at IL level — it cannot be instantiated, so there is no object to dispatch interface calls through. Interfaces require an instance for virtual dispatch. If you need a “static implementation” of a contract, the patterns are:

  • Use static abstract/virtual interface members (C# 11+) with generics: where T : IMyInterface.
  • Use a singleton instance of a regular class that implements the interface.
  • Use delegates/Func<T> instead of an interface.
Classes

A sealed override stops further overriding, but can a derived class use new to hide the sealed method? What happens at runtime?

Yes, new compiles and hides the sealed method. But the behavior depends on the variable’s compile-time type:

class Base { public virtual void Do() => Console.WriteLine("Base"); }
class Middle : Base { public sealed override void Do() => Console.WriteLine("Middle"); }
class Bottom : Middle { public new void Do() => Console.WriteLine("Bottom"); }
 
Bottom b = new Bottom();
b.Do();           // "Bottom" — resolved at compile time as Bottom.Do
Middle m = b;
m.Do();           // "Middle" — virtual dispatch resolves to Middle.Do (sealed)
Base x = b;
x.Do();           // "Middle" — same virtual dispatch

The new method is completely invisible to polymorphic code. This is almost always a design smell — if you need to change behavior, the method should not have been sealed, or you should use composition instead.

Classes

Can you have an abstract sealed class? What about in IL?

In C# source code, you cannot write abstract sealed class — the compiler rejects it as contradictory. However, at the IL level, static classes compile to exactly abstract sealed. The CLR treats abstract sealed as “cannot be instantiated and cannot be inherited.” So every static class in C# is literally an abstract sealed class in metadata. You can verify this with ildasm or reflection: typeof(Math).IsAbstract && typeof(Math).IsSealed is true.

Classes

Why can partial be dangerous with source generators? Give a concrete scenario.

Partial classes merge at compile time, and source generators can add members to your type that you do not see in your source file. Dangers:

  • Name collisions: a generator adds a method Validate() and you also write Validate() — compile error with a confusing message pointing at generated code.
  • Implicit behavior changes: a generator adds INotifyPropertyChanged implementation and overrides Equals. Your tests pass locally but break in CI where a different generator version runs.
  • Debugging opacity: stepping through code jumps into generated files that may not be in source control.
  • Partial method removal: if a partial method (without accessibility modifier) has no implementing declaration, the compiler silently removes all calls to it. If you forget to generate the body, the call vanishes with no warning. Best practice: always inspect generated output (visible in IDE under Dependencies and Analyzers), and write tests that verify generator-dependent behavior explicitly.
Classes

Two classes have identical fields and values. You compare them with ==. Why is it false, and what are the three ways to fix it?

== compares references by default for classes — two separate new calls produce different heap objects with different references. Fixes:

  1. Override Equals/GetHashCode and overload ==/!= — full manual control, but tedious and error-prone.
  2. Implement IEquatable<T> — avoids boxing in generic code and gives a clean Equals(T) method. Still need to overload ==.
  3. Use record class instead — the compiler generates value-based Equals, GetHashCode, ==, and != automatically. This is the recommended approach for data-carrier types.
Classes

A static constructor throws an exception. What happens on subsequent accesses to that type?

The runtime marks the type as permanently broken. Every subsequent attempt to access any member of the type throws a TypeInitializationException wrapping the original exception — even if the condition that caused the failure has been resolved. The type cannot be re-initialized for the lifetime of the AppDomain (or AssemblyLoadContext in .NET Core). This is why static constructors should be kept minimal and defensive because failures are unrecoverable.

Classes

What does a delegate compile to in IL/runtime terms?

A delegate declaration becomes a sealed type derived from System.MulticastDelegate with Invoke, BeginInvoke, and EndInvoke metadata. Delegate instances carry a target object (or null for static methods), a method pointer, and optionally an invocation list. In modern .NET (6+), calling delegate BeginInvoke/EndInvoke is not supported and throws PlatformNotSupportedException.

Delegates

How do you isolate failures in a multicast delegate so one bad subscriber does not break the rest?

Iterate GetInvocationList(), cast each entry to the concrete delegate type, invoke in a per-handler try/catch, and optionally aggregate errors. Direct multicast invocation stops at first exception.

Delegates

Why is using Func<Task> in multicast pipelines often wrong for async fan-out?

Direct multicast invocation returns only the last task, so earlier handlers can run unobserved. For async fan-out, iterate GetInvocationList() and await each task explicitly (sequentially or with Task.WhenAll), depending on ordering requirements.

Delegates

How is an event different from a delegate field in terms of access control?

An event exposes only add/remove from outside the declaring type. A delegate field can be invoked, replaced, or nulled by external callers. Events preserve publisher ownership of invocation.

Events

Why do event leaks happen, and how do you prevent them?

The publisher keeps strong references to subscriber handlers. If the publisher outlives subscribers, those subscribers cannot be garbage-collected. Prevent with explicit unsubscribe (Dispose), weak-event pattern, or scoped subscription helpers.

Events

How do you handle exceptions in event subscribers without losing later handlers?

Copy the invocation list using GetInvocationList() and invoke handlers individually in try/catch. Direct event invocation stops at first exception.

Events

In record Wrapper(List<int> Items), if var b = a with { }; and an item is added to b.Items, does a observe the change, and why?

Yes — a sees the change. with performs a shallow copy: it copies references, not the underlying objects. Both a.Items and b.Items point to the same List<int> instance. Furthermore, a == b was true before the mutation (same reference in both), but the equality check still uses List<T>.Equals which is reference equality — so it remains true even after the content changes. To get proper deep value semantics, use immutable collections (ImmutableList<T>, ImmutableArray<T>) or override Equals to compare content.

Records

When would you choose record class over readonly record struct?

Choose record class when:

  • The data contains variable-length reference types (strings, collections) — the struct would not avoid heap allocation anyway.
  • The type participates in an inheritance hierarchy (only record classes support inheritance).
  • The data is large (more than ~16 bytes of value-type fields) — copy cost outweighs GC cost.
  • You need null semantics (e.g. optional return values without Nullable<T>).

Choose readonly record struct when:

  • The data is small and all fields are value types — avoids heap allocation entirely.
  • You are on a hot path where GC pressure matters (e.g. tight loops, high-throughput pipelines).
  • No inheritance is needed.
Records

If Equals on a positional record is overridden to ignore one property, does GetHashCode still include that property, and what breaks?

Yes — the compiler-generated GetHashCode includes all positional properties regardless of your Equals override. This breaks the contract: two objects that are “equal” (by your custom Equals) may have different hash codes, causing them to land in different buckets in Dictionary, HashSet, or any hash-based collection. Lookups silently fail. Rule: whenever you override Equals, always override GetHashCode to match. The compiler emits a warning (CS8851) if you override one without the other, but it is only a warning — not an error.

Records

Can a record struct be used as a Dictionary key safely? What do you need to watch out for?

Yes, record struct works well as a dictionary key because the compiler generates value-based Equals and GetHashCode by default. Watch out for:

  • Mutable record structs — if a key is mutated after insertion, its hash code changes and it becomes unreachable in the dictionary. Use readonly record struct for keys.
  • Reference-type properties — if the record struct contains a reference-type property (e.g. string[]), the generated GetHashCode calls that property’s GetHashCode, which for arrays is reference-based (not content-based). Two structurally identical keys with different array instances will hash differently. Override GetHashCode or use immutable value-semantic collections.
Records

When should you choose StringBuilder over string?

  • Use StringBuilder for iterative construction (loops, batched appends, streaming transforms) where many intermediate strings would otherwise be allocated.
  • Prefer interpolation/concatenation for one-off formatting with a small number of values because readability is usually better.
  • In hot paths, benchmark both options and pre-size StringBuilder capacity to reduce buffer growth and copying.
Strings

Why can ReferenceEquals(a, b) be false even when a == b is true for strings?

  • == for strings compares content, while ReferenceEquals checks object identity.
  • Two strings can contain identical text but be different objects (for example, literal vs runtime-composed value).
  • Use ReferenceEquals only for diagnostics/allocation analysis, not for business equality logic.
Strings

How should string comparisons be written in production code?

  • Always call APIs that accept StringComparison (string.Equals, StartsWith, EndsWith, IndexOf) instead of culture-implicit overloads.
  • Use Ordinal/OrdinalIgnoreCase for identifiers, protocol values, keys, and security-sensitive comparisons.
  • Use culture-aware comparison only for user-facing natural-language text where locale behavior is expected.
  • Keep the comparison policy explicit and consistent across reads/writes to avoid cross-locale bugs.
Strings

For a high-throughput service processing 100k messages per second (Id, Timestamp, Status), would class or struct be the better model, and why?

A readonly struct (or readonly record struct) is the best fit:

  • Total payload is roughly 28 bytes before alignment (Guid 16 + DateTime 8 + enum ~4), so it is above the strict 16-byte heuristic and should be benchmarked in your workload.
  • Value semantics avoid a separate object allocation per message when values stay inline and are not boxed/captured, reducing GC pressure at 100k/s.
  • readonly prevents accidental mutation.
  • If the message grows or needs richer behavior/reference sharing, a class can become the better tradeoff.
Structs

Why does mutating a struct returned from a property or indexer not compile (or silently do nothing)?

  • Property/indexer access usually returns a value copy, not a reference to the original storage.
  • Mutating that copy would be discarded immediately, so the compiler blocks common forms (for example CS1612 scenarios).
  • Even when a pattern compiles, mutations can affect only the temporary copy, not the original struct instance.
  • Mitigate with readonly struct for safer value semantics, or expose ref/ref readonly returns when true by-reference behavior is required.
Structs

Why does ValueType.Equals perform so poorly on structs with reference-type fields? What should you do about it?

  • Default value-type equality is field-by-field and can be expensive in hot paths.
  • Depending on type shape/runtime path, equality can include reflection-like overhead and extra indirection.
  • Using such structs as keys in dictionaries/sets amplifies the cost because equality and hashing are called frequently.
  • Implement IEquatable<T> and override Equals/GetHashCode to define stable semantics and avoid hidden performance costs.
Structs

Why can updating a value-type item inside foreach fail to persist, and what are safe fixes?

  • The loop variable was a copy of a value type, so mutations were applied to the copy.
  • The same issue appears when mutating structs returned by properties, because property access often returns a copy.
  • Fix by making the struct immutable and replacing whole values, or by redesigning to avoid mutable structs in those paths.
  • If mutation is required, use APIs with explicit ref semantics very carefully.
Types

Where does boxing usually sneak in, and what is the practical mitigation in production code?

  • Boxing happens when a value type is converted to object or interface-typed APIs.
  • Each boxing operation allocates and can increase GC pressure in hot paths.
  • Prefer generic APIs (List<T>, EqualityComparer<T>, generic interfaces) so calls stay strongly typed.
  • Verify with profiling before optimizing, then remove high-frequency boxing boundaries.
Types

What criteria should drive choosing between struct, class, and record class?

  • Use struct for tiny immutable value objects when copy semantics are desired and boxing is controlled.
  • Use class for identity-rich entities where reference identity and lifecycle matter.
  • Use record class for data-centric models where value-based equality improves correctness.
  • Validate the choice against mutation rules, size/copy costs, and equality requirements.
Types

Other

What problem did OWIN solve?

  • It standardized the server-application boundary so middleware/app components were portable across hosts.
  • It enabled a composable pipeline model independent of System.Web internals.
  • It reduced host lock-in for teams maintaining ASP.NET-era applications.
OWIN

How should incremental migration vs full rewrite be chosen when modernizing an OWIN app under strict uptime requirements?

  • Prefer incremental migration when downtime risk and release pressure are high; isolate seams (auth, API endpoints, cross-cutting middleware) and move components in slices.
  • Prefer rewrite when current architecture blocks critical goals (performance, security posture, operability) and business can fund a transition window.
  • Decide with hard constraints: SLA tolerance, test coverage quality, team expertise, and ability to run parallel environments safely.
OWIN

When should you use SignalR versus polling or server-sent events?

Use SignalR when you need bidirectional real-time communication (chat, live dashboards, collaborative editing). SignalR abstracts the transport layer (WebSockets with SSE and long polling as fallbacks). Use server-sent events (SSE) when you only need server-to-client push and want a simpler protocol. Use polling when real-time is not actually needed and you want maximum simplicity and cacheability.

Other

Why would a .NET team still need to understand OWIN today?

OWIN matters for teams maintaining or migrating legacy ASP.NET Framework applications. Understanding the OWIN middleware pipeline model helps when migrating to ASP.NET Core’s similar but distinct middleware pipeline, and when troubleshooting Katana-hosted services still running in production.

Other

When is SignalR a good fit?

  • Use SignalR when clients need server-pushed updates with low latency (chat, collaboration, live telemetry).
  • It is most valuable when update frequency is high enough that polling wastes bandwidth or increases staleness.
  • If updates are rare and latency tolerance is high, simpler HTTP polling can be cheaper to operate.
SignalR

What is the first scaling problem you will hit?

  • Cross-node message fan-out: messages sent on one server do not automatically reach clients connected to another node.
  • Plan scale-out early with Azure SignalR Service or a supported backplane, then validate routing under load tests.
  • Also validate sticky-session requirements for your chosen topology and transport strategy.
SignalR

Why are SignalR groups not enough for authorization?

  • Groups control message routing, not permission checks.
  • Membership can change/rejoin over reconnect paths, so relying on groups alone risks privilege drift.
  • Enforce security with authentication and policy/role-based authorization on hub methods.
SignalR

Runtime

What is managed vs unmanaged code? Why does unmanaged interop require careful lifetime management?

Managed code runs under the .NET runtime (CLR) and benefits from runtime services like type safety checks, exception handling, garbage collection, and JIT/AOT compilation. Unmanaged code runs directly as native machine code under the OS (for example, C/C++ binaries). It does not run under the CLR and typically requires explicit resource and lifetime management. P/Invoke calls into native libraries require marshaling data across the managed/unmanaged boundary, which adds overhead and risks memory corruption if signatures are wrong. Use SafeHandle (not raw IntPtr) to wrap unmanaged handles — it ensures deterministic release even if exceptions occur and prevents handle recycling attacks. Interop lets you reuse native libraries and OS APIs, but every boundary crossing costs marshaling overhead and opens the door to bugs (dangling pointers, double-free) the GC cannot prevent.

Common Language Runtime

What is the CLR and IL? How does JIT compilation affect startup vs steady-state performance, and when is NativeAOT a better choice?

The CLR (Common Language Runtime) is the execution engine of .NET. It loads assemblies, verifies and executes IL, compiles IL to native code (JIT or AOT), manages memory (GC), handles exceptions, supports threading and interop, and provides other runtime services. IL (also called CIL or MSIL) is the CPU-independent intermediate instruction set produced by .NET language compilers and stored in assemblies together with metadata. The CLR turns IL into native code for the current platform. JIT compilation adds latency on first method call. Tiered compilation mitigates this: Tier 0 compiles quickly with minimal optimization, then hot methods are recompiled at Tier 1 with full optimization — giving fast startup and high steady-state throughput. NativeAOT eliminates JIT entirely by compiling to native code at publish time — fastest startup, smallest working set, but no runtime code generation (limits reflection, dynamic assembly loading, and some serialization patterns). JIT with tiered compilation is the right default for long-running server workloads; NativeAOT wins for CLI tools, serverless cold-start SLAs, and embedded scenarios where startup and binary size matter more than runtime flexibility.

Common Language Runtime

When would you choose NativeAOT over JIT compilation?

NativeAOT eliminates JIT startup cost and produces a self-contained native binary — ideal for CLI tools, serverless functions with cold-start SLAs, or embedded scenarios. The cost is longer publish time, larger binary, and loss of runtime reflection-heavy features (dynamic code generation, some serialization patterns). For long-running server workloads, JIT with tiered compilation typically wins on peak throughput.

Common Language Runtime

Why does the GC use generations?

Most objects die young (short-lived allocations like request-scoped objects). Generational GC exploits this by collecting Gen 0 (newest, smallest) most frequently and cheaply. Long-lived objects are promoted to Gen 1 and Gen 2, which are collected less often. This reduces the cost of GC for the common case while still reclaiming long-lived garbage.

Common Language Runtime

How does .NET's generational GC work? Why does it use generations, and what are the main tuning tradeoffs?

The GC is the .NET runtime’s automatic memory manager for managed objects. It periodically finds objects that are no longer reachable from GC roots, reclaims their memory, and (on the SOH) typically compacts surviving objects to reduce fragmentation. The GC is generational (Gen 0/1/2): most collections are small and fast, while full collections are less frequent. Generations exploit the generational hypothesis — most objects die young — so collecting Gen 0 frequently and cheaply avoids scanning the entire heap. Workstation GC (default) runs collections on the allocating thread; Server GC uses dedicated threads per logical processor for higher throughput but more memory overhead. Background GC (default for Gen 2) allows Gen 0/1 collections to proceed during a long Gen 2 collection, reducing pause times for latency-sensitive workloads. Server GC maximizes throughput for multi-core services but uses more memory per heap; Workstation GC minimizes footprint for client apps and small containers. Choose based on whether the workload cares more about pause duration or throughput.

Garbage Collector

What are the Small Object Heap (SOH) and the Large Object Heap (LOH)?

The SOH stores most objects (typically smaller than ~85,000 bytes) and is compacted regularly. The LOH stores large allocations (typically 85,000 bytes and above, often large arrays). It is collected with Gen 2 and can become fragmented; compaction behavior differs from the SOH and is more expensive.

Garbage Collector

What is a memory leak? Is it possible in .NET? How?

A memory leak is memory that is no longer needed but cannot be reclaimed, so the process keeps growing over time. Yes, it is possible in .NET:

  • Managed leaks: objects stay reachable (for example via static caches, event subscriptions, long-lived collections), so GC cannot collect them.
  • Unmanaged leaks: native memory/handles are allocated (directly or indirectly) and not released (for example, missing Dispose() / using).
Memory Leaks

What is the call stack? Can it overflow? What happens then?

The call stack is a per-thread LIFO memory region that stores stack frames for active method calls (return address, parameters, locals, etc.). It can overflow (for example, due to deep or infinite recursion or very large stack allocations). In .NET this typically results in StackOverflowException, and the process is terminated (it cannot be reliably handled).

Memory Leaks

Why do we need using {} if there is a GC?

using provides deterministic cleanup for resources that are not just managed memory (file handles, sockets, OS handles, unmanaged buffers). GC runs non-deterministically and does not guarantee timely release of such resources. The using statement compiles to try/finally so Dispose() is called even when exceptions occur.

Memory Leaks

What are IDisposable and Finalize?

IDisposable is an interface for explicit, deterministic cleanup via Dispose(). Finalize (a finalizer, written as ~TypeName() in C#) is called by the GC for objects that have a finalizer, but it is non-deterministic and adds overhead. Finalizers should only be used to release unmanaged resources, and Dispose() typically calls GC.SuppressFinalize(this).

Memory Leaks

What is the disposable (dispose) pattern?

A standard way to implement IDisposable so both explicit cleanup (Dispose()) and (optionally) finalization are supported. Typical shape: Dispose() calls Dispose(true) and then GC.SuppressFinalize(this); a finalizer (if needed) calls Dispose(false); Dispose(bool disposing) releases unmanaged resources and, when disposing is true, also disposes managed fields.

Memory Leaks

What does the CLR do when your application starts, and why does startup behavior matter?

The CLR loads assemblies, verifies IL safety, JIT-compiles methods on first call (or uses tiered compilation to optimize hot paths later), sets up the GC, and initializes the thread pool. This matters because startup latency, JIT warmup effects, and thread pool sizing all affect real-world behavior — especially for serverless and containerized deployments with cold starts.

Runtime

How does garbage collection affect production latency, and what are the main tuning levers?

GC pauses application threads (in workstation GC) or background-collects (in server GC) to reclaim memory. Gen0/Gen1 collections are fast; Gen2 collections are expensive and can cause visible latency spikes. Main tuning levers: choose Server vs Workstation GC mode, minimize large object heap allocations (objects over 85KB), reduce Gen2 promotion rates by controlling object lifetimes, and use GC.TryStartNoGCRegion for latency-critical paths. Always measure with GC event traces (dotnet-counters, PerfView) before tuning — premature GC optimization often makes things worse.

Runtime

Can managed code have memory leaks, and what are the common causes?

Yes. Common causes: event handler subscriptions never unsubscribed, static collections that grow indefinitely, closures capturing references unexpectedly, and finalizer queue stalls blocking reclamation. These are not OS-level leaks but logical leaks — the GC cannot collect objects that are still reachable through a live reference chain, even if the application no longer needs them.

Runtime

Computer Science

Why does Big O drop constant factors and lower-order terms?

Because it describes growth as n → ∞, where the fastest-growing term dominates everything else and the machine-specific constants wash out. n² + 100n + 500 is O(n²): past some size the n² term buries the rest. This makes the notation a hardware-independent way to compare scaling, at the deliberate cost of saying nothing about behaviour at small n, where the dropped constants are exactly what decides the winner.

Big O Notation

What is the difference between average-case and amortised complexity?

Average case is the expected cost of one operation over a distribution of inputs — a hash lookup is O(1) average because random keys spread across buckets. Amortised is the cost of one operation averaged over a sequence of operations on the same structure — a dynamic-array append is O(1) amortised because the occasional O(n) resize is paid for by the many cheap appends around it. One averages over inputs, the other over a run of operations.

Big O Notation

Why include the call stack in space complexity?

Because recursion consumes memory per active frame, and that memory is bounded by the recursion depth. An algorithm can allocate no heap yet still be O(n) in space if it recurses n deep — and it fails by overflowing the stack, not by running slowly. A recursive DFS on a 100k-node chain is the canonical example; converting it to an explicit-stack loop moves the same O(n) to the heap where it won’t overflow.

Big O Notation

When is an algorithm with worse Big O the right choice?

When inputs are small and bounded, so the dropped constants dominate — insertion sort inside a hybrid sort below ~16 elements, or a linear scan over 10 items instead of building a hash set. Big O is an asymptotic statement; below the crossover point a “worse” class with a smaller constant and better cache behaviour is genuinely faster. Narrow candidates by complexity class, then measure on representative data.

Big O Notation

When does algorithmic complexity matter less than constant factors?

When input sizes are small and bounded (e.g., iterating over 10 HTTP headers), constant factors and cache locality dominate. A theoretically better algorithm with higher overhead (setup cost, memory indirection) can be slower than a simpler one on small inputs. This is why .NET’s Array.Sort uses insertion sort for small subarrays inside its introspective sort implementation.

Computer Science

How do you decide between optimizing data structure choice versus algorithm choice?

Start with the data structure. The right structure often eliminates the need for a clever algorithm — a HashSet<T> gives O(1) lookup without binary search, a SortedSet<T> gives ordered iteration without explicit sorting. Optimize the algorithm when the structure is fixed by external constraints (e.g., searching within a sorted array from an external source).

Computer Science

Algorithms

What is an algorithm? How is its efficiency measured?

  • An algorithm is a finite, ordered set of steps that transforms input into the required output.
  • Time complexity describes how runtime grows as input size increases.
  • Space complexity describes extra memory needed as input grows.
  • Big O notation is used to compare growth classes independent of hardware details.
  • Why it matters: complexity awareness helps avoid implementations that fail under production scale.
Algorithms

Why is Big O not enough to choose the fastest algorithm in practice?

Big O describes asymptotic growth and ignores constant factors, cache locality, branch prediction, and real input distributions. Quick Sort is O(n log n) average like Merge Sort but often faster on arrays due to cache-friendly in-place partitioning. Measure on representative data after narrowing candidates by complexity class.

Algorithms

What is the difference between worst-case, average-case, and amortized complexity?

Worst-case guarantees behavior under adversarial or degenerate inputs (important for security and SLAs). Average-case describes typical behavior under random inputs. Amortized complexity describes cost spread over a sequence of operations (e.g., dynamic array resizing is O(1) amortized even though individual resizes are O(n)). Choose based on whether you control the input distribution and whether tail latency matters.

Algorithms

Graph Algorithms

What does admissibility guarantee, and what does consistency add on top?

Admissibility (h(n) never exceeds the true remaining cost) makes A* return an optimal path: when the goal is popped its f equals its actual cost, and every frontier node’s f is a lower bound on any path through it, so nothing cheaper is hidden. Consistency (h(n) ≤ cost(n, n') + h(n')) additionally forces f to be non-decreasing along a path, so a node’s first pop is already optimal — graph-search A* can close it and never reopen it, expanding each node at most once.

A-Star Search

An inadmissible heuristic returns a longer path with no error. What causes that?

Overestimating the remaining cost for a node on the true optimal path inflates that node’s f. A* then pops the goal through a cheaper-looking detour before it expands the node on the real shortest path. The search still terminates and returns a valid path — just not the minimum-cost one — because the inflated f reordered the frontier against the optimum. Weighted A* (f = g + ε·h, ε > 1) does this on purpose for a path bounded within ε of optimal.

A-Star Search

Why is memory the usual failure mode, and what do IDA* and weighted A* trade for it?

A* keeps every generated node in the open frontier and closed set, O(nodes stored), which on a large state space exhausts memory before running out of time. IDA* keeps only the current path (O(L) memory) and re-expands nodes across rising f-cost thresholds, paying repeated work for a small footprint. Weighted A* keeps A*‘s structure but scales h to shrink the frontier, trading a bounded loss of optimality for fewer stored nodes.

A-Star Search

What is the articulation-point rule for a non-root vertex versus the DFS root, and why do they differ?

A non-root u is a cut vertex when it has a tree child v with low[v] >= disc[u]: nothing in v’s subtree reaches above u, so u is that subtree’s only exit. The root has no ancestor, so the same inequality is vacuously true for its first child and would over-report. The root is a cut vertex only when it has two or more tree children, since those subtrees can reach each other only through the root.

Articulation Points and Bridges

Why is the bridge test strict ( low[v] > disc[u]) while the articulation test is not (low[v] >= disc[u])?

low[v] == disc[u] means the deepest a back edge from v’s subtree reaches is exactly u. That back edge bypasses the edge (u, v), so the edge is not a bridge — hence strict >. The same back edge still routes all of the subtree’s traffic through the vertex u, so u remains a cut vertex — hence non-strict >=. The one operator carries the entire difference between cut edges and cut vertices.

Articulation Points and Bridges

How do parallel edges break the usual parent check, and what corrects it?

The common guard if (v == parent) continue; skips every edge back to the parent vertex. With two parallel edges between u and v, it discards the second edge too, so v’s subtree appears to have no route up and (u, v) is reported as a bridge — although the duplicate edge is itself the route keeping them connected. Skipping only the specific parent edge by id leaves the duplicate as a back edge that lowers low[v] and cancels the false bridge.

Articulation Points and Bridges

Why exactly V−1 rounds, and what does a relaxation on the V-th round prove?

Without a negative cycle, every shortest path is simple and spans at most V−1 edges. Round k finalizes every shortest path of at most k edges, so V−1 rounds finalize all of them. A relaxation still possible on the V-th round means a path is shortening past that V−1-edge limit, which only a negative cycle permits, so the extra round is the negative-cycle detector rather than wasted work.

Bellman-Ford

How is the actual negative cycle recovered after detection?

Take a vertex that relaxed on the V-th round; it lies on a negative cycle or is reachable from one. Walking pred back V times cannot exit a cycle once inside it, so the walk ends on a cycle vertex. Following pred from there until it returns to that vertex lists the cycle.

Bellman-Ford

After a reachable negative cycle is detected, what are the correct distances?

Vertices reachable through the cycle have distance −∞, because each lap lowers the total without bound; vertices with no path have +∞; the rest are finite. The V−1-round numbers for the −∞ region are a mid-descent snapshot, so reporting them as finite distances is the common bug.

Bellman-Ford

Why does bidirectional search cut O(b^d) to O(b^(d/2))?

A single BFS reaching depth d expands a frontier that grows as b^d. Splitting the path into two halves lets a forward search and a backward search each stop at depth d/2, so each explores about b^(d/2) vertices and the total is 2·b^(d/2). The reduction lands on the exponent, which is why it compounds with depth: every extra level of separation that would multiply a one-sided search by another factor of b only adds half a level to each frontier. The same halving applies to space, since both sides must be held in memory to detect the meeting.

Bidirectional Search

Why is stopping at the first frontier collision correct on an unweighted graph but wrong on a weighted one?

Unweighted BFS expands level by level, settling distances in nondecreasing order, so the first vertex shared by both visited sets already lies on a shortest path. With weights the frontiers advance by cumulative cost, and the first shared vertex can sit on an expensive path while a cheaper meeting is one relaxation away elsewhere. The weighted version must track the best gF[u] + gB[u] and keep expanding until the two frontiers’ combined radius reaches that best, proving no unexpanded path can beat it.

Bidirectional Search

What must the graph and the goal provide before bidirectional search applies?

The target must be a concrete vertex, because the backward search needs a starting point — a goal given only as a predicate has nothing to expand backward from. The graph must expose predecessors, either as a reverse adjacency list or as an invertible move operator, since the backward frontier walks edges in reverse. Without both, the second frontier cannot reach depth d/2 and the halving disappears.

Bidirectional Search

Why does finding all components need an outer loop over every vertex?

A single DFS or BFS from one source only reaches that source’s component. In a disconnected graph, vertices in other components are never touched by that flood. Scanning every vertex and starting a new flood from each still-unlabelled one is what guarantees full coverage; each such vertex is provably in a component no earlier flood reached, so it starts a new component id.

Connected Components

When is union-find preferable to a traversal for components?

When edges are not all available up front, or when connected(a, b) queries interleave with edge additions. Union-find merges endpoints in O(α(V)) amortised and answers connectivity immediately, with no re-traversal after each new edge. A traversal would have to re-run from scratch after every change. The trade-off is that union-find reports set membership and counts, but listing a component’s members needs a final grouping pass over the roots.

Connected Components

Why do DFS and BFS produce the same components but different from strongly connected components?

Connected components depend only on which vertices are mutually reachable, which is order-independent, so DFS and BFS partition identically. Strongly connected components are defined on directed graphs and require a path each way between every pair; a single undirected-style traversal cannot detect that, which is why the directed case needs Tarjan’s or Kosaraju’s two-way-reachability algorithm.

Connected Components

Why does BFS return a fewest-edge path while DFS does not?

The FIFO queue dequeues nodes in nondecreasing distance from the source, so the first time BFS reaches a node it is along a path with the minimum number of edges. DFS’s LIFO stack commits to one branch, so it can reach a node through a longer branch before a shorter one would surface; the first path it records carries no length guarantee.

DFS BFS

What sets BFS versus DFS auxiliary space, and when does each lose?

BFS’s queue holds a full distance layer, so its space is O(V) and grows with graph width — a wide, shallow graph can put most vertices in the frontier at once. DFS’s frontier is the current root-to-node path, O(h); a deep, narrow graph makes that path long, and recursive DFS then overflows the call stack.

DFS BFS

Why is a visited set required for termination, and when must a node be marked?

Without it, a cycle re-adds a node to the frontier forever and the traversal never ends. A node must be marked at discovery, when it enters the frontier, because between discovery and expansion it can be reached from several neighbours; marking only at expansion lets duplicates accumulate in BFS, and in iterative DFS forces a second visited check at pop time.

DFS BFS

Why does directed cycle detection need three states rather than a visited flag?

A visited flag cannot tell a back edge, into a node still on the current DFS path and therefore closing a cycle, from a cross or forward edge into a node whose subtree already finished. Tracking unvisited, in-progress, and done marks a cycle only when an edge leads into an in-progress node.

DFS BFS

Why does the settle-once rule require non-negative edge weights?

When a node is popped it is treated as final. The proof that this is safe relies on the tail of any alternative path — from the first unsettled node it reaches onward — having non-negative length, so that first node’s tentative distance already bounds the whole path. A negative edge lets that tail subtract cost, so a later path can beat a node’s settled distance and the reported distance is wrong.

Dijkstra

What does the if settled[node] continue guard fix?

A binary heap has no decrease-key, so relaxing a node pushes a new pair and leaves the old larger one behind. The guard skips a node the second time it is popped, after a cheaper pair already settled it. Without the guard, the stale pair re-relaxes that node’s edges from an outdated distance and can lower a frontier neighbour incorrectly.

Dijkstra

Why can an array scan beat a binary heap on a dense graph?

With a heap, each of the E relaxations may cost O(log V), giving O((V + E) log V). On a dense graph E ≈ V², so the heap term dominates at O(V² log V). Scanning all vertices to pick the minimum is O(V) per step and O(V²) overall, which drops the log V factor entirely.

Dijkstra

Why is the k-loop the outermost of the three?

After stage k, dist[i][j] is defined as the shortest i→j path using intermediates only from {0..k}, so k names a stage that must complete over the entire matrix before the next begins. The relaxation reads dist[i][k] and dist[k][j] expecting the previous stage’s values; with i or j outermost those cells belong to an unfinished stage, and the recurrence consumes half-updated data. The output is still finite and still returned, so the error is silent.

Floyd-Warshall

Why is the in-place update on a single matrix correct, and what does that save?

Within stage k neither dist[i][k] nor dist[k][j] can improve, because a shortest path routed through k never uses k as an intermediate of its own two legs. Reading and writing the one matrix therefore returns the same values a separate previous copy would have held. Keeping a distinct matrix per stage would cost Θ(V³) space; the observation drops it to Θ(V²).

Floyd-Warshall

How does Floyd-Warshall surface a negative cycle, and how does that differ from Bellman-Ford?

After the sweep, any dist[i][i] < 0 means a negative-weight path leaves i and returns to it — a negative cycle through i — reported for all vertices at once with no extra pass. Bellman-Ford instead runs one additional relaxation from a chosen source and can walk predecessors to extract the concrete cycle, which is what arbitrage-style problems need.

Floyd-Warshall

When do you pick BFS over DFS?

  • BFS is preferred for shortest path by edge count in unweighted graphs.
  • DFS is preferred for deep traversal tasks like cycle detection and topological ordering.
  • BFS uses more memory on wide graphs because of the frontier queue.
  • Both are O(V+E), so pick by the property you need, not by speed: BFS guarantees shortest paths but its frontier can hold a whole layer; DFS uses depth-bounded memory but gives no distance guarantee.
Graph Algorithms

Why is Dijkstra not valid with negative edges?

  • Dijkstra assumes once a node is finalized, its best distance is known.
  • Negative edges can later produce a shorter route to a finalized node.
  • Bellman Ford handles negative edges by repeated relaxation.
  • Dijkstra is faster (O((V+E) log V)) but only valid with non-negative weights; Bellman–Ford accepts negative edges at O(V·E) — pay the slower cost only when weights can go negative.
Graph Algorithms

Adjacency list or adjacency matrix?

  • Adjacency list is the default for sparse graphs (most real-world graphs): O(V+E) space and efficient neighbor iteration.
  • Adjacency matrix uses O(V²) space but answers “is there an edge A→B?” in O(1).
  • In .NET, Dictionary<T, List<T>> is a common adjacency-list implementation.
  • The list saves memory and speeds traversal on sparse graphs; the matrix trades O(V²) memory for constant-time edge checks, so reach for it only on dense graphs or edge-query-heavy workloads.
Graph Algorithms

Why is Greedy Best-First Search neither optimal nor complete?

It orders the frontier by h(n) alone and never accounts for g(n), the cost already spent. A neighbor that is close in straight-line distance but reached by a long detour is expanded before a farther-looking neighbor that sits beside the goal, so the returned path can be far from shortest — not optimal. On an infinite graph a monotonically improving h can lead expansion down a branch that never reaches the goal — not complete. A finite graph with a visited set terminates, but the path it returns can still be long.

Greedy Best-First Search

Why does a concave obstacle around the goal cause thrashing?

Every cell inside the pocket is geometrically near the goal, so all of them score a low h and the frontier keeps selecting barrier-hugging cells. The direct heading is blocked, and the accumulated g that would reveal the long way around is never read, so expansion oscillates along the wall before escaping. It is the h-only ordering, not the map, that has no way to notice the pocket is a dead pull.

Greedy Best-First Search

What does the visited set guarantee, and what does it not fix?

It stops a node from being enqueued twice, which prevents cycles from looping forever and guarantees termination on a finite graph. It does not make the returned path optimal, and it cannot bound an infinite graph where h keeps improving down a fruitless branch.

Greedy Best-First Search

Why do residual back-edges make greedy augmenting paths exact?

A greedy first path can commit flow to an edge the optimum routes around, and conservation then blocks every forward correction. Each unit sent u → v creates a residual back-arc v → u of equal capacity, and a later augmenting path can traverse it to retract and reroute that flow. The loop therefore cannot stop until no s → t path remains — which, by max-flow min-cut, is the maximum. Without the back-arcs the algorithm can wedge strictly below it.

Maximum Flow

How is the minimum cut recovered once the flow is maximal?

Let S be the vertices reachable from s in the final residual graph; t is not among them. Every original edge from S to T is saturated, so the sum of their capacities equals the flow value. The cut side comes from residual reachability, but the reported edges are the original forward edges out of S.

Maximum Flow

Why is Edmonds–Karp O(V·E²) while Ford–Fulkerson is only O(E·\|f\|)?

Ford–Fulkerson bounds iterations by the flow value because each augmentation adds at least one unit — a bound that inflates with large capacities and fails outright on irrational ones. Edmonds–Karp augments along the fewest-edge path via BFS; the BFS distance to each vertex never decreases, so each edge is the bottleneck O(V) times, capping augmentations at O(V·E). With O(E) per BFS that is O(V·E²), independent of capacity magnitudes.

Maximum Flow

When does Dinic or push–relabel earn its extra complexity over Edmonds–Karp?

Dinic wins whenever the graph is large or the workload is bipartite matching: same model, O(V) phases instead of O(V·E) augmentations, and an O(E·√V) unit-capacity bound. Push–relabel wins on dense graphs, where its O(V²·√E) / O(V³) bounds beat Dinic’s O(V²·E), accepting a harder implementation and a less direct min-cut readout.

Maximum Flow

Why are Prim's and Kruskal's optimal even though they never reconsider an edge?

The cut property: for any partition of the vertices, the minimum-weight edge crossing it lies in some MST. Prim’s applies it to the (in-tree, out-of-tree) cut; Kruskal’s to the cut between the two components an edge would join. Each added edge is therefore provably safe, so a chain of greedy choices reaches a global optimum without backtracking.

Minimum Spanning Tree

What guarantees the edge Kruskal's adds is the safe one?

Edges are processed in ascending weight, so when an edge joining two different components is reached, every lighter edge has already been consumed or rejected. Nothing lighter connects those two components, which makes this edge the minimum one crossing the cut between them — exactly the edge the cut property certifies.

Minimum Spanning Tree

Why can the tree path between two vertices be longer than their shortest path?

An MST minimizes the total weight of all its edges, not the distance between any given pair. In the triangle A–B = 3, B–C = 3, A–C = 4, the MST drops A–C and routes A→C through B at cost 6, versus the direct edge’s 4. Pairwise shortest paths come from Dijkstra over the full graph, not from an MST.

Minimum Spanning Tree

What do the algorithms produce on a disconnected graph?

No MST exists. Kruskal’s returns a spanning forest — one minimum tree per component — and Prim’s from a single start reaches only that vertex’s component. Both finish with fewer than V − 1 edges, and that shortfall is how the disconnection is detected.

Minimum Spanning Tree

In Tarjan's algorithm, what does low[v] == disc[v] mean, and why does it identify an SCC root?

disc[v] is v’s discovery index; low[v] is the smallest discovery index reachable from v’s subtree through tree edges and at most one edge to an on-stack vertex. Equality means nothing in the subtree found a route back to a vertex discovered before v, so v is the entry point of its component. Popping the stack down to v emits exactly that SCC.

Strongly Connected Components

Why may only vertices still on the stack lower low[v]?

The stack holds vertices whose component is not yet finished. An edge to an on-stack vertex stays inside the component under construction, so it legitimately extends the reach recorded by low. An edge to an already-popped vertex points into a finished component the search can never climb back through; taking its discovery index would drag low[v] below disc[v], break the root test, and merge two separate SCCs.

Strongly Connected Components

Why does Kosaraju's second DFS run on the transpose in decreasing finish order?

First-pass finish times form a reverse topological order of the condensation, so the last vertex to finish lies in a source SCC. Reversing every edge turns that source into a sink — reachable from the start vertex but with no edge leading out to a not-yet-emitted component. Each second-pass DFS therefore fills one SCC and stops. Removing either the finish order or the transpose destroys that isolation.

Strongly Connected Components

Why is the condensation of a digraph always a DAG, and what does that buy?

Each SCC is a maximal mutually reachable set. A cycle between two distinct components would make every vertex in both mutually reachable, so they would already be one component — a contradiction. With no cycles left, the condensation admits a topological order, which lets DAG-only techniques (topological sort, DAG dynamic programming, 2-SAT implication solving) run on graphs that originally contained cycles.

Strongly Connected Components

Why does a topological order exist only for a DAG?

A valid order must place u before v for every edge u → v. A cycle a → b → … → a would require a before b and b before a at once, which is impossible. So an order exists iff the graph is acyclic, and the construction that finds the order doubles as the acyclicity test.

Topological Sort

How does each construction detect a cycle?

Kahn’s only enqueues a vertex when its in-degree reaches 0. A vertex on a cycle keeps at least one incoming edge from another cycle member, so its count never reaches 0 and it is never emitted; an output shorter than V means a cycle remains. The DFS construction reports a cycle when it follows an edge to a vertex still open on the recursion stack — a descendant reaching an ancestor.

Topological Sort

Why is the topological order generally not unique, and how is a deterministic one obtained?

Whenever two or more vertices have in-degree 0 at the same time, either may be emitted next, so most DAGs admit many orders. Keying Kahn’s frontier with a priority queue instead of a plain queue fixes the choice — for example always taking the smallest label yields the lexicographically smallest order.

Topological Sort

Paradigms

What turns brute-force enumeration into backtracking?

A feasibility test applied to a partial candidate. Brute force builds every complete configuration and tests it at the end; backtracking tests the prefix after each choice and, on a violation, discards every configuration sharing that prefix without generating them.

Backtracking

Why must a choice be undone after its subtree is explored?

The partial candidate is a single mutable buffer shared by all branches to avoid copying. After a subtree is exhausted the choice is reverted so the next sibling starts from the state its parent established. An un-reverted choice leaks into siblings and silently corrupts the enumeration.

Backtracking

Why is the worst-case class still exponential despite pruning?

Pruning removes subtrees but does not shrink the complete search tree, which has O(b^d) nodes. When no prefix can be rejected before a leaf, every candidate is still visited. Pruning improves the constant and the practical node count, not the asymptotic class.

Backtracking

Why must the bounding function be optimistic?

The bound gates pruning: a subtree is discarded when its bound cannot beat the incumbent. If a maximisation bound under-estimates a subtree’s true best, that subtree can be pruned while it still holds the optimum, and the search returns a suboptimal incumbent labelled as proven-optimal — a silent correctness failure. The requirement is exactly A* search’s admissibility: the estimate may only err optimistically. An LP relaxation satisfies it because a fractional optimum can only meet or exceed the integer one.

Branch and Bound

What determines how much of the tree is actually explored?

Two factors independent of the worst-case class: the tightness of the bound and how early a strong incumbent is found. A tight bound rejects more subtrees per comparison; an early incumbent raises the bar every later bound must clear. Exploration order feeds the second — best-first reaches a strong incumbent with the fewest expansions but stores a large frontier, while depth-first reaches some incumbent fast in O(depth) memory.

Branch and Bound

Does branch-and-bound lower the complexity class of an NP-hard problem?

No. The worst case stays exponential; on an adversarial instance the bound prunes nothing and it reduces to brute-force enumeration. It improves the constant factor and the typical case, often by orders of magnitude, and returns a certificate of optimality — but not polynomial time. On very large instances the practical answer is an approximation, not a proven optimum.

Branch and Bound

How does the Master Theorem produce merge sort's Θ(n log n)?

Merge sort’s recurrence is 2T(n/2) + Θ(n), so a = 2, b = 2, and the leaf work is n^(log_2 2) = n. The combine cost f(n) = Θ(n) matches the leaf term exactly — Case 2 — so every one of the log n levels costs Θ(n), and the total is Θ(n log n).

Divide and Conquer

Why does a "split in half" algorithm not automatically run in O(n log n)?

The bound depends on the combine cost f(n), not the split. If f(n) grows faster than the leaf work n^(log_b a) — a quadratic combine over a linear-leaf recurrence, for instance — the recurrence falls into Case 3 and the result is Θ(f(n)), dominated entirely by the top-level combine. The recurrence has to be classified, not read off the fact that the input was halved.

Divide and Conquer

Why do production divide-and-conquer sorts stop recursing above the base case?

Per-call overhead — stack frames and function-call cost — outweighs the algorithmic advantage once a slice is small, and deep recursion risks stack overflow on adversarial input. Below a threshold of roughly 16–32 elements, insertion sort’s tight loop is faster, so hybrids such as Introsort and Tim Sort cut over to it there.

Divide and Conquer

What two properties must hold for DP to apply, and what does each guarantee?

Optimal substructure — an optimal solution is composed of optimal solutions to subproblems — makes combining sub-answers valid. Overlapping subproblems — the same subproblem recurs — makes caching worthwhile. Without the first, the recurrence yields a wrong optimum; without the second, a memo never gets a second hit and only adds overhead, which is the divide-and-conquer regime.

Dynamic Programming

Where does DP's running time come from?

The number of distinct states multiplied by the work to combine each state from its sub-states. LCS over lengths m and n has m·n prefix-pair states with O(1) work each, giving O(m·n). The state count is also the memory bound, unless a rolling array drops rows that are no longer read.

Dynamic Programming

How can a correct recurrence still produce wrong answers once implemented?

If the state omits an argument the answer depends on, two different subproblems map to the same cache slot and the second read returns a stale value with no error raised. Bottom-up, the analogous failure is filling a cell before its dependencies, reading uninitialised sub-answers.

Dynamic Programming

Why does greedy solve coin change for {1, 5, 10, 25} but not {1, 3, 4}?

Standard denominations are constructed so the largest coin not exceeding the remainder always belongs to some optimal solution — the greedy-choice property holds, and an exchange argument confirms it. For {1, 3, 4} making 6, the largest-coin rule yields 4 + 1 + 1 (three coins) while 3 + 3 (two coins) is optimal, so the first greedy commitment is in no optimal solution and the property fails. The loop is identical in both cases; only the denomination set decides correctness.

Greedy Algorithms

What does an exchange argument establish?

That the greedy first choice is contained in some optimal solution. Starting from an arbitrary optimal solution, one of its choices is swapped for the greedy choice and shown not to reduce quality; repeating the swap transforms it into the greedy solution without ever getting worse, so greedy is at least as good as any optimum. Combined with optimal substructure, induction extends the result to the entire run.

Greedy Algorithms

Why is fractional knapsack inside greedy but 0/1 knapsack outside it?

In fractional knapsack the highest value-per-weight item can always be taken — cut to fit the remaining capacity — so it belongs to an optimal solution and the greedy-choice property holds. In 0/1 knapsack an item is all-or-nothing, so a high-ratio item can be excluded from every optimal packing when a different indivisible combination uses its capacity for more total value; the greedy-choice property fails even though the problem keeps optimal substructure — the max(OPT(cap, items∖{i}), v_i + OPT(cap − w_i, items∖{i})) recurrence that the O(nW) DP exploits. The failing property is the greedy choice, not the substructure.

Greedy Algorithms

What is the relationship between memoization and dynamic programming?

Memoization is the top-down implementation of DP: write the recurrence, cache each subproblem’s result. Bottom-up tabulation is the other implementation. Both require overlapping subproblems (or the cache never hits) and optimal substructure (or the recurrence is wrong). Memoization additionally evaluates only the states the recursion reaches, where tabulation fills the whole table.

Memoization

Why must a memoised function be pure, and what breaks if it isn't?

The cache returns a stored result for repeated arguments without re-running the function. If the output also depends on hidden state — a global, the clock, an I/O read — a cache hit hands back a value computed under conditions that may no longer hold, and side effects the caller expected simply don’t happen on a hit. Only same-input-same-output, side-effect-free functions are safe to memoise.

Memoization

What is the most common correctness bug when memoising a recurrence?

An incomplete cache key. If the key omits an argument the result depends on — caching a (i, capacity) knapsack state on i alone — two different subproblems map to the same slot and the second read returns a stale value, silently. The key must be the full state, the same requirement DP calls state design.

Memoization

When should a memoised recurrence be rewritten as bottom-up tabulation?

When the recursion is deep enough to risk a stack overflow (a long chain of dependencies), when essentially every state gets visited so laziness saves nothing, or when a rolling-array reduction can cut memory that the recursive form can’t exploit. Tabulation runs in tight iterative loops with O(1) stack, at the cost of computing states a lazy memo might have skipped.

Memoization

Patterns

What precondition makes binary search on the answer valid, and how is it checked?

The feasibility predicate must be monotone in x: feasible flips exactly once — false→true (or true→false) — across the answer range. That single boundary is what the search locates. It is verified by argument, not code: show that increasing the candidate can only make the condition easier (or only harder) to satisfy, so it never reverses. If the predicate can flip back and forth, no boundary exists and the search discards the half that holds the answer.

Binary Search on Answer

Why is the time O(n · log(range)) rather than tied to the input length the usual way?

The log factor counts probes over the numeric answer interval [lo, hi], each halving it, so log(hi − lo) probes. Every probe runs feasible, typically an O(n) pass over the input, giving O(n · log(range)). The log is over the value range, so a 10^18-wide answer space is still only about 60 probes — which is why the search wins when checking a candidate is far cheaper than computing the optimum directly.

Binary Search on Answer

How does the termination differ between an integer answer and a real-valued one?

Over integers, lo < hi with the mid + 1 update collapses the interval to one value and stops exactly. Over reals the interval never reaches a single point, so termination comes from a fixed iteration count — roughly 100 halvings drops the interval below double precision — instead of an eps comparison, whose correct value is problem-dependent and can stall on floating-point rounding.

Binary Search on Answer

Why does ternary search not substitute for this pattern?

Ternary search optimises a unimodal objective — a value that rises then falls (or the reverse) with one extremum — by discarding an outer third each step. Binary search on the answer needs a monotone yes/no predicate with one boundary. The preconditions differ: a monotone predicate has no peak to find, and a unimodal objective has no single true/false flip, so neither reduction applies to the other’s input.

Binary Search on Answer

Why does n & (n - 1) clear only the lowest set bit?

Subtracting 1 borrows through the trailing zeros and flips the lowest set bit to 0, leaving every higher bit unchanged; the two values differ exactly in that bit and the zeros beneath it. AND keeps the shared high bits and clears the low region. Iterating it touches each set bit once, so Kernighan’s popcount runs in O(number of set bits).

Bit Manipulation

Why does XOR-ing an entire array isolate a single unpaired value?

XOR is commutative and self-inverse: a ^ a == 0 and a ^ 0 == a. Every value appearing an even number of times cancels to 0 regardless of order, so the accumulator ends holding only the value with no partner — O(n) time, O(1) space, no auxiliary set.

Bit Manipulation

What distinguishes an arithmetic right shift from a logical one, and when does the choice matter?

An arithmetic shift copies the sign bit into the vacated high positions (>> on signed types in C# and Java), so -8 >> 1 == -4. A logical shift zero-fills (>>>, or >> on unsigned types). The difference only shows on values with the high bit set; using the arithmetic shift where zero-fill was intended leaves stray 1s in the high bits.

Bit Manipulation

Why does n & -n isolate the lowest set bit only under two's-complement?

-n is computed as ~n + 1, which inverts every bit above the lowest set bit while reproducing that bit and its trailing zeros; AND with n keeps just that bit. The identity is a property of two’s-complement negation and does not hold under a sign-magnitude representation.

Bit Manipulation

Why is Cyclic Sort O(n) despite an inner loop that can revisit an index?

A swap fires only when it moves a value into a home that did not already hold it, and a placed value never moves again. With n values, at most n − 1 swaps occur across the whole run; the outer walk adds n steps. Amortising over “each swap finalises one element” gives O(n), not O(n²).

Cyclic Sort

What precondition does the method require, and why does it fail on arbitrary arrays?

Values must map to indices in a known contiguous range so each value has exactly one home. That mapping is what places elements without comparisons. Arbitrary integers have no home index, so the swap target is undefined and the linear-time argument disappears.

Cyclic Sort

What makes the guard a[home] != v rather than i != home?

When a duplicate exists its home already holds an equal value. Comparing values recognises that slot as satisfied, which both terminates the loop and marks the duplicate. Comparing indices never sees the slot as done, so the two equal values swap forever.

Cyclic Sort

Why is a meeting between the pointers equivalent to the existence of a cycle?

Once both pointers are inside a loop, the fast pointer gains one node per step, so their separation modulo the cycle length runs through zero and they must coincide. With no loop the fast pointer reaches null and the walk ends with no meeting. Neither direction admits a false result, so a meeting is exactly a cycle.

Fast and Slow Pointers

After the first meeting, why does resetting one pointer to the head locate the cycle entry?

At the meeting the slow pointer has gone d steps and the fast 2d; the surplus d must be a whole number of laps, k·λ. That places the meeting node μ mod λ steps before the entry. A pointer restarted at the head reaches the entry in μ steps; the other, stepped μ times from the meeting node, covers the remaining distance plus whole laps and lands on the entry at the same time.

Fast and Slow Pointers

Why does this pattern extend to Find the Duplicate Number, and what plays the role of next?

The array of n + 1 values in [1..n] is read as edges i → nums[i]. Because some value repeats, two indices point to the same node, which forces a cycle; the cycle’s entry is the duplicated value. The successor function next(i) = nums[i] replaces the list’s next pointer, so the same detection and entry-finding phases apply in O(1) space without mutating the array.

Fast and Slow Pointers

When is a hash set of visited nodes the better choice than fast/slow?

Both detect a cycle in O(n) time; the set costs O(n) space and Floyd costs O(1). The set returns the entry as the first repeated node with no second phase and gives the full set of visited nodes for free. It wins when that memory is affordable and the visited set or immediate entry is wanted; Floyd wins when memory is tight or the structure is read-only.

Fast and Slow Pointers

Why does sorting by start reduce merging to a single linear sweep?

After ascending starts, every interval later in the list starts at or after the current one. If next.start > current.end, no later interval can reach back to current either, so current is final and can be emitted. One comparison per interval decides overlap, turning an O(n²) all-pairs check into an O(n) pass on top of the O(n log n) sort.

Merge Intervals

Why take max(current.end, next.end) when extending rather than next.end?

next can be entirely nested inside current — merging [1,10] with [2,3] should stay [1,10]. Assigning current.end = next.end would shrink the block to [1,3] and lose coverage. The max keeps current.end at the furthest right edge merged so far, which is the value the next overlap test depends on.

Merge Intervals

How does the interval convention change the result, and where does it show up in code?

Closed intervals count touching endpoints as overlap ([1,2] and [2,3] merge); half-open intervals do not. The convention is the difference between <= and < in the overlap test. Choosing wrong yields off-by-one merges that only fail on boundary-touching inputs. Meeting-room problems usually want half-open so back-to-back bookings share a room.

Merge Intervals

When does a sweep-line over events or an interval tree replace sort-then-merge?

A sweep-line over separated start/end events is needed when the answer is a concurrency count — maximum overlap or minimum rooms — rather than merged ranges. An interval tree replaces both when intervals are inserted and queried dynamically, trading an O(n log n) build for O(log n) overlap queries against a changing set.

Merge Intervals

Why is a monotonic stack O(n) when the code reads as a nested loop?

Every index is pushed exactly once and popped at most once, so the total number of pops across the whole scan is bounded by n. The inner “pop while” therefore does O(n) work summed over all outer iterations, not O(n) per iteration. Charging each pop to the unique element that performed it gives the amortized linear bound.

Monotonic Stack and Queue

Why must a monotonic deque store indices rather than values?

A window maximum is evicted precisely when its position falls out of range, so the front check compares stored indices against the window bound. A deque of raw values has discarded the positions and cannot tell when a candidate has left the window, returning a stale maximum from a slot no longer in scope.

Monotonic Stack and Queue

How does the monotone direction relate to which query is answered?

The direction of the stack fixes the query. A decreasing stack (values fall bottom to top) resolves each popped index against the arriving larger value, yielding the next greater element; reversing the pop comparison makes the stack increasing and yields the next smaller element. Choosing the direction backwards produces a fully populated result that answers the opposite question.

Monotonic Stack and Queue

Why does prefix[r + 1] - prefix[l] yield the sum of a[l..r]?

prefix[r + 1] is the sum of every element before index r + 1, i.e. a[0..r], and prefix[l] is the sum of a[0..l-1]. Their difference cancels the shared head a[0..l-1] exactly, leaving a[l] + ... + a[r]. The prefix[0] = 0 sentinel lets l = 0 use the same formula with no special case.

Prefix Sum

What makes prefix sums unsuitable once the array is updated between queries?

Writing one element changes every running total from that index onward, so the whole tail of prefix is invalid and must be rebuilt in O(n). With q interleaved updates this degrades to O(nq). A Fenwick tree (point update and prefix query in O(log n)) or a segment tree (range update and query) keeps updates cheap instead.

Prefix Sum

How does a difference array relate to a prefix sum?

It is the dual. A prefix sum makes range queries O(1) by storing running totals; a difference array makes range updates O(1) by storing deltas at the two endpoints (diff[l] += v, diff[r + 1] -= v). A single prefix-sum pass over the difference array then reconstructs the final values in O(n).

Prefix Sum

Why is a sliding window O(n) and not O(n·k)?

Each index is added to the aggregate once, when the right boundary passes it, and removed at most once, when the left boundary passes it. Total boundary movement is bounded by 2n and each move is O(1), so window width never enters the cost — the aggregate is adjusted by its two boundary elements rather than rescanned.

Sliding Window

What property must the aggregate have, and which aggregate violates it?

The new window’s value must be reconstructable in O(1) from a single entering or leaving element. A running sum and a per-key frequency count are; a window maximum is not, because removing the element that held the maximum leaves no cheap way to recover the next-largest element still inside the window. That case uses a monotonic deque of candidate indices instead of a scalar.

Sliding Window

Why do negative numbers break sum-based sliding windows?

The contraction rule “shrink while the sum exceeds the target” relies on the sum rising on every entry and falling on every removal, which makes the smallest valid window length monotone as the right boundary advances. Negatives remove that monotonicity — a longer window can carry a smaller sum — so a failed window can become valid by extending. Those problems use prefix sums plus a hash map rather than a moving window.

Sliding Window

To find the k largest elements, why is the heap a min-heap rather than a max-heap?

The size-k min-heap keeps its root as the smallest of the k best elements seen so far — the weakest survivor. A new element is relevant only when it beats that root, which a min-heap exposes as an O(1) peek, and when it does the root is the correct element to evict. A max-heap would surface the largest retained element, which is never the one to drop, so it cannot drive the scan.

Top-K Elements

Why is the streaming heap O(n log k) rather than O(n log n), and where does that matter beyond speed?

The heap is capped at k entries, so each insert or replace is O(log k), giving O(n log k) over n elements. The bound beats a full sort when k ≪ n, but the decisive property is the O(k) resident set: it never needs all n elements in memory, so it is the only option that runs on a stream too large to hold.

Top-K Elements

What disqualifies Quickselect for a top- k problem, and what is its worst case?

Quickselect needs the entire array addressable and mutates it in place, so a streaming or immutable input rules it out. On a static array it is O(n) average from geometric partition shrink, but degrades to O(n²) when pivots are consistently bad, such as a naive pivot on sorted input. A randomized pivot makes that improbable; median-of-medians guarantees O(n) worst case at a larger constant.

Top-K Elements

Why does the converging two-sum require sorted input?

The move “advance left or retreat right” is justified only because raising left can only increase the sum and lowering right can only decrease it. Sorted order guarantees that monotonicity. On unsorted data the relationship is gone, so a pointer move can step over the pair that answers the query.

Two Pointers

When a[left] + a[right] is too small, which pairs become impossible and why is left++ safe?

Every pair that keeps the current left and uses any partner at or below right is even smaller than the current sum, so no pair anchored at left can reach the target. That entire column is eliminated, and left++ discards it without losing a possible answer.

Two Pointers

What does a hash set buy over two pointers for two-sum?

A hash set removes the sorted-input precondition and finds a complement in O(n) time on unsorted data. The cost is O(n) extra memory and the loss of ordered traversal, which two pointers keep for free when the array is already sorted.

Two Pointers

Search Algorithms

Why does Binary Search require sorted data?

The comparison at mid must prove that an entire half cannot contain the target. Ordering supplies that proof: if a[mid] < target, every earlier value is also too small. Without ordering, moving either boundary is a guess and can discard the answer.

Binary Search

How do you find the first occurrence of a duplicated value?

A first-occurrence variant stores mid as the current answer and continues through the left half with right = mid - 1. The stored candidate becomes the result when the range is empty. This removes the plain search’s early exit in exchange for a defined duplicate policy.

Binary Search

When is a hash lookup a better fit?

Hash-based lookup fits repeated exact-match searches where ordering is irrelevant and the additional memory is acceptable. Binary Search retains an advantage when sorted order already exists or supports adjacent operations such as lower bounds, insertion points, and range queries.

Binary Search

Why is exponential search O(log i) rather than O(log n)?

Doubling stops as soon as bound reaches or passes the target’s position i, after about log i steps, and the bracket it leaves spans fewer than i elements, so the closing binary search is another O(log i). Neither phase inspects the whole array, so the cost tracks the answer’s position, not the array length — strictly better than O(log n) when the target is near the front and no worse when it is near the end.

Exponential Search

Why must the high end of the bracket be clamped, and what breaks without it?

The final doubling makes bound the first power of two at or beyond the target, so it can land past the last valid index. Bisecting [bound/2, bound] without clamping the upper end to min(bound, n − 1) reads outside the array; on an unbounded source the same overshoot indexes past end-of-stream. bound *= 2 can also overflow a 32-bit index into a negative probe.

Exponential Search

What property of a value distribution forces the linear worst case?

A single endpoint value that dwarfs the rest of the span — as with exponentially growing keys, clustered timestamps, or Zipfian counts. Once the maximum is far larger than most values, any target that is only a small fraction of that maximum interpolates to a position near lo, so each estimate advances the boundary by about one element instead of shrinking the range geometrically, and isolating the target costs O(n).

Interpolation Search

Why can it not run on arbitrary comparable keys?

The probe computes (target - a[lo]) * (hi - lo) / (a[hi] - a[lo]), which needs subtraction and a ratio with numeric meaning. Ordering-only types such as strings under a custom comparator support comparison but not that arithmetic, so no position can be estimated and only comparison-based search applies.

Interpolation Search

Why is m = √n the block size that minimizes total work?

The cost is n/m jumps to reach the target’s block plus up to m steps to scan it, so f(m) = n/m + m. Its derivative −n/m² + 1 is zero at m = √n, where the two phases are equal and the total is 2√n. Larger blocks lengthen the scan; smaller blocks multiply the jumps. A constant block size leaves the jump phase linear in n, so the bound degrades to O(n).

Jump Search

What breaks when Jump Search runs on unsorted input?

The jump phase assumes a[block] < target proves the target lies further ahead, which requires monotonic order. On unsorted data a block end can exceed the target while the matching value sits in an earlier, already-skipped block, so the scan examines the wrong block. The failure is a silent false negative rather than a crash.

Jump Search

Why does Linear Search require no precondition on its input?

It tests each element independently and never compares two elements to each other or computes a position, so it needs neither ordering nor an index. The cost of that generality is that it cannot skip any element it has not yet read.

Linear Search

What is the average comparison count for a present versus an absent target?

A present target with uniform position averages (n + 1) / 2 comparisons. An absent target always performs all n, so a miss, not a hit, is the true worst case.

Linear Search

Why can no preprocessing beat O(n) for a single search over unsorted data?

Any correct method must at least read the elements it rules out, and reading them is the whole cost. With one query there is nothing to amortize a build against, so preprocessing only pays back across repeated searches.

Linear Search

When does building an index beat repeated linear scans?

When the same collection is searched many times: q scans cost O(n·q), while a one-time O(n log n) sort plus Binary Search, or an O(n) hash build plus average O(1) lookups, amortizes to O(n log n + q log n) or O(n + q).

Linear Search

What is the first decision before picking a search algorithm?

  • Check whether data is sorted, because that immediately enables Binary Search.
  • Identify data shape: array, graph, or text stream, because each has specialized methods.
  • Decide whether worst-case guarantees or average speed matters more.
  • Checking these preconditions first avoids picking an algorithm whose assumptions your data violates — the most common source of wrong or slow searches.
Search Algorithms

Why is one search algorithm never best for all cases?

  • Different algorithms optimize for different constraints such as ordering, memory, and preprocessing.
  • Workload shape changes the winner: single lookup, repeated queries, or many patterns.
  • Correctness constraints can force specific methods, for example sorted input for Binary Search.
  • Every choice trades preprocessing and memory against query speed; the senior move is to weigh those for the actual workload instead of reaching for a default.
Search Algorithms

When does preprocessing (sorting or indexing) pay off versus a plain linear scan?

  • A one-off search over unsorted data is just O(n) — sorting first (O(n log n)) would cost more than it saves.
  • Once many queries hit the same data, a single sort or index build is amortized across all of them and each query drops to O(log n) or O(1).
  • Indexes (hash maps, B-trees) trade memory and write cost for fast reads.
  • Preprocessing front-loads cost and memory to make repeated queries cheap, so justify it by query volume, not by instinct.
Search Algorithms

Why does the smaller of the two probe values mark a discardable third?

Under strict unimodality f rises to the peak then falls. The probe returning the smaller value sits farther down a slope, on the side away from the peak, so the interval beyond it lies entirely on that slope and cannot contain the maximum. Two probes are needed because a single point on a non-monotone function cannot reveal which side the peak is on.

Ternary Search

What does golden-section search change, and when does it matter?

It places the probes at the golden ratio so one probe of each step reuses an evaluation from the previous step. Golden-section retains ≈0.618 of the interval per step versus ternary’s 0.667 — a slightly faster shrink — while reusing one of the two probe evaluations, so it costs one new function call per step instead of two. It dominates ternary on both axes at once whenever evaluating f is expensive — a simulation or a measurement rather than an array read.

Ternary Search

What input makes the discard rule return a wrong answer?

A non-unimodal function or a flat plateau. With two humps, probes straddling the valley can discard the third holding the global maximum and return a local one. A plateau makes f(m1) == f(m2) uninformative, so a bound can move past part of the optimal set. Strict increase-then-decrease is the precondition that excludes both.

Ternary Search
String Matching

Why is search cost Θ(n + z) independent of the number of patterns?

All patterns share one trie, so common prefixes collapse into shared paths and the text drives a single walk through the automaton. Each character makes one forward move plus failure hops that amortize to O(1) over the whole scan, and the output walk emits exactly the z matches found. Nothing in the loop scales with k, the pattern count, so a thousand signatures cost the same per character as one.

Aho-Corasick

What does a failure link point to, and what invariant holds after reading i characters?

A state’s failure link points to the state for the longest proper suffix of its string that is still a prefix of some pattern — KMP’s longest-prefix-suffix relation lifted onto the trie. That link maintains the invariant that after i characters the automaton sits at the state whose string is the longest suffix of the first i characters that is a prefix of some pattern, which is precisely the state from which every match ending at i can be read.

Aho-Corasick

What do output links add, and what fails silently without them?

A single state can complete several patterns when a shorter one is a suffix of a longer one, such as he ending inside she. Output links chain each state to the nearest shorter pattern that also ends there, and walking the chain enumerates every simultaneous match. Without them the scan is still correctly positioned but reports only the pattern the current node ends, silently dropping the nested ones — the classic loss of he when matching { he, she, hers } in ushers.

Aho-Corasick

Why does adding a pattern after construction force a rebuild?

Failure and output links encode suffix relationships across the entire pattern set and are resolved together by the single construction BFS. A new pattern can change the longest-suffix target of many existing states, so the links are no longer valid and must be recomputed. That is why the automaton fits a dictionary built once and reused across texts rather than one that changes per query.

Aho-Corasick

Why does comparing right-to-left let Boyer-Moore skip characters it never reads?

A mismatch at the pattern’s last position exposes a text character together with its offset. If that character is absent from the pattern, no alignment that places any pattern character over it can match, so the pattern jumps clear past it — up to m positions — without comparing the characters in between. Left-to-right scanning learns nothing about the text ahead, so it can never justify a jump larger than one on the same information.

Boyer-Moore

What does each shift rule contribute, and why do most implementations drop the good-suffix rule?

The bad-character rule aligns the pattern’s rightmost copy of the mismatching text character, giving large skips on large alphabets. The good-suffix rule reuses an already-matched suffix, which helps on repetitive patterns the bad-character rule handles poorly. The algorithm takes the larger shift, so it is never worse than either alone. The good-suffix table’s index arithmetic is error-prone for a small real gain, so production code such as GNU grep’s fixed-string search ships Boyer-Moore-Horspool with the bad-character rule only.

Boyer-Moore

Why is the plain worst case O(n·m), and what recovers a linear bound?

The sublinear behavior depends on frequent large skips, which vanish when text and pattern share long repeated runs — as with an all-equal pattern aaaa over aaaa…a, where every alignment is a full match over all m characters and the good-suffix rule then shifts by only one. Galil’s rule remembers how much of the pattern is already known to match after a shift and skips re-comparing those positions, bounding total comparisons at O(n).

Boyer-Moore

Why does the text index never move backward, and what does that buy?

On a mismatch the algorithm only lowers the match length j via π[j-1]; it never decrements the text index i. Because j can fall back at most as much as it climbed, total comparisons stay at 2n, giving the Θ(n + m) bound. A monotonic text pointer also lets the search run over a stream that cannot be rewound.

KMP (Knuth-Morris-Pratt) Algorithm

What does π[j] encode, and how is it used on a mismatch?

π[j] is the length of the longest proper prefix of pattern[0..j] that is also a suffix of it. On a mismatch after matching j characters, j resets to π[j-1], which realigns that shared prefix/suffix against the text so no already-matched characters are re-read.

KMP (Knuth-Morris-Pratt) Algorithm

On what input does KMP's guarantee actually pay off, and where does it gain nothing?

It pays off on repetitive input such as text = aⁿ, pattern = aᵐ⁻¹b, where naive search degrades to Θ(n·m) while KMP stays Θ(n + m). It gains nothing on large alphabets: unlike Boyer-Moore it reads essentially every character and cannot skip.

KMP (Knuth-Morris-Pratt) Algorithm

What is the standard bug when building the failure table?

Resetting the length pointer to 0 on a mismatch instead of falling back through failure[k-1]. That corrupts entries where the prefix overlaps itself — AABAAAB builds as [0,1,0,1,2,1,0] rather than [0,1,0,1,2,2,3] — and the search then misses matches that depend on the longer overlap.

KMP (Knuth-Morris-Pratt) Algorithm

How does sliding the window keep the hash update at O(1)?

The window hash is a base-b polynomial mod p. Moving one position right subtracts the outgoing character’s weighted term T[i]·b^(m-1), multiplies by b to shift the rest up one place, and adds the incoming T[i+m] — a fixed count of modular operations, independent of m. Recomputing from the m characters instead would make each step O(m) and the scan O(nm).

Rabin Karp Search

Why does a hash match still require a character comparison?

The hash maps m-character strings onto residues mod p, a many-to-one map, so two different windows can share a value. A match on the hash means only that the strings might be equal; the O(m) character check confirms it and stops a collision from being reported as a match.

Rabin Karp Search

What turns the expected Θ(n + m) into the Θ(nm) worst case?

A hash match at almost every position, each forcing an O(m) verification. It arises with genuine matches everywhere (text aaaa searched for aa) or with a weak or small modulus that makes collisions frequent. A large prime modulus keeps spurious matches rare, which is the low-collision assumption behind the average bound.

Rabin Karp Search

Why does KMP never re-examine a text character after a mismatch?

Its prefix (failure) function records, for every pattern position, the length of the longest proper prefix that is also a suffix. On a mismatch the pattern shifts so that this already-matched prefix aligns, so the text pointer only ever advances. That is what turns the naive O(n·m) scan into O(n + m).

String Matching

When is Boyer-Moore's O(n/m) best case actually realised, and when does it degrade?

Sublinear scanning appears with long patterns over large alphabets, where a mismatched character usually isn’t in the pattern at all and the bad-character rule shifts by nearly the full pattern length. It degrades toward O(n·m) on small alphabets or highly repetitive text; the Galil rule caps the worst case at O(n).

String Matching

Why choose Rabin–Karp over a linear automaton method?

Because it fingerprints windows of the text with a rolling hash rather than building an automaton, it extends naturally where the automaton methods don’t: matching many equal-length patterns at once (compare each window’s hash against a set), plagiarism detection, and 2-D pattern matching. The price is that a hash collision forces a character-by-character verification, so its worst case is O(n·m).

String Matching

How is Aho-Corasick related to KMP?

It is KMP lifted from a single pattern to a set. The patterns are stored in a trie, and each node’s failure link points to the longest proper suffix that is a prefix of some pattern — the multi-pattern analogue of KMP’s failure function. One pass over the text then reports every occurrence of every pattern in O(n + Σmᵢ + matches).

String Matching

What does z[i] measure, and why does the Z-box keep the whole pass linear?

z[i] is the length of the longest substring starting at index i that also matches a prefix of the string. The box [l, r] is the match interval with the largest r; a position inside it copies its value from the mirror z[i-l] when that mirror ends before the edge, spending no comparisons. Direct comparisons occur only while extending past r, and each either fails once or pushes r one step right. Since r never retreats and stops at |S| - 1, total comparisons are Θ(|S|).

Z-Algorithm

Why should the concatenation separator lie outside the input alphabet?

To keep the cap: a separator absent from P and T holds every text-region z[i] to at most |P|, so z[i] == |P| and z[i] >= |P| coincide and no match spans the pattern/text join. If the separator also appears in the input, a genuine occurrence can extend across the join and produce z[i] > |P| — a strict == |P| test would then drop it. The shipped >= |P| test survives this (a text-region z[i] >= |P| is always |P| real characters of T matching P); a sentinel outside the alphabet is what lets the simpler == formulation stay correct too.

Z-Algorithm

Inside the box, when can z[i] be copied from the mirror, and when must it be recomputed?

When z[i-l] < r - i + 1 the mirrored match ends strictly before the box edge, so it is fully verified and z[i] = z[i-l]. When z[i-l] >= r - i + 1 the mirror only guarantees a match up to r; the characters past r were never compared, so z[i] is reset to the box remainder r - i + 1 and extended from r + 1.

Z-Algorithm

Sorting Algorithms

What does the swapped flag detect, and what does omitting it cost?

A pass that completes with no swap means no adjacent pair is out of order, so the array is sorted and the loop can stop. On already-sorted input this ends the sort after one O(n) pass. Without the flag the double loop always runs its full Θ(n²) comparisons, so sorted input costs as much as random input and the O(n) best case is gone.

Bubble Sort

Why can a large value reach its final slot in one pass while a small value at the tail takes many?

A left-to-right pass carries the running maximum forward through consecutive swaps, so a large value can cross the whole array in a single pass. The same pass only ever compares a given element with its left neighbor once, so a small value (“turtle”) near the end moves toward the front by at most one index per pass and needs about one pass per position it must travel.

Bubble Sort

Why is bubble sort stable?

A swap happens only on a strict a[i] > a[i+1]. Equal keys never satisfy that test, so they are never exchanged and their original relative order is preserved through every pass.

Bubble Sort

Where does the Θ(n) average bound actually come from?

From the assumption that keys are roughly uniform over a known range with m ≈ n buckets. Under it, each bucket holds O(1) elements in expectation, so the sum of per-bucket sort costs Σ tᵢ² is Θ(n). The bound is conditional: it is a property of the input distribution, not of the algorithm alone.

Bucket Sort

Why can the sorted buckets be concatenated with no comparison between buckets?

Bucket i covers a strictly lower slice of the range than bucket i + 1, so every key in one bucket is smaller than every key in the next by construction. Reading internally sorted buckets in index order therefore emits a globally sorted sequence, and no per-bucket sort ever inspects a key outside its own bucket.

Bucket Sort

What input drives Bucket Sort to Θ(n²), and why is the result still correct?

A skewed distribution — Zipfian, exponential, or duplicate-heavy — that drops most keys into one bucket. That bucket’s inner Insertion Sort gains nothing from the partition and costs Θ(n²). The partition still separates ranges correctly, so the output stays sorted; only the running time collapses.

Bucket Sort

What is a turtle, and how does the gap remove it?

A turtle is a small value near the end of the array. A bubble-style adjacent pass moves it left one position at a time, so it needs O(n) passes to reach the front — the asymmetry that keeps bubble sort quadratic. A wide initial gap compares distant pairs, so one swap can carry the turtle up to gap positions toward the front, and the shrinking gap then resolves the remaining local disorder.

Comb Sort

Why is comb sort's sub-quadratic behavior not a guarantee?

The ≈ n log n figure is an empirical measurement on random input tied to the 1.3 shrink factor, not a proven bound. No structural argument prevents Θ(n²), and adversarial inputs still reach it. The combsort11 special case for gaps of 9 and 10 exists precisely because the constant was tuned by experiment rather than derived.

Comb Sort

Why does Counting Sort avoid the Ω(n log n) comparison lower bound?

The bound counts the yes/no answers a comparison sort needs to separate n! orderings, and a pairwise comparison is its only source of information. Counting Sort never compares keys — it reads each key’s value directly as an array index — so the argument does not apply to it. That indexing costs Θ(n + k) and only works for integer keys over a bounded range.

Counting Sort

What makes the placement pass stable, and when does that matter?

After the prefix sum, count[v] is one past the last slot for value v. Consuming the input tail-first and decrementing before each write puts the last-seen equal key in the highest slot of its block and earlier ones beneath it, preserving input order. A forward pass reverses equal keys. Stability is optional standalone but required when Counting Sort is the digit pass inside LSD Radix Sort.

Counting Sort

Why is the cost Θ(n + k) in every case rather than O(n + k)?

The two input scans and the prefix-sum sweep run to completion regardless of input order, so no arrangement adds or removes work. Lower and upper bounds coincide, which makes the notation tight — Θ. The only lever on cost is k, the key range, not the data.

Counting Sort

When does a small n still make Counting Sort the wrong choice?

When k ≫ n. The count array holds one cell per representable key value, not per element, so sorting eight 64-bit integers by raw value needs a 2^64-cell array. The + k term dominates and the allocation fails. Radix Sort keeps each digit pass’s range small; a comparison sort removes the range dependence altogether.

Counting Sort

Why does build-heap cost O(n) rather than O(n log n)?

Bottom-up sift-down moves each node down only as far as its own subtree height. Most nodes are near the leaves and barely descend; only the few near the root can travel log n. Summing height × count across the levels converges to O(n). Inserting n elements one at a time, by contrast, pays up to O(log n) each and totals O(n log n).

Heap Sort

Where does heap sort's instability come from?

The extraction swaps. Moving the root to the end and sifting a new root down relocates elements by heap geometry, not by input order, so two equal keys can be swapped past each other with nothing to restore their original sequence. Merge sort’s merge step, choosing the left element on ties, keeps equal keys in input order.

Heap Sort

What keeps the prefix sorted after each insertion?

The inner loop shifts every prefix element greater than the key one slot right and stops at the first element <= key. The key is written into that gap, so nothing to its left is larger and everything to its right was already ordered; a[0..j] is sorted for the next step.

Insertion Sort

Why is reverse-sorted input the worst case?

Each key is smaller than every element already placed, so it shifts the entire prefix before landing at the front. The shifts sum to n(n-1)/2, making the run O(n²)—the maximum possible number of inversions.

Insertion Sort

Why does binary insertion sort not lower the asymptotic time?

Binary search finds the insertion slot in O(log j) comparisons, but the elements between that slot and the key must still be shifted right one at a time. The shifts stay O(n²), so the total is unchanged; only comparison-heavy workloads gain.

Insertion Sort

Why do Timsort and Introsort fall back to insertion sort on small partitions?

On a few dozen elements the quadratic term is small and bounded, while insertion sort allocates nothing, accesses memory sequentially, and reaches O(n) on the nearly-sorted runs those algorithms produce. Below the crossover it beats the recursion and constant-factor overhead of an O(n log n) sort.

Insertion Sort

What makes introsort switch to heap sort, and why is 2⌊log₂ n⌋ the threshold?

The switch fires when the current partition’s recursion depth reaches 2⌊log₂ n⌋. Balanced quicksort bottoms out near ⌊log₂ n⌋ levels, so twice that depth is reached only when partitions stay badly unbalanced — the path toward O(n²). Finishing that partition with heap sort caps its cost at O(n log n).

Introsort

Why can no setting of the depth limit or cutoff make introsort stable?

Both strategies it switches between are unstable: quicksort’s partition and heap sort each move equal keys past one another. The depth limit only chooses which unstable strategy runs, so equal-key order is lost regardless of the threshold or the small-partition cutoff.

Introsort

Why doesn't an input that is bad for the pivot rule always trigger the fallback?

The depth limit reacts to cumulative recursion depth, not to any single partition’s balance. Imbalance that never sustains past 2⌊log₂ n⌋ levels stays in the quicksort phase and is sorted at quicksort’s normal constants; the fallback bounds sustained degeneration, not one bad split.

Introsort

The heap-sort branch almost never runs — why keep it?

Runtimes sort untrusted input, so quicksort’s O(n²) path is a denial-of-service vector. The branch existing at all is what turns the bound into a contract: whether or not it fires on a given input, no input can exceed O(n log n). The ceiling is a correctness property, not a speedup.

Introsort

Why is merge sort O(n log n) on every input, with no faster best case?

The split is by position, not by value, so the recursion tree has the same ⌈log₂ n⌉ levels for sorted, reversed, or random input, and every level merges all n elements. Element values only decide which read head is taken at each step, not how many merges run, and there is no early-exit test on an already-sorted input.

Merge Sort

What single line makes the array merge stable, and why?

The comparison a[i] <= a[j], which emits the left run’s element on a tie. The left run holds elements that appeared earlier in the original array, so taking it first preserves the original order of equal keys. Using a[i] < a[j] instead emits the right element on ties and makes the sort unstable.

Merge Sort

Why does a linked-list merge sort need only O(1) extra space while the array version needs O(n)?

A linked-list merge re-links existing nodes by pointer, so it never copies elements into a scratch array and never needs index access to a[mid]. The array merge must write its output somewhere while both inputs are still being read, which forces an O(n) buffer. Both still carry O(log n) recursion-stack space.

Merge Sort

What does an in-place block merge trade to remove the O(n) buffer?

It keeps the O(n log n) time bound but replaces the single linear copy with rotations and block swaps, adding a constant-factor slowdown and making stability harder to preserve. The buffer exists to avoid overwriting unread input; removing it means doing that bookkeeping with in-place moves instead.

Merge Sort

Why can the two sides of a partition be sorted without ever combining them?

Partitioning places the pivot at its final sorted index and guarantees every element to its left is not greater and every element to its right is greater. No element on one side belongs on the other, so the two subranges are independent sorting problems. Correct placement of each pivot is the only merge step quick sort performs.

Quick Sort

What input drives quick sort to O(n²) with a first- or last-element pivot, and why?

Two different inputs, failing for two different reasons. Ordered data — already-sorted or reverse-sorted — makes a fixed first- or last-element pivot the extreme of its range, so each partition peels off one element into an n − 1 side; n levels of O(n) work give O(n²). A randomized or median-of-three pivot removes that case. Input dominated by one repeated key degenerates independently of the pivot: Lomuto sends every element ≤ pivot to the left, so identical keys pile onto one side no matter which pivot is chosen — randomization does not help, and three-way (Dutch-flag) partitioning is the fix.

Quick Sort

Why is quick sort's worst-case stack O(n), and how is it bounded to O(log n)?

Degenerate partitions nest the recursion n deep, and a naive version that recurses into both sides holds all those frames. Recursing into the smaller side first and iterating on the larger (tail-call elimination) keeps at most O(log n) frames live, because the smaller side is at most half the range.

Quick Sort

Why must each per-digit pass be stable?

Each pass sorts on one digit and trusts that ties on that digit are already ordered by the less-significant digits sorted in earlier passes. A stable counting sort preserves that established order; an unstable one reorders the ties and destroys the work of every prior pass, producing output that is sorted only on the final digit. Stability is a correctness requirement here, not an optimization.

Radix Sort

When does a comparison sort beat Radix Sort?

When keys are wide, variable-length without a bound, or not decomposable into digits. The d passes carry a real cost, so once d grows relative to log₂ n — long strings, big integers — a tuned Quick Sort wins on wall-clock time. Keys exposed only through a comparator have no digit to bucket on, so radix does not apply at all.

Radix Sort

Why are the comparisons Θ(n²) even on already-sorted input?

The suffix scan is unconditional: pass i compares the running minimum against all n − 1 − i remaining elements, with no early-exit test and no check for existing order. The n(n−1)/2 total depends only on n, not on arrangement, so a sorted array costs exactly what a reversed one does. The algorithm is non-adaptive.

Selection Sort

Why is standard selection sort unstable, and what does the stable fix cost?

The swap that places the suffix minimum can carry an equal-keyed element across its partner: [5a, 3, 5b, 1] becomes [1, 3, 5b, 5a], reversing the two 5s. Restoring stability means shifting the intervening elements instead of swapping, which raises writes to Θ(n) per pass and forfeits the linear-write property that was the reason to use it.

Selection Sort

Why is Shell sort faster than a plain insertion sort when its final pass is a full insertion sort?

The coarse gap passes move far-out-of-place elements h slots at a time, so most long-distance disorder is cleared before the h = 1 pass runs. Insertion sort is near-linear on nearly-sorted input, so the final pass has little left to shift. The speedup comes from relocating where the shift work happens — into cheap coarse passes — not from a cheaper comparison.

Shell Sort

Why does the gap sequence, rather than the input, decide Shell sort's complexity class?

A pass only guarantees the array is h-sorted, and how much disorder survives into the next pass depends on how the gaps interleave positions. Shell’s n/2^k keeps every gap even until the last, so parity classes never mix and the worst case is Θ(n²); Hibbard’s coprime gaps provably reach Θ(n^1.5); Ciura’s tuned gaps measure fastest but have no proven bound. The sequence is a tunable parameter, so Shell sort is a family of algorithms rather than one.

Shell Sort

Why is Shell sort unstable?

A shift relocates an element by a whole gap h, so it can carry a key past an equal key that lies between them. No later pass records or restores their original relative order, so records that compare equal can emerge reversed.

Shell Sort

Why can Shell sort not offer a contractual O(n log n)?

The sequences with the best proven worst-case bounds are not the fastest: Pratt’s 3-smooth gaps prove Θ(n log² n) but run slowly due to many passes, while the fast Ciura sequence has no proven bound at all, and the optimal general sequence is an open problem. A workload needing a guaranteed bound uses Heap Sort or Introsort instead.

Shell Sort

How do you choose between Merge Sort and Quick Sort in production?

  • Merge sort gives reliable O(n log n) worst-case behavior and stable ordering.
  • Quick sort is often faster in practice on in-memory arrays due to cache behavior.
  • Quick sort has worst-case O(n^2) if pivot strategy is poor, so randomized or introspective variants are safer.
  • Why it matters: this choice affects latency tail risk, memory usage, and correctness when stable ordering is required.
Sorting Algorithms

When is Insertion Sort still a good choice?

  • It is strong on very small arrays because constant overhead is tiny.
  • It performs well on nearly sorted data where shifts are minimal.
  • It is commonly used as a base case inside hybrid production sort implementations.
  • Why it matters: knowing this avoids overengineering and explains hybrid sort internals in interviews.
Sorting Algorithms

What does .NET's built-in Array.Sort use, and why?

.NET uses an introspective sort (IntroSort): it starts with Quick Sort for fast average performance, switches to Heap Sort when recursion depth exceeds a threshold (to guarantee O(n log n) worst case), and uses Insertion Sort for small partitions (to exploit its low overhead on nearly-sorted data). This hybrid approach demonstrates why production sort implementations combine multiple algorithms rather than using a single one.

Sorting Algorithms

Why does Tim sort reach Θ(n) on some inputs while its worst case is still Θ(n log n)?

Run detection scans for maximal ascending or strictly-descending stretches and merges only across their boundaries. An already-ordered array (ascending, or descending and reversed in place) is a single run, so the scan finishes in one Θ(n) pass with no merges. Unstructured input yields ~n / minrun short runs that still merge across ~log n balanced levels, giving Θ(n log n).

Tim Sort

What do the run-stack size invariants guarantee, and what breaks without them?

For the top three run lengths X, Y, Z (Z deepest), Tim sort keeps Z > Y + X and Y > X, merging when either fails. This bounds the size ratio of adjacent runs so every merge joins near-equal lengths, which is what caps total merge work at Θ(n log n). Without the bound, a merge could repeatedly join a tiny run into a huge one and drift toward quadratic cost.

Tim Sort

What did the 2015 formal-verification effort reveal, and how did it tie back to the run stack?

de Gouw et al. proved the merge-collapse routine only restored the invariant among the top runs, leaving deeper violations reachable by a crafted run-length sequence. Because the run-length stack was pre-sized assuming the invariant held, the violation could overflow it into an ArrayIndexOutOfBoundsException. The stopgap enlarged the stack; the real fix widened the invariant check to also test the run below the top three.

Tim Sort

Why must descending-run detection use strict > rather than >=?

A descending run is reversed in place. If detection used >=, it would reverse stretches of equal keys and silently swap their relative order, breaking stability. Strict descent guarantees equal keys never sit inside a run that gets reversed, so the reversal preserves input order.

Tim Sort

Data Structures

What is a data structure? Which ones do you know? Which of them exist in .NET?

A data structure is a way to organize related data into a collection-like object. Examples include arrays, lists, queues, stacks, linked lists, dictionaries/hash tables, hash sets, graphs, and trees. .NET provides built-in implementations for many of these (for example Array, List<T>, Queue<T>, Stack<T>, LinkedList<T>, Dictionary<TKey, TValue>, HashSet<T>).

Data Structures

How do you choose between List<T>, Dictionary<TKey, TValue>, and HashSet<T>?

Use List<T> when you need ordered, index-based access and the primary operations are iteration or positional lookup. Use Dictionary<TKey, TValue> when you need fast lookup, insertion, and deletion by a unique key. Use HashSet<T> when you only need membership testing and set operations (union, intersection, difference) without associated values. The wrong choice shows up as O(n) scans that should be O(1) lookups.

Data Structures

Why does collection choice matter more than micro-optimization?

Switching from O(n) linear search to O(1) hash lookup reduces work by orders of magnitude at scale. No amount of loop unrolling or SIMD on the O(n) path matches that. Focus on algorithmic complexity first, then optimize constant factors within the chosen structure if profiling shows it matters.

Data Structures

When would you use LinkedList<T> over List<T> in .NET?

Almost never in practice. List<T> (backed by a contiguous array) has better cache locality, lower memory overhead per element, and faster iteration. LinkedList<T> only wins when you need frequent insertions/deletions in the middle of a very large collection and already hold a reference to the node. In most .NET code, List<T> is the correct default.

Data Structures

Composite Structures

Why must the recency list be doubly linked rather than singly linked?

A get promotes a node from the middle of the list to the head, which means unlinking it in O(1). Splicing a node out needs its predecessor. A doubly-linked node exposes prev directly, so the rewrite is constant time. A singly-linked list would scan from the head to find the predecessor — O(n) — collapsing the cache’s constant-time guarantee.

LRU Cache

How do the map and the list divide the work, and why store the key inside the node?

The map answers “where is key k” in O(1); the list answers “what is the recency order” and supports O(1) move-to-head and remove-from-tail. Eviction starts from the tail node and must delete the matching map entry, so the node carries its own key to recover it without a reverse lookup from node to key.

LRU Cache

What corruption results from updating only one of the two structures on eviction?

Removing the tail node but leaving its key in the map produces stale keys that resolve to evicted nodes — false hits and an unreachable node that leaks. Deleting the map entry but leaving the node linked produces an orphan that occupies a recency slot yet can never be looked up, permanently reducing effective capacity.

LRU Cache

Why is every LRU operation worst-case O(1) rather than amortized, and where does that break down?

No operation traverses the list: the map turns lookup into a hash probe and the tail sentinel makes the victim a pointer read, so each op is a fixed number of rewrites. The one dependency is the map’s own lookup, which degrades to O(n) under adversarial hash collisions — the same caveat as the underlying hash map.

LRU Cache

Graph Structures

How is a disjoint set represented in memory?

A parent array stores a forest: parent[i] is the next index toward the representative, and each root points to itself. Rank or size is stored in a parallel array for roots. No linked node objects are required.

Disjoint Set

Why does Union link roots rather than the original elements?

A root represents an entire existing set. Linking one root under another merges both complete sets. Re-parenting an interior node can move only its subtree and leave other members behind, breaking the partition semantics.

Disjoint Set

Why is the useful bound amortized rather than worst-case constant time?

One Find can still traverse several parent indices. Path compression pays extra writes during that operation so later finds become shorter. Across a sequence, the total work is O(m α(n)) for m operations, even though a particular operation is not guaranteed to be constant time.

Disjoint Set

When does a disjoint set beat BFS for connectivity, and what do you give up?

When edges arrive over time and connectivity queries interleave with insertions: each union/find is O(α(n)) ≈ O(1), versus O(V + E) to re-traverse per query. You give up everything except component identity — no paths, no distances, no directed reachability, and merges are irreversible (no edge deletion).

Graph Structures

Why does the adjacency list make neighbor iteration cheap but the adjacency matrix make edge existence cheap?

The list physically stores only u’s actual neighbors, so enumerating them is O(deg u) with nothing wasted, but confirming a specific edge means scanning that list. The matrix reserves a fixed cell for every ordered pair, so [u, v] is a single O(1) index, but reading all of u’s neighbors means scanning a full row of V cells, most of them empty on a sparse graph.

Graph

What makes the adjacency matrix a bad fit for a sparse graph?

Its space is O(V²) regardless of how many edges exist. A graph with 10 000 vertices and 50 000 edges stores ~60 000 list entries but 100 million matrix cells, nearly all sentinel values. The matrix charges for every possible edge, so it only breaks even when E approaches V².

Graph

How is an undirected edge encoded in each representation, and why does that matter for mutation?

The adjacency list stores it twice, as mirrored entries in both endpoints’ lists; the matrix stores it as two symmetric cells [u, v] and [v, u]; the edge list stores one tuple read in both directions. For the list and matrix, symmetry is a maintained invariant — removing or updating the edge must touch both stored copies, or the graph silently becomes directed.

Graph

When is an edge list the right storage despite its O(E) edge test?

When the algorithm consumes every edge in a single pass and never queries an individual edge — relaxing all edges in Bellman-Ford, or sorting edges by weight for Kruskal’s MST. Both want a flat, iterable edge set; neither benefits from per-vertex indexing, so the edge list’s weakness never triggers.

Graph

Why does union by rank alone bound tree height at O(log n)?

A root’s rank increases only when two trees of equal rank merge, so a tree of rank r holds at least 2^r nodes. With n nodes, no rank — and therefore no height — can exceed log₂ n. Attaching the lower-rank root under the higher one never lengthens the taller tree’s longest path.

Union-Find

Why is the O(α(n)) cost amortized rather than a single-operation guarantee?

A single find can still traverse an O(log n) parent chain. What path compression buys is that the writes it performs during that walk flatten the path, so later finds on those nodes are cheap. The near-constant figure is the total work over a sequence of m operations divided across them, not a bound on any one call.

Union-Find

Why does path compression make a structure unsuitable for rollback?

Compression rewrites the parent of every node on a walked path, erasing the forest’s earlier shape. There is no record of what a node pointed to before, so a merge cannot be reversed. Rollback DSU keeps union by rank but omits compression so each union mutates exactly one parent-and-rank pair, which it can then undo.

Union-Find

How does the variant chosen change the cost of union and find?

Quick-find gives O(1) finds but O(n) unions; plain quick-union is O(n) for both in the worst case; union by rank alone makes both O(log n); rank plus path compression drops both to O(α(n)) amortized while leaving the single-operation worst case at O(log n).

Union-Find

Hash-based Structures

Why can a Bloom filter produce false positives but never false negatives?

Add only ever sets bits to 1 and never clears them, so every bit a present element touched is still 1 and that element always passes its query — no false negatives. A queried element’s k bits can all have been set to 1 by other elements, though, which makes a never-added element report “possibly present”: a false positive.

Bloom Filter

Why does the standard filter forbid deletion, and what changes to allow it?

No bit belongs to a single element; bits are shared across everything that hashed to them. Clearing an element’s bits could clear one another present element depends on, turning its next query into a false “definitely absent”. A counting Bloom filter stores a small counter per slot instead of one bit, so a remove decrements rather than clears, at several times the space.

Bloom Filter

What do m and k control, and what is the optimal k?

m is the bit-array size and k the number of hash functions. The false-positive rate is p ≈ (1 − e^(−kn/m))^k for n inserted elements. For a fixed m/n, k = (m/n)·ln 2 minimises p, driving about half the bits to 1; larger m lowers p by adding room, while k balances too few probes against filling the bits too fast.

Bloom Filter

Why is the space O(m) bits rather than O(n)?

The filter stores no elements — only the m-bit array and k hash functions. Its footprint is fixed at construction and does not grow with n, which is why it can track billions of keys in megabytes. The cost of discarding the keys is the false-positive rate and the loss of enumeration, retrieval, and deletion.

Bloom Filter

Why do "open hashing" and "open addressing" mean opposite things?

They name different axes. “Open hashing” describes the storage: chaining’s per-bucket lists grow open-endedly outside the array. “Open addressing” describes the key’s address: the key may land in a slot other than its home, so its address is open. Chaining is open hashing but closed addressing (the bucket is fixed); probing is closed hashing (fixed array) but open addressing (the slot drifts).

Collision Resolution

Why can a load factor exceed 1 with chaining but not with open addressing?

Chaining stores entries in lists outside the array, so the array can hold more entries than it has slots — α > 1 just means the average chain is longer than one. Open addressing stores every entry in the array, so it cannot hold more entries than slots; it needs empty slots to terminate probe sequences, and its cost blows up as α → 1.

Collision Resolution

Why does deleting from an open-addressed table need a tombstone?

A lookup follows a probe chain until it hits an empty slot, which it reads as “not present”. If a delete simply emptied a slot in the middle of a chain, every key that had probed past it would become unreachable — the empty slot would end the search early. A tombstone marks the slot as “deleted, keep probing”, preserving the chain; a later rehash clears the accumulated tombstones.

Collision Resolution

What makes bucketed hashing fast for on-disk and SIMD tables?

The bucket is sized to the expensive access unit — a disk page or a cache line — so one I/O or memory fetch brings in B slots at once and scanning them is nearly free. A bucket also absorbs up to B collisions before any overflow logic runs, so local hot spots that would cause long probe runs in a flat table stay contained in one block.

Collision Resolution

Why is the O(1) membership bound an average rather than a guarantee?

It assumes the hash spreads elements roughly uniformly and the load factor caps expected chain length at a constant. When many elements collide into one bucket — weak hashCode or adversarial keys — that bucket becomes a linear list and Contains/Add/Remove degrade to O(n).

Hash Set

Why can a member become unreachable after insertion?

Membership routes an element to a bucket via hashCode, then confirms with Equals. Mutating a field that participates in hashCode after adding leaves the element in its original bucket while lookups probe the new one, so Contains returns false on an element that is still stored.

Hash Set

When does putting a Bloom filter in front of a HashMap (or disk lookup) pay off?

When most queries are for absent keys and the authoritative lookup is expensive (disk read, network call, or a map too big for RAM). The filter answers “definitely not” from a few bits per element, skipping the expensive path; only “maybe” (true hits plus the ~1% false positives) pays full price. If most queries hit, the filter is pure overhead — every hit still does the real lookup.

Hash-based Structures

What single failure mode degrades every hash-based structure at once?

Skewed hash distribution. A weak or non-uniform GetHashCode degrades all three: the map and set walk O(n) bucket chains, and the Bloom filter’s false-positive rate inflates as correlated hashes concentrate bits. The hash-flooding attack specifically targets the bucketed structures — an attacker packs one chain with chosen keys.

Hash-based Structures

What assumptions make hash-map operations O(1) on average?

A hash function that distributes keys close to uniformly, so buckets stay short, and a load factor bounded by resizing, so buckets do not fill up. Both must hold. Without them, keys concentrate in a few buckets and each operation walks a long chain toward O(n).

HashMap

Why is insert amortized O(1) rather than strictly O(1)?

A single insert can push the load factor past its threshold and rehash every existing entry into a larger array, an O(n) step. Averaged over the inserts that grew the map to that size, the rehash cost is O(1) per insert. The guarantee is over a sequence; any individual insert can still cost O(n).

HashMap

What happens to an entry whose key is mutated after insertion?

The entry stays in the bucket the key hashed to at insertion time, but a lookup recomputes the bucket from the key’s new hash and searches a different one. The entry is present in memory yet unreachable — orphaned. Keys must be immutable, or at least never change a hash-participating field after insertion.

HashMap

When is a balanced tree preferable to a hash map?

When the workload needs ordered iteration, range queries, or nearest-key lookups. A hash map scatters keys across buckets and cannot answer those without a full scan and sort; a balanced tree keeps keys sorted at O(log n) per operation, which is the price for that ordering.

HashMap

Linear Structures

Why is array indexing O(1) and independent of the index?

The element address is computed directly as base + i * elementSize — a multiply and an add — so a[i] costs the same for any i. This works only because every element is the same width and the block is contiguous, which lets the offset be pure arithmetic instead of a walk.

Arrays

Why does a middle insert cost O(n)?

An array keeps its slots packed with no gaps. Inserting at index k in an n-element array shifts the n − k following elements up by one slot to open the position, so the work is proportional to the tail length. The contiguity that makes indexing cheap is the same property that forces the shift.

Arrays

Why can an array not append, and what changes with a dynamic array?

Capacity is fixed at allocation, so there is no free slot past the last element; adding one requires a new, larger block and a full copy. A dynamic array owns that resize policy — typically doubling — which spreads the copy cost so that append is amortized O(1) while keeping the same contiguous layout and O(1) index.

Arrays

Why can an array scan beat an asymptotically better structure at small n?

Contiguity keeps neighbors in the same cache lines, and the prefetcher streams the next lines ahead of a sequential scan, so most accesses hit L1 (~1 ns) rather than main memory (~100 ns). A scattered structure pays a near-full miss per node. Big-O counts operations; the array’s constant factor is far smaller, so the crossover where a better bound wins can sit past the sizes a workload reaches.

Arrays

Why do a full ring and an empty ring both satisfy head == tail, and how is the collision resolved?

Empty rings put the read and write cursors on the same slot with nothing between them; a full ring wraps tail all the way around until it lands back on head. The index pair is identical in both states. Resolutions: store an explicit count (empty is 0, full is capacity), or leave one slot unused so full becomes (tail + 1) % capacity == head while empty stays head == tail.

Circular Buffer

Why are the O(1) operation bounds worst-case rather than amortized, unlike a growable queue?

The backing array is allocated once and never resized, so no operation can ever trigger a copy of existing elements. Each enqueue or dequeue is a single slot access plus a modulo increment, with a constant cost every time. A growable queue’s O(1) is amortized precisely because occasional writes pay for a resize.

Circular Buffer

On reaching capacity, what distinguishes an overwrite ring from a reject ring, and when does each fit?

Overwrite advances head over the write, dropping the oldest element so the newest N always remain — right for logs, telemetry, and frame buffers where old data is disposable. Reject leaves the buffer unchanged and signals the producer to back off — right for a work queue where every item must be processed. The structure is identical; only the full-buffer branch of enqueue differs.

Circular Buffer

Why must a reference-type ring null out dequeued slots?

The backing array holds references for every physical slot, including ones whose logical element was already dequeued. Until a slot is overwritten by a later enqueue, its stale reference keeps the object alive, so a long-lived ring can pin objects long after they left the queue. Assigning default on dequeue releases the reference for collection.

Circular Buffer

How do a head index and a count let a ring-buffer deque touch both ends in O(1)?

The occupied slots are head through (head + count - 1) % cap. PushBack writes at (head + count) % cap and increments count; PushFront decrements head (mod capacity), writes there, and increments count; each pop reads an end slot and adjusts head or count. Because every access wraps modulo capacity and only head and count change, no element is ever shifted.

Deque

Why is the ring-buffer push O(1) amortized but not O(1) worst case?

A push onto a full buffer must copy all n elements into a larger array — an O(n) single operation. Geometric doubling makes that copy happen rarely enough that a run of m pushes costs O(m) total, so the amortized cost is O(1); but any individual overflowing push is still O(n), which shows up as a latency spike.

Deque

Ring buffer versus doubly-linked list as the deque backing — how do they differ?

The ring buffer stores elements contiguously: O(1) index, good locality, no per-element allocation, but an occasional O(n) resize. The linked list gives unconditional O(1) ends with no resize spike, at the cost of a node allocation (~40 bytes overhead on x64) per element, no O(1) index, and pointer-chasing traversal. The ring buffer is the default; the linked list fits when worst-case per-op latency or O(1) removal of held interior nodes matters more than locality.

Deque

Why is append amortized O(1) even though a resize copies every element?

Doubling the buffer makes resizes exponentially rarer as the array grows. Over n appends the resizes copy 1 + 2 + 4 + … + n < 2n elements total — fewer than two copies per element — so the average cost per append is constant. The individual append that triggers a resize is still O(n); the constant bound is a property of the whole sequence, not any single call.

Dynamic Array

What breaks if the buffer grows by a fixed amount instead of a constant factor?

Fixed-increment growth resizes every constant number of appends, so the copies sum to O(n²) across n appends and append degrades to O(n) amortized. Geometric growth (e.g. 2×) is what spaces resizes far enough apart to keep the amortized cost constant.

Dynamic Array

Why does a resize invalidate held references and iterators?

Growth allocates a new backing array and copies elements into it, then drops the old buffer. Any pointer, cached index target, or iterator bound to the old array now refers to a stale allocation, which is why appending during iteration is unsound.

Dynamic Array

Why does the Prev ↔ Next invariant matter during a removal?

Adjacency is stored twice: a.Next == b must agree with b.Prev == a. A removal must re-point both directions across the gap. Updating only one leaves a half-linked chain where forward and backward traversal disagree about membership, corrupting the list.

LinkedList

Why does a queue use a circular buffer or linked list instead of a plain array?

A plain array that dequeues from index 0 must shift every remaining element down one slot, making each dequeue O(n) and a full drain Θ(n²). A circular buffer moves a head index modulo capacity instead of moving data, and a linked list unlinks a node — both keep dequeue O(1) while preserving arrival order.

Queue

What does "amortized O(1) enqueue" mean for a growable queue?

A single enqueue is O(1) until the backing array is full; that enqueue triggers an O(n) copy into a larger array. Because the array doubles, the copy happens once per n insertions, so the total cost of n enqueues is O(n) and the per-operation average stays O(1), even though one operation spikes.

Queue

When is a queue the wrong structure, and what replaces it?

When the next item must be chosen by priority rather than by arrival time, a FIFO queue would serve an older low-priority item ahead of an urgent newer one. A priority queue backed by a Heap restores correct order at O(log n) per operation. When both ends must be read and written, a Deque fits instead.

Queue

How is a Span<T> represented, and why is its size independent of the window length?

It is a value type holding two fields — a managed reference to the first element in view and an integer length. The elements stay in the memory it points at, so the span itself is two machine words whether it covers 2 elements or 2 million.

Span

What makes Slice zero-copy, and what is the observable consequence?

Slice returns a new span with the reference advanced to start and a new length; no memory is allocated and no element is copied. Because the result aliases the same backing store, a write through the slice changes the element seen through the original span.

Span

Why can a Span<T> not cross an await boundary, and what replaces it there?

As a ref struct it is confined to the stack; crossing await (or yield) would move it to the heap-allocated state machine, where the buffer’s lifetime no longer guards it, so the compiler rejects it. Memory<T> is the heap-storable handle for those cases, yielding a Span<T> through .Span at the synchronous point of use.

Span

When is a copy into a fresh array required instead of a span or Memory<T>?

When the data must outlive the buffer it came from. A span or Memory<T> only references existing memory; once that backing store is freed or reused, the view is invalid, so surviving data has to be copied into an owned array.

Span

Why is only the top of a stack reachable, and what does that buy?

Every operation is fixed to a single end, so there is no addressing scheme for interior or bottom elements. That restriction is what keeps push, pop, and peek at O(1) — no shifting, no search, no index arithmetic. Reaching a buried element requires popping everything above it, which is the information the stack trades away.

Stack

Why is array-backed Push amortized O(1) rather than worst-case constant?

Most pushes write one slot and bump a counter. When the backing array is full, that push allocates a doubled array and copies all n elements — an O(n) single call. Doubling makes any run of n pushes cost O(n) in total, so the per-push average stays constant, but a specific push can spike.

Stack

Why convert deep recursion into an explicit stack?

The call stack is itself a LIFO stack on a fixed-size memory region; deep enough recursion overflows it with an uncatchable StackOverflowException. An explicit Stack<T> holds the same pending frames on the heap, where depth is bounded by available memory instead. The logic maps directly: push where the recursion would call, pop where it would return.

Stack

Trees

What is the AVL invariant and when is it checked?

For every node, height(left) − height(right) must stay in {−1, 0, +1}. It is checked on the way back up after an insert or delete: each node’s stored height is recomputed along the touched path, and the first node whose balance factor reaches ±2 is rotated.

AVL Tree

Why can an insert need at most one rebalance but a delete need O(log n)?

An insert’s single rebalance (one single or one double rotation) restores the rebalanced subtree to its pre-insert height, so nothing above it changed and the fix stops. A delete can leave the rotated subtree one level shorter, which can unbalance an ancestor, so rebalancing may cascade all the way to the root.

AVL Tree

Why do the Left-Right and Right-Left cases require a double rotation?

A single rotation on a zig-zag shape only mirrors the imbalance to the other side. The inner (median) node has to be rotated outward into a straight chain first, after which a single rotation lifts it to the top.

AVL Tree

What two structural changes turn a B-tree into a B+ tree, and which query do they serve?

All (key, value) pairs move to the leaves, leaving internal nodes as a pure routing key index, and the leaves are chained into a sorted linked list. Both changes serve the range scan: after one descent, matching keys are read by walking the leaf chain in order instead of re-ascending into internal nodes.

B+ Tree

Why is a B+ range scan O(log_m n + k) rather than O(k log_m n)?

The log_m n descent locates the first matching leaf once. From there the leaf next links yield the remaining k entries in order as a sequential walk, adding one term per result. Without leaf links, each successor would require its own descent, multiplying the height into the cost.

B+ Tree

Why does removing internal values raise fan-out, and why does that help on disk?

A separator is just a key and a child pointer, far smaller than a full record, so a routing page packs many more entries. Higher fan-out means fewer levels, and the small routing levels tend to stay in the buffer pool, so a lookup often costs a single physical read of the leaf.

B+ Tree

How does a B-tree stay balanced with all leaves at one depth, and without rotations?

Height changes only at the root. An overflowing node splits and pushes its median key into the parent; if the split cascades to the root, a new root adds a single level. Because growth happens only at the top and every split keeps both halves at least half full, all leaves remain at equal depth by construction.

B-tree

What fixes a node that drops below its minimum fill on delete?

If an adjacent sibling has a spare key, the node borrows: the parent’s separator rotates down and the sibling’s key rotates up. If both siblings are minimal, the node merges with a sibling and the separating parent key into one node, which can cascade upward and shrink the tree by a level.

B-tree

Why does an in-order traversal of a BST produce sorted keys?

The ordering invariant places every smaller key in the left subtree and every larger key in the right. Visiting left, then the node, then right therefore reaches the smallest key first and the largest last, in strictly increasing order — the invariant made observable.

Binary Search Tree

What happens when a plain BST receives keys in sorted order, and why?

Each new key is larger than everything present, so every insert descends right. The tree becomes a right-leaning chain of height n — functionally a linked list — and search, insert, and delete all degrade to O(n). The invariant is preserved; only the height blows up, which is why balanced variants add rotations.

Binary Search Tree

How is a node with two children deleted?

Its key is replaced by the in-order successor — the minimum of the right subtree, found by stepping right once then left to the end. That successor has no left child, so removing it reduces to the leaf or one-child case. This keeps the ordering invariant intact without orphaning either subtree.

Binary Search Tree

When does a hash map beat a BST despite the BST's ordered access?

When the workload is purely exact-match lookup and insertion with no need for range queries, successors, min/max, or sorted iteration. The hash map delivers O(1) average operations; the ordering a BST maintains is pure overhead if nothing queries it.

Binary Search Tree

What determines how many slots a Fenwick tree operation touches?

The set bits of the index. Prefix(i) reads one slot per set bit of i (popcount(i)), clearing the lowest set bit each step; Update(i, …) visits one slot per higher bit while adding the lowest set bit. Both are bounded by ⌊log₂ n⌋ + 1, and the bound is deterministic rather than amortized.

Fenwick Tree

Why can a Fenwick tree answer range sums but not range minimums?

A range is reconstructed only as Prefix(r) − Prefix(l − 1), which needs the aggregate to have an inverse. Sum has subtraction; minimum does not — min(1..r) and min(1..l−1) reveal nothing about min(l..r). Non-invertible aggregates therefore need a segment tree.

Fenwick Tree

Why is the array 1-indexed, and what breaks at index 0?

i & -i isolates the lowest set bit, but 0 has none, so 0 & -0 == 0. The update loop advances by i += i & -i, which never moves off 0, and the prefix loop uses i > 0 as its terminator. A 0-based layout stalls the walk immediately.

Fenwick Tree

How does a Fenwick tree handle a range update with a point query?

Not with the plain layout. It is built over a difference array: Update(l, +delta) and Update(r + 1, -delta) record the range increment, and a point query at i becomes Prefix(i). Range update with range query extends this to two BITs run in parallel.

Fenwick Tree

Why isn't a quadtree an O(log n) balanced search tree?

It has no balancing invariant — nothing forces the four subtrees to hold comparable amounts of data, and there are no rotations or fill rules. A point quadtree can become a chain through insertion order. A PR quadtree repeatedly subdivides the occupied region and needs an explicit finite-resolution or overflow rule; a region quadtree is bounded by raster resolution. The payoff is spatial pruning of regions during range and nearest-neighbor queries.

Quadtree

When would you reach for a geohash instead of a quadtree?

Use Geohash when spatial candidates must ride ordinary sorted indexes, cache keys, or shard prefixes and exact filtering can repair cell-boundary error. Use a quadtree when adaptive subdivision and tree traversal are the mechanism: collision broad-phase, nearest-neighbor pruning, or image-region compression. Geohash pays with neighboring-cell expansion; quadtree pays with pointer structure and distribution-dependent depth.

Quadtree

How does a point quadtree differ from a PR quadtree?

A point quadtree splits the plane at the coordinates of each inserted point (like a 2D BST), so its shape depends on insertion order and a bad order degrades it. A PR (point-region) quadtree always splits space into four equal quadrants independent of point coordinates, subdividing a bucket only when it overflows — so its shape depends solely on where the points are, not the order they arrived.

Quadtree

Why is a red-black tree's height at most 2·log₂(n+1)?

Equal black counts on every root-to-leaf path (invariant 4) make the all-black skeleton balanced, and “no red parent with a red child” (invariant 3) means reds can at most double a path by interleaving between blacks. The longest path is therefore at most twice the shortest, so height stays within 2·log₂(n+1).

Red-Black Tree

Why are new nodes inserted red rather than black?

A red node adds no black to any path, so it can only break the “no two reds” invariant, which is a local violation fixable near the insertion point. A black insert would add a black to one path only, breaking the equal-black-height invariant along an entire root-to-leaf path — a global violation that is far more expensive to repair.

Red-Black Tree

How can the rotation count per insert be bounded when the recoloring is not?

Recoloring only rewrites color bits and can propagate up to the root, but it never reshapes the tree. Once the fixup reaches a black uncle, one or two rotations resolve the violation and the loop terminates. So the structural work (rotations) is capped at two per insert while the unbounded work (recoloring) stays cheap.

Red-Black Tree

What makes red-black delete more error-prone than insert?

Insert only ever repairs a local red-red violation. Delete can remove a black node and break the black-height invariant along a whole path, producing “double-black” cases that branch on the sibling’s color and its children’s colors. A subtle mistake leaves BST order intact — so lookups still return correct results — while silently losing the height guarantee.

Red-Black Tree

How is a segment tree laid out in memory, and why 4n slots?

A flat array indexed heap-style: the root at 1, node i’s children at 2i and 2i + 1. A recursive build over a non-power-of-two n can reach index 4n − 1 on an unbalanced spine, so 4 * n is the safe allocation; a power-of-two n needs only 2n.

Segment Tree

Why does a range query cost O(log n) instead of touching every element in the range?

Any [l, r] decomposes into at most O(log n) canonical nodes whose ranges lie fully inside it. Each such node’s aggregate is already computed, so the query reads it and stops descending, never reaching the leaves beneath.

Segment Tree

What does lazy propagation defer, and how does that failure show up?

A range update stores a pending tag on each covering node instead of rewriting its whole subtree; the tag is pushed to children only when a later operation descends past that node. Forgetting a push-down returns a stale aggregate — a plausibly-wrong number, not a crash — which makes it the bug-prone part of the structure.

Segment Tree

Why does only the eq link advance to the next character?

Lo and Hi answer “is the current character smaller or larger than this node’s split?” — they move sideways within the BST of alternatives at the same string position. Eq fires only when the character matches the split, meaning that position is resolved, so it is the one link that steps forward to the next character. Counting eq links from the root gives a node’s character position exactly.

Ternary Search Tree

Why is a TST lookup O(L + log n) rather than the trie's O(L)?

A plain trie resolves “which child” with a single array index or hash, O(1) per character, giving O(L). A TST resolves it by descending a BST of the alternatives at that position, which costs O(log n) when balanced. Summed over the L matched positions the descents amortise, and the standard result is roughly L + log n character comparisons for a hit.

Ternary Search Tree

How does inserting keys in sorted order hurt a TST?

Each string position is a BST built by insertion. Feeding characters in ascending order at some position makes that BST a right-leaning chain, so the O(log n) descent degrades to O(n) and lookups approach O(L · n). Randomising the insertion order, or balancing the per-position BSTs, keeps the logarithmic factor — the same fix a degenerate Binary Search Tree needs.

Ternary Search Tree

What can a TST do that a Dictionary-backed trie cannot do cheaply?

Emit keys in sorted order without a separate sort (in-order lo/eq/hi traversal), and run near-neighbour queries — partial-match wildcards and edit-distance-one spell-check — by descending lo, eq, and hi together at a mismatch position. A hash-map trie has no ordering among children, so both require extra work it doesn’t natively support.

Ternary Search Tree

Which built-in .NET collection is closest to a self-balancing tree?

SortedSet<T> (and SortedDictionary<TKey, TValue> for key-value scenarios).

Trees

When would you avoid recursive tree traversal?

On unknown/deep depth, where iterative traversal with an explicit stack is safer.

Trees

Why is a trie lookup O(L) and not affected by the number of stored keys?

The walk follows one labelled edge per character of the query, so the work equals the key length L. The path a query traces is fixed by the query string; adding more keys creates other branches but never lengthens that path. A comparison tree, by contrast, reads the key O(log n) times as n grows.

Trie

How does the same walk serve both exact search and a prefix query?

Both follow the query’s characters edge by edge from the root. Exact search additionally requires the terminal node’s end-of-word flag, proving the path is a complete stored key. A prefix query stops at “did the path exist”, since reaching the node already certifies that at least one stored key starts with the fragment.

Trie

Why can deleting one key not simply remove the nodes along its path?

Nodes are shared. car and card share the c → a → r path, so deleting car must clear the r node’s end-of-word flag but keep the node, because d still descends from it. Only nodes that become both unflagged and childless may be pruned, walking up until that condition stops holding.

Trie

What does a radix (PATRICIA) trie change about the representation, and why?

It collapses each chain of single-child nodes into one edge labelled with the whole substring. The plain trie reserves a σ-wide child slot at every node, so long sparse keys waste memory on near-empty arrays; compressing the chains cuts node count while keeping the O(L) walk and the same prefix and ordering queries.

Trie
Heap-like

How does extract-min stay O(log n) when the heap is a forest rather than one tree?

The minimum is a root (heap order holds within each tree), so it is found by scanning the ≤ log n roots. Removing a root of order k exposes its k children, which already have orders k−1 … 0 — a valid binomial forest. Reversing them into a root list and melding that back costs another O(log n).

Binomial Queues

Why is insert amortized O(1) when its worst case is O(log n)?

Insert melds in a single B₀, which is a binary increment. A long carry chain that links at every order is the O(log n) worst case, but it can only happen because earlier cheap inserts left those orders filled. The potential argument that bounds a binary counter’s increment at amortized O(1) per operation applies directly, so m inserts cost O(m) total.

Binomial Queues

What does the laziness actually defer, and to where?

Insert, merge, and decrease-key avoid any tree reorganization: insert and merge only splice into the root list, and decrease-key cuts the node loose instead of sifting it. The deferred work — linking trees so degrees become distinct — is done once by the next extract-min during consolidation, so many cheap operations prepay one expensive cleanup.

Fibonacci Heaps

Why are the mark bit and cascading cut necessary for the O(1) decrease-key bound?

Cutting nodes out on decrease-key can thin a high-degree node until its subtree is almost empty, which would let consolidation link wide, shallow trees and blow past O(log n). Allowing each node to lose only one child before it too is cut guarantees a degree-k node keeps at least F(k + 2) descendants, capping the maximum degree at O(log n). The marks are the stored potential that funds the cascade, keeping decrease-key O(1) amortized.

Fibonacci Heaps

Why is find-min O(1) but extract-min O(log n)?

Heap order forces the global minimum to be a root, and the heap keeps a direct pointer to it, so find-min is a single read. Extract-min must remove that root, promote its children, and then consolidate the whole root list — linking equal-degree roots until degrees are distinct — which costs O(log n) amortized because the maximum degree is O(log n).

Fibonacci Heaps

Why can a complete binary tree be stored without any pointers?

Completeness means levels fill left to right with no gaps, so node positions are contiguous. A node at index i therefore has children at 2i+1 and 2i+2 and a parent at (i-1)/2, computed arithmetically. A tree with holes would break that indexing and force explicit links.

Heap

Why is build-heap O(n) while inserting n elements one by one is O(n log n)?

Bottom-up sift-down does at most h work for a node at height h, and there are only about n / 2^{h+1} nodes at that height. Summing h · n / 2^{h+1} over all heights converges to O(n); most nodes are leaves that move zero or one level. Repeated insertion instead sifts each new element up toward the root, costing O(log n) apiece.

Heap

Why does a plain binary heap need an external map to support decrease-key?

The heap exposes elements only by heap-order position, not by identity, and offers no way to locate an arbitrary value without an O(n) scan. A side table mapping each element to its current array index — updated on every swap — is required to point at the node before re-keying and sifting it in O(log n).

Heap

What does the leftist invariant bound, and how does that make merge logarithmic?

npl(left) ≥ npl(right) at every node forces the right spine to length ≤ log(n + 1), since a right spine of length r requires at least 2^r − 1 nodes. Merge recurses only down the two right spines, so it does O(log n) work in the worst case.

Leftist Heaps

Why swap children after each recursive merge step?

The recursive merge attaches the result as the right child, which may make the right subtree deeper than the left and violate the invariant. Swapping pushes the heavier subtree to the left — where no operation walks it — keeping the right spine short. Omitting the swap lets the right spine grow to O(n) and degrades every operation to linear.

Leftist Heaps

How do insert and extract-min reduce to merge?

Insert merges the heap with a single-node heap. Extract-min removes the root and merges its left and right subtrees. One correct merge implements the whole API and every operation inherits its O(log n) worst-case bound.

Leftist Heaps

How can O(log n) amortized hold when one merge can be O(n)?

A potential function counts heavy nodes — those whose right subtree outweighs their left. An expensive merge traverses many heavy nodes, but each unconditional swap makes a heavy node light. The costly traversal discharges potential accumulated by earlier cheap operations and leaves the heap cheap to merge again, so the per-operation cost averages to O(log n) even though a single call is not bounded by it.

Skew Heaps

Data Persistence

What isolation level should you use for a read-modify-write transaction, and why?

Match the protection to the invariant. Repeatable Read stabilizes repeated reads and may protect a same-row update under the engine’s semantics, but it does not generically prevent write skew. For a financial or inventory decision spanning rows or a predicate, use Serializable or explicitly lock the full invariant set. Optimistic row-version checks are a lower-contention option only when the transaction validates every record whose version informed the decision.

ACID

How does write-ahead logging (WAL) implement durability?

Before a changed data page may reach durable storage, the database must flush the WAL records needed to reconstruct it. Under a strict commit setting, the commit record is also flushed before success is acknowledged. Recovery replays WAL so committed changes missing from data pages are restored; engine-specific transaction metadata keeps uncommitted changes from becoming visible. Sequential log writes and group commit let data-page writes happen later without placing every commit behind a random page write.

ACID

How do you reduce cache stampede?

Add jitter to expirations so hot keys do not all expire simultaneously. Use request coalescing (singleflight pattern) so only one caller recomputes while others await the result. Consider stale-while-revalidate or background refresh to avoid blocking on recomputation entirely. HybridCache in .NET 9+ handles coalescing automatically.

Caching

Why is a bigger connection pool often worse, not better?

Each connection costs a server-side backend/worker plus memory, and once the database’s CPU and disk are saturated, additional concurrent queries just contend and add latency — throughput can actually drop. The right size is a small multiple of the database’s core count, sized against max_connections across the whole fleet, not maximized per app.

Connection Pooling

What causes connection-pool exhaustion and how do you fix it?

All connections are checked out and new requests block until timing out. Usual causes: a pool too small for real concurrency, connections leaked by missing Dispose()/using, or connections held across slow I/O or long transactions. Fixes: scope every connection with using, keep the checkout window to just the query, right-size the pool, and add a server-side pooler if the fleet is large.

Connection Pooling

Why is connection pooling hard in serverless environments?

Serverless platforms run many short-lived, isolated function instances that can’t share an in-process pool, and each may open its own connections — so a spike spawns thousands of connections and exhausts the database. The standard remedy is an external pooler (RDS Proxy, PgBouncer) that all instances share, multiplexing them onto a small set of real connections.

Connection Pooling

How should you choose between SQL and NoSQL for a new service?

  • Default to a relational database: mature tooling, ACID transactions, flexible ad-hoc queries, and joins cover the majority of workloads and let the schema enforce invariants
  • Reach for NoSQL when a specific access pattern dominates and relational cost is real: key-value or document stores for simple high-throughput lookups, wide-column for massive write volume, graph stores for relationship-heavy traversal
  • The honest driver is usually the data model and query pattern, not scale — most services never outgrow a well-indexed relational database, and “web-scale” is rarely the actual constraint
  • Combining them is common and often better than committing everything to one model: a relational system of record with a document or cache layer for a hot read path
Data Persistence

NoSQL

Why can a successful Elasticsearch write be invisible to search briefly?

The primary has accepted the operation and recorded it in its indexing/translog path, but search runs against opened Lucene segments. A refresh publishes recent segments to search. Durability acknowledgement and search visibility are separate boundaries.

Elasticsearch

Why use both text and keyword for one field?

text is analyzed into terms for relevance-ranked full-text matching. keyword keeps the exact value for equality filters, sorting, and aggregations. One representation cannot efficiently provide both semantics.

Elasticsearch

Why are SSTables immutable?

Immutability lets the engine create SSTables and compaction outputs with sequential file writes, then install the completed files without overwriting data that concurrent readers are using. Recovery still has work to do: replay the WAL into a memtable, read the manifest or version set to identify the live SSTables, and discard incomplete output files that were never installed. Dense, static files also compress well. The price is that updates and deletes write new versions and tombstones, so background compaction must reconcile versions and reclaim space.

LSM-Tree

What is the read-vs-write amplification tradeoff, and how does compaction strategy control it?

Write amplification is bytes written to disk per byte the app wrote; read amplification is disk reads per logical read; space amplification is on-disk bytes per byte of live data. Compaction cannot minimize all three at once. Size-tiered rewrites data rarely (low write amp) but leaves a key’s live version spread across tiers (high read and space amp). Leveled keeps non-overlapping ranges per level so a read hits at most one file per level (low read and space amp) but rewrites each key as it descends levels (high write amp). You choose which amplification the workload can afford.

LSM-Tree

When does a B-tree beat an LSM-Tree?

When reads dominate and latency must be predictable. A B-tree point lookup follows one root-to-leaf path; a range lookup follows that path to the first matching leaf, then traverses the required leaf pages. An LSM range scan instead performs a k-way merge across the memtable and every overlapping SSTable run, while point reads may probe several Bloom-filter candidates. That difference favors B-trees for read-heavy OLTP and range scans; LSM-Trees favor write-heavy ingest but pay multi-run read and background compaction costs.

LSM-Tree

How do you choose between the four NoSQL families?

By access pattern. Key-value when you only ever fetch by a single key (cache, session). Document when data forms self-contained aggregates with flexible schema (profiles, catalogs). Wide-column when you need extreme write throughput along a known partition key (time-series, logs). Graph when queries traverse relationships many hops deep (social, recommendations, fraud). The query shape, not the data size, drives the choice.

NoSQL Database Types

Why is NoSQL data modeling "query-first" instead of normalized?

There are no joins, so you can’t assemble data from many tables at read time cheaply. Instead you store data pre-shaped for each read — often duplicating it across multiple “tables”/documents (one per query). You trade storage and write-time duplication for fast, single-lookup reads. This is the opposite of relational normalization.

NoSQL Database Types

What does "polyglot persistence" mean and why is it common?

Using different databases for different jobs within one system — e.g. PostgreSQL as the system of record, Redis for caching/sessions, Elasticsearch for search, Neo4j for a recommendation graph. No single store is best at everything, so mature systems combine them, accepting the operational cost of running several.

NoSQL Database Types

Which NoSQL family fits a user-profile API with very frequent reads by user id?

  • Key-value or document store, because the access pattern is dominated by point reads on a single id.
  • Use key-value if it is almost entirely get/put by id with no rich querying.
  • Use document if you read/update an aggregate (profile + preferences) and occasionally query a few indexed fields.
  • A key-value API keeps point lookup semantics narrow; a document engine commonly adds secondary-query options at some indexing and storage cost. Exact latency and query support depend on the product.
NoSQL

When is NoSQL a bad idea?

  • When the core use case needs relational constraints and multi-entity ACID transactions, or queries are fundamentally join-heavy.
  • Forcing those onto NoSQL pushes join logic and consistency into application code, which is error-prone.
  • Often the better move is to keep SQL and add caching, read replicas, or a denormalized read model.
  • If the specialized store cannot enforce the required joins, constraints, or transaction boundary, its access-pattern advantage does not pay for moving those guarantees into application code.
NoSQL

Why does NoSQL push you toward denormalization and data duplication?

  • When the chosen store cannot execute an efficient join across the required data, the cheapest read is often one that fetches a whole aggregate in a single hit.
  • You then model that read shape explicitly, sometimes duplicating fields across documents or rows instead of normalizing them once.
  • That makes reads fast and partition-friendly but means a single logical change may touch many copies.
  • You accept write-side duplication and a synchronization policy in exchange for cheaper reads; whether copies may be temporarily inconsistent is a separate consistency decision.
NoSQL

When is Redis safe as a system of record?

Only when the deployment’s eviction, persistence acknowledgement, replication/failover loss window, backup, restore, and capacity contracts meet the data’s requirements. Enabling AOF is not enough: asynchronous failover can still lose acknowledged writes, disk and rewrite failures still need handling, and maxmemory must not evict authoritative keys.

Redis

Why can a big key hurt Redis even with appendfsync everysec?

The main thread still receives, parses, mutates, and copies the large command into AOF and replication buffers. Background fsync removes one disk wait from the request path; it does not remove network, allocator, serialization, copy-on-write, rewrite, or deletion work.

Redis

ORMs

How does EF Core's change tracker work, and when should you disable it?

  • EF Core takes a snapshot of each loaded entity’s property values. On SaveChangesAsync(), it compares current values to the snapshot and generates SQL for changed properties.
  • Overhead: change tracking adds memory (snapshot storage) and CPU (comparison on save) per tracked entity.
  • Disable with .AsNoTracking() for read-only queries (reports, API responses that don’t modify data). This is the single most impactful EF Core performance optimization for read-heavy workloads.
  • Tradeoff: .AsNoTracking() entities cannot be modified and saved — you must re-attach them or use ExecuteUpdateAsync() for bulk updates.
Entity Framework

What is the N+1 query problem and how do you detect it?

  • N+1 occurs when loading N entities and then accessing a navigation property on each, triggering N additional queries.
  • Detection: enable EF Core query logging (LogTo(Console.WriteLine)) or use MiniProfiler/Application Insights to see query counts per request.
  • Fix: use Include() for eager loading, or split into two queries and join in memory for large result sets.
  • Tradeoff: Include() generates a JOIN, which can produce a large result set if the included collection is large. For collections with >100 items per parent, consider AsSplitQuery() to use separate queries instead of a JOIN.
Entity Framework

SQL

Why is a saga not equivalent to a cross-shard ACID transaction?

A saga commits each local step independently and compensates later. Other transactions can observe intermediate states, and compensation may not restore the exact prior world. It is a workflow consistency model, not distributed isolation.

Cross-Shard Operations

How can a system enforce a globally unique username across shards?

Route the username to one reservation authority, conditionally insert it there, and use that reservation as the operation’s idempotent proof before creating the user on the owning shard. A unique index on each user shard proves only per-shard uniqueness.

Cross-Shard Operations

When would you use an Update (U) lock instead of a Shared (S) lock?

During the search phase of a read-then-write operation such as an UPDATE or DELETE that must scan to find the row it will modify. If that scan took an S lock, two transactions could both hold S on the target row and then both try to upgrade S→X, each blocked by the other’s S — an instant deadlock. A U lock prevents it: only one transaction may hold U on a resource at a time (so the upgrade race can’t form), yet U is compatible with existing S locks (so it doesn’t block concurrent readers during the scan). When the write finally happens, U converts to X.

Database Locks

Locks vs MVCC — how does each enforce isolation?

Locking blocks incompatible access, so a reader may wait for a writer under lock-based isolation. MVCC instead keeps older committed versions and can serve the reader from a snapshot, removing most read-write blocking. MVCC is not inherently optimistic: the same engine can use versioned reads, pessimistic write locks, and optimistic version checks. Locks add waiting and can deadlock; MVCC adds version bookkeeping (PostgreSQL’s VACUUM, SQL Server’s tempdb version store).

Database Locks

Why do intent locks exist, and what does lock escalation trade away?

Intent locks (IS/IX/SIX) let a hierarchical lock manager detect conflicts across granularities cheaply: before locking a row, a transaction places an intent lock on the table above it, so a request for a table-level lock can check that single intent lock instead of scanning every row lock. Lock escalation goes the other way — it collapses thousands of fine row/page locks into one coarse table lock to reclaim lock-manager memory. The trade is concurrency: the escalated transaction now holds the whole table, so unrelated transactions that only wanted other rows suddenly block — the “concurrency cliff.”

Database Locks

Why is “use a hash index because lookup is O(1)” incomplete database advice?

The engine must support the hash index for the target table and operator, equality is the only useful ordering relation, and collisions, bucket growth, concurrency, durability, and cache behavior still affect cost. A B+ tree often wins for mixed equality and range work because one structure supports both.

Indexes

Why can a low-cardinality column still participate in a useful index?

A filtered index can store only the rare subset, a composite key can use the column after a useful leading prefix, and a covering index can avoid base-row reads. Measure the complete query plan; cardinality is evidence, not a standalone rule.

Indexes

What is normalization and why do most systems stop at 3NF/BCNF?

Normalization eliminates redundancy by decomposing tables so each fact is stored once, preventing update anomalies. Most production OLTP systems stop at 3NF or BCNF because higher forms (4NF, 5NF) address rare anomalies (multivalued and join dependencies) at the cost of more tables and more joins. The decomposition overhead rarely pays off outside temporal or analytical databases.

Normalization Denormalization

When would you denormalize a table, and what risks does it introduce?

Denormalize when a read-heavy workload has expensive joins that indexes can’t fix: common in reporting, analytics, or high-throughput APIs. Risks: update anomalies (copies get out of sync), data inconsistency if writes don’t update all copies atomically, and increased write complexity. Always measure before denormalizing; premature denormalization adds maintenance cost for no proven benefit.

Normalization Denormalization

What is the difference between 2NF and 3NF, and how would you recognize a violation of each?

2NF eliminates partial dependencies: every non-key attribute must depend on the entire composite primary key, not just part of it. A violation looks like a column that depends on only one column of a multi-column PK. 3NF eliminates transitive dependencies: non-key attributes must depend directly on the PK, not through another non-key attribute. A violation looks like column A depending on the PK, column B depending on A (not on the PK directly). Fixing both involves decomposing the offending columns into separate tables.

Normalization Denormalization

What are the three replication lag anomalies, and how do you mitigate each?

  • Read-your-writes: user writes then reads from a stale replica and sees their write missing. Fix: route post-write reads to the leader for a short window, or use LSN/timestamp tracking to wait for the replica to catch up.
  • Monotonic reads: user sees newer data, then an older replica. Carry the highest observed position or causal token and require the next source to have reached it; stickiness alone fails after replica failover.
  • Consistent prefix reads: a dependent write appears before its prerequisite. Preserve one ordered stream or causal dependency token and read from a position that includes the prerequisite; a shared partition key helps only when its ordering contract covers the failover path.
Replication

When would you choose synchronous vs asynchronous replication?

  • Synchronous: when acknowledged commits must survive failover. The leader confirms only after the configured standby acknowledgement, and failover must select an eligible durable standby. This protects acknowledged durability; replica reads still need a replay-position check or primary routing to observe the commit.
  • Asynchronous: when avoiding the standby round trip justifies weaker failover durability. The leader does not wait for a standby, and replicas may be current or lagging. An acknowledged commit can be lost on failover only if no eligible surviving node received it.
  • SQL Server Always On configures synchronous-commit or asynchronous-commit per availability replica. PostgreSQL uses synchronous_standby_names to select synchronous standbys and synchronous_commit to choose the acknowledgement boundary; standbys not selected for synchronous confirmation remain asynchronous.
Replication

How does split-brain occur and how is it prevented?

  • A network partition isolates the primary from the rest of the cluster. A quorum of remaining nodes elects a new primary. When the partition heals, both nodes believe they are primary and have accepted divergent writes.
  • A majority vote prevents two candidates in the same voting configuration from both being elected. Fencing separately terminates or revokes the old primary before the new one accepts writes.
  • WSFC uses quorum witnesses; Patroni uses distributed coordination in systems such as etcd or Consul. Without endpoint revocation or fencing, election quorum alone is insufficient because a slow former leader can keep serving writes after losing authority.
Replication

Should the most selective equality column always lead a composite index?

No. If all equality columns are constrained, their internal order often matters less than which leading prefix other important queries can reuse. Choose from the workload’s prefix and ordering requirements, then verify selectivity and estimates in the actual plan.

Rowstore Index Design

When should a selected column be included instead of keyed?

Include it when the query needs the value but its ordering does not help a seek, join, grouping, or required sort. Keep it in the key when its position supplies navigation or order. Either choice still has storage and write cost.

Rowstore Index Design

What is the difference between WHERE and HAVING?

WHERE filters rows before grouping and cannot use aggregate results. HAVING filters groups after GROUP BY and can use aggregates such as COUNT(*). Put non-aggregate predicates in WHERE so fewer rows enter grouping.

SQL

What is a stored procedure and how is it different from a function?

A stored procedure can run multi-step data-changing logic and return result sets or output parameters. A function returns a scalar or table value for use in a query and is constrained by the engine’s function rules. In SQL Server, eligible scalar UDFs can be inlined; older or ineligible scalar UDFs may execute row by row and inhibit parallel plans.

SQL

What is a Common Table Expression (CTE) and when should you use a temp table instead?

A CTE is a statement-scoped named query expression. Do not assume it is materialized or reused. Use a temp table when you need a stable intermediate result, indexes on that result, or guaranteed reuse across several operations.

SQL

What are SQL Server transaction isolation levels?

SQL Server provides READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE, and SNAPSHOT. Read Committed Snapshot Isolation changes READ COMMITTED reads to statement-level row versions. NOLOCK is not a performance switch: it permits rolled-back, missing, and duplicate observations.

SQL

When should you shard a database?

After evidence shows that write throughput or storage exceeds one ownership domain and simpler measures cannot remove the ceiling. Read replicas help reads, caches remove repeated reads, and in-engine partitioning improves manageability without creating cross-database transactions.

Sharding

How much data should move when one equal node is added to a balanced consistent-hash cluster?

With N balanced equal-capacity nodes before the addition, the new node’s expected final share is about 1 / (N + 1), so that is the expected movement. Token imbalance changes the actual fraction; virtual nodes reduce variance.

Sharding

What makes a useful shard key?

It distributes bytes and request load, appears in the dominant operations, co-locates data that must transact together, and remains stable. High cardinality is useful only when the traffic and tenant-size distribution are also balanced.

Sharding

Networks

How do you choose between REST/HTTP, gRPC, and WebSockets for service-to-service communication?

  • REST over HTTP is the default for public and cross-team APIs: cacheable, debuggable with ordinary tools, and universally supported, at the cost of verbose payloads and request/response-only semantics
  • gRPC wins for high-volume internal calls: binary Protobuf encoding, HTTP/2 multiplexing, and generated typed clients cut latency and boilerplate, but it needs tooling and is awkward to call from a browser
  • WebSockets fit genuinely bidirectional workloads like chat and collaborative editing; Server-Sent Events fit one-way server push — live updates, notifications — where the alternative is the client polling
  • The deciding questions are audience (public vs internal), traffic shape (request/response vs streaming), and whether human-debuggable payloads are worth the extra bytes
Networks

What's the practical difference between a Layer 4 and a Layer 7 load balancer?

An L4 balancer routes by IP and port only — it’s fast and protocol-agnostic but can’t see inside the connection, so it distributes whole TCP connections (and pins all multiplexed HTTP/2/gRPC streams to one backend). An L7 balancer parses the application protocol (HTTP), so it can route by URL/host/header, balance individual requests, terminate TLS, and retry — at higher per-request cost. The choice hinges on whether you need application-aware routing.

OSI Model

How does the OSI model map onto the actual TCP/IP stack?

TCP/IP has four layers and folds OSI 5–7 into a single Application layer: Application (HTTP/gRPC/TLS/DNS) ↔ OSI 5–7, Transport (TCP/UDP) ↔ OSI 4, Internet (IP) ↔ OSI 3, Link (Ethernet/Wi-Fi) ↔ OSI 1–2. In conversation, “Layer 4” means TCP/UDP and “Layer 7” means HTTP-level protocols.

OSI Model

At which layer do IP addresses, ports, and MAC addresses each operate?

MAC addresses at Layer 2 (Data Link) identify a NIC on a local link; IP addresses at Layer 3 (Network) identify a host across networks and enable routing; ports at Layer 4 (Transport) identify the specific process/socket on that host. A connection is uniquely identified by the L3+L4 tuple (src IP:port, dst IP:port).

OSI Model

Architecture & Ops

Why does a CDN reduce latency, and what determines a cache hit?

Latency is dominated by distance (round-trip time grows with how far light/packets travel). A CDN serves content from an edge node geographically near the user instead of a distant origin, cutting RTT. Whether a request is a hit depends on the cache key (usually the URL) and freshness: if a fresh copy exists per the Cache-Control/ETag headers, it’s served from the edge; otherwise the edge fetches from origin (a miss) and caches the result.

CDN

How do you push a change live when content is cached on a CDN?

Either purge/invalidate the specific URL or cache tag (explicit but rate-limited and not instantaneous everywhere), or — better for static assets — use content-hashed, immutable URLs so a new version is a brand-new URL that nothing stale points to. For HTML that references those assets, keep a short TTL or no-cache so it picks up the new asset URLs promptly.

CDN

What content should you NOT cache on a CDN, and why?

Per-user or sensitive responses (anything containing a logged-in user’s data, or behind auth) must not sit in a shared edge cache, or one user’s data can be served to another. Mark them Cache-Control: private / no-store, or only cache with a per-user cache key. Static, public, identical-for-everyone content is the ideal CDN candidate.

CDN

Why is consistency hard in P2P systems?

P2P systems have no central authority to act as the source of truth. When peers hold different versions of data and there is no coordinator to resolve conflicts, the system must rely on eventual consistency — peers eventually converge to the same state through gossip or reconciliation protocols. This is acceptable for file distribution (BitTorrent) or content-addressed storage (IPFS) where data is immutable, but it makes P2P unsuitable for systems requiring strong consistency (financial transactions, inventory).

Peer-2-Peer

How does WebRTC use P2P, and what is the role of STUN/TURN servers?

WebRTC establishes direct peer connections between browsers for audio, video, and data. The challenge is NAT traversal: most browsers are behind NAT and don’t have public IP addresses. STUN servers help peers discover their public IP/port. When direct connection fails (symmetric NAT), TURN servers relay traffic. The goal is to minimize relay usage — direct P2P connections reduce latency and server cost; TURN relay is the fallback.

Peer-2-Peer

What is split tunneling and when would you use it?

Split tunneling routes only traffic destined for private resources through the VPN; internet traffic goes directly. Use it to reduce gateway bandwidth cost and latency for internet-bound traffic. Avoid it when corporate policy requires all traffic to pass through security inspection (content filtering, DLP).

VPN

Why does WireGuard have a smaller attack surface than IPsec?

WireGuard is ~4,000 lines of code (auditable by a small team) vs IPsec’s ~400,000 lines. Fewer lines means fewer potential vulnerabilities. WireGuard also uses a fixed, modern cryptographic suite (ChaCha20, Curve25519, BLAKE2) with no negotiation — removing cipher selection as an attack vector.

VPN

What causes DNS leaks in a VPN setup, and how do you fix them?

DNS queries bypass the encrypted tunnel — typically because the OS sends DNS requests to the default interface rather than the VPN interface. Fix: route DNS through the tunnel by setting DNS to an internal server and ensuring DNS requests use the VPN’s routing table, or by running a local DNS resolver that forces all queries through the tunnel.

VPN

Protocols

Why does DNS migration feel delayed?

Because recursive and client caches hold old TTLs; lowering TTL at cutover does not rewrite already-cached answers instantly.

DNS

Why can authoritative data be correct while clients still use old IPs?

Those clients or resolvers still hold old cached records; the old value remains valid until TTL expiry and resolver policies permit refresh.

DNS

What problem does HTTP/2 multiplexing solve compared to HTTP/1.1?

HTTP/1.1 has no independent request streams. Optional pipelining permits multiple outstanding requests on one connection, but responses must return in request order, so a slow earlier response delays later ones. Browsers typically work around this with 6–8 parallel connections. HTTP/2 multiplexes independent streams on one connection, removing that response-order dependency and reducing connection overhead. Cost: a single TCP connection means TCP-layer packet loss affects all streams simultaneously.

HTTP 2

Why does HTTP/2 still have head-of-line blocking?

HTTP/2 removes HTTP/1.1’s response-order dependency but not TCP’s connection-wide ordered delivery. A missing TCP segment can delay later data for every stream on that connection. HTTP/3 limits transport loss recovery to the affected QUIC stream, while ordered delivery still applies inside that stream and shared compression/control dependencies can introduce other waits.

HTTP 2

When would you choose HTTP/1.1 over HTTP/2?

When the network has high packet loss (mobile, satellite) and you cannot use HTTP/3 — multiple HTTP/1.1 connections isolate packet loss better than a single HTTP/2 connection. Also when connecting to legacy servers or proxies that don’t support HTTP/2.

HTTP 2

What does a retry-time 412 mean for a conditional PUT?

The first request may already have succeeded and changed the entity tag. The retry is allowed because PUT is idempotent, but its 412 cannot distinguish a successful first attempt from another writer’s update. Read the current representation and reconcile before choosing a new precondition.

REST

When should POST be idempotent in practice?

Only when the client sends a durable idempotency key and the server enforces dedupe boundaries; otherwise POST can create duplicates.

REST

Why is RPC's 'local call' abstraction considered leaky?

RPC hides the network boundary, but the network introduces failures that local calls never have: calls can time out, be delivered twice (at-least-once delivery), or fail with the server having already executed the operation. A failed RPC does not mean the server didn’t execute — the response may have been lost. This forces callers to implement idempotency keys, retries with backoff, and circuit breakers — concerns that don’t exist for local calls. The abstraction is useful but must not be trusted blindly.

RPC

When should you choose gRPC over REST?

Choose gRPC for internal service-to-service communication where: (1) you need high throughput or low latency (binary Protobuf is 3-10x smaller than JSON), (2) you need bidirectional streaming, or (3) you want strongly typed contracts enforced at compile time. Choose REST for public APIs, browser clients (gRPC requires a proxy for browsers), or when human-readable payloads matter for debugging. The key constraint: gRPC requires HTTP/2 and a Protobuf toolchain; REST works with any HTTP client.

RPC

Why does SPF/DKIM/DMARC improve delivery but not guarantee it?

They verify different authenticity and alignment properties, but inbox placement still depends on reputation, engagement, anti-abuse decisions, and content quality.

SMTP

How is a WebSocket different from a raw TCP socket?

A raw socket is an OS-level endpoint where the application defines framing and handshakes, and ordinary browser JavaScript cannot open one. WebSocket standardizes messages and control frames and is exposed through the browser WebSocket API. It commonly uses HTTP/1.1 Upgrade over TCP and can also use HTTP/2 Extended CONNECT.

WebSockets

Why does a WebSocket connection start as an HTTP request?

So it can reuse origins, TLS, proxies, load balancers, and browser security policy on ports 80/443. HTTP/1.1 uses 101 Switching Protocols; HTTP/2 uses a successful Extended CONNECT stream. Both then carry WebSocket frames under the negotiated transport.

WebSockets

When would you choose Server-Sent Events over WebSockets?

When you only need server→client push (live feeds, notifications, progress updates) and not a client→server channel. SSE runs over plain HTTP, is simpler, and has automatic reconnection built into the browser EventSource API. Choose WebSockets when you need true bidirectional, low-latency messaging.

WebSockets

Why does gRPC not work well with L4 load balancers, and how do you fix it?

Expected answer:

  • gRPC multiplexes all calls over a single HTTP/2 TCP connection.
  • L4 load balancers distribute at the TCP connection level — they cannot see individual HTTP/2 streams within that connection.
  • All calls from one client land on the same backend, defeating load distribution.
  • Fix: use an L7 proxy (Envoy, Linkerd, YARP) that terminates HTTP/2 and distributes individual streams across backends. Or use client-side load balancing with service discovery.

Why this matters: the most common production surprise when adopting gRPC; tests understanding of HTTP/2 multiplexing at the transport layer.

gRPC

What happens if you call a gRPC service without setting a deadline?

Expected answer:

  • The call has no timeout and can hang indefinitely.
  • Resources (threads, sockets, memory) on both client and server are consumed with no bound.
  • In a microservice chain, one hanging call can exhaust connection pools upstream, causing cascading failures across services.
  • gRPC intentionally has no default deadline because the right value depends on the operation.
  • Use EnableCallContextPropagation() to automatically forward deadlines through a service chain.

Why this matters: deadlines are the single most important production gRPC configuration; missing them is the top cause of gRPC-related outages.

gRPC

Why is renaming a proto field safe but renumbering it is not?

Expected answer:

  • Protobuf binary encoding uses the field number as the wire identifier, not the name.
  • Renaming a field changes only the generated code accessor — the wire format is unchanged, so old and new clients interoperate seamlessly.
  • Renumbering changes the wire identity — old clients sending the old number will have their data silently interpreted as the new field by the updated server.
  • When removing fields, use reserved to prevent the number and name from being reused in future schema changes.

Why this matters: proto versioning is the contract management layer of gRPC; getting it wrong causes silent data corruption that is extremely hard to debug.

gRPC

Transport & Sockets

Why do partial reads happen with TCP sockets?

TCP is a byte stream protocol. The network may deliver data in smaller chunks than the sender wrote. A single ReadAsync call returns however many bytes are available at that moment, which may be less than the full message. Applications must loop until the expected number of bytes is received.

Sockets

What is the difference between Socket, TcpClient, and TcpListener?

Socket is the low-level OS abstraction supporting TCP, UDP, and other protocols. TcpClient wraps a TCP socket and exposes a NetworkStream for stream-based I/O. TcpListener wraps a server-side TCP socket and provides AcceptTcpClientAsync for accepting connections. Use TcpClient/TcpListener for most TCP work; drop to Socket when you need protocol-level control.

Sockets

When would you choose UDP over TCP for a production system?

When latency matters more than delivery guarantees and the application can tolerate or recover from packet loss. Examples: real-time game state updates (stale frames are discarded anyway), DNS queries (fast retry is cheaper than TCP handshake), telemetry/metrics (occasional loss is acceptable). The application must implement its own reliability if needed.

Sockets

Why does TCP's three-way handshake exist, and when does its latency cost become a real problem?

  • The handshake synchronizes sequence numbers and confirms both sides are reachable before sending data.
  • It adds one full round-trip of latency before the first byte of application data can be sent.
  • For short-lived connections (single HTTP request, DNS-over-TCP), handshake latency dominates total request time.
  • HTTP/2 multiplexing amortizes the handshake across many requests on one connection. QUIC combines transport and TLS setup; a new connection normally needs one round trip, and 0-RTT is available only for resumptions whose replay risk the application accepts.
  • The handshake guarantees reliable setup but costs a full round-trip; reach for connection pooling or QUIC when that overhead is unacceptable.
TCP IP

How do flow control and congestion control differ, and what happens when you confuse them?

  • Flow control protects the receiver: the receive window limits how much unacknowledged data the sender can push.
  • Congestion control protects the network: the congestion window limits send rate based on detected packet loss.
  • The effective send rate is the minimum of both windows.
  • Tuning only one side causes problems: a large receive window with aggressive congestion control still causes network drops; a small receive window with conservative congestion control wastes available bandwidth.
  • Flow control is endpoint-local and deterministic; congestion control is network-wide and heuristic — tune them together or one silently caps the other.
TCP IP

When is UDP preferable to TCP, and what reliability mechanisms do applications add on top?

UDP is preferable when the application benefits from lower connection-establishment and transport-retransmission overhead and can own recovery. DNS retries lost queries; games and telemetry can sequence, acknowledge, and retry critical events; stale media or state samples can be dropped. Applications may also add FEC (Forward Error Correction) for loss recovery. QUIC is the canonical example — it builds reliable ordered streams on UDP while avoiding TCP’s head-of-line blocking.

UDP

Why does UDP have no congestion control, and what are the consequences?

UDP sends at whatever rate the application dictates. Under congestion, UDP traffic doesn’t back off — it can starve TCP connections sharing the same link. Applications using UDP for sustained traffic must implement congestion control (QUIC does this). Small datagrams are not inherently harmless: many DNS, telemetry, or game-state packets can aggregate into a high packet and byte rate, so pacing must account for the whole flow.

UDP

What is QUIC and why is it built on UDP rather than TCP?

QUIC (HTTP/3) implements reliable, ordered, multiplexed streams on top of UDP. It’s built on UDP to avoid TCP’s head-of-line blocking (a lost TCP segment blocks all streams; QUIC streams are independent), to enable faster connection establishment (0-RTT resumption), and to allow protocol evolution without OS kernel changes. TLS 1.3 is built into QUIC — there’s no separate TLS handshake.

UDP

Architecture

How do you choose between a monolith, a modular monolith, and microservices?

  • The decision is mainly organizational: microservices buy independent deployment and team autonomy at the cost of network calls, distributed failure modes, and operational overhead
  • A modular monolith captures most of the boundary benefits — clear module seams, enforced dependencies — while keeping one deployable and in-process calls; it’s the right default for most teams
  • Reach for microservices when independent scaling or deployment or team boundaries genuinely demand it, not because it’s fashionable, and rarely before you understand the domain boundaries
  • Migration is one-directional and cheap the right way round: a well-structured modular monolith can later be carved into services along its existing seams
Architecture

Application Architecture

How does Clean Architecture differ from traditional N Layer, and when does the extra indirection pay off

  • N Layer often keeps downward dependencies from UI to business to data, while Clean Architecture enforces inward dependencies toward policy.
  • In Clean Architecture, inner layers define contracts and outer layers implement them, so domain logic survives framework changes.
  • The indirection pays off when business rules are complex, testing speed matters, and infrastructure churn is expected over multiple years.
  • For short lived services with shallow rules, the same indirection can slow delivery with little resilience gain.
  • Tradeoff statement: Clean Architecture buys adaptability and testability by paying upfront complexity and wiring overhead.
Clean Architecture

What happens when you apply Clean Architecture to a simple CRUD service

  • You often create command handlers ports and adapters that do little more than pass data through, so cycle time drops without stronger correctness.
  • Teams spend effort maintaining abstractions instead of shipping user value because domain complexity never materializes.
  • Operationally it can still work, but the architecture tax shows up in onboarding and debugging cost.
  • A pragmatic approach is to keep boundaries light and evolve toward stricter clean boundaries only where invariants and integration volatility appear.
  • Tradeoff statement: using full Clean Architecture too early protects against hypothetical future change while charging real present complexity.
Clean Architecture

What is the Dependency Rule and why does it matter?

All source code dependencies must point inward — toward higher-level policies (domain). Outer layers (infrastructure, UI) depend on inner layers; inner layers never depend on outer layers. This means you can change databases, frameworks, or delivery mechanisms without touching business rules. Cost: requires defining interfaces in inner layers and wiring implementations in outer layers — more upfront structure.

Layered Architecture

What is the difference between traditional layered and Onion/Clean Architecture?

Traditional layered: UI → Business Logic → Data Access → Database. Changing the DB affects everything above. Onion/Clean: Infrastructure → Application → Domain. The Domain has zero dependencies; Infrastructure implements Domain interfaces. The key difference is dependency inversion at the data access boundary.

Layered Architecture

What is the key difference between MVC and MVVM?

In MVC, the Controller handles requests and explicitly selects which View to render — the View is passive. In MVVM, the View binds to the ViewModel’s observable state — the ViewModel does not know about the View. MVVM enables automatic UI updates via data binding; MVC requires the Controller to pass data to the View on each request. Cost: MVVM requires binding infrastructure (INotifyPropertyChanged, ICommand) which adds boilerplate; MVC is simpler for stateless request/response flows.

MVC MVVM

Why is the ViewModel more testable than the Controller?

The ViewModel has no dependency on HTTP context, routing, or view rendering — it is a plain C# class. You can unit-test it by calling commands and asserting property values. The Controller depends on HttpContext, IActionResult, and the MVC pipeline, requiring more setup or integration tests.

MVC MVVM

How do you prevent version conflicts between plug-ins that depend on different versions of the same library?

Use AssemblyLoadContext to isolate each plug-in’s dependencies. Each context has its own assembly resolution scope, so plug-in A’s dependency on Newtonsoft.Json 12.x does not conflict with plug-in B’s dependency on 13.x. Cost: each context adds memory overhead and prevents type sharing across contexts — objects from one context cannot be cast to types from another.

Plug-in Architecture (MicroKernel)

How do you version extension point interfaces without breaking existing plug-ins?

Treat the extension point interface as a public API with semantic versioning. For breaking changes, introduce a new interface version (IPlugin, IPlugin2) and support both simultaneously via an adapter. Existing plug-ins implement the old interface; new plug-ins implement the new one. The core adapts old plug-ins to the new contract. Cost: adapter proliferation over time — set a deprecation timeline and remove old interfaces after a migration window.

Plug-in Architecture (MicroKernel)

When is plug-in architecture the wrong choice?

When all features are known upfront and no third-party extensibility is needed. The loading complexity, versioning challenges, and security surface (untrusted code running in-process) are not justified for internal applications. A monolith with feature flags is simpler and safer. Plug-in architecture earns its complexity when customers or third parties need to extend the product without modifying the core.

Plug-in Architecture (MicroKernel)

Distributed Systems

How do you design gateway aggregation endpoints for client efficiency, and what do you keep out of the gateway?

Use gateway routing for normal traffic and add a few targeted aggregation endpoints where a client — usually mobile — would otherwise make five round trips for one screen. The gateway composes those reads and tunes the payload, but it stays thin: auth, throttling, routing, transformation, observability, and nothing else. Business rules, transactions, and domain invariants live in the backend services, with correlation IDs flowing across the fan-out so you can trace a slow screen. The line to hold: aggregation is response-shaping, not orchestration — the moment decision logic creeps in, you have a distributed monolith.

API Gateway

When would you choose a single API gateway versus a BFF approach?

A single gateway is simpler to run and the right default when clients have similar needs and ship on a shared cadence. Reach for BFF — a gateway or route set per client type — when web, mobile, and partner clients want materially different payloads, auth, or latency profiles, or when separate teams own them and want deployment autonomy. The catch is operational: every extra gateway is one more thing to deploy, monitor, and secure. Start with one and split to BFF only when client divergence or team boundaries actually justify the cost.

API Gateway

Where do API Gateway and service mesh responsibilities belong in one architecture?

They handle different traffic planes, so they sit side by side rather than compete. The gateway owns north-south traffic — clients entering the system — so edge auth, TLS termination, external rate limits, and API surface control belong there. The mesh owns east-west traffic between internal services: mTLS, retries, traffic shifting, and per-service telemetry. The gateway guards the front door; the mesh governs the hallways.

API Gateway

If CAP is only about partitions, why do we still tune consistency levels on healthy clusters?

Because CAP only describes the partition case, and partitions are rare — PACELC covers the other 99% of the time. Its “ELC” half says that even with healthy links (Else), a replicated store still trades Latency against Consistency: wait for a quorum to confirm and you get fresher, better-ordered reads at higher latency; answer from the nearest replica and you get speed with a chance of staleness. That is exactly what read-consistency levels, session guarantees, quorum sizes, and timeout tuning are dialing in. CAP tells you how to fail; PACELC tells you what you pay every day.

CAP theorem

Is a system "CP" or "AP" as a whole?

Don’t think of it system-wide — decide per operation, because different endpoints have different correctness budgets. A PlaceOrder or ledger write wants CP: refuse it under partition rather than risk split-brain or a double charge. A GetRecommendations read wants AP: keep serving slightly stale data because availability beats freshness there. A profile read might only need session consistency. So the same system is CP on some paths and AP on others; labeling the whole platform and applying one rule everywhere is the mistake. Map each operation to its business invariant and its allowed staleness window.

CAP theorem

Why is "pick two of three" a misleading way to state CAP?

Because in any system that replicates data across machines, partition tolerance isn’t something you opt out of — networks drop packets and isolate nodes whether you like it or not. So “CA” isn’t really on the menu: the moment a partition hits, a system that didn’t plan for it just stops being correct or stops responding. The honest framing is that P is a given, and the real decision is what you do during a partition — sacrifice consistency to stay available (AP), or sacrifice availability to stay consistent (CP). With no partition you can have both; CAP only forces the choice inside the failure window.

CAP theorem

How do you guarantee read-your-writes when writes go to a strong store but reads come from an eventually consistent cache?

The problem is the gap between a committed write and a cache that hasn’t caught up. The cleanest fix is to route that user’s immediate post-write reads past the cache to a strong or session-consistent path, just while freshness matters. Pair it with write-through or explicit invalidation on update so the cache converges quickly, and carry a version or timestamp so you can reject a stale follow-up write. Keep the writes idempotent so a retry is harmless. You don’t need global strong consistency — only read-your-writes for the one user who just acted.

Consistency Models

Which chat features should use linearizable, causal, and eventual consistency?

Map each feature to the weakest model that still feels correct. Message ordering and reply chains need at least causal consistency — a reply must never show up before the message it answers. Typing indicators can be eventual; a dropped or reordered one costs nothing, and the latency win is worth it. Read receipts in a compliance-sensitive context might justify something stronger. The skill on display is refusing a one-size-fits-all level: spend strong-consistency latency only where a user would actually notice the inconsistency.

Consistency Models

Why can linearizability reduce availability during a network partition, and how do you contain the blast radius?

Linearizable operations need a quorum to agree on one order, and a partition can cut a node off from that quorum. To stop two sides committing conflicting truths, the isolated side must reject or delay those operations — that refusal is the availability you lose (the CP corner of CAP). Contain the blast radius by scoping strong consistency to the writes that truly need it — payments, inventory decrements — and serving everything else from weaker models with explicit degraded-mode behavior. Partition tolerance isn’t optional; the real choice is which operations fail closed and which keep serving.

Consistency Models

Why is 2PC problematic in microservices?

  • 2PC requires all participants to hold locks during the prepare-to-commit window. In microservices, this window spans network calls, making lock duration unpredictable.
  • The coordinator is a single point of failure. A crash after PREPARE but before COMMIT leaves participants locked indefinitely.
  • Most microservice infrastructure (HTTP APIs, NoSQL, cloud queues) doesn’t support XA transactions.
  • Tradeoff: 2PC gives strong consistency but at the cost of availability and throughput. CAP theorem: under a partition, you must choose consistency or availability — 2PC chooses consistency.
Distributed Transactions

How does the Outbox pattern guarantee at-least-once event delivery?

  • The event is written to an OutboxMessages table in the same DB transaction as the domain change. If the transaction commits, the event is durable.
  • A background worker reads unprocessed outbox entries and publishes them to the broker, retrying until the broker acknowledges.
  • This guarantees at-least-once delivery (the event may be published more than once if the worker crashes after publishing but before marking the entry as processed).
  • Consumers must be idempotent to handle duplicate events.
  • Tradeoff: adds a background worker and an extra DB table. The cost is worth it for any event that must not be lost.
Distributed Transactions

Why is idempotency essential for reliable retry strategies, and what happens without it?

  • Retries are a certainty in distributed systems because clients cannot always distinguish failure from delayed success.
  • Idempotency converts ambiguous outcomes into deterministic behavior, so retries do not amplify side effects.
  • Without it, transient faults become correctness bugs such as duplicate charges or duplicate shipment commands.
  • Idempotency also improves operational recovery because replay jobs and dead letter reprocessing become safe.
  • Stronger idempotency guarantees cost extra storage, key lifecycle management, and stricter request validation.
Idempotency

How do you implement idempotency for a payment endpoint that processes charges through a third party provider?

  • Require Idempotency-Key for POST /payments and hash a canonical request payload.
  • Use an atomic insert into an idempotency table keyed by that header to gate concurrent duplicates.
  • If key exists with matching hash and completed state, return cached response with same status code.
  • Pass the same key to the provider when supported so upstream retries are also deduplicated.
  • If key exists but hash differs, reject with conflict to prevent accidental key reuse.
  • Caching and replaying responses makes retries predictable, but it adds storage overhead and couples you to the stored response schema.
Idempotency

How do you pick a load-balancing algorithm for requests with highly variable duration, like AI inference?

Round robin is the right default when instances are similar and requests cost about the same, but it falls apart when duration varies — exactly the inference case, where one long completion ties up an instance while round robin keeps handing it new work. Least-connections handles that better by routing to the instance with the fewest in-flight requests, which tracks real load when durations are uneven. The catch is that connection count doesn’t capture per-request CPU cost, so a few short-but-expensive prompts can still skew it. For inference you validate the choice by measuring p95/p99 latency and backend saturation under representative load rather than trusting any default.

Load Balancing

When do you choose an L4 load balancer over L7?

L4 balances on connection metadata — IP, port, protocol — without reading the payload, so it’s fast and works for any TCP/UDP traffic, but it can’t decide based on HTTP path, host, or headers. L7 parses HTTP, so it can route by path or header and do canary, A/B, and edge auth, at the cost of more processing per request. Choose L4 when you need raw throughput and generic transport routing; choose L7 the moment routing depends on request content. Plenty of systems use both — L4 at the edge for raw distribution, L7 deeper for content-aware routing.

Load Balancing

Why must readiness checks differ from liveness checks behind a load balancer?

They answer different questions, and conflating them keeps broken instances in rotation. Liveness is “is the process alive” — if it fails, the orchestrator restarts the pod. Readiness is “can this instance serve traffic right now,” so it must actually probe critical dependencies (database, cache, downstream) and let the balancer pull a started-but-not-ready instance out of rotation without restarting it. The classic bug is a /health that always returns 200: the process is up, its database is unreachable, and the balancer keeps routing real traffic into failures. Keep liveness cheap and dependency-free; put the dependency checks in readiness.

Load Balancing

How do you design webhook consumers to prevent event loss and duplicate processing?

Verify the HMAC signature on every request first, to reject forged payloads. Then defend against duplicates with the provider’s event ID as an idempotency key — stored durably and checked before you act, because at-least-once delivery means the same webhook arrives twice eventually. Acknowledge fast: return 200 and process asynchronously from a durable queue so slow work never trips the sender’s timeout. And since your consumer can be down when an event fires, add reconciliation — periodically poll the provider’s event-list API to catch what you missed. Signature for trust, idempotency for duplicates, reconciliation for gaps.

Webhooks

When would you choose webhooks over a shared message broker for inter-service event delivery?

Webhooks win when you cross an organizational boundary — a SaaS or third-party provider you don’t control can’t publish into your broker, but it can POST to a URL. Inside your own platform a message broker is usually better: durable fan-out, back-pressure, replay, and per-key ordering that raw HTTP callbacks don’t give you. They aren’t exclusive, though — the common pattern is to receive external webhooks at the edge and immediately republish them onto an internal broker, so you get HTTP reach at the boundary and broker guarantees everywhere inside.

Webhooks

How do you protect a webhook endpoint against replay attacks?

Sign a timestamp alongside the payload — in the body or as a signed header — and reject anything outside a tight window, say older than five minutes, so a captured request can’t be replayed later. The HMAC has to cover both the payload and the timestamp together, or an attacker just swaps one without invalidating the signature. Inside the window, your idempotency key (the delivery ID) still catches a fast replay. Signature proves authenticity, the timestamp bounds the replay window, idempotency handles what slips through.

Webhooks

Message Queues

How do you design a Kafka topic to keep per-customer ordering while handling high throughput?

Use customer_id as the partition key so every event for a customer lands in the same partition and stays ordered — Kafka only guarantees order within a partition. Then provision enough partitions for parallelism, sized from measured per-partition consumer throughput against your latency target, and scale the consumer group up to (but not beyond) the partition count, since extra consumers just sit idle. Watch for hot partitions: if one customer dominates traffic, a plain customer_id key overloads one partition, so move to a composite key like customer_id:region. Keep consumers idempotent, because rebalances and retries will reprocess records. Ordering and scale pull against each other, and the partition key is where you resolve the tension.

Kafka

Compare at-most-once, at-least-once, and exactly-once in Kafka — which fits payment events?

At-most-once commits the offset before processing, so a crash loses events — almost never acceptable for payments. At-least-once processes first and commits after, so a crash before commit reprocesses; it’s the common production default and safe as long as handlers are idempotent. Exactly-once is real but narrow: Kafka transactions plus an idempotent producer give atomic consume-process-produce, but only for Kafka-to-Kafka flows — a database write or HTTP call inside the handler still needs its own idempotency or an outbox. For payments, the usual answer is at-least-once with strict idempotency (dedupe on a payment ID), reaching for exactly-once only when a duplicate side effect is too costly to risk and the whole flow stays inside Kafka.

Kafka

Why is MSMQ a maintenance choice rather than a new-system choice?

It’s Windows-only — it cannot run on Linux or in containers, which blocks containerization and cloud-native deployment. Its distributed-transaction story depends on MSDTC, which is fragile and unavailable in containers, and System.Messaging doesn’t exist in .NET 5+. Modern cross-platform brokers (RabbitMQ, Azure Service Bus, Kafka) provide the same durable async messaging without the platform lock-in, so MSMQ is justified only where it’s already deployed and migration cost isn’t worth it.

MSMQ

How does a transactional receive prevent message loss in MSMQ?

The message is removed from the queue only if the transaction commits. You Begin a MessageQueueTransaction, Receive under it, process the message, then Commit. If processing throws, you Abort and the message is returned to the queue for retry. This is the local-transaction equivalent of an ack: a crash mid-processing leaves the message available rather than consumed-and-lost (the same guarantee modern brokers give via manual ack / visibility timeout).

MSMQ

What replaces MSMQ's MSDTC distributed transactions today?

The Outbox pattern: write the outgoing message to a table in the same local DB transaction as the domain change, then a background relay publishes it to the broker with retries (at-least-once + idempotent consumers). For multi-step cross-service workflows, a Saga with compensating transactions. Both avoid MSDTC’s coordinator fragility — see Distributed Transactions.

MSMQ

How do you use RabbitMQ to absorb bursty ingress when producers outpace consumers?

Put a durable queue between the ingress and the workers so bursts land in the queue instead of overwhelming the worker tier. Keep the ingress path thin — validate, enqueue, return 200 fast — so a webhook sender never waits on your processing. Then scale competing consumers horizontally off the same queue, and use prefetch (BasicQos) so one slow worker can’t hoard unacked messages while others sit idle. Route poison messages to a dead-letter exchange and watch queue depth, redelivery rate, and message age. The queue is the shock absorber: it turns a traffic spike into a temporary backlog instead of dropped requests or a melted worker tier.

RabbitMQ

How do you implement at-least-once delivery, and what new risk appears?

At-least-once is four settings working together: a durable queue, persistent messages (DeliveryMode = 2), publisher confirms so the producer knows the broker accepted the message, and manual consumer ack so a message isn’t removed until processing succeeds. If a consumer crashes before acking, the broker redelivers. The risk that buys you is duplicates — a redelivery can race an ack — so consumers must be idempotent, keyed on a stable message ID with a dedupe store. You trade the possibility of loss for the certainty of occasional duplicates, which is the far easier problem to make safe.

RabbitMQ

When would you choose RabbitMQ over Kafka?

Choose RabbitMQ when you want a smart broker doing the routing — direct, topic, fanout, and header exchanges, per-message TTL, priorities, dead-lettering — for task queues, request-reply, and command dispatch where low latency and flexible routing matter more than retention. Choose Kafka when you need a durable, replayable log: high-throughput event streams, ordering per partition, and multiple independent consumers re-reading history by offset. The rough line is that RabbitMQ moves a message and forgets it, while Kafka stores an event history. If you keep wishing you could re-consume past messages, you actually wanted Kafka.

RabbitMQ

Scalability Patterns

What architectural prerequisites must be met before horizontal scaling works?

Expected answer:

  • Service must be stateless — no in-memory session or local file state.
  • All shared state externalized (Redis for cache/session, blob storage for files, database for persistent data).
  • A load balancer must distribute traffic across instances.
  • Without statelessness, adding instances creates inconsistency rather than capacity.
  • Without a load balancer, all traffic still hits one node. Why this is strong: It shows horizontal scaling is an architectural property, not just an ops action. Many engineers think “add more servers” is always safe.
Horizontal Scaling

Why can horizontal scaling fail even with many instances?

Expected answer:

  • Database becomes the bottleneck — each instance opens its own connection pool (20 instances × 100 pool size = 2,000 connections).
  • Stateful services silently break (in-memory session on pod 1 invisible to pod 2).
  • Uneven load distribution from sticky sessions or hot partitions.
  • Thundering herd during scale-out when new instances aren’t ready fast enough.
  • Connection pool or thread pool saturation before CPU becomes the bottleneck. Why this is strong: It demonstrates awareness that scaling one tier shifts the bottleneck downstream — a classic distributed-systems trap.
Horizontal Scaling

When would you choose read replicas instead of CQRS for a scaling problem?

Expected answer:

  • Choose read replicas when main pressure is read throughput on an existing relational model.
  • Choose CQRS when read/write models diverge and read projections need different shape or storage.
  • Mention consistency behavior: replicas have lag; CQRS read models are eventually consistent by design.
  • Mention complexity: replicas are simpler operationally than full CQRS/event projection pipelines. Why this is strong: It balances architecture fit, consistency, and operational cost.
Scalability Patterns

When is vertical scaling the right first move over horizontal scaling?

Expected answer:

  • When the workload is stateful and hard to shard (e.g., a relational database).
  • When the bottleneck is clearly resource-bound on a single node.
  • When the operational cost of distributed coordination (consensus, partitioning, eventual consistency) exceeds the cost of a larger machine.
  • Vertical scaling is faster to implement and avoids distributed-systems complexity.
  • It’s the right first move for most monoliths and managed databases in early growth phases. Why this is strong: It shows the candidate reasons about tradeoffs (complexity vs cost) rather than defaulting to “just add more servers.”
Vertical Scaling

Why does vertical scaling have diminishing returns at high scale?

Expected answer:

  • Hardware cost scales super-linearly: premium SKUs charge a multiplier for the same resource increment.
  • Not all workloads parallelize across additional cores — Amdahl’s Law bounds speedup by the serial fraction.
  • A process with 20% serial code can never exceed 5x speedup regardless of core count.
  • Memory bandwidth and cache coherency become bottlenecks as core counts grow.
  • The cost-per-unit-of-capacity curve inflects sharply at high tiers. Why this is strong: It demonstrates understanding of hardware economics and parallelism limits, not just “bigger is better.”
Vertical Scaling

What's the failure mode of relying solely on vertical scaling for availability?

Expected answer:

  • A single large node is a single point of failure — any hardware fault, OS crash, or maintenance window causes full outage.
  • Vertical scaling increases capacity but does nothing for availability.
  • Mitigation: pair with redundancy (active-passive failover, read replicas, multi-AZ deployment).
  • Azure SQL Business Critical tier includes a built-in secondary replica for reads and failover.
  • This is distinct from Horizontal Scaling, which inherently distributes failure across nodes. Why this is strong: It shows the candidate distinguishes capacity from availability — a common interview blind spot.
Vertical Scaling

Patterns

Why does CQS make code easier to reason about?

When a method returns a value, you know it has no side effects — you can call it freely, cache its result, or call it multiple times without changing state. When a method returns void, you know it changes state but produces no data you depend on. This separation eliminates the need to read the implementation to understand whether a call is safe to repeat or reorder. It also makes testing easier: queries can be tested without checking state changes; commands can be tested without checking return values.

CQS

When is it pragmatic to violate CQS?

Three common justified exceptions: (1) Stack.Pop() — splitting into Peek() + Remove() introduces a race condition in concurrent code. (2) Repository.Add() returning the generated ID — the ID is produced by the database; returning it avoids an extra round-trip. (3) Async I/O methods — Task<T> methods that perform I/O and return a result are idiomatic in .NET even when they have side effects. The principle is a guideline: apply it where it improves clarity, relax it where strict adherence creates awkward APIs.

CQS

Explain Transient, Scoped, and Singleton lifetimes with a safe production example each.

The three differ by how long the container keeps one instance. Transient is a fresh instance per resolution — fine for lightweight stateless services like mappers or formatters. Scoped is one instance per scope, which in ASP.NET Core means per HTTP request; the canonical case is DbContext, where one unit of work per request keeps tracking and transactions coherent. Singleton is one instance for the app’s lifetime — good for thread-safe shared state like a cache, an IClock, or IHttpClientFactory — but it must be thread-safe and must never capture a scoped dependency. The thing to get right: lifetime is about shared state and thread-safety, not performance.

Dependency Injection

Why is Service Locator an anti-pattern in business logic, and when is it acceptable?

Service Locator means reaching into IServiceProvider (GetRequiredService<T>()) from inside a class instead of declaring the dependency in its constructor. In business logic that hides what the class actually needs — the constructor stops telling the truth — so a missing registration fails at runtime instead of compile time, and tests have to mimic the container instead of just passing fakes. It’s acceptable in the places that genuinely pick implementations at runtime: factories, middleware activation, and explicit scope management in background jobs. The rule: constructor injection for anything with business logic; the locator only in infrastructure that has no other way to resolve.

Dependency Injection

What is a captive dependency, and how do you fix it?

It’s when a longer-lived service captures a shorter-lived one — classically a singleton or IHostedService holding a scoped DbContext. The scoped object then outlives its intended request boundary, giving you stale state, broken EF Core change tracking, and disposal at the wrong time. In Development the scope validator catches it and throws InvalidOperationException; in Production it just misbehaves quietly. The fix is to not inject the scoped service at all: inject IServiceScopeFactory, open a short scope where you need the work, resolve inside it, and let it dispose. The tell to watch for is a singleton constructor asking for anything request-scoped.

Dependency Injection

How does an event bus differ from a message broker?

  • Event bus is a dispatch mechanism — resolves handlers and invokes them, typically in-process
  • Message broker is infrastructure — durable middleware (RabbitMQ, Kafka, Service Bus) that persists and delivers messages across processes
  • In-process bus loses unprocessed events on crash; broker persists them
  • Bus abstraction can sit on top of a broker (MassTransit does this) — bus adds handler resolution and retry semantics, broker provides transport and durability
  • Cost of broker: operational complexity, network latency, serialization overhead
  • Cost of in-process bus: no durability, no cross-service delivery
  • A broker guarantees delivery at the cost of operational complexity; the in-process bus is simple but ephemeral
Event Bus

Why should event handlers be independent rather than ordered?

  • Event bus contract is fan-out — “these N things happen when X occurs” — not a sequential pipeline
  • If handler B depends on handler A’s side effect, you have a hidden workflow disguised as fan-out
  • Execution order depends on DI registration order, which is fragile and undocumented
  • Breaks when: order changes (new handler inserted), parallelism enabled, or handler moved to another service
  • Fix: model the dependency as a saga or command chain where A publishes a new event that triggers B
  • This makes the dependency visible, testable, and deployable independently
  • You end up with more events and handlers, but each is self-contained and the workflow is explicit in the codebase
Event Bus

When is MediatR INotification insufficient as an event bus?

  • Exception isolation: default MediatR stops at the first handler failure — unacceptable when independent side effects (email, analytics, inventory) must not block each other
  • Scope isolation: all handlers share the same DI scope, so DbContext mutations in handler A leak into handler B
  • Execution strategy: default is sequential; parallel execution requires custom INotificationPublisher
  • Each fix requires either replacing MediatR’s publisher or building a custom bus
  • A custom bus gives you control over all three concerns, but you lose MediatR’s pipeline-behavior ecosystem (validation, logging, caching) and auto-discovery
Event Bus

Why does EF Core's DbContext already implement the Unit of Work pattern?

  • DbContext tracks all changes to loaded entities in its change tracker.
  • SaveChangesAsync() wraps all pending inserts, updates, and deletes in a single database transaction.
  • This means multiple repository operations within one request share the same DbContext instance and commit atomically — exactly what Unit of Work provides.
  • Tradeoff: this only works if all repositories share the same DbContext instance (Scoped lifetime in ASP.NET Core DI). If you accidentally register DbContext as Singleton or Transient, the Unit of Work semantics break.
Repository & UoW

When is a generic IRepository<T> an anti-pattern?

  • A generic repository exposes the same interface for every entity, including operations that don’t make sense for some aggregates (e.g., GetAll() on an Order aggregate with millions of rows).
  • It encourages callers to treat all entities the same, bypassing aggregate-specific access patterns and invariants.
  • It often leaks IQueryable<T>, coupling callers to EF Core.
  • Better: aggregate-specific interfaces with domain-meaningful methods. Use a generic base class for the implementation, but expose a specific interface.
Repository & UoW

Architectural Patterns

When is CQRS worth the operational complexity, and when is it an anti-pattern?

CQRS earns its complexity when the read and write sides genuinely want different shapes — a high read:write ratio, broad or expensive queries, real invariants on the write side, and a need to scale the two paths independently. Then a denormalized read model serves screens cheaply while the write model stays focused on enforcing rules. It’s an anti-pattern for ordinary CRUD at low scale: the projection pipelines, eventual-consistency handling, and two models to evolve and test are pure tax when one model would have served both. Apply it per bounded context where the constraints justify it, never as a global default.

CQRS

Does CQRS require Event Sourcing or two databases?

No — and conflating them is the most common CQRS misconception. CQRS is only about separating the command model from the query model, and that can be two classes against the same database. Separate stores, message brokers, and Event Sourcing are options you add when scale or auditability demands them, not requirements. You can run CQRS on a single relational database, projecting into a denormalized view table inside the same transaction. Start with the cheapest separation that solves the problem and add infrastructure only when a real constraint forces it.

CQRS

How do you handle eventual consistency between the write and read models?

With asynchronous projection a command can succeed before the read model catches up, so the user who just acted sees stale data. The pragmatic fixes: serve that user’s immediate follow-up read from the write model (read-your-own-writes for the session), show a soft “updating…” state, and monitor projection lag so you notice drift. Underneath, make projections idempotent — brokers are at-least-once, so a projector can see the same event twice — using upserts and a record of handled event IDs. Eventual consistency is a UX-and-idempotency problem, not a reason to abandon the split.

CQRS

What is the difference between an Entity and a Value Object?

  • Entity: has a unique identity that persists over time. Two entities with the same data are different if their IDs differ. Example: Order with OrderId.
  • Value Object: defined entirely by its attributes, no identity. Two Value Objects with the same data are equal. Immutable. Example: Money(10, "USD").
  • Rule of thumb: if you care about which instance it is, it’s an Entity. If you only care about what it contains, it’s a Value Object.
  • Tradeoff: Value Objects are simpler to reason about (immutable, no identity tracking) but require copying on mutation. Use them for concepts like Money, Address, DateRange, Coordinates.
Domain-Driven Design

Why should external code only interact with an Aggregate through its root?

  • The Aggregate Root enforces all invariants for the cluster. If external code modifies a LineItem directly, it bypasses the Order’s consistency checks.
  • Example: adding a line item after an order is confirmed should be rejected. If LineItem is modified directly, the Order never gets a chance to enforce this rule.
  • Practical implication: repositories load and save entire Aggregates, not individual child entities. EF Core’s change tracking makes this natural.
  • Tradeoff: loading the full Aggregate for every operation can be expensive if the Aggregate is large. This is a signal that the Aggregate boundary is too wide.
Domain-Driven Design

When does Event Sourcing justify its complexity over CRUD plus an audit-log table?

  • CRUD + audit table can satisfy compliance for many systems with lower operational overhead.
  • Event Sourcing is justified when domain behavior depends on historical intent and replay, not only final values.
  • If you need deterministic rebuild of multiple read models, Event Sourcing is stronger.
  • If temporal queries are frequent and core to product value, Event Sourcing can pay off.
  • If team maturity for schema evolution and projection operations is low, choose CRUD first.
  • The honest default is CRUD plus an audit table; Event Sourcing earns its cost only when replay and historical intent are core to the product, not merely compliance.
Event Sourcing

How do you evolve event schemas safely without breaking old streams?

  • Use explicit event versioning strategy.
  • Prefer backward-compatible additive changes.
  • Introduce upcasters/adapters for old payloads.
  • Keep integration tests that replay production-like historical streams.
  • Treat event contracts as long-lived public interfaces.
  • Schema evolution is the most common production failure mode in event-sourced systems — especially when teams skip compatibility testing across historical streams.
Event Sourcing

Design Patterns

How do you decide which GoF category a pattern belongs to?

Creational if the problem is about object construction — hiding how, when, or which type to instantiate. Structural if the problem is about composing classes or adapting interfaces into larger, more convenient structures. Behavioral if the problem is about assigning responsibility or defining the communication flow between objects.

  • The category signals intent, not class structure: Proxy (Structural) and Decorator (Structural) look identical structurally but serve different behavioral intents.
  • When in doubt: ask “Is this about making objects, assembling objects, or communicating between objects?”
  • Tradeoff: categories help communicate design intent quickly, but the same structure can serve different intents — always explain the why, not just the pattern name.
Design Patterns

When does using a design pattern become an anti-pattern?

When the variation it enables doesn’t exist yet and isn’t clearly anticipated. Patterns add abstractions — more classes, more indirection, harder debugging — and abstraction has a cost.

  • Introduce a pattern at the second variation point (rule of three / YAGNI), not speculatively.
  • Most common offenders: Singleton hiding shared mutable state, Factory Method for a class that never has variants, Builder for objects with 2 properties.
  • Tradeoff: patterns pay off over time through extension without modification; they cost upfront in complexity. The break-even is when the second concrete variant appears or when the roadmap makes variation certain.
Design Patterns

What is the Information Expert principle and why does it reduce coupling?

Information Expert assigns responsibility to the class that has the information needed to fulfill it. This keeps related data and behavior together, reducing the need for one class to reach into another to get data. The result is lower coupling and higher cohesion — each class does what it knows.

GRASP

How does GRASP differ from SOLID?

GRASP focuses on responsibility assignment — which class should own a behavior or data. SOLID focuses on design principles for maintainability (single responsibility, open/closed, etc.). They are complementary: GRASP guides initial design decisions; SOLID guides refactoring and long-term maintainability.

GRASP
Behavioral

How does ASP.NET Core Middleware differ from a classical Chain of Responsibility?

Classical CoR uses a linked list of handlers where each holds a reference to the next. ASP.NET Core Middleware uses a delegate pipeline: each middleware is a Func<RequestDelegate, RequestDelegate> that wraps the next RequestDelegate. The pipeline is compiled at startup into a single delegate chain. The difference: ASP.NET Core’s pipeline is immutable after build (no dynamic handler insertion); classical CoR can be modified at runtime. ASP.NET Core’s approach is more performant (no virtual dispatch per handler) but less flexible. The tradeoff: startup-time composition vs runtime flexibility.

Chain of Responsibility

When should a handler stop the chain vs pass the request along?

Stop the chain when the handler has definitively handled the request — either successfully (order approved) or with a terminal failure (fraud detected, stop processing). Pass along when the handler’s check passes and subsequent handlers may still reject. The key question: is this handler’s decision final? Fraud detection is final (don’t process fraudulent orders regardless of other checks). Stock check is final for that item but not for the order (other items may still be available). Design each handler to be authoritative about its own domain and ignorant of others.

Chain of Responsibility

How do you handle a request that should be processed by multiple handlers (not just the first one)?

Change the chain to not short-circuit — every handler processes the request and the results are aggregated. This is the “collect all errors” variant of validation: instead of stopping at the first failure, all handlers run and all errors are collected. The tradeoff: short-circuiting is faster (stops at first failure) but gives less feedback; full traversal is slower but gives complete error information. For user-facing validation, full traversal is usually better UX. For security checks (fraud, sanctions), short-circuit immediately — don’t reveal which check failed.

Chain of Responsibility

When is undo not feasible, and how do you handle it?

Undo is not feasible when the operation has external side effects that can’t be reversed: sending an email (can’t unsend), charging a card (refund is a new operation, not a reversal), or publishing an event to a message bus. In these cases, implement compensating transactions instead of true undo: CancelOrderCommand is the compensation for PlaceOrderCommand. Document which commands support true undo and which only support compensation. The tradeoff: true undo is simpler for the caller; compensating transactions are more realistic for distributed systems.

Command

How does MediatR implement the Command pattern, and what does it add?

MediatR separates the command (data + intent) from the handler (execution logic). PlaceOrderCommand carries the order data; PlaceOrderCommandHandler knows how to process it. The mediator routes commands to handlers without the sender knowing the handler. MediatR adds: pipeline behaviors (Chain of Responsibility around the handler), notification publishing (one command → multiple handlers), and request/response typing. The tradeoff: MediatR adds a dependency and indirection; direct service calls are simpler. MediatR earns its complexity when you need pipeline behaviors (validation, logging, caching) applied consistently across all commands.

Command

How does EF Core use LINQ Expression Trees as an Interpreter?

When you write dbContext.Orders.Where(o => o.Total > 100), the C# compiler doesn’t compile the lambda to IL — it compiles it to an Expression<Func<Order, bool>> (an expression tree, an AST). EF Core’s query translator walks this AST using ExpressionVisitor: VisitBinary for o.Total > 100 emits Total > @p0; VisitMember for o.Total emits the column name. The final SQL is assembled from the visited nodes. This is the Interpreter pattern: the expression tree is the grammar; EF Core is the interpreter; SQL is the output. The cost: EF Core can only interpret expressions it knows — calling a custom method in a LINQ query throws InvalidOperationException because the interpreter doesn’t have a rule for that method.

Interpreter

When should you compile an expression tree to a delegate instead of interpreting it?

When the same expression is evaluated many times (e.g., a discount rule evaluated on every order in a batch). Expression.Lambda<Func<DiscountContext, bool>>(tree).Compile() converts the expression tree to IL and returns a delegate that runs at near-native speed. The compilation itself is expensive (milliseconds); cache the compiled delegate. The tradeoff: compilation adds startup cost and memory; interpretation adds per-evaluation cost. Break-even is roughly 100+ evaluations of the same expression. EF Core compiles and caches query expressions for this reason.

Interpreter

What's the difference between Interpreter and Strategy for runtime rule selection?

Strategy selects a pre-written algorithm at runtime. Interpreter evaluates a rule defined as data at runtime. With Strategy, the algorithms are written in code and selected by name or type. With Interpreter, the rule is a data structure (expression tree, string) that the interpreter evaluates. Interpreter is more flexible (rules can be composed from primitives without code changes) but more complex (requires a grammar and interpreter). Strategy is simpler (just inject the right implementation) but requires a code change for each new algorithm. Use Strategy when the set of algorithms is known at compile time; use Interpreter when rules are defined by non-developers or change without deployment.

Interpreter

When should you return IEnumerable<T> vs IReadOnlyList<T> vs IAsyncEnumerable<T>?

Return IReadOnlyList<T> when the collection is fully materialized and callers need random access or Count. Return IEnumerable<T> when the collection is lazy or the caller only needs sequential access. Return IAsyncEnumerable<T> when the source is async (database, network) and you want to stream results without buffering all of them. The tradeoff: IReadOnlyList<T> is simpler but requires full materialization; IAsyncEnumerable<T> is memory-efficient but requires await foreach at the call site. Default to IReadOnlyList<T> for small collections; use IAsyncEnumerable<T> when the collection could be large or unbounded.

Iterator

What does the compiler generate for a yield return method?

The compiler generates a private class implementing IEnumerator<T> and IEnumerable<T>. The method body is split into states at each yield return point. MoveNext() advances the state machine to the next yield return, executes the code between yields, and returns true. Current returns the last yielded value. The generated class captures all local variables as fields. This is why yield return methods can’t use ref locals or unsafe code — the state machine can’t capture those.

Iterator

When does Mediator become a bottleneck or anti-pattern?

When the mediator becomes a god class that knows too much — if CheckoutCommandHandler grows to 300 lines with complex branching, the complexity moved from the controller to the handler without being reduced. The mediator pattern reduces coupling but doesn’t reduce complexity. Also avoid Mediator when the interaction is simple and direct: if OrderService only ever calls InventoryService, a direct dependency is clearer than routing through a mediator. The signal: if you can’t explain what the mediator does without listing all its handlers, it’s too complex.

Mediator

How do MediatR pipeline behaviors implement the Chain of Responsibility pattern?

Each IPipelineBehavior<TRequest, TResponse> wraps the next behavior in the pipeline. Handle(request, next, ct) calls next() to continue or returns early to short-circuit. Behaviors are registered in order; the outermost runs first. This is exactly Chain of Responsibility: each behavior decides whether to pass the request along. The difference from a manual chain: MediatR’s pipeline is configured via DI registration order, not explicit SetNext() calls. The tradeoff: DI-based ordering is less explicit but easier to configure.

Mediator

How do you prevent the memento from growing unbounded in memory?

Limit the history depth: keep only the last N mementos (a bounded stack). For long-running sessions, serialize mementos to Redis or a database instead of keeping them in memory. For abandoned cart recovery, store only the latest snapshot (not the full history). The tradeoff: deeper history = more undo steps but more memory. For most UX scenarios, 10-20 undo steps is sufficient. For audit/compliance scenarios, store all snapshots in a database with a TTL.

Memento

When is Memento overkill compared to simpler approaches?

When the state is small and the undo operation is simple. If “undo remove item” just means re-adding the item, store the removed item directly — no need for a full cart snapshot. Memento earns its complexity when: (1) the state is complex and interrelated (discount + items + shipping options all affect each other), (2) you need multiple undo levels, or (3) you need to restore state across sessions (abandoned cart). For single-step undo of simple operations, store the delta (what changed) rather than the full snapshot.

Memento

Why does subscribing to an event with async void cause problems?

async void event handlers can’t be awaited. If the handler throws, the exception propagates to the synchronization context (often crashing the app) rather than being catchable by the publisher. If the handler does async work, the publisher doesn’t know when it completes — the event returns before the async work finishes. Use async void only for top-level event handlers in UI frameworks where the framework expects it. For server-side observers, use an explicit Task-returning interface and await Task.WhenAll(observers.Select(o => o.OnStatusChangedAsync(...))).

Observer

How do you prevent memory leaks from event subscriptions?

Three approaches: (1) Unsubscribe in Dispose() — implement IDisposable and unsubscribe in Dispose(). (2) Weak event pattern — use WeakEventManager (WPF) or WeakReference<T> to hold subscriber references; the publisher doesn’t prevent GC. (3) Scoped lifetime — if both publisher and subscriber have the same DI lifetime (both scoped), they’re collected together. The tradeoff: explicit unsubscription is reliable but requires discipline; weak events are automatic but add complexity and slight performance overhead.

Observer

When should you use IObservable<T> (Rx) instead of plain events?

When you need stream operators: filtering (Where), transformation (Select), throttling (Throttle), combining multiple streams (Merge, CombineLatest), or backpressure. Plain events are push-only with no composition. Rx adds a rich operator library over the observable stream. Use Rx when: you’re processing a stream of events with complex filtering or timing requirements (e.g., “notify only if status changes twice within 5 seconds”). The cost: Rx has a steep learning curve and adds a dependency. For simple one-to-many notification, plain events or explicit interfaces are sufficient.

Observer

How does the async/await compiler implement the State pattern?

The compiler transforms an async method into a struct implementing IAsyncStateMachine. The struct has a state field (an integer) representing the current position in the method. MoveNext() is a switch on state: each case resumes execution from the last await point. When an await suspends, the state is saved and MoveNext() returns. When the awaited task completes, MoveNext() is called again with the next state. Local variables become fields on the struct (captured state). This is exactly the State pattern: the method’s execution state drives behavior, and transitions happen automatically at each await.

State

When should you use the Stateless library instead of hand-written state classes?

When the state machine has many states and transitions that are better expressed declaratively. Stateless lets you define machine.Configure(State.Paid).Permit(Trigger.Ship, State.Shipped).OnEntry(() => SendShipmentEmail()) — the transition table is explicit and readable. Hand-written state classes are better when each state has complex behavior beyond transitions (not just “what’s the next state” but “what does Ship() do in this state”). The tradeoff: Stateless is concise for transition-heavy machines; hand-written classes are better for behavior-heavy states.

State

How do you handle state transitions that require async operations (e.g., sending a refund on cancel)?

Make TransitionTo() async and await the side effects before completing the transition. Or use the Observer pattern: raise a StatusChanged event after transitioning, and let async observers handle side effects. The second approach keeps state transitions synchronous and side effects decoupled. The tradeoff: synchronous transitions are simpler but can’t await side effects; event-based side effects are decoupled but harder to reason about ordering and failure handling.

State

When should you use a Strategy interface vs a Func<T, TResult> delegate?

Use a Func<T, TResult> delegate when the strategy is simple (one method, no state, no dependencies). Func<Order, decimal> is a perfectly valid shipping cost strategy for simple cases. Use an interface when: the strategy has multiple methods, needs DI-injected dependencies, needs to be registered in a DI container, or needs to carry state. The tradeoff: delegates are simpler and more flexible; interfaces are more explicit and support DI. Start with delegates; introduce an interface when the strategy grows beyond a single expression.

Strategy

How do you handle strategy selection when multiple strategies apply?

Define a priority or exclusivity rule. Options: (1) First-match wins — strategies are ordered by priority; the first applicable one is used. (2) Explicit selection — the caller specifies the strategy by name or type. (3) Composite strategy — all applicable strategies run and results are combined (e.g., sum all applicable discounts). The tradeoff: first-match is simple but order-dependent; explicit selection is clear but requires the caller to know strategy names; composite is flexible but may produce unexpected combinations.

Strategy

When should you use Template Method vs Strategy for algorithm variation?

Template Method uses inheritance — the variation is in a subclass. Strategy uses composition — the variation is in an injected object. Use Template Method when: the algorithm skeleton is stable, the variations are tightly coupled to the base class, and you don’t need to swap algorithms at runtime. Use Strategy when: you need to swap algorithms at runtime, the algorithm is independent of the class using it, or you want to avoid inheritance. The tradeoff: Template Method is simpler (no extra interface) but creates tight inheritance coupling; Strategy is more flexible but requires an extra interface and injection.

Template Method

What's the "Hollywood Principle" and how does Template Method implement it?

“Don’t call us, we’ll call you.” The base class calls the subclass’s methods (abstract steps), not the other way around. The subclass doesn’t control when its methods are called — the template method does. This inverts the typical inheritance relationship: instead of the subclass calling super.method(), the base class calls this.abstractStep(). The benefit: the algorithm’s structure is controlled by the base class; subclasses can’t accidentally skip steps or change the order. The cost: subclasses are tightly coupled to the base class’s algorithm structure.

Template Method

What is double dispatch and why does Visitor need it?

Single dispatch: the method called depends on the runtime type of ONE object (the receiver). Double dispatch: the method called depends on the runtime types of TWO objects (the element AND the visitor). Without double dispatch, visitor.Visit(item) would call the Visit(ICartItem) overload — the compiler resolves overloads at compile time based on the declared type. item.Accept(visitor) → visitor.Visit(this) forces the compiler to resolve the overload based on this’s concrete type at runtime. The cost: two virtual calls instead of one; the pattern is non-obvious to developers unfamiliar with it.

Visitor

When does EF Core use ExpressionVisitor, and what does it do?

EF Core’s query translator is an ExpressionVisitor that walks the LINQ expression tree and converts it to SQL. dbContext.Orders.Where(o => o.Total > 100).Select(o => o.Id) builds an expression tree; EF Core visits each node: VisitMethodCall for Where and Select, VisitBinary for o.Total > 100, VisitMember for o.Total and o.Id. Each visit emits SQL fragments. The final SQL is assembled from the visited nodes. This is why EF Core can translate LINQ to SQL but throws InvalidOperationException for expressions it can’t translate — the visitor doesn’t know how to visit that node type.

Visitor

When should you use pattern matching instead of Visitor?

When the element hierarchy is small (2-4 types), changes frequently (new types added regularly), or the operations are simple (one-liners per type). Pattern matching is more readable, requires no Accept() method on elements, and handles new element types with a compiler warning (exhaustiveness checking with _ catch-all). Visitor earns its complexity when: you have 5+ element types, 5+ operations, and the hierarchy is stable. The signal: if adding a new element type requires editing more than 3 visitor classes, the hierarchy is too unstable for Visitor.

Visitor
Creational

How do you add a new product type (e.g., IFraudDetector) to an existing Abstract Factory without breaking all existing factories?

You can’t without modifying the interface — that’s the fundamental tension. Options: (1) Add the method with a default implementation in the interface (default IFraudDetector CreateFraudDetector() => new NoOpFraudDetector()), which avoids breaking existing factories but hides missing implementations. (2) Use a separate IFraudDetectorFactory interface and compose it with IPaymentProviderFactory at the call site. (3) Accept the breaking change and update all factories — justified when the product is truly part of the family. The tradeoff: default implementations hide gaps; separate interfaces reduce cohesion; breaking changes are honest but costly.

Abstract Factory

When is Abstract Factory overkill compared to a simpler approach?

When you have only one product family (no need to swap providers), or when products don’t need to stay compatible with each other. A single IPaymentProcessor interface with DI registration is sufficient if you never need IReceiptGenerator to match the processor’s provider. Abstract Factory’s value is the family constraint — if that constraint doesn’t exist in your domain, you’re adding indirection for no benefit. Cost: every new product type requires updating the factory interface and all implementations.

Abstract Factory

How does Abstract Factory relate to the DI container in modern .NET?

The DI container IS an Abstract Factory at runtime. services.AddSingleton<IPaymentProcessor, StripePaymentProcessor>() registers a factory for IPaymentProcessor. The container creates compatible families when you register all related services together. The difference: DI containers don’t enforce family consistency at compile time — you can register StripePaymentProcessor with PayPalReceiptGenerator and the compiler won’t complain. A typed Abstract Factory interface enforces this at compile time. Use DI for flexibility; use a typed factory when family consistency is a hard requirement.

Abstract Factory

When does Builder's Build() method justify its existence over a constructor?

When Build() does work a constructor shouldn’t: cross-field validation (gift wrap requires a message), computed fields (total = subtotal - discount), default derivation (billing = shipping if not set), or async initialization. Constructors should be fast and never throw business logic exceptions. Build() is the right place for invariant enforcement that spans multiple fields. The tradeoff: Build() can throw at runtime; required properties fail at compile time. Use required when all fields are independent; use Builder when fields interact.

Builder

How does the Director role work, and when do you need it?

A Director encapsulates a specific construction sequence, calling builder methods in a fixed order. Example: StandardOrderDirector.BuildGiftOrder(builder, customer, items) always calls ShipTo, WithGiftWrap, WithShipping(Express) in the right order. Directors are useful when the same construction sequence is reused across multiple call sites, or when the sequence itself is a business rule (gift orders always use express shipping). Without a director, each call site must know the correct sequence — a form of knowledge duplication. The tradeoff: directors add a class; without them, call sites are more flexible but less consistent.

Builder

Why does WebApplicationBuilder use a builder instead of a constructor with parameters?

Because WebApplication construction is a multi-step process where each step can affect subsequent steps: adding a service can change how middleware behaves; configuration sources are layered in priority order; logging providers must be registered before the host starts. A constructor can’t express this ordering or allow conditional registration. The builder accumulates all configuration, then Build() wires everything together in the correct dependency order. The cost: Build() can fail at runtime if configuration is invalid — there’s no compile-time guarantee that all required services are registered.

Builder

When does Factory Method become the wrong choice?

When you need to create a family of related objects that must stay compatible — use Abstract Factory instead. Factory Method creates one product type; if PaymentProcessor and ReceiptGenerator must always come from the same provider (Stripe or PayPal), a single factory method can’t enforce that constraint. Also avoid Factory Method when the creation logic is trivial and unlikely to vary — the extra abstraction adds indirection without benefit.

Factory Method

How does Factory Method support the Open/Closed Principle?

The creator class is closed for modification: its NotifyOrderConfirmedAsync algorithm never changes. It’s open for extension: adding a new channel means a new subclass of NotificationCreator, not an edit to existing code. The tradeoff is class proliferation — each new product type requires a new creator subclass. For many variants, Abstract Factory or a registry-based approach scales better.

Factory Method

When should you use Prototype instead of just calling new?

When construction is expensive (loading from DB, computing derived fields) and you need many similar objects. Prototype amortizes the construction cost: build one template, clone it N times with small variations. Also use it when the exact type isn’t known at compile time — prototype.Clone() works without knowing the concrete type. The tradeoff: cloning can be as expensive as construction if the object graph is deep; profile before assuming it’s faster.

Prototype

What's the difference between shallow and deep copy, and when does it matter?

Shallow copy duplicates the object’s fields but shares reference-type values. Deep copy recursively duplicates the entire object graph. It matters when the copied object contains mutable reference types: two shallow copies sharing a List<OrderItem> will interfere with each other. Use deep copy when the clone must be fully independent. Use shallow copy when shared references are intentional (e.g., Customer is shared across orders — that’s correct). The cost of deep copy scales with graph depth; for large graphs, consider serialization-based cloning (JsonSerializer.Deserialize(JsonSerializer.Serialize(obj))).

Prototype

Why is the classical Singleton considered an anti-pattern in DI-based applications?

Three reasons: (1) Hidden dependency — AppConfig.Instance doesn’t appear in the constructor, so the dependency is invisible to callers and can’t be mocked. (2) Testability — tests can’t inject a different implementation; they’re forced to use the real singleton, making tests environment-dependent. (3) Lifetime control — the class controls its own lifetime, bypassing the DI container’s lifetime management. The DI-managed singleton solves all three: the dependency is explicit, injectable, and mockable. The tradeoff: DI-managed singletons require a DI container; classical singletons work without one (useful in library code or console apps without a host).

Singleton

What is a captive dependency and how do you detect it?

A captive dependency occurs when a longer-lived service holds a reference to a shorter-lived service. Example: a singleton OrderService injected with a scoped DbContext — the DbContext is captured for the application’s lifetime, not the request’s lifetime. This causes stale EF Core change tracking, connection pool exhaustion, and concurrency bugs across requests. Detection: enable ValidateScopes = true in AddSingleton options (default in development). The container throws at startup if a singleton depends on a scoped service. In production, also enable ValidateOnBuild = true. The fix: inject IServiceScopeFactory and create a scope per operation, or redesign the dependency to be singleton-safe.

Singleton

When is the classical Singleton still appropriate?

In library code without a DI container, or when you need a truly global, immutable resource that must be accessible without injection (e.g., a logging sink initialized before the DI container starts). Lazy<T> is the correct implementation in these cases. Also appropriate for value objects that are expensive to construct and inherently stateless (e.g., a compiled Regex pattern). The signal: if the singleton holds mutable state or has dependencies, use DI. If it’s an immutable, stateless resource, classical form is acceptable.

Singleton
Structural

How do you test code that uses an Adapter?

Test the consumer (OrderService) by injecting a mock IInventoryService — the adapter is invisible to the test. Test the adapter itself with integration tests against the real legacy system (or a recorded response). Unit-testing the adapter with a mock LegacyInventorySystem is valid but limited — the real value is verifying the XML translation is correct, which requires the actual legacy format. The tradeoff: integration tests are slower and environment-dependent; unit tests are fast but may miss translation bugs.

Adapter

When does an Adapter become a Facade?

When the adapter starts simplifying the interface rather than just translating it. An Adapter preserves the full capability of the adaptee — every method on IInventoryService maps to a corresponding legacy operation. A Facade intentionally hides complexity, exposing only a subset of the subsystem’s capabilities. If your “adapter” only exposes 3 of the legacy system’s 20 operations and adds orchestration logic, it’s a Facade. The distinction matters for maintenance: an Adapter should be a thin translation layer; a Facade can contain business logic.

Adapter

How do you decide whether to use Bridge or Strategy for payment provider selection?

Strategy selects an algorithm at runtime — the client chooses which strategy to inject. Bridge separates two dimensions of variation that both need to evolve independently. If you only need to swap payment providers (one dimension), Strategy is sufficient: inject IPaymentGateway and let the client choose. Bridge adds value when you also need payment types to vary independently — when both dimensions grow. The structural difference: Strategy has one interface; Bridge has two (abstraction + implementation). Use Strategy first; introduce Bridge when the second dimension appears.

Bridge

Why does ADO.NET use Bridge instead of just having SqlCommand implement ICommand directly?

Because the abstraction (DbCommand) and the implementation (SqlConnection) need to vary independently. DbCommand defines what operations are possible (execute, prepare, cancel); SqlCommand defines how they’re executed against SQL Server. A new database provider (PostgreSQL) can implement DbConnection/DbCommand without changing the abstraction. A new command type (batch command) can be added to the abstraction without changing providers. If SqlCommand directly implemented ICommand without the Bridge hierarchy, adding PostgreSQL support would require duplicating the entire command abstraction. The cost: the hierarchy is complex; understanding ADO.NET requires understanding both layers.

Bridge

What's the difference between Bridge and Dependency Injection?

DI is a mechanism for providing dependencies; Bridge is a structural pattern for organizing class hierarchies. They’re complementary: you use DI to inject the IPaymentGateway implementation into the PaymentOperation abstraction. DI doesn’t tell you how to structure the classes — Bridge does. Without Bridge, DI would inject a single IPaymentService that handles both dimensions; with Bridge, DI injects the gateway into the abstraction, and the abstraction handles the payment type logic. Bridge defines the structure; DI wires it together.

Bridge

How does Composite relate to the Visitor pattern?

They’re complementary. Composite defines the tree structure and uniform traversal. Visitor adds new operations to the tree without modifying the node classes. Example: TaxVisitor, ShippingVisitor, and DiscountVisitor can each traverse the same IOrderComponent tree without adding methods to SingleProduct or ProductBundle. Use Composite when the structure varies; use Visitor when the operations vary. Together, they handle both dimensions of variation.

Composite

When does a Composite tree become a performance problem?

When the tree is large, deep, or frequently traversed. GetPrice() on a bundle with 10,000 SKUs traverses all 10,000 nodes on every call. Mitigations: (1) cache computed values and invalidate on mutation, (2) use lazy evaluation (compute only when accessed), (3) denormalize — store the precomputed total and update it incrementally. The signal: profiling shows GetPrice() appearing in hot paths. The cost of caching: stale values if the tree is mutated without invalidation.

Composite

How does ASP.NET Core Middleware implement the Decorator pattern?

Each middleware is a decorator over RequestDelegate next. app.UseAuthentication() registers a middleware that calls next(context) after authenticating. The pipeline is built by composing these decorators at startup: each Use() call wraps the current pipeline in a new decorator. The outermost middleware runs first. This is exactly the Decorator pattern: each middleware implements the same interface (RequestDelegate), holds a reference to the next, and adds behavior before/after. The cost: middleware ordering bugs are runtime errors, not compile-time errors.

Decorator

When should you use Decorator vs inheritance for adding behavior?

Decorator when: (1) you need to add behavior at runtime or compose behaviors dynamically, (2) the class is sealed or from a third-party library, (3) you need multiple independent behaviors that can be combined in different ways. Inheritance when: the new behavior is a fundamental specialization of the type, not a cross-cutting concern. The key difference: inheritance is static (decided at compile time); Decorator is dynamic (decided at composition time). Decorator also avoids the fragile base class problem — changes to the base class don’t affect decorators.

Decorator

What's the performance cost of a deep decorator chain?

Each decorator adds one virtual dispatch and one async state machine (if async). For a 5-layer chain, that’s 5 virtual calls and 5 async allocations per request. In practice, this is negligible compared to I/O (DB queries, HTTP calls). Profile before optimizing. If the chain is genuinely hot (millions of calls/second with no I/O), consider collapsing the chain into a single class for that specific path. The tradeoff: performance vs maintainability. Premature optimization of decorator chains is a common mistake.

Decorator

When does a Facade become a "god class" anti-pattern?

When it starts containing business logic instead of just orchestrating subsystems. A Facade should be a thin coordinator — it calls subsystems in the right order but doesn’t make business decisions. If OrderFacade starts calculating discounts, validating business rules, or managing state, it’s accumulating responsibilities it shouldn’t have. The signal: the facade has more than 200-300 lines, or it’s the hardest class to test. The fix: extract business logic into domain services; keep the facade as a pure orchestrator. The tradeoff: a thin facade is easy to test (mock all subsystems); a fat facade is hard to test and hard to change.

Facade

Should a Facade expose the subsystems it wraps, or hide them completely?

Hide them. If callers can access orderFacade.Payment.ChargeAsync() directly, they bypass the facade’s orchestration and the workflow guarantee breaks. The facade’s value is the guaranteed sequence: check stock → charge → reserve → ship → notify. Exposing subsystems lets callers skip steps. The tradeoff: hiding subsystems means callers can’t do advanced operations that the facade doesn’t expose. In that case, add a new method to the facade rather than exposing the subsystem — the facade’s interface should grow to cover legitimate use cases.

Facade

How do you identify intrinsic vs extrinsic state?

Intrinsic state is the same for all objects in a group — it doesn’t change based on context. Extrinsic state is unique per object or changes based on context. For Product: TaxRate is intrinsic (same for all Electronics); Price is extrinsic (unique per SKU). The test: if two objects can share a piece of state without one affecting the other, it’s intrinsic. If sharing would cause one object’s change to affect another, it’s extrinsic. The tradeoff: the more state you classify as intrinsic, the more memory you save, but the flyweight becomes less flexible.

Flyweight

When is Flyweight not worth the complexity?

When the memory savings are negligible relative to the system’s total memory use, or when the shared objects are already small. Flyweight adds a factory, a cache, and the intrinsic/extrinsic split — meaningful complexity. Profile first: if 100,000 products each hold a 50-byte category struct, that’s 5MB — probably not worth the pattern. If each holds a 10KB category object with images and rules, that’s 1GB — Flyweight is justified. The signal: memory profiler shows many identical large objects.

Flyweight

How does EF Core's lazy-loading proxy differ from a manually written virtual proxy?

EF Core generates the proxy class at runtime using Castle DynamicProxy — you don’t write it. The proxy overrides every virtual navigation property; accessing the property triggers a DB query if the value isn’t loaded. The difference from a manual proxy: EF Core’s proxy is generated per entity type, not per instance; it uses the same DbContext that loaded the entity. A manual proxy is written once and works for all instances. The tradeoff: EF Core’s proxy is automatic but requires virtual properties and a parameterless constructor; a manual proxy is explicit but requires more code.

Proxy

When should you use a caching proxy vs IMemoryCache directly in the service?

Use a caching proxy when you want caching to be transparent to the service — the service doesn’t know it’s being cached. This is useful when the service is a third-party class you can’t modify, or when you want to swap caching strategies (in-memory vs distributed) without changing the service. Use IMemoryCache directly in the service when caching is a core concern of the service (e.g., a product catalog service that always caches), or when you need fine-grained control over cache keys and expiration. The tradeoff: proxy keeps the service pure but adds indirection; direct caching is explicit but couples the service to the caching strategy.

Proxy

What's the difference between a Proxy and a Decorator in terms of intent?

Intent is the key difference — the structure is identical. A Proxy controls access: it decides whether to forward the call at all (protection proxy), when to forward it (virtual proxy), or whether to use a cached result instead (caching proxy). A Decorator always forwards the call and adds behavior around it. A logging decorator always calls next.HandleAsync(order) — it never skips the call. A protection proxy may throw before calling the real service. If the wrapper can prevent the call from reaching the real object, it’s a Proxy. If it always calls through and adds behavior, it’s a Decorator.

Proxy

Resilience Patterns

Why is retry placement relative to circuit breaker important?

  • Retry should execute inside the same resilience pipeline before breaker decisions.
  • Outer retries around an already open breaker create extra pressure and useless attempts.
  • Proper ordering gives cleaner failure accounting and earlier protection.
  • In Polly’s outer-to-inner model this means placing retry outside the breaker, so each failed retry still passes through breaker evaluation and counts toward tripping it.
Circuit Breaker

How do you avoid a half-open thundering herd in Kubernetes-scale deployments?

  • Limit probe concurrency and keep half-open trial volume small.
  • Add jitter to retry and recovery timing.
  • Use global controls (rate limits, queueing, bulkheads) so per-pod recovery does not synchronize spikes.
  • Watch fleet-wide metrics, not only single-instance breaker events.
  • The core trap: breaker state is process-local, so per-pod recovery must be coordinated with fleet-level controls or every pod probes in lockstep.
Circuit Breaker

Which failures should trip the breaker, and which should not?

  • Trip on dependency-health signals: timeouts, connection failures, HTTP 5xx, and 429 when the client cannot absorb it.
  • Do not trip on caller-side 4xx like 400/404 — those reflect bad input, not an unhealthy dependency.
  • Encode this in ShouldHandle so the breaker measures the dependency, not your users’ mistakes.
  • Get it wrong and the breaker opens on validation errors, blocking healthy traffic to a perfectly good dependency.
Circuit Breaker

Your AI service wraps OpenAI APIs with per-tenant limits and runs on 4 instances. How do you enforce limits accurately, and which algorithm do you choose?

Expected answer

  • Use distributed shared state, usually Redis, because per-instance memory breaks global accuracy.
  • Partition by tenant ID so quotas align with billing and fairness.
  • Choose token bucket when tenants need controlled burst capacity with stable average throughput.
  • Use atomic operations (Lua or transaction pattern) for refill and consume to avoid race conditions.
  • Return 429 with Retry-After and remaining quota headers to support client backoff. Why this question matters
  • It tests algorithm choice plus distributed systems correctness, not just definition recall.
Rate Limiting

When would you prefer sliding window counter over fixed window in a public API?

Expected answer

  • Prefer sliding window counter when edge fairness matters and fixed window boundary bursts are unacceptable.
  • It gives near-rolling behavior with lower memory than sliding log.
  • Accept approximation error in exchange for better operational cost.
  • Keep fixed window only where simplicity dominates and traffic patterns are predictable. Why this question matters
  • It checks whether the candidate can justify tradeoffs under realistic constraints.
Rate Limiting

What failure mode should you choose if Redis-based rate limiting is unavailable: fail-open or fail-closed?

Expected answer

  • Decide by endpoint risk profile, not globally.
  • Fail-open for low-risk endpoints when availability is the top priority.
  • Fail-closed for sensitive operations where abuse or cost explosion is unacceptable.
  • Document and test the behavior with chaos drills. Why this question matters
  • It tests operational judgment and explicit risk tradeoff reasoning.
Rate Limiting

Why does retry without jitter make outages worse and how does jitter fix it

  • Without jitter each client computes nearly identical retry times so failures synchronize into periodic traffic spikes.
  • Those spikes hit while the dependency is already degraded which increases queue depth and recovery time.
  • Jitter randomizes each delay so retries spread over time and reduce synchronized pressure.
  • This improves recovery odds and stabilizes shared infrastructure such as load balancers and connection pools.
  • Tradeoff jitter reduces herd effects but increases per request timing variance and makes behavior slightly harder to predict.
Retry and Timeout Patterns

How do you prevent retry amplification in a multi layer microservices system

  • Assign retry ownership to one layer per call path usually the edge caller or the service nearest the user boundary.
  • Propagate cancellation tokens and deadlines so downstream services respect the remaining time budget.
  • Keep low retry counts and combine with circuit breaker and rate limits to avoid multiplicative pressure.
  • Measure effective attempts per request in telemetry and alert when fan out exceeds budget.
  • Tradeoff centralizing retries improves control and cost but can reduce local autonomy for service teams.
Retry and Timeout Patterns

System Architecture

When would you choose orchestration over choreography in an event-driven workflow?

Orchestrate when the workflow is long-running and needs real ordering or compensation — a checkout that charges payment, reserves inventory, then ships. A central process manager keeps that flow easy to follow and roll back, at the cost of one more component that can bottleneck. Choreography fits loosely-related reactions, like “order placed” fanning out to email, analytics, and search: autonomous and decoupled, but no single place knows the whole story, so tracing gets harder as subscriptions grow. Rule of thumb — orchestrate transactions you must reason about end to end; choreograph independent reactions.

Event-Driven Architecture

How do you evolve integration event contracts without breaking consumers?

Treat the event as a public API — other teams deploy against it on their own schedule. Keep changes additive; never rename or drop a field in place. For a genuine breaking change, version it (OrderPlaced.v2) and publish both through a migration window until consumers move over. Consumer-driven contract tests in CI catch regressions before release, and deserialization-failure metrics surface a bad change in minutes. The mindset that keeps you safe: an event schema is a long-lived contract, not an internal DTO you can refactor freely.

Event-Driven Architecture

How do you process events reliably under at-least-once delivery?

Duplicates and reordering are normal, so the goal is making at-least-once safe, not chasing exactly-once. The core move is idempotent consumers: key on the EventId, keep a durable set of handled IDs, and make the second delivery a no-op. A transactional outbox closes the publishing gap so a committed change always emits its event — no “DB committed but publish lost” holes. Where order matters, partition by aggregate key and carry a sequence number so consumers can drop stale events. Exactly-once is a myth; idempotency plus the outbox is how you fake it safely.

Event-Driven Architecture

Why can microservices lead to distributed data consistency problems, and how do you address them?

  • Each service owns its data, so cross-service business actions cannot rely on one ACID transaction.
  • A later step can fail after an earlier local commit, producing partial state.
  • Use sagas with compensating actions across local transactions.
  • Use outbox/inbox and idempotent handlers to survive retries and duplicates.
  • Accept eventual consistency and make process state observable.
Microservices

How do you decide between monolith, modular monolith, and microservices for a new product?

  • Decide from team size, release pressure, domain volatility, and ops maturity.
  • One team in discovery phase usually benefits most from monolith speed.
  • Clear domains with limited platform capacity often fit modular monolith.
  • Choose microservices when independent deploy/scale constraints are proven.
  • Re-evaluate architecture periodically as constraints change.
Microservices

How do you enforce module boundaries in a modular monolith to prevent it from degrading into a traditional monolith?

  • Split each module into contracts core and infrastructure assemblies and allow cross module references only to contracts.
  • Enforce table ownership and block cross module joins from application code.
  • Add architecture tests that fail CI on forbidden project references and namespace dependencies.
  • Keep integration through explicit APIs events or mediator requests rather than direct class calls.
  • Track boundary drift with code review checklists and dependency graph checks.
  • Tradeoff: stronger enforcement raises short term friction but prevents long term structural decay.
Modular Monolith

When would you choose a modular monolith over microservices, and what signals tell you it is time to extract?

  • Choose modular monolith when domains are clear enough for module ownership but not enough operational pressure exists to justify distributed systems overhead.
  • Prefer it when one platform team can run a single deployment reliably and release cadence is still mostly coordinated.
  • Extract when one module needs independent scaling, independent release frequency, or different reliability posture that the shared deployment cannot satisfy.
  • Extract when change coupling metrics show one module repeatedly blocked by unrelated regression risk in the shared deploy.
  • Keep contract stable first then swap transport from in process implementation to HTTP or gRPC.
  • Tradeoff: delaying extraction avoids premature complexity but waiting too long can slow teams once scaling pressure is persistent.
Modular Monolith

What is a modular monolith and when is it better than microservices?

A modular monolith enforces explicit module boundaries (clear interfaces, no internal coupling) within a single deployment. It gives you maintainability and team autonomy without distributed systems complexity. It is better than microservices when independent deployment is not yet a hard requirement — which is most early-stage products. Cost: requires discipline to maintain module boundaries; without enforcement, it degrades into a big ball of mud.

Monolith Architecture

When do microservices become justified over a monolith?

When independent deployment is a hard requirement and the team can support the operational complexity (distributed tracing, network failures, eventual consistency, service mesh). The signal is usually: teams are blocked by each other’s deployments, or a specific component needs independent scaling that the monolith cannot provide.

Monolith Architecture

How do you mitigate cold start latency in serverless functions?

Cold starts occur when the provider scales from zero — the runtime must boot, load the function, and initialize dependencies (500ms–3s for .NET). Mitigations: (1) Premium Plan (Azure) or Provisioned Concurrency (AWS) keeps instances warm at a fixed cost. (2) Native AOT compilation reduces .NET startup time by eliminating JIT. (3) Minimize dependencies loaded at startup — lazy-initialize anything not needed on every invocation. Accept cold starts for background jobs where latency doesn’t matter; eliminate them for latency-sensitive APIs.

Serverless Architecture

How do you avoid vendor lock-in with serverless functions?

Isolate business logic from the function host. The function handler should be a thin adapter that reads the trigger event, calls a provider-agnostic service, and returns a response. The core logic lives in a class library with no provider-specific dependencies. This makes the business logic portable even if the host (Azure Functions, AWS Lambda) is not. Cost: requires discipline to keep the adapter thin; teams often let provider-specific bindings leak into business logic.

Serverless Architecture

How do you model cost for a serverless workload vs a container-based one?

Serverless: cost = (invocations × duration × memory) + egress. Scales to zero when idle — no cost for unused capacity. Container: cost = (instances × uptime) regardless of traffic. Serverless wins for bursty or infrequent workloads; containers win for steady high-throughput traffic where the per-invocation overhead adds up. The crossover point is roughly when serverless cost exceeds the minimum container cost at sustained load.

Serverless Architecture

When is SOA still the right choice over microservices?

SOA remains appropriate for enterprise integration scenarios: connecting heterogeneous legacy systems (SAP, Salesforce, custom ERP) via a shared integration layer, shared services used by multiple business units where microservice decomposition overhead isn’t justified, and regulated industries requiring centralized governance and audit trails. Microservices are better for greenfield cloud-native systems where teams can own independent services end-to-end.

Service-Oriented Architecture

Software Architecture

How do you choose between a monolith, a modular monolith, and microservices?

  • The decision is mainly organizational: microservices buy independent deployment and team autonomy at the cost of network calls, distributed failure modes, and operational overhead
  • A modular monolith captures most of the boundary benefits — clear module seams, enforced dependencies — while keeping one deployable and in-process calls; it’s the right default for most teams
  • Reach for microservices when independent scaling or deployment or team boundaries genuinely demand it, not because it’s fashionable, and rarely before you understand the domain boundaries
  • Migration is one-directional and cheap the right way round: a well-structured modular monolith can later be carved into services along its existing seams
Software Architecture

Application Architecture

How does Clean Architecture differ from traditional N Layer, and when does the extra indirection pay off

  • N Layer often keeps downward dependencies from UI to business to data, while Clean Architecture enforces inward dependencies toward policy.
  • In Clean Architecture, inner layers define contracts and outer layers implement them, so domain logic survives framework changes.
  • The indirection pays off when business rules are complex, testing speed matters, and infrastructure churn is expected over multiple years.
  • For short lived services with shallow rules, the same indirection can slow delivery with little resilience gain.
  • Tradeoff statement: Clean Architecture buys adaptability and testability by paying upfront complexity and wiring overhead.
Clean Architecture

What happens when you apply Clean Architecture to a simple CRUD service

  • You often create command handlers ports and adapters that do little more than pass data through, so cycle time drops without stronger correctness.
  • Teams spend effort maintaining abstractions instead of shipping user value because domain complexity never materializes.
  • Operationally it can still work, but the architecture tax shows up in onboarding and debugging cost.
  • A pragmatic approach is to keep boundaries light and evolve toward stricter clean boundaries only where invariants and integration volatility appear.
  • Tradeoff statement: using full Clean Architecture too early protects against hypothetical future change while charging real present complexity.
Clean Architecture

What is the Dependency Rule and why does it matter?

All source code dependencies must point inward — toward higher-level policies (domain). Outer layers (infrastructure, UI) depend on inner layers; inner layers never depend on outer layers. This means you can change databases, frameworks, or delivery mechanisms without touching business rules. Cost: requires defining interfaces in inner layers and wiring implementations in outer layers — more upfront structure.

Layered Architecture

What is the difference between traditional layered and Onion/Clean Architecture?

Traditional layered: UI → Business Logic → Data Access → Database. Changing the DB affects everything above. Onion/Clean: Infrastructure → Application → Domain. The Domain has zero dependencies; Infrastructure implements Domain interfaces. The key difference is dependency inversion at the data access boundary.

Layered Architecture

What is the key difference between MVC and MVVM?

In MVC, the Controller handles requests and explicitly selects which View to render — the View is passive. In MVVM, the View binds to the ViewModel’s observable state — the ViewModel does not know about the View. MVVM enables automatic UI updates via data binding; MVC requires the Controller to pass data to the View on each request. Cost: MVVM requires binding infrastructure (INotifyPropertyChanged, ICommand) which adds boilerplate; MVC is simpler for stateless request/response flows.

MVC MVVM

Why is the ViewModel more testable than the Controller?

The ViewModel has no dependency on HTTP context, routing, or view rendering — it is a plain C# class. You can unit-test it by calling commands and asserting property values. The Controller depends on HttpContext, IActionResult, and the MVC pipeline, requiring more setup or integration tests.

MVC MVVM

How do you prevent version conflicts between plug-ins that depend on different versions of the same library?

Use AssemblyLoadContext to isolate each plug-in’s dependencies. Each context has its own assembly resolution scope, so plug-in A’s dependency on Newtonsoft.Json 12.x does not conflict with plug-in B’s dependency on 13.x. Cost: each context adds memory overhead and prevents type sharing across contexts — objects from one context cannot be cast to types from another.

Plug-in Architecture (MicroKernel)

How do you version extension point interfaces without breaking existing plug-ins?

Treat the extension point interface as a public API with semantic versioning. For breaking changes, introduce a new interface version (IPlugin, IPlugin2) and support both simultaneously via an adapter. Existing plug-ins implement the old interface; new plug-ins implement the new one. The core adapts old plug-ins to the new contract. Cost: adapter proliferation over time — set a deprecation timeline and remove old interfaces after a migration window.

Plug-in Architecture (MicroKernel)

When is plug-in architecture the wrong choice?

When all features are known upfront and no third-party extensibility is needed. The loading complexity, versioning challenges, and security surface (untrusted code running in-process) are not justified for internal applications. A monolith with feature flags is simpler and safer. Plug-in architecture earns its complexity when customers or third parties need to extend the product without modifying the core.

Plug-in Architecture (MicroKernel)

What is the key difference between MVC and MVVM?

MVC uses a controller to handle a request and select a view. MVVM exposes observable state and commands that a long-lived view binds to. MVVM pays for binding infrastructure; MVC fits stateless request/response rendering with less ceremony.

Presentation Architecture Variants

When does a coordinator earn its place?

Add one when navigation has branching policy that must be tested independently of screen state, such as checkout flows that can continue to confirmation, authentication, or payment recovery.

Presentation Architecture Variants

Distributed Systems

How do you design gateway aggregation endpoints for client efficiency, and what do you keep out of the gateway?

Use gateway routing for normal traffic and add a few targeted aggregation endpoints where a client — usually mobile — would otherwise make five round trips for one screen. The gateway composes those reads and tunes the payload, but it stays thin: auth, throttling, routing, transformation, observability, and nothing else. Business rules, transactions, and domain invariants live in the backend services, with correlation IDs flowing across the fan-out so you can trace a slow screen. The line to hold: aggregation is response-shaping, not orchestration — the moment decision logic creeps in, you have a distributed monolith.

API Gateway

Where do API Gateway and service mesh responsibilities belong in one architecture?

They handle different traffic planes, so they sit side by side rather than compete. The gateway owns north-south traffic — clients entering the system — so edge auth, TLS termination, external rate limits, and API surface control belong there. The mesh owns east-west traffic between internal services: mTLS, retries, traffic shifting, and per-service telemetry. The gateway guards the front door; the mesh governs the hallways.

API Gateway

Is a system "CP" or "AP" as a whole?

Don’t think of it system-wide — decide per operation, because different endpoints have different correctness budgets. A PlaceOrder or ledger write wants CP: refuse it under partition rather than risk split-brain or a double charge. A GetRecommendations read wants AP: keep serving slightly stale data because availability beats freshness there. A profile read might only need session consistency. So the same system is CP on some paths and AP on others; labeling the whole platform and applying one rule everywhere is the mistake. Map each operation to its business invariant and its allowed staleness window.

CAP theorem

Why is "pick two of three" a misleading way to state CAP?

Because in any system that replicates data across machines, partition tolerance isn’t something you opt out of — networks drop packets and isolate nodes whether you like it or not. So “CA” isn’t really on the menu: the moment a partition hits, a system that didn’t plan for it just stops being correct or stops responding. The honest framing is that P is a given, and the real decision is what you do during a partition — sacrifice consistency to stay available (AP), or sacrifice availability to stay consistent (CP). With no partition you can have both; CAP only forces the choice inside the failure window.

CAP theorem

How do you guarantee read-your-writes when writes go to a strong store but reads come from an eventually consistent cache?

The problem is the gap between a committed write and a cache that hasn’t caught up. The cleanest fix is to route that user’s immediate post-write reads past the cache to a strong or session-consistent path, just while freshness matters. Pair it with write-through or explicit invalidation on update so the cache converges quickly, and carry a version or timestamp so you can reject a stale follow-up write. Keep the writes idempotent so a retry is harmless. You don’t need global strong consistency — only read-your-writes for the one user who just acted.

Consistency Models

Which chat features should use linearizable, causal, and eventual consistency?

Map each feature to the weakest model that still feels correct. Message ordering and reply chains need at least causal consistency — a reply must never show up before the message it answers. Typing indicators can be eventual; a dropped or reordered one costs nothing, and the latency win is worth it. Read receipts in a compliance-sensitive context might justify something stronger. The skill on display is refusing a one-size-fits-all level: spend strong-consistency latency only where a user would actually notice the inconsistency.

Consistency Models

Why can linearizability reduce availability during a network partition, and how do you contain the blast radius?

Linearizable operations need a quorum to agree on one order, and a partition can cut a node off from that quorum. To stop two sides committing conflicting truths, the isolated side must reject or delay those operations — that refusal is the availability you lose (the CP corner of CAP). Contain the blast radius by scoping strong consistency to the writes that truly need it — payments, inventory decrements — and serving everything else from weaker models with explicit degraded-mode behavior. Partition tolerance isn’t optional; the real choice is which operations fail closed and which keep serving.

Consistency Models

Why is 2PC problematic in microservices?

  • 2PC requires all participants to hold locks during the prepare-to-commit window. In microservices, this window spans network calls, making lock duration unpredictable.
  • The coordinator is a single point of failure. A crash after PREPARE but before COMMIT leaves participants locked indefinitely.
  • Most microservice infrastructure (HTTP APIs, NoSQL, cloud queues) doesn’t support XA transactions.
  • Tradeoff: 2PC gives strong consistency but at the cost of availability and throughput. CAP theorem: under a partition, you must choose consistency or availability — 2PC chooses consistency.
Distributed Transactions

How does the Outbox pattern guarantee at-least-once event delivery?

  • The event is written to an OutboxMessages table in the same DB transaction as the domain change. If the transaction commits, the event is durable.
  • A background worker reads unprocessed outbox entries and publishes them to the broker, retrying until the broker acknowledges.
  • This guarantees at-least-once delivery (the event may be published more than once if the worker crashes after publishing but before marking the entry as processed).
  • Consumers must be idempotent to handle duplicate events.
  • Tradeoff: adds a background worker and an extra DB table. The cost is worth it for any event that must not be lost.
Distributed Transactions

Why is idempotency essential for reliable retry strategies, and what happens without it?

  • Retries are a certainty in distributed systems because clients cannot always distinguish failure from delayed success.
  • Idempotency converts ambiguous outcomes into deterministic behavior, so retries do not amplify side effects.
  • Without it, transient faults become correctness bugs such as duplicate charges or duplicate shipment commands.
  • Idempotency also improves operational recovery because replay jobs and dead letter reprocessing become safe.
  • Stronger idempotency guarantees cost extra storage, key lifecycle management, and stricter request validation.
Idempotency

How do you implement idempotency for a payment endpoint that processes charges through a third party provider?

Reserve a durable local attempt, commit, then call a provider that honors the same key outside the database transaction. A timeout becomes an unknown outcome that is reconciled with the same provider key; it is not evidence that no charge happened. Payment Systems contains the complete sequence and its boundary-specific guarantee.

Idempotency

How do you pick a load-balancing algorithm for requests with highly variable duration, like AI inference?

Round robin is the right default when instances are similar and requests cost about the same, but it falls apart when duration varies — exactly the inference case, where one long completion ties up an instance while round robin keeps handing it new work. Least-connections handles that better by routing to the instance with the fewest in-flight requests, which tracks real load when durations are uneven. The catch is that connection count doesn’t capture per-request CPU cost, so a few short-but-expensive prompts can still skew it. For inference you validate the choice by measuring p95/p99 latency and backend saturation under representative load rather than trusting any default.

Load Balancing

When do you choose an L4 load balancer over L7?

L4 balances on connection metadata — IP, port, protocol — without reading the payload, so it’s fast and works for any TCP/UDP traffic, but it can’t decide based on HTTP path, host, or headers. L7 parses HTTP, so it can route by path or header and do canary, A/B, and edge auth, at the cost of more processing per request. Choose L4 when you need raw throughput and generic transport routing; choose L7 the moment routing depends on request content. Plenty of systems use both — L4 at the edge for raw distribution, L7 deeper for content-aware routing.

Load Balancing

Why must readiness checks differ from liveness checks behind a load balancer?

They drive different actions. Liveness asks whether the process can make progress and commonly triggers restart, so it stays local and dependency-free. Readiness asks whether this instance should receive new traffic. It can include instance-specific initialization or a dependency failure unique to this replica, but blindly checking a database shared by every replica can evict the entire fleet. Keep shared-dependency health observable and put it in readiness only when routing elsewhere can help.

Load Balancing

How do you design webhook consumers to prevent event loss and duplicate processing?

Verify the provider-defined signature or MAC over its documented input and enforce its replay-protection contract. Store the provider event ID and inbox/queue payload durably before returning an accepted response, then process it idempotently in a worker. Since the producer can exhaust its retry window while the consumer is unavailable, reconcile against the provider’s event-list API or replay facility.

Webhooks

When would you choose webhooks over a shared message broker for inter-service event delivery?

Webhooks win when you cross an organizational boundary — a SaaS or third-party provider you don’t control can’t publish into your broker, but it can POST to a URL. Inside your own platform a message broker is usually better: durable fan-out, back-pressure, replay, and per-key ordering that raw HTTP callbacks don’t give you. They aren’t exclusive, though — the common pattern is to receive external webhooks at the edge and immediately republish them onto an internal broker, so you get HTTP reach at the boundary and broker guarantees everywhere inside.

Webhooks

How do you protect a webhook endpoint against replay attacks?

Follow the provider’s replay-protection contract: it may sign a timestamp, use a nonce, or issue short-lived asymmetric signatures. For an HMAC contract with a signed timestamp, verify the MAC over the exact documented payload and timestamp, reject requests outside the permitted clock window, and compare the MAC in constant time. A durable delivery ID still catches duplicate deliveries inside that window.

Webhooks

Message Queues

How do you design a Kafka topic to keep per-customer ordering while handling high throughput?

Use customer_id when all events for one customer require one partition order. Size partitions from measured consumer throughput and scale a consumer group only to the partition count. If a hot customer forces a composite key such as customer_id:region, the guarantee is now only per customer per region; any cross-region customer order must be serialized by another sequencer, version check, or downstream merge rule. Keep consumers idempotent because retries and rebalances can repeat records.

Kafka

Compare at-most-once, at-least-once, and exactly-once in Kafka — which fits payment events?

At-most-once commits the offset before processing, so a crash loses events — almost never acceptable for payments. At-least-once processes first and commits after, so a crash before commit reprocesses; it’s the common production default and safe as long as handlers are idempotent. Exactly-once is real but narrow: Kafka transactions plus an idempotent producer give atomic consume-process-produce, but only for Kafka-to-Kafka flows — a database write or HTTP call inside the handler still needs its own idempotency or an outbox. For payments, the usual answer is at-least-once with strict idempotency (dedupe on a payment ID), reaching for exactly-once only when a duplicate side effect is too costly to risk and the whole flow stays inside Kafka.

Kafka

Why is MSMQ a maintenance choice rather than a new-system choice?

It’s Windows-only — it cannot run on Linux or in containers, which blocks containerization and cloud-native deployment. Its distributed-transaction story depends on MSDTC, which is fragile and unavailable in containers, and System.Messaging doesn’t exist in .NET 5+. Modern cross-platform brokers (RabbitMQ, Azure Service Bus, Kafka) provide the same durable async messaging without the platform lock-in, so MSMQ is justified only where it’s already deployed and migration cost isn’t worth it.

MSMQ

How does a transactional receive prevent message loss in MSMQ?

The message is removed from the queue only if the transaction commits. You Begin a MessageQueueTransaction, Receive under it, process the message, then Commit. If processing throws, you Abort and the message is returned to the queue for retry. This is the local-transaction equivalent of an ack: a crash mid-processing leaves the message available rather than consumed-and-lost (the same guarantee modern brokers give via manual ack / visibility timeout).

MSMQ

What replaces MSMQ's MSDTC distributed transactions today?

The Outbox pattern: write the outgoing message to a table in the same local DB transaction as the domain change, then a background relay publishes it to the broker with retries (at-least-once + idempotent consumers). For multi-step cross-service workflows, a Saga with compensating transactions. Both avoid MSDTC’s coordinator fragility — see Distributed Transactions.

MSMQ

In at-least-once processing, how do you prevent loss and duplicate side effects when a consumer crashes?

Acknowledge only after the business commit. A crash before acknowledgement then causes redelivery rather than loss. Reserve the message or business idempotency key atomically, bound retries, and route persistent failures to a dead-letter queue.

Message Queues

When do you choose Kafka over RabbitMQ for a .NET service?

Choose Kafka for high-throughput, replayable event streams and RabbitMQ for low-latency work queues or routing-key patterns. In either case, define the ordering boundary per partition or queue key and include operational complexity and team experience in the decision.

Message Queues

Which metrics should page you first in queue-driven systems?

Page on the age of the oldest message and end-to-end processing latency because depth alone can hide one permanently stuck item. Correlate those with consumer lag or unacknowledged work, retry rate, dead-letter rate, and failure ratio.

Message Queues

How do you use RabbitMQ to absorb bursty ingress when producers outpace consumers?

Put a durable queue between the ingress and the workers so bursts land in the queue instead of overwhelming the worker tier. Keep the ingress path thin — validate, enqueue, return 200 fast — so a webhook sender never waits on your processing. Then scale competing consumers horizontally off the same queue, and use prefetch (BasicQos) so one slow worker can’t hoard unacked messages while others sit idle. Route poison messages to a dead-letter exchange and watch queue depth, redelivery rate, and message age. The queue is the shock absorber: it turns a traffic spike into a temporary backlog instead of dropped requests or a melted worker tier.

RabbitMQ

How do you implement at-least-once delivery, and what new risk appears?

At-least-once is four settings working together: a durable queue, persistent messages (DeliveryMode = 2), publisher confirms so the producer knows the broker accepted the message, and manual consumer ack so a message isn’t removed until processing succeeds. If a consumer crashes before acking, the broker redelivers. The risk that buys you is duplicates — a redelivery can race an ack — so consumers must be idempotent, keyed on a stable message ID with a dedupe store. You trade the possibility of loss for the certainty of occasional duplicates, which is the far easier problem to make safe.

RabbitMQ

When would you choose RabbitMQ over Kafka?

Choose RabbitMQ when you want a smart broker doing the routing — direct, topic, fanout, and header exchanges, per-message TTL, priorities, dead-lettering — for task queues, request-reply, and command dispatch where low latency and flexible routing matter more than retention. Choose Kafka when you need a durable, replayable log: high-throughput event streams, ordering per partition, and multiple independent consumers re-reading history by offset. The rough line is that RabbitMQ moves a message and forgets it, while Kafka stores an event history. If you keep wishing you could re-consume past messages, you actually wanted Kafka.

RabbitMQ

Scalability Patterns

What architectural prerequisites must be met before horizontal scaling works?

Expected answer:

  • Service must be stateless — no in-memory session or local file state.
  • All shared state externalized (Redis for cache/session, blob storage for files, database for persistent data).
  • A load balancer must distribute traffic across instances.
  • Without statelessness, adding instances creates inconsistency rather than capacity.
  • Without a load balancer, all traffic still hits one node. Why this is strong: It shows horizontal scaling is an architectural property, not just an ops action. Many engineers think “add more servers” is always safe.
Horizontal Scaling

Why can horizontal scaling fail even with many instances?

Expected answer:

  • Database becomes the bottleneck — each instance opens its own connection pool (20 instances × 100 pool size = 2,000 connections).
  • Stateful services silently break (in-memory session on pod 1 invisible to pod 2).
  • Uneven load distribution from sticky sessions or hot partitions.
  • Thundering herd during scale-out when new instances aren’t ready fast enough.
  • Connection pool or thread pool saturation before CPU becomes the bottleneck. Why this is strong: It demonstrates awareness that scaling one tier shifts the bottleneck downstream — a classic distributed-systems trap.
Horizontal Scaling

When would you choose read replicas instead of CQRS for a scaling problem?

Expected answer:

  • Choose read replicas when main pressure is read throughput on an existing relational model.
  • Choose CQRS when read/write models diverge and read projections need different shape or storage.
  • Mention consistency behavior: replicas have lag; CQRS read models are eventually consistent by design.
  • Mention complexity: replicas are simpler operationally than full CQRS/event projection pipelines. Why this is strong: It balances architecture fit, consistency, and operational cost.
Scalability Patterns

When is vertical scaling the right first move over horizontal scaling?

Expected answer:

  • When the workload is stateful and hard to shard (e.g., a relational database).
  • When the bottleneck is clearly resource-bound on a single node.
  • When the operational cost of distributed coordination (consensus, partitioning, eventual consistency) exceeds the cost of a larger machine.
  • Vertical scaling is faster to implement and avoids distributed-systems complexity.
  • It’s the right first move for most monoliths and managed databases in early growth phases. Why this is strong: It shows the candidate reasons about tradeoffs (complexity vs cost) rather than defaulting to “just add more servers.”
Vertical Scaling

Why does vertical scaling have diminishing returns at high scale?

Expected answer:

  • Hardware cost scales super-linearly: premium SKUs charge a multiplier for the same resource increment.
  • Not all workloads parallelize across additional cores — Amdahl’s Law bounds speedup by the serial fraction.
  • A process with 20% serial code can never exceed 5x speedup regardless of core count.
  • Memory bandwidth and cache coherency become bottlenecks as core counts grow.
  • The cost-per-unit-of-capacity curve inflects sharply at high tiers. Why this is strong: It demonstrates understanding of hardware economics and parallelism limits, not just “bigger is better.”
Vertical Scaling

What's the failure mode of relying solely on vertical scaling for availability?

Expected answer:

  • A single large node is a single point of failure — any hardware fault, OS crash, or maintenance window causes full outage.
  • Vertical scaling increases capacity but does nothing for availability.
  • Mitigation: pair with redundancy (active-passive failover, read replicas, multi-AZ deployment).
  • Azure SQL Business Critical tier includes a built-in secondary replica for reads and failover.
  • This is distinct from Horizontal Scaling, which inherently distributes failure across nodes. Why this is strong: It shows the candidate distinguishes capacity from availability — a common interview blind spot.
Vertical Scaling

Patterns

Why does CQS make code easier to reason about?

When a method returns a value, you know it has no side effects — you can call it freely, cache its result, or call it multiple times without changing state. When a method returns void, you know it changes state but produces no data you depend on. This separation eliminates the need to read the implementation to understand whether a call is safe to repeat or reorder. It also makes testing easier: queries can be tested without checking state changes; commands can be tested without checking return values.

CQS

When is it pragmatic to violate CQS?

Three common justified exceptions: (1) Stack.Pop() — splitting into Peek() + Remove() introduces a race condition in concurrent code. (2) Repository.Add() returning the generated ID — the ID is produced by the database; returning it avoids an extra round-trip. (3) Async I/O methods — Task<T> methods that perform I/O and return a result are idiomatic in .NET even when they have side effects. The principle is a guideline: apply it where it improves clarity, relax it where strict adherence creates awkward APIs.

CQS

Explain Transient, Scoped, and Singleton lifetimes with a safe production example each.

The three differ by how long the container keeps one instance. Transient is a fresh instance per resolution — fine for lightweight stateless services like mappers or formatters. Scoped is one instance per scope, which in ASP.NET Core means per HTTP request; the canonical case is DbContext, where one unit of work per request keeps tracking and transactions coherent. Singleton is one instance for the app’s lifetime — good for thread-safe shared state like a cache, an IClock, or IHttpClientFactory — but it must be thread-safe and must never capture a scoped dependency. The thing to get right: lifetime is about shared state and thread-safety, not performance.

Dependency Injection

Why is Service Locator an anti-pattern in business logic, and when is it acceptable?

Service Locator means reaching into IServiceProvider (GetRequiredService<T>()) from inside a class instead of declaring the dependency in its constructor. In business logic that hides what the class actually needs — the constructor stops telling the truth — so a missing registration fails at runtime instead of compile time, and tests have to mimic the container instead of just passing fakes. It’s acceptable in the places that genuinely pick implementations at runtime: factories, middleware activation, and explicit scope management in background jobs. The rule: constructor injection for anything with business logic; the locator only in infrastructure that has no other way to resolve.

Dependency Injection

What is a captive dependency, and how do you fix it?

It’s when a longer-lived service captures a shorter-lived one — classically a singleton or IHostedService holding a scoped DbContext. The scoped object then outlives its intended request boundary, giving you stale state, broken EF Core change tracking, and disposal at the wrong time. In Development the scope validator catches it and throws InvalidOperationException; in Production it just misbehaves quietly. The fix is to not inject the scoped service at all: inject IServiceScopeFactory, open a short scope where you need the work, resolve inside it, and let it dispose. The tell to watch for is a singleton constructor asking for anything request-scoped.

Dependency Injection

How does an event bus differ from a message broker?

  • Event bus is a dispatch mechanism — resolves handlers and invokes them, typically in-process
  • Message broker is infrastructure — durable middleware (RabbitMQ, Kafka, Service Bus) that persists and delivers messages across processes
  • In-process bus loses unprocessed events on crash; broker persists them
  • Bus abstraction can sit on top of a broker (MassTransit does this) — bus adds handler resolution and retry semantics, broker provides transport and durability
  • Cost of broker: operational complexity, network latency, serialization overhead
  • Cost of in-process bus: no durability, no cross-service delivery
  • A broker guarantees delivery at the cost of operational complexity; the in-process bus is simple but ephemeral
Event Bus

Why should event handlers be independent rather than ordered?

  • Event bus contract is fan-out — “these N things happen when X occurs” — not a sequential pipeline
  • If handler B depends on handler A’s side effect, you have a hidden workflow disguised as fan-out
  • Execution order depends on DI registration order, which is fragile and undocumented
  • Breaks when: order changes (new handler inserted), parallelism enabled, or handler moved to another service
  • Fix: model the dependency as a saga or command chain where A publishes a new event that triggers B
  • This makes the dependency visible, testable, and deployable independently
  • You end up with more events and handlers, but each is self-contained and the workflow is explicit in the codebase
Event Bus

When is MediatR INotification insufficient as an event bus?

  • Exception isolation: default MediatR stops at the first handler failure — unacceptable when independent side effects (email, analytics, inventory) must not block each other
  • Scope isolation: all handlers share the same DI scope, so DbContext mutations in handler A leak into handler B
  • Execution strategy: default is sequential; parallel execution requires custom INotificationPublisher
  • Each fix requires either replacing MediatR’s publisher or building a custom bus
  • A custom bus gives you control over all three concerns, but you lose MediatR’s pipeline-behavior ecosystem (validation, logging, caching) and auto-discovery
Event Bus

Why does EF Core's DbContext already implement the Unit of Work pattern?

  • DbContext tracks all changes to loaded entities in its change tracker.
  • SaveChangesAsync() wraps all pending inserts, updates, and deletes in a single database transaction.
  • This means multiple repository operations within one request share the same DbContext instance and commit atomically — exactly what Unit of Work provides.
  • Tradeoff: this only works if all repositories share the same DbContext instance (Scoped lifetime in ASP.NET Core DI). If you accidentally register DbContext as Singleton or Transient, the Unit of Work semantics break.
Repository & UoW

When is a generic IRepository<T> an anti-pattern?

  • A generic repository exposes the same interface for every entity, including operations that don’t make sense for some aggregates (e.g., GetAll() on an Order aggregate with millions of rows).
  • It encourages callers to treat all entities the same, bypassing aggregate-specific access patterns and invariants.
  • It often leaks IQueryable<T>, coupling callers to EF Core.
  • Better: aggregate-specific interfaces with domain-meaningful methods. Use a generic base class for the implementation, but expose a specific interface.
Repository & UoW

Architectural Patterns

When is CQRS worth the operational complexity, and when is it an anti-pattern?

CQRS earns its complexity when the read and write sides genuinely want different shapes — a high read:write ratio, broad or expensive queries, real invariants on the write side, and a need to scale the two paths independently. Then a denormalized read model serves screens cheaply while the write model stays focused on enforcing rules. It’s an anti-pattern for ordinary CRUD at low scale: the projection pipelines, eventual-consistency handling, and two models to evolve and test are pure tax when one model would have served both. Apply it per bounded context where the constraints justify it, never as a global default.

CQRS

Does CQRS require Event Sourcing or two databases?

No — and conflating them is the most common CQRS misconception. CQRS is only about separating the command model from the query model, and that can be two classes against the same database. Separate stores, message brokers, and Event Sourcing are options you add when scale or auditability demands them, not requirements. You can run CQRS on a single relational database, projecting into a denormalized view table inside the same transaction. Start with the cheapest separation that solves the problem and add infrastructure only when a real constraint forces it.

CQRS

How do you handle eventual consistency between the write and read models?

With asynchronous projection a command can succeed before the read model catches up, so the user who just acted sees stale data. The pragmatic fixes: serve that user’s immediate follow-up read from the write model (read-your-own-writes for the session), show a soft “updating…” state, and monitor projection lag so you notice drift. Underneath, make projections idempotent — brokers are at-least-once, so a projector can see the same event twice — using upserts and a record of handled event IDs. Eventual consistency is a UX-and-idempotency problem, not a reason to abandon the split.

CQRS

What makes a Bounded Context boundary real?

A context owns a coherent model and language. Integrations translate through explicit contracts or an anti-corruption layer rather than sharing one ambiguous domain model. Separate schemas, deployments, and aligned teams can reinforce that boundary, but none is mandatory by itself.

Domain-Driven Design

When does Event Sourcing justify its complexity over CRUD plus an audit-log table?

  • CRUD + audit table can satisfy compliance for many systems with lower operational overhead.
  • Event Sourcing is justified when domain behavior depends on historical intent and replay, not only final values.
  • If you need deterministic rebuild of multiple read models, Event Sourcing is stronger.
  • If temporal queries are frequent and core to product value, Event Sourcing can pay off.
  • If team maturity for schema evolution and projection operations is low, choose CRUD first.
  • The honest default is CRUD plus an audit table; Event Sourcing earns its cost only when replay and historical intent are core to the product, not merely compliance.
Event Sourcing

How do you evolve event schemas safely without breaking old streams?

  • Use explicit event versioning strategy.
  • Prefer backward-compatible additive changes.
  • Introduce upcasters/adapters for old payloads.
  • Keep integration tests that replay production-like historical streams.
  • Treat event contracts as long-lived public interfaces.
  • Schema evolution is a common production failure mode in event-sourced systems — especially when teams skip compatibility testing across historical streams.
Event Sourcing

Design Patterns

How do you decide which GoF category a pattern belongs to?

Creational if the problem is about object construction — hiding how, when, or which type to instantiate. Structural if the problem is about composing classes or adapting interfaces into larger, more convenient structures. Behavioral if the problem is about assigning responsibility or defining the communication flow between objects.

  • The category signals intent, not class structure: Proxy (Structural) and Decorator (Structural) look identical structurally but serve different behavioral intents.
  • When in doubt: ask “Is this about making objects, assembling objects, or communicating between objects?”
  • Tradeoff: categories help communicate design intent quickly, but the same structure can serve different intents — always explain the why, not just the pattern name.
Design Patterns

When does using a design pattern become an anti-pattern?

When the variation it enables doesn’t exist yet and isn’t clearly anticipated. Patterns add abstractions — more classes, more indirection, harder debugging — and abstraction has a cost.

  • The Rule of Three normally tolerates one duplication and refactors when a third concrete occurrence confirms a stable pattern; a measured earlier boundary can still justify action.
  • Most common offenders: Singleton hiding shared mutable state, Factory Method for a class that never has variants, Builder for objects with 2 properties.
  • Tradeoff: patterns pay off through repeated variation but cost upfront complexity. Refactor when recurring evidence makes the abstraction cheaper than another duplication, not merely because a second example exists.
Design Patterns

What is the Information Expert principle and why does it reduce coupling?

Information Expert assigns responsibility to the class that has the information needed to fulfill it. This keeps related data and behavior together, reducing the need for one class to reach into another to get data. The result is lower coupling and higher cohesion — each class does what it knows.

GRASP

How does GRASP differ from SOLID?

GRASP focuses on responsibility assignment — which class should own a behavior or data. SOLID focuses on design principles for maintainability (single responsibility, open/closed, etc.). They are complementary: GRASP guides initial design decisions; SOLID guides refactoring and long-term maintainability.

GRASP
Behavioral

How does ASP.NET Core Middleware differ from a classical Chain of Responsibility?

Classical CoR uses a linked list of handlers where each holds a reference to the next. ASP.NET Core Middleware uses a delegate pipeline: each middleware is a Func<RequestDelegate, RequestDelegate> that wraps the next RequestDelegate. The pipeline is compiled at startup into a single delegate chain. The difference: ASP.NET Core’s pipeline is immutable after build (no dynamic handler insertion); classical CoR can be modified at runtime. ASP.NET Core’s approach is more performant (no virtual dispatch per handler) but less flexible. The tradeoff: startup-time composition vs runtime flexibility.

Chain of Responsibility

When should a handler stop the chain vs pass the request along?

Stop the chain when the handler has definitively handled the request — either successfully (order approved) or with a terminal failure (fraud detected, stop processing). Pass along when the handler’s check passes and subsequent handlers may still reject. The key question: is this handler’s decision final? Fraud detection is final (don’t process fraudulent orders regardless of other checks). Stock check is final for that item but not for the order (other items may still be available). Design each handler to be authoritative about its own domain and ignorant of others.

Chain of Responsibility

How do you handle a request that should be processed by multiple handlers (not just the first one)?

Change the chain to not short-circuit — every handler processes the request and the results are aggregated. This is the “collect all errors” variant of validation: instead of stopping at the first failure, all handlers run and all errors are collected. The tradeoff: short-circuiting is faster (stops at first failure) but gives less feedback; full traversal is slower but gives complete error information. For user-facing validation, full traversal is usually better UX. For security checks (fraud, sanctions), short-circuit immediately — don’t reveal which check failed.

Chain of Responsibility

When is undo not feasible, and how do you handle it?

Undo is not feasible when the operation has external side effects that can’t be reversed: sending an email (can’t unsend), charging a card (refund is a new operation, not a reversal), or publishing an event to a message bus. In these cases, implement compensating transactions instead of true undo: CancelOrderCommand is the compensation for PlaceOrderCommand. Document which commands support true undo and which only support compensation. The tradeoff: true undo is simpler for the caller; compensating transactions are more realistic for distributed systems.

Command

How does MediatR implement the Command pattern, and what does it add?

MediatR separates the command (data + intent) from the handler (execution logic). PlaceOrderCommand carries the order data; PlaceOrderCommandHandler knows how to process it. The mediator routes commands to handlers without the sender knowing the handler. MediatR adds: pipeline behaviors (Chain of Responsibility around the handler), notification publishing (one command → multiple handlers), and request/response typing. The tradeoff: MediatR adds a dependency and indirection; direct service calls are simpler. MediatR earns its complexity when you need pipeline behaviors (validation, logging, caching) applied consistently across all commands.

Command

How does EF Core use LINQ Expression Trees as an Interpreter?

When you write dbContext.Orders.Where(o => o.Total > 100), the C# compiler doesn’t compile the lambda to IL — it compiles it to an Expression<Func<Order, bool>> (an expression tree, an AST). EF Core’s query translator walks this AST using ExpressionVisitor: VisitBinary for o.Total > 100 emits Total > @p0; VisitMember for o.Total emits the column name. The final SQL is assembled from the visited nodes. This is the Interpreter pattern: the expression tree is the grammar; EF Core is the interpreter; SQL is the output. The cost: EF Core can only interpret expressions it knows — calling a custom method in a LINQ query throws InvalidOperationException because the interpreter doesn’t have a rule for that method.

Interpreter

When should you compile an expression tree to a delegate instead of interpreting it?

When the same expression is evaluated many times (e.g., a discount rule evaluated on every order in a batch). Expression.Lambda<Func<DiscountContext, bool>>(tree).Compile() converts the expression tree to IL and returns a delegate that runs at near-native speed. The compilation itself is expensive (milliseconds); cache the compiled delegate. The tradeoff: compilation adds startup cost and memory; interpretation adds per-evaluation cost. Break-even is roughly 100+ evaluations of the same expression. EF Core compiles and caches query expressions for this reason.

Interpreter

What's the difference between Interpreter and Strategy for runtime rule selection?

Strategy selects a pre-written algorithm at runtime. Interpreter evaluates a rule defined as data at runtime. With Strategy, the algorithms are written in code and selected by name or type. With Interpreter, the rule is a data structure (expression tree, string) that the interpreter evaluates. Interpreter is more flexible (rules can be composed from primitives without code changes) but more complex (requires a grammar and interpreter). Strategy is simpler (just inject the right implementation) but requires a code change for each new algorithm. Use Strategy when the set of algorithms is known at compile time; use Interpreter when rules are defined by non-developers or change without deployment.

Interpreter

When should you return IEnumerable<T> vs IReadOnlyList<T> vs IAsyncEnumerable<T>?

Return IReadOnlyList<T> when the collection is fully materialized and callers need random access or Count. Return IEnumerable<T> when the collection is lazy or the caller only needs sequential access. Return IAsyncEnumerable<T> when the source is async (database, network) and you want to stream results without buffering all of them. The tradeoff: IReadOnlyList<T> is simpler but requires full materialization; IAsyncEnumerable<T> is memory-efficient but requires await foreach at the call site. Default to IReadOnlyList<T> for small collections; use IAsyncEnumerable<T> when the collection could be large or unbounded.

Iterator

What does the compiler generate for a yield return method?

The compiler generates a private class implementing IEnumerator<T> and IEnumerable<T>. The method body is split into states at each yield return point. MoveNext() advances the state machine to the next yield return, executes the code between yields, and returns true. Current returns the last yielded value. The generated class captures all local variables as fields. This is why yield return methods can’t use ref locals or unsafe code — the state machine can’t capture those.

Iterator

When does Mediator become a bottleneck or anti-pattern?

When the mediator becomes a god class that knows too much — if CheckoutCommandHandler grows to 300 lines with complex branching, the complexity moved from the controller to the handler without being reduced. The mediator pattern reduces coupling but doesn’t reduce complexity. Also avoid Mediator when the interaction is simple and direct: if OrderService only ever calls InventoryService, a direct dependency is clearer than routing through a mediator. The signal: if you can’t explain what the mediator does without listing all its handlers, it’s too complex.

Mediator

How do MediatR pipeline behaviors implement the Chain of Responsibility pattern?

Each IPipelineBehavior<TRequest, TResponse> wraps the next behavior in the pipeline. Handle(request, next, ct) calls next() to continue or returns early to short-circuit. Behaviors are registered in order; the outermost runs first. This is exactly Chain of Responsibility: each behavior decides whether to pass the request along. The difference from a manual chain: MediatR’s pipeline is configured via DI registration order, not explicit SetNext() calls. The tradeoff: DI-based ordering is less explicit but easier to configure.

Mediator

How do you prevent the memento from growing unbounded in memory?

Limit the history depth: keep only the last N mementos (a bounded stack). For long-running sessions, serialize mementos to Redis or a database instead of keeping them in memory. For abandoned cart recovery, store only the latest snapshot (not the full history). The tradeoff: deeper history = more undo steps but more memory. For most UX scenarios, 10-20 undo steps is sufficient. For audit/compliance scenarios, store all snapshots in a database with a TTL.

Memento

When is Memento overkill compared to simpler approaches?

When the state is small and the undo operation is simple. If “undo remove item” just means re-adding the item, store the removed item directly — no need for a full cart snapshot. Memento earns its complexity when: (1) the state is complex and interrelated (discount + items + shipping options all affect each other), (2) you need multiple undo levels, or (3) you need to restore state across sessions (abandoned cart). For single-step undo of simple operations, store the delta (what changed) rather than the full snapshot.

Memento

Why does subscribing to an event with async void cause problems?

async void event handlers can’t be awaited. If the handler throws, the exception propagates to the synchronization context (often crashing the app) rather than being catchable by the publisher. If the handler does async work, the publisher doesn’t know when it completes — the event returns before the async work finishes. Use async void only for top-level event handlers in UI frameworks where the framework expects it. For server-side observers, use an explicit Task-returning interface and await Task.WhenAll(observers.Select(o => o.OnStatusChangedAsync(...))).

Observer

How do you prevent memory leaks from event subscriptions?

Three approaches: (1) Unsubscribe in Dispose() — implement IDisposable and unsubscribe in Dispose(). (2) Weak event pattern — use WeakEventManager (WPF) or WeakReference<T> to hold subscriber references; the publisher doesn’t prevent GC. (3) Scoped lifetime — if both publisher and subscriber have the same DI lifetime (both scoped), they’re collected together. The tradeoff: explicit unsubscription is reliable but requires discipline; weak events are automatic but add complexity and slight performance overhead.

Observer

When should you use IObservable<T> (Rx) instead of plain events?

When you need stream operators: filtering (Where), transformation (Select), throttling (Throttle), combining multiple streams (Merge, CombineLatest), or backpressure. Plain events are push-only with no composition. Rx adds a rich operator library over the observable stream. Use Rx when: you’re processing a stream of events with complex filtering or timing requirements (e.g., “notify only if status changes twice within 5 seconds”). The cost: Rx has a steep learning curve and adds a dependency. For simple one-to-many notification, plain events or explicit interfaces are sufficient.

Observer

How does the async/await compiler implement the State pattern?

The compiler transforms an async method into a struct implementing IAsyncStateMachine. The struct has a state field (an integer) representing the current position in the method. MoveNext() is a switch on state: each case resumes execution from the last await point. When an await suspends, the state is saved and MoveNext() returns. When the awaited task completes, MoveNext() is called again with the next state. Local variables become fields on the struct (captured state). This is exactly the State pattern: the method’s execution state drives behavior, and transitions happen automatically at each await.

State

When should you use the Stateless library instead of hand-written state classes?

When the state machine has many states and transitions that are better expressed declaratively. Stateless lets you define machine.Configure(State.Paid).Permit(Trigger.Ship, State.Shipped).OnEntry(() => SendShipmentEmail()) — the transition table is explicit and readable. Hand-written state classes are better when each state has complex behavior beyond transitions (not just “what’s the next state” but “what does Ship() do in this state”). The tradeoff: Stateless is concise for transition-heavy machines; hand-written classes are better for behavior-heavy states.

State

How do you handle state transitions that require async operations (e.g., sending a refund on cancel)?

Make TransitionTo() async and await the side effects before completing the transition. Or use the Observer pattern: raise a StatusChanged event after transitioning, and let async observers handle side effects. The second approach keeps state transitions synchronous and side effects decoupled. The tradeoff: synchronous transitions are simpler but can’t await side effects; event-based side effects are decoupled but harder to reason about ordering and failure handling.

State

When should you use a Strategy interface vs a Func<T, TResult> delegate?

Use a Func<T, TResult> delegate when the strategy is simple (one method, no state, no dependencies). Func<Order, decimal> is a perfectly valid shipping cost strategy for simple cases. Use an interface when: the strategy has multiple methods, needs DI-injected dependencies, needs to be registered in a DI container, or needs to carry state. The tradeoff: delegates are simpler and more flexible; interfaces are more explicit and support DI. Start with delegates; introduce an interface when the strategy grows beyond a single expression.

Strategy

How do you handle strategy selection when multiple strategies apply?

Define a priority or exclusivity rule. Options: (1) First-match wins — strategies are ordered by priority; the first applicable one is used. (2) Explicit selection — the caller specifies the strategy by name or type. (3) Composite strategy — all applicable strategies run and results are combined (e.g., sum all applicable discounts). The tradeoff: first-match is simple but order-dependent; explicit selection is clear but requires the caller to know strategy names; composite is flexible but may produce unexpected combinations.

Strategy

When should you use Template Method vs Strategy for algorithm variation?

Template Method uses inheritance — the variation is in a subclass. Strategy uses composition — the variation is in an injected object. Use Template Method when: the algorithm skeleton is stable, the variations are tightly coupled to the base class, and you don’t need to swap algorithms at runtime. Use Strategy when: you need to swap algorithms at runtime, the algorithm is independent of the class using it, or you want to avoid inheritance. The tradeoff: Template Method is simpler (no extra interface) but creates tight inheritance coupling; Strategy is more flexible but requires an extra interface and injection.

Template Method

What's the "Hollywood Principle" and how does Template Method implement it?

“Don’t call us, we’ll call you.” The base class calls the subclass’s methods (abstract steps), not the other way around. The subclass doesn’t control when its methods are called — the template method does. This inverts the typical inheritance relationship: instead of the subclass calling super.method(), the base class calls this.abstractStep(). The benefit: the algorithm’s structure is controlled by the base class; subclasses can’t accidentally skip steps or change the order. The cost: subclasses are tightly coupled to the base class’s algorithm structure.

Template Method

What is double dispatch and why does Visitor need it?

Single dispatch: the method called depends on the runtime type of ONE object (the receiver). Double dispatch: the method called depends on the runtime types of TWO objects (the element AND the visitor). Without double dispatch, visitor.Visit(item) would call the Visit(ICartItem) overload — the compiler resolves overloads at compile time based on the declared type. item.Accept(visitor) → visitor.Visit(this) forces the compiler to resolve the overload based on this’s concrete type at runtime. The cost: two virtual calls instead of one; the pattern is non-obvious to developers unfamiliar with it.

Visitor

When does EF Core use ExpressionVisitor, and what does it do?

EF Core’s query translator is an ExpressionVisitor that walks the LINQ expression tree and converts it to SQL. dbContext.Orders.Where(o => o.Total > 100).Select(o => o.Id) builds an expression tree; EF Core visits each node: VisitMethodCall for Where and Select, VisitBinary for o.Total > 100, VisitMember for o.Total and o.Id. Each visit emits SQL fragments. The final SQL is assembled from the visited nodes. This is why EF Core can translate LINQ to SQL but throws InvalidOperationException for expressions it can’t translate — the visitor doesn’t know how to visit that node type.

Visitor

When should you use pattern matching instead of Visitor?

When the element hierarchy is small (2-4 types), changes frequently (new types added regularly), or the operations are simple (one-liners per type). Pattern matching is more readable, requires no Accept() method on elements, and handles new element types with a compiler warning (exhaustiveness checking with _ catch-all). Visitor earns its complexity when: you have 5+ element types, 5+ operations, and the hierarchy is stable. The signal: if adding a new element type requires editing more than 3 visitor classes, the hierarchy is too unstable for Visitor.

Visitor
Creational

How do you add a new product type (e.g., IFraudDetector) to an existing Abstract Factory without breaking all existing factories?

You can’t without modifying the interface — that’s the fundamental tension. Options: (1) Add the method with a default implementation in the interface (default IFraudDetector CreateFraudDetector() => new NoOpFraudDetector()), which avoids breaking existing factories but hides missing implementations. (2) Use a separate IFraudDetectorFactory interface and compose it with IPaymentProviderFactory at the call site. (3) Accept the breaking change and update all factories — justified when the product is truly part of the family. The tradeoff: default implementations hide gaps; separate interfaces reduce cohesion; breaking changes are honest but costly.

Abstract Factory

When is Abstract Factory overkill compared to a simpler approach?

When you have only one product family (no need to swap providers), or when products don’t need to stay compatible with each other. A single IPaymentProcessor interface with DI registration is sufficient if you never need IReceiptGenerator to match the processor’s provider. Abstract Factory’s value is the family constraint — if that constraint doesn’t exist in your domain, you’re adding indirection for no benefit. Cost: every new product type requires updating the factory interface and all implementations.

Abstract Factory

How does Abstract Factory relate to the DI container in modern .NET?

The DI container IS an Abstract Factory at runtime. services.AddSingleton<IPaymentProcessor, StripePaymentProcessor>() registers a factory for IPaymentProcessor. The container creates compatible families when you register all related services together. The difference: DI containers don’t enforce family consistency at compile time — you can register StripePaymentProcessor with PayPalReceiptGenerator and the compiler won’t complain. A typed Abstract Factory interface enforces this at compile time. Use DI for flexibility; use a typed factory when family consistency is a hard requirement.

Abstract Factory

When does Builder's Build() method justify its existence over a constructor?

When Build() does work a constructor shouldn’t: cross-field validation (gift wrap requires a message), computed fields (total = subtotal - discount), default derivation (billing = shipping if not set), or async initialization. Constructors should be fast and never throw business logic exceptions. Build() is the right place for invariant enforcement that spans multiple fields. The tradeoff: Build() can throw at runtime; required properties fail at compile time. Use required when all fields are independent; use Builder when fields interact.

Builder

How does the Director role work, and when do you need it?

A Director encapsulates a specific construction sequence, calling builder methods in a fixed order. Example: StandardOrderDirector.BuildGiftOrder(builder, customer, items) always calls ShipTo, WithGiftWrap, WithShipping(Express) in the right order. Directors are useful when the same construction sequence is reused across multiple call sites, or when the sequence itself is a business rule (gift orders always use express shipping). Without a director, each call site must know the correct sequence — a form of knowledge duplication. The tradeoff: directors add a class; without them, call sites are more flexible but less consistent.

Builder

Why does WebApplicationBuilder use a builder instead of a constructor with parameters?

Because WebApplication construction is a multi-step process where each step can affect subsequent steps: adding a service can change how middleware behaves; configuration sources are layered in priority order; logging providers must be registered before the host starts. A constructor can’t express this ordering or allow conditional registration. The builder accumulates all configuration, then Build() wires everything together in the correct dependency order. The cost: Build() can fail at runtime if configuration is invalid — there’s no compile-time guarantee that all required services are registered.

Builder

When does Factory Method become the wrong choice?

When you need to create a family of related objects that must stay compatible — use Abstract Factory instead. Factory Method creates one product type; if PaymentProcessor and ReceiptGenerator must always come from the same provider (Stripe or PayPal), a single factory method can’t enforce that constraint. Also avoid Factory Method when the creation logic is trivial and unlikely to vary — the extra abstraction adds indirection without benefit.

Factory Method

How does Factory Method support the Open/Closed Principle?

The creator class is closed for modification: its NotifyOrderConfirmedAsync algorithm never changes. It’s open for extension: adding a new channel means a new subclass of NotificationCreator, not an edit to existing code. The tradeoff is class proliferation — each new product type requires a new creator subclass. For many variants, Abstract Factory or a registry-based approach scales better.

Factory Method

When should you use Prototype instead of just calling new?

When construction is expensive (loading from DB, computing derived fields) and you need many similar objects. Prototype amortizes the construction cost: build one template, clone it N times with small variations. Also use it when the exact type isn’t known at compile time — prototype.Clone() works without knowing the concrete type. The tradeoff: cloning can be as expensive as construction if the object graph is deep; profile before assuming it’s faster.

Prototype

What's the difference between shallow and deep copy, and when does it matter?

Shallow copy duplicates the object’s fields but shares reference-type values. Deep copy recursively duplicates the entire object graph. It matters when the copied object contains mutable reference types: two shallow copies sharing a List<OrderItem> will interfere with each other. Use deep copy when the clone must be fully independent. Use shallow copy when shared references are intentional (e.g., Customer is shared across orders — that’s correct). The cost of deep copy scales with graph depth; for large graphs, consider serialization-based cloning (JsonSerializer.Deserialize(JsonSerializer.Serialize(obj))).

Prototype

Why is the classical Singleton considered an anti-pattern in DI-based applications?

Three reasons: (1) Hidden dependency — AppConfig.Instance doesn’t appear in the constructor, so the dependency is invisible to callers and can’t be mocked. (2) Testability — tests can’t inject a different implementation; they’re forced to use the real singleton, making tests environment-dependent. (3) Lifetime control — the class controls its own lifetime, bypassing the DI container’s lifetime management. The DI-managed singleton solves all three: the dependency is explicit, injectable, and mockable. The tradeoff: DI-managed singletons require a DI container; classical singletons work without one (useful in library code or console apps without a host).

Singleton

What is a captive dependency and how do you detect it?

A captive dependency occurs when a longer-lived service holds a reference to a shorter-lived service. Example: a singleton OrderService injected with a scoped DbContext — the DbContext is captured for the application’s lifetime, not the request’s lifetime. This causes stale EF Core change tracking, connection pool exhaustion, and concurrency bugs across requests. Detection: enable ValidateScopes = true in AddSingleton options (default in development). The container throws at startup if a singleton depends on a scoped service. In production, also enable ValidateOnBuild = true. The fix: inject IServiceScopeFactory and create a scope per operation, or redesign the dependency to be singleton-safe.

Singleton

When is the classical Singleton still appropriate?

In library code without a DI container, or when you need a truly global, immutable resource that must be accessible without injection (e.g., a logging sink initialized before the DI container starts). Lazy<T> is the correct implementation in these cases. Also appropriate for value objects that are expensive to construct and inherently stateless (e.g., a compiled Regex pattern). The signal: if the singleton holds mutable state or has dependencies, use DI. If it’s an immutable, stateless resource, classical form is acceptable.

Singleton
Structural

How do you test code that uses an Adapter?

Test the consumer (OrderService) by injecting a mock IInventoryService — the adapter is invisible to the test. Test the adapter itself with integration tests against the real legacy system (or a recorded response). Unit-testing the adapter with a mock LegacyInventorySystem is valid but limited — the real value is verifying the XML translation is correct, which requires the actual legacy format. The tradeoff: integration tests are slower and environment-dependent; unit tests are fast but may miss translation bugs.

Adapter

When does an Adapter become a Facade?

When the adapter starts simplifying the interface rather than just translating it. An Adapter preserves the full capability of the adaptee — every method on IInventoryService maps to a corresponding legacy operation. A Facade intentionally hides complexity, exposing only a subset of the subsystem’s capabilities. If your “adapter” only exposes 3 of the legacy system’s 20 operations and adds orchestration logic, it’s a Facade. The distinction matters for maintenance: an Adapter should be a thin translation layer; a Facade can contain business logic.

Adapter

How do you decide whether to use Bridge or Strategy for payment provider selection?

Strategy selects an algorithm at runtime — the client chooses which strategy to inject. Bridge separates two dimensions of variation that both need to evolve independently. If you only need to swap payment providers (one dimension), Strategy is sufficient: inject IPaymentGateway and let the client choose. Bridge adds value when you also need payment types to vary independently — when both dimensions grow. The structural difference: Strategy has one interface; Bridge has two (abstraction + implementation). Use Strategy first; introduce Bridge when the second dimension appears.

Bridge

Why does ADO.NET use Bridge instead of just having SqlCommand implement ICommand directly?

Because the abstraction (DbCommand) and the implementation (SqlConnection) need to vary independently. DbCommand defines what operations are possible (execute, prepare, cancel); SqlCommand defines how they’re executed against SQL Server. A new database provider (PostgreSQL) can implement DbConnection/DbCommand without changing the abstraction. A new command type (batch command) can be added to the abstraction without changing providers. If SqlCommand directly implemented ICommand without the Bridge hierarchy, adding PostgreSQL support would require duplicating the entire command abstraction. The cost: the hierarchy is complex; understanding ADO.NET requires understanding both layers.

Bridge

What's the difference between Bridge and Dependency Injection?

DI is a mechanism for providing dependencies; Bridge is a structural pattern for organizing class hierarchies. They’re complementary: you use DI to inject the IPaymentGateway implementation into the PaymentOperation abstraction. DI doesn’t tell you how to structure the classes — Bridge does. Without Bridge, DI would inject a single IPaymentService that handles both dimensions; with Bridge, DI injects the gateway into the abstraction, and the abstraction handles the payment type logic. Bridge defines the structure; DI wires it together.

Bridge

How does Composite relate to the Visitor pattern?

They’re complementary. Composite defines the tree structure and uniform traversal. Visitor adds new operations to the tree without modifying the node classes. Example: TaxVisitor, ShippingVisitor, and DiscountVisitor can each traverse the same IOrderComponent tree without adding methods to SingleProduct or ProductBundle. Use Composite when the structure varies; use Visitor when the operations vary. Together, they handle both dimensions of variation.

Composite

When does a Composite tree become a performance problem?

When the tree is large, deep, or frequently traversed. GetPrice() on a bundle with 10,000 SKUs traverses all 10,000 nodes on every call. Mitigations: (1) cache computed values and invalidate on mutation, (2) use lazy evaluation (compute only when accessed), (3) denormalize — store the precomputed total and update it incrementally. The signal: profiling shows GetPrice() appearing in hot paths. The cost of caching: stale values if the tree is mutated without invalidation.

Composite

How does ASP.NET Core Middleware implement the Decorator pattern?

Each middleware is a decorator over RequestDelegate next. app.UseAuthentication() registers a middleware that calls next(context) after authenticating. The pipeline is built by composing these decorators at startup: each Use() call wraps the current pipeline in a new decorator. The outermost middleware runs first. This is exactly the Decorator pattern: each middleware implements the same interface (RequestDelegate), holds a reference to the next, and adds behavior before/after. The cost: middleware ordering bugs are runtime errors, not compile-time errors.

Decorator

When should you use Decorator vs inheritance for adding behavior?

Decorator when: (1) you need to add behavior at runtime or compose behaviors dynamically, (2) the class is sealed or from a third-party library, (3) you need multiple independent behaviors that can be combined in different ways. Inheritance when: the new behavior is a fundamental specialization of the type, not a cross-cutting concern. The key difference: inheritance is static (decided at compile time); Decorator is dynamic (decided at composition time). Decorator also avoids the fragile base class problem — changes to the base class don’t affect decorators.

Decorator

What's the performance cost of a deep decorator chain?

Each decorator adds one virtual dispatch and one async state machine (if async). For a 5-layer chain, that’s 5 virtual calls and 5 async allocations per request. In practice, this is negligible compared to I/O (DB queries, HTTP calls). Profile before optimizing. If the chain is genuinely hot (millions of calls/second with no I/O), consider collapsing the chain into a single class for that specific path. The tradeoff: performance vs maintainability. Premature optimization of decorator chains is a common mistake.

Decorator

When does a Facade become a "god class" anti-pattern?

When it starts containing business logic instead of just orchestrating subsystems. A Facade should be a thin coordinator — it calls subsystems in the right order but doesn’t make business decisions. If OrderFacade starts calculating discounts, validating business rules, or managing state, it’s accumulating responsibilities it shouldn’t have. The signal: the facade has more than 200-300 lines, or it’s the hardest class to test. The fix: extract business logic into domain services; keep the facade as a pure orchestrator. The tradeoff: a thin facade is easy to test (mock all subsystems); a fat facade is hard to test and hard to change.

Facade

Should a Facade expose the subsystems it wraps, or hide them completely?

Hide them. If callers can access orderFacade.Payment.ChargeAsync() directly, they bypass the facade’s orchestration and the workflow guarantee breaks. The facade’s value is the guaranteed sequence: check stock → charge → reserve → ship → notify. Exposing subsystems lets callers skip steps. The tradeoff: hiding subsystems means callers can’t do advanced operations that the facade doesn’t expose. In that case, add a new method to the facade rather than exposing the subsystem — the facade’s interface should grow to cover legitimate use cases.

Facade

How do you identify intrinsic vs extrinsic state?

Intrinsic state is the same for all objects in a group — it doesn’t change based on context. Extrinsic state is unique per object or changes based on context. For Product: TaxRate is intrinsic (same for all Electronics); Price is extrinsic (unique per SKU). The test: if two objects can share a piece of state without one affecting the other, it’s intrinsic. If sharing would cause one object’s change to affect another, it’s extrinsic. The tradeoff: the more state you classify as intrinsic, the more memory you save, but the flyweight becomes less flexible.

Flyweight

When is Flyweight not worth the complexity?

When the memory savings are negligible relative to the system’s total memory use, or when the shared objects are already small. Flyweight adds a factory, a cache, and the intrinsic/extrinsic split — meaningful complexity. Profile first: if 100,000 products each hold a 50-byte category struct, that’s 5MB — probably not worth the pattern. If each holds a 10KB category object with images and rules, that’s 1GB — Flyweight is justified. The signal: memory profiler shows many identical large objects.

Flyweight

How does EF Core's lazy-loading proxy differ from a manually written virtual proxy?

EF Core generates the proxy class at runtime using Castle DynamicProxy — you don’t write it. The proxy overrides every virtual navigation property; accessing the property triggers a DB query if the value isn’t loaded. The difference from a manual proxy: EF Core’s proxy is generated per entity type, not per instance; it uses the same DbContext that loaded the entity. A manual proxy is written once and works for all instances. The tradeoff: EF Core’s proxy is automatic but requires virtual properties and a parameterless constructor; a manual proxy is explicit but requires more code.

Proxy

When should you use a caching proxy vs IMemoryCache directly in the service?

Use a caching proxy when you want caching to be transparent to the service — the service doesn’t know it’s being cached. This is useful when the service is a third-party class you can’t modify, or when you want to swap caching strategies (in-memory vs distributed) without changing the service. Use IMemoryCache directly in the service when caching is a core concern of the service (e.g., a product catalog service that always caches), or when you need fine-grained control over cache keys and expiration. The tradeoff: proxy keeps the service pure but adds indirection; direct caching is explicit but couples the service to the caching strategy.

Proxy

What's the difference between a Proxy and a Decorator in terms of intent?

Intent is the key difference — the structure is identical. A Proxy controls access: it decides whether to forward the call at all (protection proxy), when to forward it (virtual proxy), or whether to use a cached result instead (caching proxy). A Decorator always forwards the call and adds behavior around it. A logging decorator always calls next.HandleAsync(order) — it never skips the call. A protection proxy may throw before calling the real service. If the wrapper can prevent the call from reaching the real object, it’s a Proxy. If it always calls through and adds behavior, it’s a Decorator.

Proxy

Resilience Patterns

Why is retry placement relative to circuit breaker important?

  • Retry should execute inside the same resilience pipeline before breaker decisions.
  • Outer retries around an already open breaker create extra pressure and useless attempts.
  • Proper ordering gives cleaner failure accounting and earlier protection.
  • In Polly’s outer-to-inner model this means placing retry outside the breaker, so each failed retry still passes through breaker evaluation and counts toward tripping it.
Circuit Breaker

How do you avoid a half-open thundering herd in Kubernetes-scale deployments?

  • Limit probe concurrency and keep half-open trial volume small.
  • Add jitter to retry and recovery timing.
  • Use global controls (rate limits, queueing, bulkheads) so per-pod recovery does not synchronize spikes.
  • Watch fleet-wide metrics, not only single-instance breaker events.
  • The core trap: breaker state is process-local, so per-pod recovery must be coordinated with fleet-level controls or every pod probes in lockstep.
Circuit Breaker

Which failures should trip the breaker, and which should not?

  • Trip on dependency-health signals: timeouts, connection failures, HTTP 5xx, and 429 when the client cannot absorb it.
  • Do not trip on caller-side 4xx like 400/404 — those reflect bad input, not an unhealthy dependency.
  • Encode this in ShouldHandle so the breaker measures the dependency, not your users’ mistakes.
  • Get it wrong and the breaker opens on validation errors, blocking healthy traffic to a perfectly good dependency.
Circuit Breaker

Your AI service wraps OpenAI APIs with per-tenant limits and runs on 4 instances. How do you enforce limits accurately, and which algorithm do you choose?

Expected answer

  • Use distributed shared state, usually Redis, because per-instance memory breaks global accuracy.
  • Partition by tenant ID so quotas align with billing and fairness.
  • Choose token bucket when tenants need controlled burst capacity with stable average throughput.
  • Use atomic operations (Lua or transaction pattern) for refill and consume to avoid race conditions.
  • Return 429 with Retry-After and remaining quota headers to support client backoff. Why this question matters
  • It tests algorithm choice plus distributed systems correctness, not just definition recall.
Rate Limiting

When would you prefer sliding window counter over fixed window in a public API?

Expected answer

  • Prefer sliding window counter when edge fairness matters and fixed window boundary bursts are unacceptable.
  • It gives near-rolling behavior with lower memory than sliding log.
  • Accept approximation error in exchange for better operational cost.
  • Keep fixed window only where simplicity dominates and traffic patterns are predictable. Why this question matters
  • It checks whether the candidate can justify tradeoffs under realistic constraints.
Rate Limiting

What failure mode should you choose if Redis-based rate limiting is unavailable: fail-open or fail-closed?

Expected answer

  • Decide by endpoint risk profile, not globally.
  • Fail-open for low-risk endpoints when availability is the top priority.
  • Fail-closed for sensitive operations where abuse or cost explosion is unacceptable.
  • Document and test the behavior with chaos drills. Why this question matters
  • It tests operational judgment and explicit risk tradeoff reasoning.
Rate Limiting

Why does retry without jitter make outages worse and how does jitter fix it

  • Without jitter each client computes nearly identical retry times so failures synchronize into periodic traffic spikes.
  • Those spikes hit while the dependency is already degraded which increases queue depth and recovery time.
  • Jitter randomizes each delay so retries spread over time and reduce synchronized pressure.
  • This improves recovery odds and stabilizes shared infrastructure such as load balancers and connection pools.
  • Tradeoff jitter reduces herd effects but increases per request timing variance and makes behavior slightly harder to predict.
Retry and Timeout Patterns

How do you prevent retry amplification in a multi layer microservices system

  • Assign retry ownership to one layer per call path usually the edge caller or the service nearest the user boundary.
  • Propagate cancellation tokens and deadlines so downstream services respect the remaining time budget.
  • Keep low retry counts and combine with circuit breaker and rate limits to avoid multiplicative pressure.
  • Measure effective attempts per request in telemetry and alert when fan out exceeds budget.
  • Tradeoff centralizing retries improves control and cost but can reduce local autonomy for service teams.
Retry and Timeout Patterns

System Architecture

When would you choose orchestration over choreography in an event-driven workflow?

Orchestrate when the workflow is long-running and needs real ordering or compensation — a checkout that charges payment, reserves inventory, then ships. A central process manager keeps that flow easy to follow and roll back, at the cost of one more component that can bottleneck. Choreography fits loosely-related reactions, like “order placed” fanning out to email, analytics, and search: autonomous and decoupled, but no single place knows the whole story, so tracing gets harder as subscriptions grow. Rule of thumb — orchestrate transactions you must reason about end to end; choreograph independent reactions.

Event-Driven Architecture

How do you evolve integration event contracts without breaking consumers?

Treat the event as a public API — other teams deploy against it on their own schedule. Keep changes additive; never rename or drop a field in place. For a genuine breaking change, version it (OrderPlaced.v2) and publish both through a migration window until consumers move over. Consumer-driven contract tests in CI catch regressions before release, and deserialization-failure metrics surface a bad change in minutes. The mindset that keeps you safe: an event schema is a long-lived contract, not an internal DTO you can refactor freely.

Event-Driven Architecture

How do you process events reliably under at-least-once delivery?

Duplicates and reordering are normal, so consumers need durable idempotency keys and conditional state changes. An outbox closes the local database-to-publish gap; per-key partitioning plus sequence numbers protects scoped order. An exactly-once claim is valid only for an explicit transactional boundary, such as a processor atomically checkpointing offsets and writing one supported sink. External calls still need their own idempotency contract.

Event-Driven Architecture

Why can microservices lead to distributed data consistency problems, and how do you address them?

  • Each service owns its data, so cross-service business actions cannot assume one local ACID transaction. A distributed transaction can coordinate supported resources, but its coupling, latency, and recovery costs make sagas and local transactions the usual design.
  • A later step can fail after an earlier local commit, producing partial state.
  • Use sagas with compensating actions across local transactions.
  • Use outbox/inbox and idempotent handlers to survive retries and duplicates.
  • Accept eventual consistency and make process state observable.
Microservices

How do you decide between monolith, modular monolith, and microservices for a new product?

  • Decide from team size, release pressure, domain volatility, and ops maturity.
  • One team in discovery phase usually benefits most from monolith speed.
  • Clear domains with limited platform capacity often fit modular monolith.
  • Choose microservices when independent deploy/scale constraints are proven.
  • Re-evaluate architecture periodically as constraints change.
Microservices

How do you enforce module boundaries in a modular monolith to prevent it from degrading into a traditional monolith?

Split each module into contracts, core, and infrastructure assemblies; allow cross-module references only to contracts. Give tables an owner, block cross-module joins, and use architecture tests to fail CI on forbidden project or namespace dependencies. The friction is intentional: a boundary that cannot reject a shortcut is only documentation.

Modular Monolith

When would you choose a modular monolith over microservices, and what signals tell you it is time to extract?

Choose the modular monolith while domains can be owned as modules and one deployment remains reliable. Extract when a module repeatedly needs independent scaling, release cadence, security isolation, or reliability posture. Before cutover, preserve the domain contract but redesign the interaction for remote deadlines, retries, observability, and transaction boundaries.

Modular Monolith

What is a modular monolith and when is it better than microservices?

A modular monolith enforces explicit module boundaries (clear interfaces, no internal coupling) within a single deployment. It gives you maintainability and team autonomy without distributed systems complexity. It is better than microservices when independent deployment is not yet a hard requirement — which is most early-stage products. Cost: requires discipline to maintain module boundaries; without enforcement, it degrades into a big ball of mud.

Monolith Architecture

When do microservices become justified over a monolith?

When independent deployment is a hard requirement and the team can support the operational complexity (distributed tracing, network failures, eventual consistency, service mesh). The signal is usually: teams are blocked by each other’s deployments, or a specific component needs independent scaling that the monolith cannot provide.

Monolith Architecture

How do you mitigate cold start latency in serverless functions?

Measure initialization and scale-out latency on the chosen product, runtime, plan, package, and network path. Reduce initialization work, reuse clients in warm environments, and buy minimum/provisioned capacity when the latency objective justifies its fixed cost. Native AOT can help .NET startup when reflection and library constraints are acceptable.

Serverless Architecture

How do you avoid vendor lock-in with serverless functions?

Isolate business logic from the function host. The function handler should be a thin adapter that reads the trigger event, calls a provider-agnostic service, and returns a response. The core logic lives in a class library with no provider-specific dependencies. This makes the business logic portable even if the host (Azure Functions, AWS Lambda) is not. Cost: requires discipline to keep the adapter thin; teams often let provider-specific bindings leak into business logic.

Serverless Architecture

How do you model cost for a serverless workload vs a container-based one?

Use the product’s actual dimensions: requests, duration, allocated CPU/memory, provisioned or minimum instances, gateway, storage, egress, logging, and downstream connections. Some services scale to zero and some do not. Run the representative traffic shape through both pricing models and include latency and operating labor rather than assuming one universal crossover.

Serverless Architecture

When is SOA still the right choice over microservices?

SOA remains appropriate for enterprise integration scenarios: connecting heterogeneous legacy systems (SAP, Salesforce, custom ERP) via a shared integration layer, shared services used by multiple business units where microservice decomposition overhead isn’t justified, and regulated industries requiring centralized governance and audit trails. Microservices are better for greenfield cloud-native systems where teams can own independent services end-to-end.

Service-Oriented Architecture

Development Practices

How much testing is enough, and what kind?

  • Match the test to the risk: unit tests for logic-heavy code, integration tests for wiring and boundaries, a few end-to-end tests for critical user paths — not 100% coverage for its own sake
  • The test pyramid is a cost model: many fast unit tests, fewer slow integration tests, minimal brittle E2E — inverting it makes the suite slow and flaky
  • TDD is a design tool as much as a verification one: writing the test first pressures you toward decoupled, testable code
  • A test that never fails teaches nothing; a test that fails randomly teaches you to ignore it — invest in determinism
Development Practices

Paradigms

How does event-driven architecture differ from event sourcing?

Event-driven architecture is a communication style: components publish events and others react asynchronously. Event sourcing is a persistence pattern: the state of an entity is derived by replaying its event history rather than storing current state. You can use event-driven communication without event sourcing (most systems do). Event sourcing requires event-driven communication but adds the constraint that events are the source of truth for state.

Event-driven

How do you guarantee ordering of events in a distributed event-driven system?

Most message brokers guarantee ordering only within a partition or queue. Kafka guarantees ordering within a partition (use a consistent partition key, e.g., order ID). Azure Service Bus sessions guarantee ordering within a session. Cross-partition ordering is not guaranteed — design consumers to be idempotent and handle out-of-order delivery. If strict global ordering is required, use a single partition (which limits throughput) or a sequencer service.

Event-driven

Why must event consumers be idempotent in an at-least-once delivery system?

Most message brokers guarantee at-least-once delivery: a message may be delivered more than once if the consumer crashes after processing but before acknowledging. Without idempotency, duplicate delivery causes double-charging, double-reserving, or duplicate records. Mitigation: track processed event IDs in a ProcessedEvents table and skip duplicates, or design operations to be naturally idempotent (SET stock = X instead of stock -= Y).

Event-driven

What makes a function "pure" and why does purity matter for testing?

  • A pure function has no side effects and is referentially transparent: same inputs always produce the same output.
  • No side effects means no I/O, no global state reads/writes, no exceptions from external dependencies.
  • Testing cost: pure functions need zero mocks — just call with inputs and assert outputs.
  • Parallelism: pure functions are safe to run concurrently without locks.
  • Tradeoff: real systems need side effects (DB writes, HTTP calls). The FP discipline is to push side effects to the edges and keep the core logic pure.
Functional Programming

How does immutability prevent bugs in concurrent code?

  • Shared mutable state is the root cause of race conditions: two threads read-modify-write the same object.
  • Immutable objects can be shared freely across threads — no lock needed because no mutation is possible.
  • In C#: record types with init-only properties, ImmutableDictionary<K,V>, string (already immutable).
  • Cost: every “mutation” allocates a new object. Acceptable for domain events and DTOs; expensive for high-frequency data structures.
  • Tradeoff: immutability shifts cost from runtime synchronization to GC pressure. Profile before applying everywhere.
Functional Programming

When would you choose LINQ over a manual loop in production code?

  • LINQ: when the transformation is a pipeline of filter/map/reduce steps and readability matters more than micro-performance.
  • Manual loop: when you need early exit with complex state, when profiling shows LINQ overhead is significant, or when you need to avoid multiple enumerations.
  • Key risk with LINQ: deferred execution — materializing with .ToList() at the right point is non-obvious and a common source of double-query bugs.
  • Tradeoff: LINQ is more declarative and composable; loops are more explicit about control flow and allocation. In hot paths (>10k iterations/sec), benchmark both.
Functional Programming

What does WebApplicationFactory test that unit tests cannot?

  • DI registration: if a service is missing from the container, the integration test fails at startup.
  • Middleware ordering: authentication, authorization, exception handling, and routing all run in the real pipeline.
  • Serialization contracts: JSON serialization settings (camelCase, nullable handling, custom converters) are applied.
  • Controller binding: model validation, route constraints, and action filters run as they would in production.
  • Tradeoff: WebApplicationFactory tests are slower than unit tests (seconds vs milliseconds). Run them in a separate test project so they don’t slow down the unit test feedback loop.
Integration Testing

When should you use Testcontainers instead of EF In-Memory?

  • When your queries use SQL features that EF In-Memory doesn’t support: raw SQL, stored procedures, database constraints, RETURNING clauses, CTEs.
  • When you need to test database migrations (EF In-Memory doesn’t run migrations).
  • When a bug was caused by SQL behavior that the in-memory provider masked.
  • Tradeoff: Testcontainers requires Docker in CI and adds 5-15 seconds of container startup per test class. The cost is worth it when in-memory tests give false confidence.
Integration Testing

When should you prefer composition over inheritance?

  • Prefer composition when the relationship is “has-a” rather than “is-a”
  • Use composition when you need to combine behaviors from multiple sources (C# has no multiple class inheritance)
  • Use composition when the base class is not designed for extension (sealed or has complex internal invariants)
  • Inheritance creates tight coupling: a change to the base class can break all derived classes
  • Composition lets you swap implementations at runtime and test components in isolation via interfaces
  • Heuristic: if you override methods to suppress or fundamentally change base behavior, composition fits better
  • Tradeoff: composition requires more explicit wiring (constructor injection, delegation) but buys flexibility and testability; inheritance is less boilerplate but creates coupling that compounds with depth
OOP

What is the Anemic Domain Model and why is it considered an anti-pattern?

  • Objects have only getters/setters with no behavior — all logic lives in service classes
  • Violates encapsulation: services reach into objects to get data, compute, and set results back
  • This is procedural programming with OOP syntax — invariants are enforced in scattered service code
  • Objects can be put into invalid states because there is no guard at the mutation point
  • Fix: move behavior that depends on an object’s data into the object itself (Information Expert principle from GRASP)
  • Tradeoff: rich domain models enforce invariants at the source but require more upfront design investment; anemic models are simpler to write initially but accumulate inconsistency bugs as the system grows
OOP

How does polymorphism reduce conditional complexity?

  • Without polymorphism, varying behavior by type requires if/switch chains that grow with every new type
  • Each new type forces editing existing code — a direct Open/Closed violation
  • With polymorphism, each type implements a shared interface and handles its own behavior
  • Adding a new type means adding a new class, not modifying existing code
  • The caller iterates over IEnumerable<IReportGenerator> and calls Generate() — the runtime dispatches to the correct implementation
  • Tradeoff: polymorphism adds indirection — debugging requires knowing which concrete type is active, and navigating call hierarchies takes extra IDE steps. Worth paying when you have 3+ variant types or expect new types over time
OOP

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

  • Interface: defines a contract without instance state; can include default method implementations (C# 8+); a class can implement many interfaces
  • Abstract class: defines a contract plus shared implementation and instance state; a class can inherit from only one abstract class
  • Use interfaces when unrelated types share a capability (IDisposable, IComparable)
  • Use abstract classes when related types form a genuine hierarchy with shared invariants
  • In practice, most OOD designs use both: interface for external consumers, abstract class for the internal hierarchy (Robot : IMovable)
  • Tradeoff: interfaces are more flexible (no coupling to shared state, multiple implementation) but force every implementer to write the full implementation; abstract classes reduce duplication but create a single coupling point that all derived classes depend on
OOP

Why does TDD improve design, not just test coverage?

  • Writing the test first forces you to define the public interface before the implementation exists.
  • If the test is hard to write (too many constructor arguments, complex setup), that’s a design signal: the class has too many responsibilities or too many dependencies.
  • TDD naturally produces smaller classes with single responsibilities because each test targets one behavior.
  • Test-after doesn’t provide this signal — the implementation already exists and the test is shaped around it.
  • Tradeoff: TDD’s design benefit requires discipline. Skipping the Red step (writing a test that already passes) eliminates the feedback loop.
Test-Driven Development

When is TDD not worth the overhead?

  • Exploratory/spike code: you don’t know the right design yet. Write the spike without tests, learn from it, then delete it and rebuild with TDD.
  • UI rendering logic: visual behavior is hard to unit-test meaningfully; use snapshot tests or manual QA instead.
  • Trivial CRUD with no branching: a single-line property setter doesn’t need a test-first cycle.
  • Tradeoff: TDD overhead is ~20-30% more time upfront. The payback is faster debugging and safer refactoring over the life of the codebase. For short-lived code, the payback never arrives.
Test-Driven Development

How do you handle external dependencies (DB, HTTP) in TDD?

  • Inject dependencies as interfaces. In tests, provide a fake or in-memory implementation.
  • For repositories: use an in-memory implementation (e.g., Dictionary<Guid, Order>) rather than mocking every method.
  • For HTTP clients: use HttpMessageHandler fakes or WireMock.Net for realistic HTTP stubs.
  • For time: inject TimeProvider (built into .NET 8+) so tests can control “now” without DateTime.UtcNow coupling.
  • Tradeoff: fakes require maintenance. If the real implementation changes behavior, the fake may diverge. Contract tests (testing the fake against the real implementation) catch this.
Test-Driven Development

What is the difference between a stub and a mock?

  • A stub provides canned return values so the test can proceed — it answers questions (“what orders does customer X have?”).
  • A mock verifies interactions — it records calls and lets you assert that a specific method was called with specific arguments.
  • Practical rule: stub data sources (repositories, config); mock side-effect sinks (email, SMS, audit log, event bus).
  • Over-mocking (mocking everything including internal collaborators) produces tests that break on every refactor without catching real bugs.
  • Tradeoff: mocks couple tests to implementation details. Prefer fakes (working in-memory implementations) when the dependency has non-trivial behavior.
Unit Testing

How do you test code that depends on the current time?

  • Inject TimeProvider (built into .NET 8+) instead of calling DateTime.UtcNow directly.
  • In tests, use FakeTimeProvider (from Microsoft.Extensions.TimeProvider.Testing) to control “now”.
  • This makes time-dependent logic (expiry checks, scheduling, TTL calculations) fully deterministic in tests.
  • Tradeoff: requires changing existing code that calls DateTime.UtcNow directly — a one-time refactor cost that pays off in every time-sensitive test.
Unit Testing

When should you NOT write unit tests?

  • Trivial property getters/setters with no logic — the test adds noise without catching real bugs.
  • UI rendering logic — visual correctness is better verified with snapshot tests or manual QA.
  • Infrastructure wiring (DI registration, config parsing) — test this with integration tests that boot the real container.
  • Exploratory spike code — write the spike without tests, learn from it, then delete it before it becomes production code.
  • Tradeoff: every test has a maintenance cost. Tests that don’t catch real bugs are pure overhead. Focus unit tests on logic with branching, edge cases, and business rules.
Unit Testing

Principles

What is DRY actually trying to prevent?

Duplicated knowledge and duplicated rules. If a change requires edits in multiple places, DRY is a signal.

DRY

When is it OK to repeat code?

When the repetition is small, the meaning is different, or the logic is expected to diverge. Local duplication is sometimes safer than a shared abstraction.

DRY

What is the difference between IoC and Dependency Injection?

IoC is the principle: the framework controls object creation and wiring instead of your code. Dependency Injection is the most common technique for implementing IoC: dependencies are passed in (injected) rather than created internally. You can implement IoC without DI (e.g., using a service locator or factory), but constructor injection via a DI container is the idiomatic .NET approach. IoC is the what; DI is the how.

IoC (Holywood Principle)

What is the difference between IoC and the Dependency Inversion Principle (DIP)?

DIP (SOLID ‘D’) is a design rule: high-level modules should depend on abstractions, not concrete implementations. IoC is a runtime mechanism: the framework wires concrete implementations to those abstractions. DIP is the why (depend on interfaces); IoC/DI is the how (let the container provide the concrete class). You can follow DIP without a DI container by manually wiring dependencies in Main, but a container makes it practical at scale.

IoC (Holywood Principle)

How do you distinguish 'simple' from 'simplistic' in a design review?

Simple: the design has the minimum number of moving parts needed to meet the current requirements, with clear failure modes and no hidden assumptions. Simplistic: the design ignores real requirements (edge cases, error handling, security) to appear simple. The test: can you explain every component’s purpose? If a component exists ‘just in case’ or ‘for future flexibility,’ it is probably over-engineering. If a component is missing and the system fails in production, it was simplistic.

KISS

When is complexity justified despite KISS?

When the complexity solves a proven, current problem: security controls (rate limiting, input validation, authentication) are mandatory for public APIs; retry logic and circuit breakers are mandatory for distributed systems; idempotency keys are mandatory for payment processing. The principle is: add complexity only to solve a proven problem, not a hypothetical one. Complexity that prevents production failures is not over-engineering.

KISS

How does KISS interact with YAGNI and DRY?

They are complementary: YAGNI says don’t build features you don’t need yet; DRY says don’t duplicate knowledge; KISS says keep the implementation simple. Tension arises when DRY requires an abstraction that adds complexity (KISS violation) for a single use case (YAGNI violation). Resolution: apply the Rule of Three — abstract when you have two concrete use cases, not one. One use case is speculation; two give you enough information to design a simple abstraction.

KISS

Which SOLID principles does a typical Singleton violate, and why does it matter in a microservice?

  • DIP: consuming code depends on a concrete global instance (Singleton.Instance) instead of an abstraction injected via DI. This makes it impossible to swap implementations per-tenant or per-environment.
  • SRP: the singleton class mixes business logic with lifecycle management (lazy initialization, thread-safety) and global access control. Changes to any of these concerns affect the same class.
  • OCP: replacing or extending behavior usually requires changing call sites or the singleton itself — you cannot plug in an alternative without modifying existing code.
  • Fix in .NET: expose an interface, register the implementation with AddSingleton<IService, ConcreteService>() in the DI container. The container manages lifetime; the class manages only its business logic.
  • DI adds indirection and configuration overhead, so a static singleton is simpler for genuinely global, stateless utilities (StringComparer.OrdinalIgnoreCase). For anything stateful or with infrastructure dependencies, DI-managed singletons win outright.
SOLID

How would you refactor a 2,000-line service class to satisfy SRP without breaking existing callers?

  • Step 1: Identify actors — group methods by which team or business process triggers changes to them. Common groupings: persistence, notification, validation, reporting.
  • Step 2: Extract method groups into focused classes (e.g., OrderValidator, OrderNotifier, OrderRepository). Each class gets one actor’s methods.
  • Step 3: Introduce interfaces for each new class. The original OrderService becomes a thin facade that delegates to the extracted classes via interfaces.
  • Step 4: Existing callers continue using OrderService (facade). New callers depend on the focused interfaces directly.
  • Step 5: Gradually migrate existing callers away from the facade as you touch them for other reasons. Eventually remove the facade.
  • The intermediate facade adds indirection without the full SRP payoff, but it buys an incremental migration over several sprints instead of a big-bang rewrite that freezes feature work.
SOLID

When is it acceptable to violate SOLID principles, and how do you decide?

  • Small scripts and prototypes: abstractions cost more than the code they protect. A 50-line console app does not need interfaces.
  • Performance-critical hot paths: virtual dispatch, interface resolution, and DI overhead are measurable in tight loops processing millions of items per second. Profile first — if the abstraction boundary appears in your flame graph, inline it.
  • Premature abstraction risk: when you have exactly one implementation and no foreseeable second one, extracting an interface adds navigation cost without substitution value. Wait for the second use case.
  • Decision heuristic: apply SOLID when you have evidence — when tests are hard to write, when unrelated teams edit the same file, when adding a feature requires modifying code that already works. Do not apply it to prevent hypothetical future problems.
  • Every SOLID application trades simplicity for flexibility. The real question is never “should I apply SOLID” but “does the flexibility outweigh the complexity it adds right now?”
SOLID

Does YAGNI mean you should skip tests and refactoring?

No. YAGNI applies to features and abstractions, not to engineering practices. Martin Fowler explicitly distinguishes: YAGNI says don’t build features you don’t need yet. It does not say skip tests, skip refactoring, or write messy code. Good engineering practices are always justified because they reduce the cost of future changes.

YAGNI

When does YAGNI conflict with good design, and how do you resolve it?

YAGNI conflicts with design when adding an abstraction now would make future changes cheaper. The resolution: add the abstraction when you have two concrete use cases, not one. One use case is speculation; two use cases give you enough information to design the abstraction correctly. This is the Rule of Three from refactoring.

YAGNI

Software Design

Where does software design end and architecture begin?

  • Software design operates at the module and class scale: naming, responsibilities, coupling, and the contracts between objects — decisions you can refactor cheaply
  • Architecture operates at the system scale: service boundaries, data ownership, and communication styles — decisions that are expensive to reverse once other teams depend on them
  • The line is fuzzy and shifts with scope: a class boundary in a monolith becomes a service boundary once it is extracted, so good design habits (clear responsibilities, low coupling) are what make that promotion possible
  • Principles, paradigms, and testing are the levers of design; they show up in architecture too, but the blast radius of getting them wrong grows with the scale
Software Design

Paradigms

How does event-driven architecture differ from event sourcing?

Event-driven architecture is a communication style: components publish events and others react asynchronously. Event sourcing is a persistence pattern: the state of an entity is derived by replaying its event history rather than storing current state. You can use event-driven communication without event sourcing (most systems do). Event sourcing requires event-driven communication but adds the constraint that events are the source of truth for state.

Event-driven

How do you guarantee ordering of events in a distributed event-driven system?

Most message brokers guarantee ordering only within a partition or queue. Kafka guarantees ordering within a partition (use a consistent partition key, e.g., order ID). Azure Service Bus sessions guarantee ordering within a session. Cross-partition ordering is not guaranteed — design consumers to be idempotent and handle out-of-order delivery. If strict global ordering is required, use a single partition (which limits throughput) or a sequencer service.

Event-driven

Why must event consumers be idempotent in an at-least-once delivery system?

Most message brokers guarantee at-least-once delivery: a message may be delivered more than once if the consumer crashes after processing but before acknowledging. Without idempotency, duplicate delivery causes double-charging, double-reserving, or duplicate records. Mitigation: track processed event IDs in a ProcessedEvents table and skip duplicates, or design operations to be naturally idempotent (SET stock = X instead of stock -= Y).

Event-driven

What makes a function "pure" and why does purity matter for testing?

  • A pure function has no side effects and is referentially transparent: same inputs always produce the same output.
  • No side effects means no I/O, no global state reads/writes, no exceptions from external dependencies.
  • Testing cost: pure functions need zero mocks — just call with inputs and assert outputs.
  • Parallelism: pure functions are safe to run concurrently without locks.
  • Tradeoff: real systems need side effects (DB writes, HTTP calls). The FP discipline is to push side effects to the edges and keep the core logic pure.
Functional Programming

How does immutability prevent bugs in concurrent code?

  • Shared mutable state is the root cause of race conditions: two threads read-modify-write the same object.
  • Immutable objects can be shared freely across threads — no lock needed because no mutation is possible.
  • In C#: record types with init-only properties, ImmutableDictionary<K,V>, string (already immutable).
  • Cost: every “mutation” allocates a new object. Acceptable for domain events and DTOs; expensive for high-frequency data structures.
  • Tradeoff: immutability shifts cost from runtime synchronization to GC pressure. Profile before applying everywhere.
Functional Programming

When would you choose LINQ over a manual loop in production code?

  • LINQ: when the transformation is a pipeline of filter/map/reduce steps and readability matters more than micro-performance.
  • Manual loop: when you need early exit with complex state, when profiling shows LINQ overhead is significant, or when you need to avoid multiple enumerations.
  • Key risk with LINQ: deferred execution — materializing with .ToList() at the right point is non-obvious and a common source of double-query bugs.
  • Tradeoff: LINQ is more declarative and composable; loops are more explicit about control flow and allocation. In hot paths (>10k iterations/sec), benchmark both.
Functional Programming

Why is “composition over inheritance” a preference rather than a ban?

Framework base classes and genuine domain taxonomies can provide stable lifecycle and invariants. The rule rejects inheritance used only to borrow implementation, because that creates a public subtype contract the design did not intend.

Inheritance and Composition

Why is “composition over inheritance” a preference rather than a ban?

Framework base classes and genuine domain taxonomies can provide stable lifecycle and invariants. The rule rejects inheritance used only to borrow implementation, because that creates a public subtype contract the design did not intend.

OOP

Why can a subtype compile and still violate its base contract?

The type system proves member shape and assignability, not behavioral promises. Throwing for an operation that the base presents as generally supported satisfies the signature while breaking callers’ expectations.

OOP

When does polymorphism beat a conditional?

Use it when behavior varies behind a stable capability and new implementations are expected. Keep a conditional when the cases are closed, local, and easier to inspect together than through several types.

OOP

Why can a subtype compile and still violate its base contract?

The type system proves member shape and assignability, not behavioral promises. Throwing for an operation that the base presents as generally supported satisfies the method signature while breaking callers’ expectations.

Subtyping and Polymorphism

What does runtime polymorphism buy?

A caller depends on one capability while the active implementation varies. That isolates provider, strategy, or domain-specific behavior, at the cost of indirection and a contract that must remain valid for every implementation.

Subtyping and Polymorphism

Principles

What is DRY actually trying to prevent?

Duplicated knowledge and duplicated rules. If a change requires edits in multiple places, DRY is a signal.

DRY

When is it OK to repeat code?

When the repetition is small, the meaning is different, or the logic is expected to diverge. Local duplication is sometimes safer than a shared abstraction.

DRY

What is the difference between IoC and Dependency Injection?

IoC is the principle: the framework controls object creation and wiring instead of your code. Dependency Injection is the most common technique for implementing IoC: dependencies are passed in (injected) rather than created internally. You can implement IoC without DI (e.g., using a service locator or factory), but constructor injection via a DI container is the idiomatic .NET approach. IoC is the what; DI is the how.

IoC (Holywood Principle)

What is the difference between IoC and the Dependency Inversion Principle (DIP)?

DIP (SOLID ‘D’) is a design rule: high-level modules should depend on abstractions, not concrete implementations. IoC is a runtime mechanism: the framework wires concrete implementations to those abstractions. DIP is the why (depend on interfaces); IoC/DI is the how (let the container provide the concrete class). You can follow DIP without a DI container by manually wiring dependencies in Main, but a container makes it practical at scale.

IoC (Holywood Principle)

How do you distinguish 'simple' from 'simplistic' in a design review?

Simple: the design has the minimum number of moving parts needed to meet the current requirements, with clear failure modes and no hidden assumptions. Simplistic: the design ignores real requirements (edge cases, error handling, security) to appear simple. The test: can you explain every component’s purpose? If a component exists ‘just in case’ or ‘for future flexibility,’ it is probably over-engineering. If a component is missing and the system fails in production, it was simplistic.

KISS

When is complexity justified despite KISS?

When the complexity solves a proven, current problem: security controls (rate limiting, input validation, authentication) are mandatory for public APIs; retry logic and circuit breakers are mandatory for distributed systems; idempotency keys are mandatory for payment processing. The principle is: add complexity only to solve a proven problem, not a hypothetical one. Complexity that prevents production failures is not over-engineering.

KISS

How does KISS interact with YAGNI and DRY?

They are complementary: YAGNI says don’t build features you don’t need yet; DRY says don’t duplicate knowledge; KISS says keep the implementation simple. Tension arises when DRY requires an abstraction that adds complexity (KISS violation) for a single use case (YAGNI violation). Resolution: apply the Rule of Three — abstract when you have two concrete use cases, not one. One use case is speculation; two give you enough information to design a simple abstraction.

KISS

Which SOLID principles does a typical Singleton violate, and why does it matter in a microservice?

  • DIP: consuming code depends on a concrete global instance (Singleton.Instance) instead of an abstraction injected via DI. This makes it impossible to swap implementations per-tenant or per-environment.
  • SRP: the singleton class mixes business logic with lifecycle management (lazy initialization, thread-safety) and global access control. Changes to any of these concerns affect the same class.
  • OCP: replacing or extending behavior usually requires changing call sites or the singleton itself — you cannot plug in an alternative without modifying existing code.
  • Fix in .NET: expose an interface, register the implementation with AddSingleton<IService, ConcreteService>() in the DI container. The container manages lifetime; the class manages only its business logic.
  • DI adds indirection and configuration overhead, so a static singleton is simpler for genuinely global, stateless utilities (StringComparer.OrdinalIgnoreCase). For anything stateful or with infrastructure dependencies, DI-managed singletons win outright.
SOLID

How would you refactor a 2,000-line service class to satisfy SRP without breaking existing callers?

  • Step 1: Identify actors — group methods by which team or business process triggers changes to them. Common groupings: persistence, notification, validation, reporting.
  • Step 2: Extract method groups into focused classes (e.g., OrderValidator, OrderNotifier, OrderRepository). Each class gets one actor’s methods.
  • Step 3: Introduce interfaces for each new class. The original OrderService becomes a thin facade that delegates to the extracted classes via interfaces.
  • Step 4: Existing callers continue using OrderService (facade). New callers depend on the focused interfaces directly.
  • Step 5: Gradually migrate existing callers away from the facade as you touch them for other reasons. Eventually remove the facade.
  • The intermediate facade adds indirection without the full SRP payoff, but it buys an incremental migration over several sprints instead of a big-bang rewrite that freezes feature work.
SOLID

When is it acceptable to violate SOLID principles, and how do you decide?

  • Small scripts and prototypes: abstractions cost more than the code they protect. A 50-line console app does not need interfaces.
  • Performance-critical hot paths: virtual dispatch, interface resolution, and DI overhead are measurable in tight loops processing millions of items per second. Profile first — if the abstraction boundary appears in your flame graph, inline it.
  • Premature abstraction risk: when you have exactly one implementation and no foreseeable second one, extracting an interface adds navigation cost without substitution value. Wait for the second use case.
  • Decision heuristic: apply SOLID when you have evidence — when tests are hard to write, when unrelated teams edit the same file, when adding a feature requires modifying code that already works. Do not apply it to prevent hypothetical future problems.
  • Every SOLID application trades simplicity for flexibility. The real question is never “should I apply SOLID” but “does the flexibility outweigh the complexity it adds right now?”
SOLID

Does YAGNI mean you should skip tests and refactoring?

No. YAGNI applies to features and abstractions, not to engineering practices. Martin Fowler explicitly distinguishes: YAGNI says don’t build features you don’t need yet. It does not say skip tests, skip refactoring, or write messy code. Good engineering practices are always justified because they reduce the cost of future changes.

YAGNI

When does YAGNI conflict with good design, and how do you resolve it?

YAGNI conflicts with design when adding an abstraction now would make future changes cheaper. The resolution: add the abstraction when you have two concrete use cases, not one. One use case is speculation; two use cases give you enough information to design the abstraction correctly. This is the Rule of Three from refactoring.

YAGNI

Testing

What does WebApplicationFactory test that unit tests cannot?

  • DI registration: if a service is missing from the container, the integration test fails at startup.
  • Middleware ordering: authentication, authorization, exception handling, and routing all run in the real pipeline.
  • Serialization contracts: JSON serialization settings (camelCase, nullable handling, custom converters) are applied.
  • Controller binding: model validation, route constraints, and action filters run as they would in production.
  • Tradeoff: WebApplicationFactory tests are slower than unit tests (seconds vs milliseconds). Run them in a separate test project so they don’t slow down the unit test feedback loop.
Integration Testing

When should you use Testcontainers instead of EF In-Memory?

  • When your queries use SQL features that EF In-Memory doesn’t support: raw SQL, stored procedures, database constraints, RETURNING clauses, CTEs.
  • When you need to test database migrations (EF In-Memory doesn’t run migrations).
  • When a bug was caused by SQL behavior that the in-memory provider masked.
  • Tradeoff: Testcontainers requires Docker in CI and adds 5-15 seconds of container startup per test class. The cost is worth it when in-memory tests give false confidence.
Integration Testing

Why does TDD improve design, not just test coverage?

  • Writing the test first forces you to define the public interface before the implementation exists.
  • If the test is hard to write (too many constructor arguments, complex setup), that’s a design signal: the class has too many responsibilities or too many dependencies.
  • TDD naturally produces smaller classes with single responsibilities because each test targets one behavior.
  • Test-after doesn’t provide this signal — the implementation already exists and the test is shaped around it.
  • Tradeoff: TDD’s design benefit requires discipline. Skipping the Red step (writing a test that already passes) eliminates the feedback loop.
Test-Driven Development

When is TDD not worth the overhead?

  • Exploratory/spike code: you don’t know the right design yet. Write the spike without tests, learn from it, then delete it and rebuild with TDD.
  • UI rendering logic: visual behavior is hard to unit-test meaningfully; use snapshot tests or manual QA instead.
  • Trivial CRUD with no branching: a single-line property setter doesn’t need a test-first cycle.
  • Tradeoff: TDD overhead is ~20-30% more time upfront. The payback is faster debugging and safer refactoring over the life of the codebase. For short-lived code, the payback never arrives.
Test-Driven Development

How do you handle external dependencies (DB, HTTP) in TDD?

  • Inject dependencies as interfaces. In tests, provide a fake or in-memory implementation.
  • For repositories: use an in-memory implementation (e.g., Dictionary<Guid, Order>) rather than mocking every method.
  • For HTTP clients: use HttpMessageHandler fakes or WireMock.Net for realistic HTTP stubs.
  • For time: inject TimeProvider (built into .NET 8+) so tests can control “now” without DateTime.UtcNow coupling.
  • Tradeoff: fakes require maintenance. If the real implementation changes behavior, the fake may diverge. Contract tests (testing the fake against the real implementation) catch this.
Test-Driven Development

Why is the test pyramid shaped the way it is?

  • It is a cost model: unit tests are fast, isolated, and cheap to run and maintain, so you can afford thousands of them; integration and end-to-end tests are slower, flakier, and more expensive, so you keep them few and targeted
  • Inverting it (an “ice-cream cone” of mostly E2E tests) yields a suite that is slow to run and brittle to change, eroding the fast feedback that makes tests worth having
  • The point is coverage of risk, not lines: push logic-heavy verification down to units and reserve higher tiers for wiring, contracts, and critical user paths
Testing

What is the difference between a stub and a mock?

  • A stub provides canned return values so the test can proceed — it answers questions (“what orders does customer X have?”).
  • A mock verifies interactions — it records calls and lets you assert that a specific method was called with specific arguments.
  • Practical rule: stub data sources (repositories, config); mock side-effect sinks (email, SMS, audit log, event bus).
  • Over-mocking (mocking everything including internal collaborators) produces tests that break on every refactor without catching real bugs.
  • Tradeoff: mocks couple tests to implementation details. Prefer fakes (working in-memory implementations) when the dependency has non-trivial behavior.
Unit Testing

How do you test code that depends on the current time?

  • Inject TimeProvider (built into .NET 8+) instead of calling DateTime.UtcNow directly.
  • In tests, use FakeTimeProvider (from Microsoft.Extensions.TimeProvider.Testing) to control “now”.
  • This makes time-dependent logic (expiry checks, scheduling, TTL calculations) fully deterministic in tests.
  • Tradeoff: requires changing existing code that calls DateTime.UtcNow directly — a one-time refactor cost that pays off in every time-sensitive test.
Unit Testing

When should you NOT write unit tests?

  • Trivial property getters/setters with no logic — the test adds noise without catching real bugs.
  • UI rendering logic — visual correctness is better verified with snapshot tests or manual QA.
  • Infrastructure wiring (DI registration, config parsing) — test this with integration tests that boot the real container.
  • Exploratory spike code — write the spike without tests, learn from it, then delete it before it becomes production code.
  • Tradeoff: every test has a maintenance cost. Tests that don’t catch real bugs are pure overhead. Focus unit tests on logic with branching, edge cases, and business rules.
Unit Testing

AI & ML

When should you reach for classic ML instead of an LLM API?

  • Classic ML wins when the task is a well-defined prediction with labeled data: classification, regression, ranking — millisecond latency and near-zero per-request cost at scale
  • LLMs win when the task involves open-ended language understanding or generation, training data is scarce, or iteration speed matters more than unit cost
  • A common production pattern: prototype with an LLM to validate the product, then distill the stable, high-volume part into a small fine-tuned model
  • Key tradeoff: classic ML trades upfront data and training effort for cheap, fast, predictable inference; LLMs trade per-call cost and latency for flexibility and zero training
AI & ML

Why does evaluation discipline matter more than model choice?

  • Without held-out evaluation, every model swap, prompt change, or retraining run is a guess — improvements cannot be distinguished from noise or regressions
  • Production failures are dominated by data and distribution problems (drift, leakage, segment regressions), which only evaluation and monitoring catch — not by raw model capability
  • A weaker model with solid evaluation and a feedback loop improves over time; a stronger model without them silently degrades
  • This is why every branch of this section has its own evaluation pages: ML Evaluation and the general LLM Evaluation, which RAG and agents specialize in RAG Evaluation and Agent Evaluation
AI & ML

How do the six principles translate into concrete engineering work?

  • Fairness → slice-based evaluation, dataset audits, per-segment production monitoring
  • Reliability and safety → adversarial testing, fallback paths, staged rollout via shadow mode
  • Privacy and security → data minimization, anonymization, encryption, RBAC on models and data
  • Inclusiveness → multilingual and accessibility evaluation, low-resource device testing
  • Transparency → model cards, decision logging, explanatory UI for consequential outputs
  • Accountability → human review gates, escalation ownership, rollback procedures
  • The common thread: every principle is testable and monitorable — if it only lives in a policy document, it is not implemented
Responsible AI

Why is aggregate accuracy insufficient evidence that a system is fair?

  • Aggregate metrics average over the whole population, so strong majority-group performance can mask poor performance on minority groups
  • A system can be 95% accurate overall while being 70% accurate for a specific demographic — the aggregate number hides exactly the disparity fairness is about
  • The fix is slice-based evaluation: measure the same metrics per demographic segment and treat a large gap as a defect, even when the aggregate looks healthy
  • This mirrors the segmentation principle used elsewhere in monitoring: averages lie
Responsible AI

LLM

How can Matryoshka dimensionality reduction lower embedding storage costs without significant recall loss?

Use the dimensions parameter to truncate vectors to 256 or 512 dimensions. Models trained with Matryoshka objectives produce vectors where any prefix is independently meaningful. Truncation to 256 dims reduces storage by ~6x and speeds up ANN search proportionally. Validate by comparing recall@k on your evaluation set at 256, 512, and full dimensions — MRL models can retain strong retrieval quality at reduced dimensions, but the actual degradation is model-specific and corpus-dependent. If recall drops unacceptably, use a two-stage approach: retrieve at reduced dimensions, then re-rank candidates using full-dimensional vectors.

Embeddings

Why can switching to a higher-scoring embedding model cause recall to drop on existing queries?

Different embedding models produce vectors in different geometric spaces — cosine similarity between vectors from two different models is meaningless. If the corpus is not re-embedded with the new model, queries encoded with the new model are compared against vectors from the old model, producing degraded or random rankings. The fix is to re-embed the entire corpus with the new model before switching query-time inference. This is also why embedding caches must key by model name and version.

Embeddings

When is domain-finetuning the embedding model justified over improving chunking or retrieval?

Finetuning is justified when retrieval failures are specifically caused by semantic mismatches on domain terminology — queries and relevant documents contain the same domain concepts but land far apart in vector space. If failures trace to split logical units (chunking issue), missing metadata filters (retrieval pipeline issue), or query ambiguity (query translation issue), finetuning the embedding model will not help. Diagnose by examining the top-k retrieved chunks for failed queries: if semantically relevant chunks exist but rank low, the embedding model is the bottleneck.

Embeddings

Why can LoRA still cause forgetting if the base weights are frozen?

The deployed output comes from the base plus the adapter’s updates. Narrow adapter training can steer that effective model away from capabilities outside the training distribution. The frozen base makes rollback easy; it does not guarantee unchanged behavior while the adapter is enabled.

Fine-tuning

How should you estimate full fine-tuning memory?

Account separately for weights, gradients, optimizer states, activations, temporary buffers, precision, and sharding. Sequence length, batch, checkpointing, and optimizer choice can move the total enough that a generic multiplier is not a safe capacity plan.

Fine-tuning

What does GRPO remove compared with PPO-style language-model training?

It removes the separately learned value model by estimating relative advantage from a group of sampled completions. It retains policy sampling, reward computation, clipped updates, and a reference-policy constraint.

Fine-tuning

What is DPO’s operational advantage over reward-model RLHF?

It trains directly from preference pairs without a separate learned reward model or online policy-optimization loop. The tradeoff is that it cannot explore and score fresh outputs during the update.

Fine-tuning

What does GRPO remove compared with PPO-style language-model training?

It removes the separately learned value model by estimating relative advantage from a group of sampled completions. It retains policy sampling, reward computation, clipped updates, and a reference-policy constraint.

GRPO

Why can training reward rise while reasoning quality does not?

The policy may exploit the verifier or output format, and group-relative rewards only compare sampled candidates under that reward. Held-out task and human evaluation are required to show that the learned behavior generalizes.

GRPO

Why is adjusting temperature and top_p simultaneously discouraged?

Both reshape the same token probability distribution but through different mechanisms. Temperature scales the logit distribution (sharpening or flattening), while top_p truncates it to a cumulative mass threshold. Changing both creates unpredictable interactions — a low temperature already concentrates probability mass, so a low top_p on top of it may have no additional effect, while a high temperature with a low top_p creates conflicting signals. Tuning one while keeping the other at default keeps behavior predictable.

Generation

Why can a grounded response still contain unsupported claims despite citation tags?

Models can attach citation markers to claims without verifying entailment. The citation looks correct but the cited passage may not actually support the claim — it may be topically related but not evidentially sufficient. This is why citation generation alone is not grounding: a separate claim-to-source verification step (NLI or similar) is needed to confirm that each cited passage actually entails the claim it is attached to.

Generation

When should constrained decoding be preferred over JSON mode for structured output?

Constrained decoding enforces a specific JSON schema at the token level during generation — the output is structurally compliant by construction. JSON mode only guarantees valid JSON without schema enforcement, so the model can return any valid JSON structure. Use constrained decoding when downstream systems depend on a specific schema (API contracts, database inserts, tool arguments). Use JSON mode when you want parsable output but the structure is flexible or exploratory.

Generation

Why is T5 generative while BERT is not an open-ended generator?

BERT is an encoder-only masked-language model that returns contextual representations. T5 has a bidirectional encoder plus an autoregressive decoder that generates a target sequence conditioned on the encoded input.

LLM Foundations and Training

What must match besides checkpoint tensor bytes?

The architecture/configuration, tensor names and layouts, tokenizer and special-token ids, adaptation or quantization metadata, and runtime operator implementations must agree. Verify the bundle with known-answer inference, not only a successful file parse.

LLM Foundations and Training

Why does architecture matter when someone says “LLM”?

Encoder-only, encoder-decoder, and decoder-only transformers expose different inputs, objectives, and output paths. A BERT checkpoint is not a causal text generator, while T5 generates through an autoregressive decoder conditioned on encoder output.

LLM

What must match besides checkpoint tensor bytes?

The architecture and configuration, tensor names and layouts, tokenizer and special-token IDs, adaptation or quantization metadata, and runtime operator implementations must agree. Verify the bundle with known-answer inference, not only a successful file parse.

LLM

Why is active parameter count not an inference-cost measurement?

It omits dense layers, memory traffic, token dispatch, interconnect communication, batching, and expert imbalance. Measure throughput and latency on the target serving stack.

LLM

How do you choose between prompting, RAG, and fine-tuning?

Start with prompting. Add RAG when the gap is current, private, or attributable knowledge. Fine-tune when a measured behavior gap remains—format, policy, style, or a narrow task that prompting cannot stabilize.

LLM

Why is active parameter count not an inference-cost measurement?

It omits dense layers, memory traffic, token dispatch, interconnect communication, batching, and expert imbalance. Measure throughput and latency on the target serving stack.

Mixture of Experts

What failure does load balancing prevent?

It prevents a few popular experts from exceeding capacity and becoming stragglers while other experts are underused. The balancing mechanism itself can trade specialization against even utilization.

Mixture of Experts

Why are “frontier is best” and “small is cheap” insufficient selection rules?

They describe common tendencies, not the result on a particular task and serving stack. Selection requires measured quality, safety, latency, and cost per successful task under the same workload.

Model Selection and Routing

When should you use a classifier instead of a cascade?

Use a classifier when difficulty can be predicted before generation and duplicate latency is expensive. Use a cascade when the first result produces a reliable, cheap failure signal. Evaluate both end to end because classifier misses and cascade retries fail differently.

Model Selection and Routing

Why not use supervised fine-tuning for every preference?

SFT imitates one selected answer. Preference objectives also learn the boundary between chosen and rejected behavior, which can be more informative when several plausible answers differ in policy or quality.

Preference Alignment

What is DPO’s operational advantage over reward-model RLHF?

It trains directly from preference pairs without a separate learned reward model or online policy-optimization loop. The tradeoff is that it cannot explore and score fresh outputs during the update.

Preference Alignment

Agents

When should you use a workflow instead of an autonomous agent?

  • Use a workflow when the task decomposes into predictable steps with clear inputs and outputs at each stage
  • Workflows are cheaper, faster, and more debuggable than autonomous agents
  • Use an autonomous agent only when steps are unpredictable, the task is open-ended, and you have feedback mechanisms (tests, eval criteria) to catch errors
  • Most production “agents” are actually workflows — and that is the right choice for the majority of use cases
  • Key tradeoff: workflows trade flexibility for reliability; agents trade reliability for adaptability
Agents

Why do autonomous agents accumulate error, and how do you bound it?

Each step an agent takes is conditioned on the output of the last one, so a small early mistake — a misread tool result, a wrong assumption — gets carried forward and compounds, pushing the agent further off track with every loop. That compounding is the main reason to prefer a workflow when you can: fixed code paths don’t drift. When you do need autonomy, you bound the damage with hard iteration caps, explicit gates that validate progress before continuing, transparency into the planning steps so you can see where it went wrong, and a human-in-the-loop escape hatch when the agent is blocked. The throughline is feedback: the agent needs a way to catch its own drift before it cascades.

Agents

What makes a task a good fit for an autonomous agent?

Two things together: the task is genuinely open-ended — you can’t predict the number of steps or write a fixed workflow for it — and it has a clear, checkable success signal the agent can use to judge its own progress. That’s why coding works (tests pass or fail), customer support works (the issue is resolved or not), and research works (claims trace back to sources). Tasks with vague or delayed success criteria are where agents flail, because there’s no feedback to correct the compounding error. If you can’t define what “done” and “correct” look like in a way the system can check, the task isn’t ready for an agent yet.

Agents

How do the five patterns form a progression, and how do you choose among them?

They climb in complexity and in how much control moves from the developer to the model:

  • Prompt chaining — fixed sequence, developer owns every step. Use when the task splits into predictable stages.
  • Routing — one classification decision fans out to specialized paths. Use for distinct input categories that need different handling.
  • Parallelization — independent calls run concurrently and aggregate. Use for speed (sectioning) or reliability (voting).
  • Orchestrator-workers — the subtasks themselves are decided at runtime. Use when you can’t enumerate the steps in advance.
  • Evaluator-optimizer — a generate/critique loop refines toward criteria. Use when there is a clear quality signal and feedback improves the output. The rule is to start at the top and move down only when a simpler pattern demonstrably fails. Every step down buys flexibility at the cost of predictability, latency, and debuggability.
Workflow Patterns

What separates orchestrator-workers from parallelization when the diagrams look nearly identical?

Both fan work out to several LLM calls and aggregate the results, so the topology is similar. The difference is when the subtasks are known. In parallelization the subtasks are fixed by the developer up front — you decide there are three sections or three votes before any call runs. In orchestrator-workers a central LLM reads the input and decides at runtime how to decompose it, how many workers to spawn, and what each should do. That runtime decision is what makes orchestrator-workers the bridge into Multi-Agentic Systems: it is the simplest pattern where the model, not the code path, controls the shape of the work.

Workflow Patterns

Why add gates between steps in prompt chaining instead of a single prompt?

Chaining trades one hard call for several easier ones, and the gates are where that trade pays off. Each step does a narrower job, so it is more reliable and easier to prompt than asking one call to do everything at once. The programmatic gates between steps — a length check, a schema validation, a “does this outline meet the criteria” test — catch failures early, before a bad intermediate result propagates and compounds downstream. The cost is added latency and more moving parts, so chaining is worth it only when the task genuinely decomposes into fixed subtasks; if it doesn’t, a single well-prompted call is simpler and cheaper.

Workflow Patterns
Evaluation

Why can the same model post very different scores on the same agent benchmark?

  • Agent benchmarks score a model plus its scaffold — planning loop, retries, tool design, prompt — and the scaffold contributes a large share of the result
  • Two teams running the same base model with different harnesses get different SWE-bench numbers, so cross-paper comparisons are unreliable unless the scaffold is held constant
  • Contamination and harness-specific prompt tuning add further variance over time
  • The takeaway: use leaderboards to shortlist models, then re-evaluate candidates under your scaffold on your tasks
  • Holding the scaffold constant for a fair comparison costs engineering setup, but without it the numbers don’t mean what they appear to
Agent Benchmarks

Why is pass^k a more honest agent metric than pass@1, and when does it matter most?

  • pass@1 averages success over independent attempts and hides variance; an agent that solves a task 6/10 times looks similar to one that solves it 10/10
  • pass^k credits a task only if solved on all k tries, directly measuring reliability — the property production users actually experience
  • It matters most for high-stakes or unattended tasks (payments, code merges) where one failure in k is unacceptable
  • pass^k is typically well below pass@1, so reporting only pass@1 overstates production readiness
  • Measuring pass^k costs k× the eval runs, so spend it on the tasks where variance is intolerable and use cheaper mean success for low-stakes breadth
Agent Benchmarks

Why is outcome-only scoring insufficient for evaluating an agent, and what do you add?

  • A correct final state can be reached by a wasteful or wrong path — three failed tool calls before a lucky success scores identically to a clean solve
  • Outcome-only hides cost, latency, and compounding error risk, so a more expensive or fragile agent looks equal to a cheaper reliable one
  • Add process metrics: tool-call validity (deterministic), tool-selection and trajectory quality (judge or reference), and efficiency counters (steps, cost, latency)
  • Add reliability: run each task k times and report pass^k, since stochastic trajectories make a single run an unreliable estimate
  • Process and reliability scoring multiply eval cost (k runs, a judge call per trace), so spend it where path quality and variance actually affect users and keep cheap outcome+efficiency gates everywhere else
Evaluation

How do you detect and measure agent non-termination and looping?

  • Non-termination shows up as runs that hit the step cap without reaching a terminal state; track cap-hit rate as a first-class metric
  • Oscillation shows up as repeated identical or alternating tool calls; detect by hashing (tool, args) per step and flagging repeats within a trajectory
  • Both inflate cost and latency long before they change task success, so latency/step-count distributions catch them earlier than outcome metrics
  • Mitigation: enforce step and cost caps, add progress checks, and make the agent’s plan explicit so a judge can see where it stalled
  • Tight caps cut runaway cost but can truncate genuinely hard tasks — set caps from the step-count distribution of known-good runs, not a round number
Evaluation

Why report tool-selection and argument accuracy separately instead of one tool-call score?

  • The two failures have different root causes and fixes: selection errors point at tool descriptions and prompt routing, argument errors point at grounding and schema design
  • A single averaged number can look healthy while one axis is broken — 95% selection with 70% arguments averages to a misleading 82%
  • They need different scorers: selection is reference/judge, arguments are exact for structured fields and semantic for free text
  • Acting on the blended number wastes effort optimizing the half that is already fine
  • Per-axis scoring costs more labels and more judge calls, but without it you can’t tell which fix to ship — spend the granularity on the tools where a wrong call is expensive
Tool-Call Evaluation

Why are deterministic schema checks necessary but not sufficient for tool-call evaluation?

  • They catch malformed JSON, unknown tools, and wrong arity for free and pre-execution, so they can block a bad call before it runs
  • They are blind to semantics: a call with perfect schema and a wrong order id or mis-scoped query passes every check and then executes against real state
  • They cannot judge selection or necessity — whether a tool should have been called at all
  • Pair them with reference argument matching (for structured fields) and a judge (for selection/necessity), and add confirmation steps for irreversible actions
  • The semantic layers cost labels and judge calls, so gate them on the high-risk tools rather than running them on every read-only call
Tool-Call Evaluation

When do you use reference-trajectory matching versus an LLM judge over the trace?

  • Reference matching fits tasks with a small, knowable set of correct paths; it is cheap, objective, and can assert hard constraints (subset = stayed in bounds, superset = required work done)
  • It breaks when many paths are valid — strict matching then penalizes correct alternate solutions and measures conformance, not quality
  • A judge handles open-ended tasks and rates plan quality, redundancy, and recovery that matching cannot see, at the cost of a model call per run and long-context bias
  • Common setup: subset match as a cheap safety gate (no out-of-scope tools) plus a judge for path quality, backed by outcome and efficiency metrics
  • Matching is cheap but brittle and quality-blind; judging is flexible but expensive and biased — choose by how enumerable the correct paths are
Trajectory Evaluation

Why can a high trajectory-judge score be misleading, and how do you guard against it?

  • If the judge sees the task succeeded, outcome leakage makes it rationalize a messy path as good
  • On long traces the judge skims the middle and rewards verbosity, so a bloated trajectory can outscore a clean one
  • Guard by withholding the outcome from the path judge (or scoring path and outcome separately) and by validating against human labels on long runs
  • Pair the judge with objective efficiency counters (steps, cost, redundant calls) that a biased judge cannot launder
  • Separate, calibrated, step-localized judging costs more calls, so spend it on the long, high-stakes trajectories where path quality actually matters
Trajectory Evaluation

Context Engineering

Why does adding more context often make answers worse, not better?

  • Model attention is uneven: it concentrates on the start and end of the window and underweights the middle (“lost in the middle”), so critical evidence placed there is effectively ignored
  • Overall quality degrades as input grows even when the added tokens are relevant — attention dilutes across more material (context rot)
  • Irrelevant tokens actively compete with relevant ones for finite attention, and larger contexts cost more and add latency
  • The fix is signal density: fewer, higher-quality, well-ordered chunks beat a larger but noisier window — measure quality as you vary context size rather than assuming more helps
Context Engineering

What are the main techniques for keeping a long-running agent's context under control?

  • Compaction: summarize or prune older turns before they crowd out the task; cap tool-result size to only the fields needed
  • Offloading: move large intermediate state to a scratchpad or filesystem and pass lightweight references back into the window
  • Isolation: split work across sub-agents along context boundaries so each window stays focused
  • Selection and ordering: retrieve few high-signal chunks and place the most important evidence at the start and end
  • Accounting: track cumulative tokens per iteration and compact or terminate before the window fills, rather than letting the runtime truncate the oldest (often most important) messages
Context Engineering
RAG

Why should retrieval cache keys be based on processed query text instead of raw embeddings?

Processed query text and transformation version are deterministic, auditable, and stable across embedding model upgrades. Raw embedding bytes are opaque, change with every model swap, and make cache invalidation on model upgrade impossible without full cache flush. Keying on processed text also aligns cache correctness with the query translation pipeline — if the translation changes, the key changes automatically.

Caching

Why is response caching riskier than embedding caching?

Embedding is a pure function: same text plus same model always produces the same vector. Response generation depends on the system prompt template, the retrieved evidence (which changes with index updates), the user’s permissions, and the model version — all mutable. A cached response can become wrong when any of these inputs change without cache invalidation. Embedding cache entries only go stale when the source text or model version changes, which are infrequent and structurally detectable.

Caching

When is semantic caching safe to deploy, and when should it be avoided?

Semantic caching is safe when the domain is narrow, queries are repetitive, false positives have low cost, and you can calibrate a reliable similarity threshold on a held-out set. It should be avoided in high-stakes domains (medical, legal, financial) where a wrong cached answer causes harm, in multi-turn conversations where context changes the correct answer, and when the query distribution is too diverse to find a threshold that balances hit rate against false-positive rate.

Caching

Why does parent-child chunking often improve answer completeness over child-only retrieval?

Child chunks maximize retrieval precision — small, focused units match specific queries well. But child-only context can miss adjacent constraints that the answer depends on (e.g., a policy rule in one child and its exception in a sibling). Parent expansion restores the surrounding context during generation, reducing partial or decontextualized answers. The tradeoff is increased context size and potential noise from the broader parent span.

Chunking

When should a team move from recursive to structure-aware chunking?

Move when retrieval errors consistently trace back to broken structural units — split tables, bisected code blocks, or separated clause/exception pairs. Recursive splitting respects paragraph boundaries but is blind to document-specific structures. Structure-aware chunking preserves those units at the cost of format-specific parser investment and ongoing parser maintenance.

Chunking

Why is semantic chunking not always superior to simpler rule-based approaches?

Semantic chunking places boundaries at actual topic shifts, which sounds ideal. But it requires embedding every span during ingestion (high cost), the similarity threshold is sensitive to embedding model and domain (tuning burden), and threshold instability can cause over-fragmentation or oversized chunks. For documents with reliable structural markers (headings, sections), recursive or structure-aware chunking achieves similar boundary quality at a fraction of the ingestion cost.

Chunking

Why is sampled LLM-as-judge scoring preferred over scoring every response in production?

  • Scoring every response doubles per-request cost and adds latency if synchronous.
  • At production scale (thousands of queries/hour), full scoring is prohibitively expensive.
  • Sampled scoring (5–20%) provides statistical coverage of the quality distribution at a fraction of the cost.
  • Stratified sampling ensures rare but important query types (multi-hop, negation, edge-case domains) are represented.
  • Async execution decouples scoring from user-facing latency — users never wait for the judge.
  • Full scoring is reserved for offline evaluation runs against labeled eval sets.
  • Lower sample rates cut cost but raise the odds of missing a localized regression in a small query cluster; start at 10–20% and reduce only once the distribution looks stable.
Monitoring

Why should RAG alerting use relative regression thresholds instead of absolute quality targets?

  • Absolute thresholds (“faithfulness > 0.9”) are brittle across corpus changes, model updates, and query distribution shifts.
  • A threshold calibrated at launch becomes meaningless after the corpus doubles or query mix evolves.
  • Relative thresholds (“no more than 5% drop from 7-day rolling baseline”) adapt automatically because the baseline tracks current system state.
  • Relative thresholds prevent the failure mode where a team sets an ambitious absolute target, cannot reach it consistently, and silently disables the alert.
  • Baselines must be recomputed after intentional pipeline changes (model swap, prompt update, index rebuild) to avoid false alarms on expected shifts.
  • Relative thresholds can miss slow drift that stays inside the rolling window, so pair them with a periodic absolute floor check — a monthly look at whether the baseline itself is still acceptable.
Monitoring

How does monitoring differ from evaluation in a RAG system, and why do you need both?

  • Evaluation validates a pipeline configuration against a labeled dataset before deployment — it gates releases.
  • Monitoring validates the pipeline continuously against live traffic after deployment — it catches production regressions.
  • Eval sets are static snapshots; production traffic shifts continuously with new query patterns, corpus updates, and model provider changes.
  • Monitoring catches failure modes that no static eval set anticipates: seasonal query shifts, silent model downgrades, load-dependent degradation.
  • The feedback loop connects them: failing production traces identified by monitoring get added to the eval set, preventing recurrence in future releases.
  • Monitoring alone catches problems only after users feel them; evaluation alone misses production-specific failures. You need both.
Monitoring

Why does query translation often improve recall but sometimes hurt precision, and how do you detect the tradeoff?

Expected answer:

  • Additional query variants cover vocabulary the original misses, hitting more document neighborhoods and improving recall
  • Weak variants introduce concepts not in the original intent, pulling related-but-irrelevant documents into the candidate set
  • Precision drop is invisible in aggregate recall metrics — recall goes up while noisy candidates are buried in the ranked list
  • Detection: measure precision@k (not just recall@k) before and after translation, segmented by query type
  • If recall@20 improves but precision@5 drops, translation helps coverage but hurts candidates that reach the generator
  • Key mitigation: always include the original query as a variant and discard translations that drift too far from the original intent
Query Translation

When is decomposition a better choice than multi-query, and when does it hurt?

Expected answer:

  • Decomposition fits when the original query has distinct sub-problems requiring separate evidence — comparisons, multi-entity questions, timeline questions
  • Multi-query fits when the question has a single intent but user phrasing may not match document terminology
  • Decomposition hurts on single-intent questions: sub-questions fragment a coherent topic and lose the constraints that make the original specific
  • The test: if sub-questions can be answered independently and concatenated answers address the original, decomposition fits
  • If sub-questions only make sense in context of the original question, use multi-query instead
  • Key tradeoff: decomposition adds a synthesis LLM call and risks context fragmentation; multi-query only adds retrieval calls
Query Translation

Why can HyDE outperform direct query embedding for vague questions but fail on specific factual queries?

Expected answer:

  • Vague queries produce sparse, underspecified embeddings equidistant from many document clusters — poor retrieval signal
  • HyDE generates a hypothetical answer paragraph, creating a denser point in embedding space closer to real answer documents
  • The encoder’s bottleneck filters hallucinated details while preserving the correct semantic neighborhood
  • For specific factual queries (error codes, versions), the LLM may hallucinate wrong details — different error code, different version
  • The hallucinated embedding steers retrieval toward documents matching the wrong specifics
  • Direct query embedding, while sparse, preserves exact tokens that keyword search in a hybrid setup can catch
  • HyDE failure is stealth: retrieved documents look topically relevant but answer the wrong specific question
Query Translation

When is GraphRAG a better fit than plain vector retrieval?

When answers require explicit entity relations, dependency paths, or multi-hop joins that are hard to recover from independent text chunks. Examples: compliance tracing across policy documents, architecture dependency analysis, supply chain impact assessment. Skip GraphRAG for simple fact lookups where vector similarity suffices.

RAG Patterns

Why is hybrid search plus reranking usually added before GraphRAG or agentic RAG?

Hybrid search and reranking fix the most common production failure first: the right evidence is missing or buried under noisy chunks. They reuse the same corpus and retrieval pipeline, so the integration cost is lower than building agents or knowledge graphs. GraphRAG and agentic RAG are justified only when evals show relationship reasoning or multi-tool orchestration is the actual bottleneck. The tradeoff is that hybrid search improves retrieval quality cheaply, while graph and agentic systems buy extra capability at a large indexing, latency, and observability cost.

RAG Patterns

Why should advanced RAG patterns be introduced incrementally instead of all at once?

Each pattern adds independent failure modes and observability needs. Incremental rollout isolates impact, allows A/B measurement against baseline, and prevents compounding complexity from masking root causes. Start with the pattern that addresses your highest-frequency failure mode.

RAG

When does fine-tuning beat adding more retrieval sophistication?

When the failure is behavioral, not factual: the model retrieves the right evidence but keeps producing the wrong format, tone, or policy behavior despite prompt iteration. Retrieval upgrades cannot fix behavior encoded in weights. Conversely, fine-tuning cannot fix missing or stale knowledge — it bakes in a snapshot that starts aging immediately and provides no source traceability. Diagnose first: if faithfulness is high but style or policy compliance is low, fine-tune; if evidence is missing or wrong, improve retrieval.

RAG

When a RAG answer is wrong, how do you tell whether retrieval or generation is at fault?

Split the pipeline and score the two halves separately, because the fixes are opposite. First check whether the right evidence was retrieved at all: if the relevant chunk never made it into the context, it’s a retrieval failure — improve chunking, hybrid search, or reranking, and no amount of prompt tuning will help. If the evidence was present but the answer ignored or contradicted it, that’s a generation/faithfulness failure — tighten the prompt, add groundedness checks, or use a stronger model. This is exactly why RAG evaluation reports retrieval and generation as separate metrics; a single end-to-end accuracy number hides which half to fix.

RAG

Why can reranking improve offline nDCG without visible quality improvement for end users?

The improvement may be in ranking positions that the generator does not use. If the generator only reads the top-3 chunks, improvements at positions 4–5 are invisible to users. Evaluate whether the reranker changes the top-k composition that actually enters the prompt, not just overall nDCG. Also verify that the eval set reflects production query distribution — gains on eval-set query types may not represent the queries users actually send.

Re-ranking

When does reranking hurt retrieval quality instead of helping?

When the reranker is out-of-distribution for your domain. A reranker trained on short web passages may misjudge relevance on long technical documents, internal terminology, or multilingual content — demoting actually relevant documents. Also when the candidate count is too small: if first-stage recall@N is already low, the reranker only reorders noise. Always compare recall and precision before and after reranking on domain-specific queries.

Re-ranking

Why is ColBERT faster than a cross-encoder at query time despite also using token-level scoring?

ColBERT pre-computes per-token document embeddings at index time and stores them. At query time, only the query tokens need encoding (one forward pass regardless of candidate count). Scoring is a MaxSim matrix operation over pre-stored vectors, not a full transformer forward pass per document. Cross-encoders must run a complete forward pass for each query-document pair because they jointly encode the concatenated input. The tradeoff is that ColBERT needs multi-vector storage, which uses more space and requires specialized indexes.

Re-ranking

Why can vector-only retrieval underperform on technical support workloads?

Technical support queries often include exact identifiers: error codes, version strings, API paths, and SKU IDs. Embedding models capture meaning, not specific tokens — a query for “error E4392 in v2.3” may retrieve content about error handling in general rather than the specific code. The failure is subtle because returned chunks are topically related, so the LLM synthesizes a plausible but wrong answer. Keyword search catches these exact tokens because they are rare in the corpus (high BM25 weight), which is why hybrid retrieval is essential for these workloads.

Retrieval

When does hybrid retrieval perform worse than single-mode retrieval?

When the weaker search mode contributes more noise than signal to the fused list. On homogeneous corpora where one mode dominates (e.g., scientific documents with consistent terminology and natural-language queries), the non-dominant mode pulls in marginally relevant candidates that dilute fused results. In production benchmarks, vector-only has beaten hybrid on scientific corpora because keyword search on specialized vocabulary introduced noise. Always evaluate hybrid against single-mode baselines on your actual corpus before committing to the added complexity.

Retrieval

Why does HNSW recall degrade silently as the vector database grows?

HNSW navigates a graph to find approximate nearest neighbors, not an exhaustive scan. The ef_search parameter controls how many candidate nodes the search visits. At small corpus sizes, a moderate ef_search finds most true neighbors. As the corpus grows, the graph becomes denser and the same ef_search misses more true neighbors — the search path does not explore enough of the graph to find them. Latency stays stable because the search still visits the same number of candidates, and no errors are raised. The only signal is less relevant retrieved chunks. Detection requires explicit Recall@k monitoring against a ground-truth test set.

Retrieval

Why do vector databases use approximate nearest-neighbor search instead of exact search?

  • Exact (brute-force) search compares the query against every stored vector — correct, but O(N) per query and far too slow once there are millions of vectors
  • ANN indexes (HNSW, IVF, IVF-PQ) trade a small, measurable amount of recall for orders-of-magnitude lower latency, returning most of the true nearest neighbors in sub-millisecond time
  • The recall given up is silent: there is no error, and latency stays stable, so it only shows up as slightly worse retrieved context unless you measure ANN recall against brute-force ground truth
  • The right operating point is chosen by tuning index parameters (ef_search, nprobe) on the recall–latency curve for your corpus and SLA
Vector Databases

When should you use pgvector or an existing search engine instead of a dedicated vector database?

  • When you already run the database (Postgres, OpenSearch) and the corpus is modest: keeping vectors beside your relational or lexical data avoids operating a second system and simplifies hybrid search
  • pgvector gives you transactional consistency and joins with existing data, which a standalone vector DB cannot
  • Reach for a dedicated vector database when scale, filtered-search recall under narrow filters, or specialized indexes (IVF-PQ, DiskANN) exceed what the add-on supports
  • The tradeoff is operational simplicity (one system) versus peak scale and index flexibility (dedicated engine) — start with the add-on and migrate only when a real limit is hit
Vector Databases

Why does HNSW recall degrade as the corpus grows, and how do you catch it?

  • HNSW finds neighbors by walking a proximity graph, exploring a number of candidates set by ef_search; as the graph grows denser, a fixed ef_search covers proportionally less of it and misses more true neighbors
  • Latency stays constant (the search visits the same number of candidates) and no error is raised, so infrastructure dashboards show everything healthy while retrieval quality silently drops
  • Detection requires an explicit Recall@k check against brute-force ground truth on a scheduled query set
  • The fix is to re-tune ef_search upward as the corpus grows, accepting a little more latency to hold recall
Vector Databases
Evaluation

Why does ANN recall degrade silently as the corpus grows while latency stays flat?

  • At fixed ef_search/nprobe, the search explores a constant amount of work regardless of corpus size
  • As more vectors crowd the graph, that fixed search path covers a proportionally smaller fraction of it, so true neighbors are missed more often
  • Latency is driven by the search budget, not corpus size, so it stays constant and gives no signal that recall has dropped
  • Detection requires explicit ANN recall checks against brute-force ground truth on a scheduled query set — dashboards watching only latency will not catch it
  • Fix: re-tune ef_search/nprobe as the corpus grows, or move to a parameterization that scales the search budget with index size
  • Raising the search budget restores recall but increases p99 latency — pick the knee of the recall-vs-latency curve rather than maxing either
Component-Level Evaluation

Why is token-level IoU more informative than Recall@k when comparing chunking strategies?

  • Recall@k only asks whether a relevant chunk appeared; it ignores how much irrelevant text rode along inside that chunk
  • A strategy can hit high Recall@k by returning large, noisy chunks that waste context-window tokens on irrelevant content
  • Token IoU scores the overlap between retrieved tokens and the gold evidence span, penalizing both missed evidence (recall) and noise (precision) in one number
  • Concrete gap: the default 800/400 OpenAI config reached 87.9% token recall but 1.4% token precision — Recall@k would call it a success while IoU exposes the waste
  • Use IoU when context budget or generator attention dilution matters; fall back to ablation via Recall@k and Faithfulness when building token-span ground truth is too expensive
  • IoU needs (query, gold_span) labels, which cost more to produce than binary relevance — invest only where chunk efficiency materially affects cost or answer quality
Component-Level Evaluation

Why can aggregate retrieval metrics improve while individual user segments degrade?

  • Aggregate metrics average across query types and tenants, masking localized regressions
  • A pipeline change improving average Recall@5 can simultaneously degrade recall by double digits on a specific tenant’s query cluster
  • RAG workloads are heterogeneous — different query types, document formats, and languages have different retrieval characteristics
  • The fix is segment-level evaluation: slice by tenant, language, query cluster, and document source type
  • Flag any segment that degrades beyond the threshold, even when the aggregate improves
  • Per-segment evaluation does cost more to maintain (more ground-truth labels, more compute per release), but it catches the most common RAG evaluation failure in production — choose granularity based on how heterogeneous your query population is
Evaluation Metrics

Given high Faithfulness (0.91) and low Context Recall (0.54), which pipeline layer do you fix first and why?

  • High faithfulness means the model correctly uses the context it receives — generation is not the problem
  • Low context recall means retrieval misses roughly half the necessary evidence — the retrieval layer is the bottleneck
  • Retrieval quality is a hard ceiling on answer quality: the generator cannot use evidence it never sees
  • Fix retrieval first: add hybrid search (BM25 + dense), expand k, review metadata filters, check embedding domain fit
  • Do not touch prompts or generation settings until recall improves — optimizing generation against incomplete evidence is wasted effort
  • After retrieval fix, re-measure both: if faithfulness drops as recall improves, the additional context is confusing the model — add re-ranking or improve prompt grounding
  • Improving recall often decreases precision (more chunks = more noise), so pair recall improvements with re-ranking to maintain context quality
Evaluation Metrics

Why decompose RAG evaluation into separate retrieval, generation, and end-to-end layers?

Because a single “quality” score tells you something broke but not where, and the fix is completely different per layer. Retrieval metrics ask whether the right evidence even reached the generator; generation metrics ask whether the answer is faithful to that evidence and actually addresses the question; end-to-end asks whether the user’s task got solved. The decomposition is what separates the two failures that look identical from outside: perfect retrieval with a model that ignores the context, and flawless generation over irrelevant documents the retriever never should have returned. Without it you chase retrieval tuning that can’t fix a generation bug — and burn a sprint doing it.

Evaluation

What belongs in RAG evaluation specifically versus general LLM evaluation?

RAG reuses the whole general eval stack — LLM-as-a-judge, deterministic checks, golden sets, synthetic generation, the online/A-B loop — and adds only what’s genuinely RAG-shaped on top. That specific part is three things: retrieval-quality metrics (did the right chunks arrive, ranked well), faithfulness/groundedness (is the answer supported by the retrieved evidence rather than the model’s parametric memory), and the labeling problem of which chunks count as relevant when a query maps to several. Everything else is inherited. Keeping that line clean is what stops the eval system from being rebuilt per domain — the general machinery lives once, and RAG, agents, and plain prompts each specialize it.

Evaluation

When several chunks are relevant to one query, how do you decide which retrieval metric to score with?

  • First classify why they are relevant — substitutable (any one fully answers) versus complementary (the answer needs all of them combined)
  • Substitutable: score HitRate@k and MRR — success is one good chunk arriving early; strict recall or MAP wrongly penalizes not retrieving redundant copies
  • Complementary: score Recall@k and Context Recall — every required chunk must arrive; HitRate is misleading because one-of-three looks like a pass but yields an incomplete answer
  • When chunks differ in usefulness rather than count, use graded labels (0/1/2) and nDCG@k, the only common metric that consumes grades and rewards ranking the best chunk first
  • Set k from how many chunks the generator actually consumes; for complementary cases k must be at least the number of required chunks or recall is capped below 1 by construction
  • Graded multi-chunk labels cost far more annotation effort than single-chunk binary labels — invest only where the generator genuinely fuses multiple sources (multi-hop, comparison), and keep single-label binary sets for simple lookup queries
Retrieval Evaluation Sets

Why do synthetically generated retrieval eval sets often report worse recall than the system delivers in production?

  • Chunk-anchored generation labels only the source chunk as relevant, but the synthesized query is frequently answerable by other unlabeled chunks (duplicated text, overview paragraphs, near-identical FAQ entries)
  • The retriever returns a genuinely relevant chunk, but because it is not in the ground truth, Precision@k and MRR score it as a miss — a false negative in the labels, not a retrieval failure
  • This deflates absolute numbers and, worse, can rank a better retriever below a worse one, corrupting model-selection decisions
  • Fix: after generation, run a second pass (the retriever plus an LLM judge over top-k) to label additional chunks that also answer the query, or discard ambiguous queries
  • Secondary cause: lexical leakage — the LLM copies rare phrasing from the source chunk, inflating exact-match and BM25 scores on queries no real user would type
  • The dedup/judge pass does add LLM cost per query and some noise of its own, but without it synthetic retrieval metrics are systematically pessimistic and unreliable for ranking pipelines
Retrieval Evaluation Sets

Evaluation

Why are relative regression thresholds preferable to absolute quality targets for release gates?

  • Absolute thresholds are brittle across data changes, model updates, and workload shifts
  • A threshold set during initial launch becomes meaningless after the data doubles or input distribution shifts
  • Relative thresholds (no more than N% regression from baseline) adapt automatically because the baseline tracks the current system state
  • They prevent the failure mode where a team sets an ambitious absolute target, cannot reach it, and disables the gate entirely
  • Relative thresholds do require a consistent baseline measurement maintained across releases, which adds CI/CD complexity — but that cost is far lower than the risk of shipping regressions absolute thresholds can’t catch after the first data evolution
Building an Evaluation Set

When should a team invest in a human-annotated golden set versus relying on synthetic generation?

  • Synthetic generation bootstraps evaluation quickly and covers breadth at low cost
  • LLM-generated inputs cluster around patterns the model finds easy to generate, missing adversarial cases, ambiguous inputs, and domain edge cases
  • A golden set is worth the investment when the system serves high-stakes decisions (medical, legal, financial) where evaluation failures have direct business or safety impact
  • Golden sets also serve as regression gates — known past failures are captured permanently, preventing recurrence (see Golden Test Set and Regression Runs)
  • In practice, combine both: synthetic for broad coverage, golden for regression gating on known failure modes
  • Golden sets do cost annotator time (typically 2-4 hours per 100 examples) and ongoing maintenance as the data evolves — invest proportionally to the cost of an undetected evaluation failure in your domain
Building an Evaluation Set

What is the minimum useful set of deterministic checks for a tool-using agent?

(1) Allowlisted tools/actions only, (2) strict output schema validation, (3) PII scanning on outputs, (4) injection-resistant formatting, (5) length constraints. These catch the most common failure modes cheaply before any expensive evaluation.

Deterministic Checks

Is a golden test set a scoring stage?

No. It is a versioned regression dataset. Exact predicates, semantic judges, and human raters score outputs generated from its cases; the regression gate compares those results with a pinned baseline or threshold.

Evaluation

When does a deterministic check have zero classification error?

Only when it evaluates an exact product predicate, such as schema validity or membership in an allowed set, and the implementation matches that contract. Deterministic PII, toxicity, and content heuristics still have false positives and false negatives.

Evaluation

Why is a strong offline score insufficient to ship?

A fixed corpus cannot reproduce every traffic shift, multi-turn interaction, or user outcome. Use offline regression as a release gate, then estimate production impact with monitoring and controlled online experiments.

Evaluation

Why keep assignment and exposure separate?

Assignment records intent; exposure records that the variant could affect behavior. Joining outcomes to actual exposure avoids attributing an effect to users who qualified but never reached the changed surface.

Experiment Platform Architecture

What does sample-ratio mismatch tell you?

The observed allocation is inconsistent with the planned randomization. It is a diagnostic for instrumentation or eligibility failure and must be resolved before reading the treatment effect.

Experiment Platform Architecture

Why keep a frozen holdout slice separate from the golden set you iterate on?

  • Iterating prompts against the golden set until scores improve turns it into a training set — improvements stop measuring generalization
  • The holdout slice is never used for tuning, so it stays an unbiased estimate of real quality
  • Use the main golden set for day-to-day iteration; check the holdout only at release gates
  • If holdout and golden-set scores diverge (golden improves, holdout does not), you have overfit to the golden set — refresh it from production traffic
  • Key tradeoff: the holdout “wastes” labeled cases you cannot tune on, but without it you cannot trust any of your numbers
Golden Test Set and Regression Runs

When does a new failure belong in the golden set versus a targeted eval suite?

  • Add it to a targeted suite when it represents a specific failure mode you want fast, focused signal on (injection, PII leakage, groundedness) — small suites run quickly and localize regressions
  • Add it to the golden set when it represents the normal operating range: a realistic user request that the system must keep handling correctly
  • Production incidents usually warrant both: a targeted case reproducing the exact failure, and a generalized case covering the query pattern
  • Key tradeoff: targeted suites give precision but narrow coverage; the golden set gives breadth but slower, noisier signal — incidents are cheap opportunities to grow both
Golden Test Set and Regression Runs

When should I prefer LLM-as-a-judge over classic metrics, and how do I know the judge is trustworthy?

Expected answer:

  • Use judges for open-ended generation where semantics matter and deterministic metrics (exact match, BLEU) can’t capture quality.
  • Use classic metrics for deterministic outputs or when you need hard guarantees.
  • The key signal: if a human would need to read the answer to evaluate it, a judge model probably should too.
  • Measure judge trustworthiness by checking agreement with a small human-labeled set.
  • Track drift over time by re-running a fixed gold dataset after model updates.
  • Why: judge reliability is not assumed — it must be validated and maintained like any other test harness.
LLM-as-a-Judge

When should I prefer pairwise comparisons over rubric scorecards?

Expected answer:

  • Pairwise works best when iterating rapidly and the goal is “better than baseline” rather than a specific threshold.
  • Scorecards work better when you need hard pass/fail criteria, want to track specific dimensions over time, or need to gate a release on a minimum score.
  • Pairwise results aggregate into win-rates or Elo ratings, which are useful for comparing prompt versions or model checkpoints.
  • Why: relative preference is cognitively easier for both humans and models than assigning an absolute score, so pairwise tends to produce more consistent verdicts on subjective quality.
LLM-as-a-Judge

What are the most dangerous pitfalls when using LLM-as-a-judge in production?

Expected answer:

  • Verbosity bias: judges prefer longer answers even when shorter ones are correct. Mitigate with a conciseness dimension and length caps.
  • Position bias: in pairwise, judges favor whichever answer appears first. Always randomize A/B order.
  • Hidden coupling: using the same model as judge and candidate inflates scores. Use a different judge model.
  • Calibration drift: judge behavior shifts as the underlying model is updated. Maintain a gold dataset and re-run calibration periodically.
  • Why: these biases are systematic, not random — they silently corrupt your eval signal and can cause you to ship regressions.
LLM-as-a-Judge

What does low statistical power mean?

A real effect often fails to reach the decision threshold, producing a false negative. If an underpowered result is significant, its estimate may be unusually large because only extreme samples crossed the threshold; confirm both practical size and uncertainty.

Online Evaluation and AB Tests

Why must analysis follow the assignment unit?

Randomization makes assigned units independent across variants. Repeated events within one unit remain correlated, so counting them as independent understates uncertainty and can create a confident-looking result from little independent evidence.

Online Evaluation and AB Tests

Why keep assignment and exposure separate?

Assignment records intent; exposure records that the variant could affect behavior. Joining outcomes to actual exposure avoids attributing an effect to users who qualified but never reached the changed surface.

Online Evaluation and AB Tests

Harness Engineering

What is harness engineering, and how does it differ from context engineering?

  • Harness engineering designs everything the model acts through: the tool surface (which tools exist, how they’re named and scoped), the wiring protocol (MCP), and the execution environment (sandboxes, permissions, approval boundaries)
  • Context engineering decides what the model sees in its window; harness engineering decides what it can do; loop engineering decides how it iterates over time
  • They intersect — tool schemas consume context budget, tool results feed the loop — but the design questions differ: capability and safety boundaries versus signal selection and ordering
Harness Engineering

Why is the runtime, not the prompt, the right place to enforce what an agent may do?

  • The model never executes tools directly — it only emits structured calls; the runtime executes, so controls placed there cannot be talked around
  • Prompt-level rules fail under prompt injection or poisoned tool descriptions; code-level sandboxes, permission gates, and human-approval boundaries hold regardless of what enters the context
  • Practical layering: sandbox execution to bound blast radius, auto-approve read-only calls, gate state-mutating ones by policy, require a human for irreversible actions
Harness Engineering

Why does harness quality deserve as much investment as prompt quality for agent reliability?

  • Agents hit the harness on every loop iteration, so tool-surface flaws compound: one ambiguous name or vague error causes a wrong turn that later steps build on
  • Precise contracts and structured errors let the model self-correct; sloppy ones produce failures that masquerade as model failures
  • Harness fixes amortize across every run and every agent sharing the surface; prompt tweaks are flow-specific and fragile
Harness Engineering

Why does MCP use a host-client-server architecture instead of letting the LLM talk to servers directly?

The host mediates all communication between the LLM and MCP servers. This is a deliberate security boundary — the host can enforce policies (which tools the model can call, which resources it can read), require user approval for sensitive actions, and aggregate capabilities from multiple servers without any server being aware of the others. If the LLM talked to servers directly, there would be no central point to enforce access control, log actions, or prevent a compromised server from manipulating the model. The client-per-server model also isolates failures — one crashed server does not affect others.

Model Context Protocol

When would you choose function calling over MCP even for a shared tool?

When the tool involves sensitive business logic that should not be externalized (e.g., pricing calculations, compliance checks), when you need tight coupling between the tool and the application’s internal state (e.g., in-memory session data), or when the overhead of running a separate server process is not justified. Function calling is also simpler to debug — the tool definition and implementation live in the same codebase. MCP’s value is reusability across clients, so if only one client will ever use the tool, function calling is the better choice.

Model Context Protocol

What makes MCP servers harder to secure than traditional REST APIs?

Three factors compound. First, tool descriptions are untrusted input that directly influences LLM behavior — a vector that traditional APIs do not have. Second, MCP servers often get broad access (database connections, file system access, API keys) because they need to fulfill diverse model requests, violating least-privilege by default. Third, the protocol lacks built-in permission scoping — OAuth authenticates and authorizes the client’s access to the server, but does not restrict which specific tools or operations the client can invoke. Traditional REST APIs typically have endpoint-level authorization, rate limiting, and input validation as standard practice. MCP servers need all of these but the ecosystem is young enough that many servers ship without them.

Model Context Protocol

Why is tool design often more impactful than prompt engineering in agentic systems?

  • In a single LLM call, the prompt is the entire interface — prompt quality is everything
  • In an agentic system, the model interacts with tools across multiple loop iterations — each tool call is a decision point where errors compound
  • Wrong tool selection, malformed arguments, or unhelpful error messages cascade across steps
  • Anthropic’s SWE-bench team spent more time improving tool interfaces than prompts, because tool quality directly determined task success rate
  • Tradeoff: tool design effort is amortized across all agent runs; prompt tweaks are fragile and session-specific
Tools

How would you decide between one broad tool and many narrow tools?

  • Narrow tools reduce parameter count and ambiguity — fewer hallucinated arguments per call
  • Too many tools degrades selection accuracy — MCPGauge measured 9.5% accuracy drop from irrelevant tool presence alone
  • Split when a tool serves genuinely different use cases needing different descriptions and parameters
  • Keep together when operations share context and the model would naturally chain them
  • As tool count grows and selection confusion appears, apply routing or tool filtering to keep the active set focused
  • Tradeoff: per-call reliability (narrow) versus selection accuracy (fewer options)
Tools

What makes a tool safe for caching in an agent loop versus unsafe?

  • Cache-safe: read-only (no side effects), deterministic output for same inputs within TTL, acceptable staleness
  • Examples: database lookups, weather API calls, search queries — all safe with short TTLs
  • Cache-unsafe: mutates state (create/update/delete), result depends on rapidly changing context, caching would mask failures
  • Critical failure mode: caching a write operation returns cached “success” without executing — silent data inconsistency
  • Tradeoff: cache hit rate and latency savings versus freshness and correctness guarantees
Tools

Loop Engineering

Why does the ReAct pattern outperform chain-of-thought reasoning alone for tasks requiring external knowledge?

Chain-of-thought generates reasoning traces but has no mechanism to verify claims against external reality. When the model encounters a factual question it must rely on parametric memory, which produces confident but fabricated answers. ReAct interleaves reasoning with tool calls — the model can search, look up, or compute before continuing its chain. This grounds each step in real data, cutting hallucination. The original paper showed this on HotpotQA: CoT alone frequently hallucinated intermediate facts, while ReAct retrieved them. The tradeoff is latency and cost — each tool call adds a round trip and tokens.

Agent Loop

What are the three most critical production safeguards for an agent loop?

  1. Iteration cap — prevents infinite loops and cost runaway. Without a hard limit a confused agent loops indefinitely. Set per-request, not globally. 2. Token budget tracking — each iteration grows the context. Track cumulative tokens and terminate or summarize before hitting the window limit. Without this the model silently loses earlier context and quality degrades. 3. Tool call validation — validate function names and arguments against the schema before execution. A hallucinated tool call that passes through can trigger unintended side effects — database writes, payments, external API calls. Return errors to the model so it can self-correct.
Agent Loop

When is a simple prompt chain preferable to an agent loop?

When the task decomposes into a fixed, predictable sequence of steps. A prompt chain — step A then validate then step B then validate then step C — is cheaper, faster, more debuggable, and produces more consistent results. The agent loop adds value only when you cannot predict the steps in advance: the model must decide dynamically which tools to call and in what order based on intermediate results. Most production systems that call themselves agents are actually prompt chains, and that is the right choice for the majority of use cases.

Agent Loop

What does loop engineering add on top of a model with tools, and why is it its own discipline?

  • A model with tools is still one call; the loop is the runtime that iterates it: observe → decide → act, repeated until a stop condition
  • Because each iteration conditions on the last, errors compound — the loop design (caps, gates, feedback) determines whether mistakes stay small or cascade
  • It owns the time dimension the other rungs lack: prompt engineering shapes one instruction, context engineering what the model sees, harness engineering what it can do; the loop shapes behavior over many turns
  • Concretely it decides: when to stop (iteration/token/cost budgets), how to verify progress (gates, self-checks, ground truth), and when to escalate to a human
Loop Engineering

What are the main ways to bound a loop, and why are prompt-level stop instructions not enough?

  • Hard iteration caps per request, cumulative token/cost budgets with early termination, and checkable stop criteria (no tool calls, tests pass, valid artifact)
  • Prompt instructions (“stop if repeating yourself”) reduce waste but are advisory — the model can ignore them, so they complement rather than replace hard limits enforced by the runtime
  • Define fallback behavior at the cap upfront: return best partial result with a warning, or escalate to a human — never silently truncate
  • The motivation is compounding error and cost runaway: a documented unbounded run burned 9.7M tokens across 369 repeated tool calls without converging (see Agent Loop)
Loop Engineering

How do loop engineering and context engineering divide the work of managing a long run's history?

  • Context engineering decides WHAT belongs in the window: selection, ordering, structure, what a summary must preserve
  • Loop engineering decides WHEN to act: compaction cadence tied to the token budget, when to offload artifacts to external storage, when to terminate instead of compact
  • The loop makes it a runtime problem — history grows every iteration, so thresholds and triggers must be part of the loop design, not a one-time assembly choice
  • Durable state (plan files, scratchpads re-read each turn) bridges the two: it survives compaction and keeps the loop anchored to the goal
Loop Engineering

When is multi-agent coordination justified over a single agent with more tools?

  • Justified under three conditions: context pollution (subtask degrades main agent reasoning), parallelization (independent paths need concurrent execution), specialization (20+ tools degrade selection accuracy, or conflicting behavioral modes needed)
  • If none apply, single agent wins: 3–10× fewer tokens, lower latency, single linear trace for debugging
  • Many teams investing months in multi-agent discover equivalent results from better prompting on one agent
  • Key tradeoff: multi-agent buys context isolation and parallelism at the cost of coordination overhead and debugging complexity
Multi-Agentic Systems

Why does context-centric decomposition outperform problem-centric decomposition?

  • Problem-centric (code agent + test agent + review agent) forces constant coordination — each agent needs context from the others, creating lossy handoffs
  • Context-centric splits along natural context boundaries — agent handling a feature also handles its tests because it already has the context
  • Introduce a new agent only when context genuinely cannot fit in one window
  • Reduces handoff count, cuts token overhead, prevents compounding information loss at each transfer
  • Key tradeoff: context-centric may produce broader agents (more tools per agent), but avoids the “telephone game” of multi-hop handoffs
Multi-Agentic Systems

What makes multi-agent failures harder to diagnose than single-agent failures?

  • Semantic opacity: natural language errors pass as “valid” data between agents — no schema violations, no exceptions raised. A hallucinated fact from Agent A becomes trusted input for Agent B
  • Non-linear traces: multiple interleaving reasoning chains with handoffs instead of one sequential trace, making root cause analysis harder
  • Emergent behavior: agent interactions produce outcomes no single agent’s instructions predict
  • Known anti-pattern: two agents entering a politeness loop, each thanking the other, consuming budget without task progress — correct behavior per agent, catastrophic in combination
  • Key tradeoff: multi-agent gains specialization but loses the single-trace debuggability that makes single-agent failures straightforward to fix
Multi-Agentic Systems

Prompt Engineering

When is automated prompt optimization worth the setup cost?

Expected answer:

  • It is worth it when prompt quality directly impacts key metrics and manual iteration is too slow.
  • You need repeatable optimization across many tasks or datasets, not one-off prompt crafting.
  • You have an evaluation loop (datasets, acceptance metrics, or human labels) to score changes objectively.
  • Without measurement infrastructure, automation usually adds complexity without reliable gains. Why this matters: the optimization loop only pays off when evaluation discipline exists.
Automated Prompt Optimization

What is PAL's key insight compared to chain-of-thought prompting?

Expected answer:

  • PAL moves intermediate reasoning from free-form text to executable code.
  • The interpreter, not the language model, performs deterministic computation.
  • This reduces arithmetic/symbolic errors common in text-only reasoning traces.
  • It naturally connects to tool-use agents that route subtasks to calculators, Python, or external systems. Why this matters: it separates language understanding from computation for better reliability.
Automated Prompt Optimization

When should you start with zero-shot versus few-shot?

Expected answer:

  • Start zero-shot for simple, well-specified tasks (basic classification, extraction, rewrite).
  • Move to one-shot/few-shot when output format is inconsistent or class boundaries are ambiguous.
  • Use one-shot first for lightweight steering, then increase shots only if failure patterns persist. Why this matters: it minimizes prompt complexity and token cost while improving reliability only where needed.
In-Context Learning

What makes a good few-shot example set?

Expected answer:

  • Consistent input and output structure.
  • Coverage of real label space and edge cases.
  • Examples representative of production distribution, not synthetic easy cases.
  • Minimal but sufficient count (often 1-5) to show mapping clearly. Why this matters: demonstration quality usually matters more than raw quantity.
In-Context Learning

What are the main failure modes of few-shot prompting?

Expected answer:

  • Breakdown on tasks requiring multi-step reasoning.
  • Sensitivity to example order and formatting drift.
  • Context-window pressure from too many demonstrations.
  • Poor transfer when task needs external facts not present in model knowledge. Why this matters: these limits tell you when to pivot to other techniques instead of adding more shots.
In-Context Learning

When should you choose prompt chaining instead of a single large prompt?

  • Choose chaining when the task has clear stages with different goals.
  • Use chaining when intermediate validation reduces business or safety risk.
  • Prefer chaining when you need step-level observability for debugging.
  • Accept the latency and token overhead when reliability matters more than speed.
  • Use a single prompt for simple, low-risk tasks with stable behavior.
Prompt Composition

What is the main risk of generated knowledge prompting, and how do you mitigate it?

  • The generated background facts can be wrong or fabricated.
  • Treat generated facts as untrusted intermediate context.
  • Constrain the final prompt to use only explicit generated facts.
  • Validate critical claims against trusted context before final output.
  • Add abstain or reject behavior when facts are inconsistent or low confidence.
Prompt Composition

What is a practical meta prompting workflow for improving a weak prompt?

  • Collect real failures and cluster them by error type.
  • Ask the model to critique the current prompt against those failures.
  • Request a revised prompt with explicit constraints and output schema.
  • Evaluate on a held-out test set, not only the examples used for revision.
  • Version prompts and keep rollback criteria if quality regresses.
Prompt Composition

Why do prompt anatomy and model settings have to be designed together?

  • Prompt text defines intent and constraints, settings define sampling behavior.
  • A precise prompt can still fail with overly random settings.
  • Conservative settings can still produce poor output if instructions are ambiguous.
  • Reliable systems tune both and evaluate with task-specific metrics.
Prompt Engineering

When should you prefer few-shot prompting over pure instruction prompting?

  • When output format is strict or hard to describe in words.
  • When label boundaries are subtle and examples clarify decision edges.
  • When consistency matters more than novelty.
  • Start with minimal examples, then add edge cases.
Prompt Engineering

How would you debug a prompt that is accurate but too verbose and expensive?

  • Tighten output indicator with length limits and schema.
  • Lower max tokens and add stop sequences.
  • Keep temperature low for deterministic concise tasks.
  • Evaluate token usage and failure rate after each change.
Prompt Engineering

When does Chain-of-Thought usually help, and when can it hurt?

Expected answer:

  • Helps on multi-step arithmetic, logic, and planning where intermediate state tracking matters.
  • Helps when tasks are decomposable into small correct sub-steps.
  • Can hurt on simple factual queries by adding unnecessary tokens and extra room for drift.
  • Can also hurt if the first bad assumption propagates through a single chain. Why: this tests whether the candidate understands mechanism-level fit, not just a slogan like “CoT is always better”.
Reasoning Techniques

Explain the cost vs. accuracy tradeoff in self-consistency.

Expected answer:

  • Accuracy often improves because multiple sampled chains reduce single-path bias.
  • Cost and latency increase roughly with sample count N.
  • Diminishing returns appear after a small N; tune N against SLA and budget.
  • Works best when there is a clear way to compare or vote on final answers. Why: this checks practical engineering judgment under production constraints.
Reasoning Techniques

When is Tree of Thoughts justified over CoT or self-consistency?

Expected answer:

  • Use ToT when success requires explicit exploration of alternatives and backtracking.
  • Typical cases: planning, combinatorial puzzles, multi-constraint decision paths.
  • Not justified for easy tasks where CoT already reaches target quality.
  • Choose search strategy (BFS/DFS/beam) based on branching factor, depth, and compute budget. Why: this evaluates whether the candidate can map algorithmic search cost to task difficulty.
Reasoning Techniques

Safety

What is the minimum useful guardrail set for a production LLM application?

(1) Allowlisted tools/actions only, (2) strict output schema validation, (3) PII scanning on outputs, (4) prompt injection detection on inputs, (5) an abstention/escalation path for uncertainty. These five controls catch the most common failure modes at low cost. Add content safety classifiers and human-in-the-loop for high-risk domains.

Guardrails

How do you test guardrails?

Build a red-team test suite: injection attempts (“ignore all previous instructions”), jailbreaks (“you are DAN”), data exfiltration attempts, and out-of-scope requests. Run the suite on every model or prompt change. Track pass rates over time — a regression in the red-team suite is a deployment blocker.

Guardrails

Why can RAG-grounded systems still hallucinate significantly?

  • RAG changes the task to summarizing retrieved evidence, but generation can still add unsupported claims beyond context.
  • The model can misread or incorrectly compose facts from valid passages.
  • Retrieval failures silently cap answer quality before generation starts.
  • Legal RAG tools reporting >17% hallucination shows the gap between grounding and guaranteed correctness.
  • RAG adds retrieval and embedding cost yet only reduces hallucination rather than eliminating it, so size the investment to the cost of an undetected fabrication.
Hallucinations

Why does RLHF increase hallucination risk while perceived quality improves?

  • Human raters generally reward confidence, verbosity, and polished style.
  • RLHF optimizes approval signals, so “sounds good” can outrank “is true.”
  • The model becomes more overconfident on wrong answers, which worsens calibration.
  • Factuality-aware optimization (for example FActScore-informed preference training) counterbalances this failure mode.
  • RLHF buys engagement and instruction-following at the risk of factual reliability, unless factuality rewards are baked into the training signal.
Hallucinations

How do you separate retrieval failure from generation hallucination in a RAG pipeline?

  • Check corpus coverage first: does the needed document exist at all.
  • Check retrieval recall next: if present, was it retrieved for this query.
  • Check claim traceability: can each answer claim be grounded to retrieved passages.
  • Not retrieved implies retrieval failure (fix chunking, embeddings, ranking); retrieved but unsupported claims imply generation hallucination (fix grounding prompt and verification).
  • Per-claim attribution (NLI on every claim) adds cost and latency, but it pays for itself by pointing at the real bottleneck instead of guessing.
Hallucinations

Why is prompt injection fundamentally harder to prevent than SQL injection?

  • SQL injection is mainly a syntax boundary problem, and parameterized queries create a deterministic code-data split.
  • Prompt injection is a semantic boundary problem where instructions and data coexist in natural language.
  • There is no universally reliable delimiter the model can always respect across direct, indirect, and multimodal inputs.
  • The practical defense model is layered and probabilistic: filtering, monitoring, privilege separation, and output sanitization.
  • Stronger defenses add latency and engineering complexity, so match the depth of controls to the expected blast radius.
OWASP vulnerabilities on AI LLM

How does Excessive Agency (LLM06) compound with Prompt Injection (LLM01)?

  • Prompt injection gives an attacker influence over model decisions.
  • Excessive agency converts that influence into real actions through tools and permissions.
  • Combined, the attacker effectively operates with the model’s authority boundary.
  • Mitigation must address both sides: reduce injection success rate and reduce available authority after compromise.
  • Tighter permissions cost automation convenience, but they realign the model’s capabilities with its trust boundary — usually the right trade for high-authority tools.
OWASP vulnerabilities on AI LLM

Why should system prompts be treated as public rather than secret?

  • Adversarial prompting and jailbreak techniques can extract hidden instructions in real systems.
  • Security that depends on prompt secrecy is obscurity, not enforceable control.
  • System prompts should be written as if attackers can read them.
  • Deterministic enforcement belongs in RBAC, output filtering, and tool permission architecture.
  • This shifts effort from prompt wording to code-level controls — more work upfront, but far more auditable and durable.
OWASP vulnerabilities on AI LLM

How do security failures and reliability failures differ, and why does the distinction matter?

  • Security failures (OWASP Top 10) have an adversary crafting input — prompt injection, data exfiltration, excessive agency; they are bounded by controls, isolation, and least privilege
  • Reliability failures (Hallucinations) have no attacker — the model produces confident but unsupported output because it optimizes likelihood, not truth; they are bounded by grounding, verification, and evaluation
  • The mitigations barely overlap: a perfect content filter does nothing for hallucination, and RAG grounding does nothing for an injection attack — a complete system needs both tracks
Safety

Why are prompt-level guardrails insufficient on their own?

  • A model instruction is advisory: it can be overridden by a higher-priority injected instruction or a poisoned tool/retrieved description (the instruction-vs-data confusion at the heart of prompt injection)
  • Durable controls are structural and live outside the model — input validation, content isolation, permission gates, sandboxed execution, and output checking in code/infrastructure
  • Defense in depth: assume any single layer can be bypassed, so critical actions are gated where the model cannot talk its way past them (see Harness Engineering)
Safety

How does the safety concern map onto the four steering rungs?

  • Prompt/Context: separate trusted instructions from untrusted data to blunt injection; ground with RAG to reduce hallucination
  • Harness: enforce sandboxing, least privilege, and permission gates in code, not the prompt
  • Loop: place human-approval boundaries around irreversible or low-confidence actions
  • Safety is orthogonal — it is designed into every rung and measured by Evaluation, not handled at one step
Safety

Machine Learning

What is the difference between data drift and concept drift?

Data drift: the input distribution P(X) changes (users ask different questions, new product categories appear). The model’s learned relationship P(Y|X) may still be valid. Concept drift: the relationship P(Y|X) changes (what constitutes spam evolves, fraud patterns shift). The model’s learned relationship is no longer valid — retraining is required. Cost of confusing them: retraining on new data for concept drift is necessary; retraining for benign data drift wastes resources and may reduce performance on the original distribution.

Data Drift

How do you detect drift when labels are delayed?

Use proxy metrics: escalation rate, user complaints, re-contact rate, or model confidence distributions. Monitor input feature distributions (PSI, KS test) as an early warning signal. When labels arrive, compute actual performance metrics and compare to baseline.

Data Drift

How should an ML pipeline be designed to ship weekly batch churn scoring now while preserving a path to real-time scoring later?

  • Start with batch to ship value, but define a stable feature contract and a single preprocessing implementation shared by batch and online
  • Store features and predictions with versioned schema so you can backtest and replay later
  • Use a model registry with stage promotion and rollback, and keep training code runnable in CI
  • Plan the online boundary now: which features are available at request time and which require async enrichment
  • Add monitoring from day one so you have drift and label delay visibility before moving to real time
Machine Learning

What should be checked first when a binary classifier shows 98 percent accuracy but support tickets rise, and which metric should be optimized next?

  • Check class balance and the confusion matrix; high accuracy can hide poor recall on the minority class
  • Inspect label quality and leakage sources, especially time based joins and post event signals
  • Pick metrics based on cost: optimize precision if false positives are expensive, recall if misses are expensive, or use PR AUC for heavy imbalance
  • Tune the decision threshold using a cost curve or expected value, not the default 0.5
  • Run slice based evaluation to find the segments where the model fails and decide whether to collect more data or add features
Machine Learning

What tradeoffs and rollout plan are appropriate when the best model exceeds a strict API latency budget?

  • Measure end to end latency budget including preprocessing, network, and tail latencies, then decide if you can meet SLO with scaling or caching
  • Consider a smaller model, distillation, quantization, or a two stage setup where a cheap model gates the expensive one
  • Use canary or A B rollout with guardrails on latency and key business metrics, plus automated rollback
  • Keep a safe fallback decision path, such as a baseline model or rules, for overload and error conditions
  • Align evaluation with production: test on representative traffic and monitor training serving skew after launch
Machine Learning

When should you fine-tune a small model instead of prompting an LLM for an NLP task?

  • Fine-tune when you have labeled training data and the task is well-defined (classification, NER, sentiment).
  • Fine-tuned small models (BERT, DistilBERT) run at millisecond latency on-device vs seconds for LLM API calls.
  • Per-request cost is near zero for on-device inference vs 0.01–0.10+ per LLM call at scale.
  • LLMs win when the task is complex (multi-step reasoning, summarization), training data is scarce, or rapid iteration matters more than unit cost.
  • Fine-tuning costs you labeled data and training effort upfront, then pays it back as millisecond latency and near-zero per-request cost at scale; prototype with an LLM, then check whether production volume justifies the fine-tune.
Natural Language Processing

Why do multilingual NLP models underperform monolingual ones, and when is that acceptable?

  • Multilingual models split their capacity across 100+ languages, so per-language representation quality is lower.
  • Monolingual models concentrate all parameters on one language, achieving higher accuracy on that language’s benchmarks.
  • Multilingual models are acceptable when you need coverage across many languages and per-language accuracy can be slightly lower.
  • For high-stakes tasks (medical NER, legal classification), the accuracy gap may be unacceptable — use language-specific models.
  • Multilingual models trade per-language accuracy for breadth of coverage; accept that when you serve many languages on limited ML infrastructure, but validate explicitly on the languages you actually care about.
Natural Language Processing

Why start every new AI feature in Shadow Mode?

Shadow Mode lets you validate accuracy on real production data without any risk of automated errors. You measure false positive rate, edge cases, and distribution shift before giving the model any authority. Skipping Shadow Mode means discovering failures in production where they have real consequences.

Spectrum Of Automations

What signals indicate it is safe to move from Partial to Full Automation?

Precision on automated cases is consistently above your acceptable error threshold across multiple weeks. Monitoring dashboards show stable metrics with no degradation. The cost of errors is low enough that automated mistakes are recoverable. You have alerting in place to detect metric drift quickly.

Spectrum Of Automations

Evaluation

Why can a model with high ROC-AUC still produce unusable probabilities?

  • ROC-AUC measures ranking — whether positives score above negatives — and is invariant to any monotonic transform of the scores
  • That invariance means the absolute probability values can be arbitrarily distorted (all squashed near 0.9, say) without changing AUC at all
  • Downstream logic that compares p to a threshold or computes p × value then makes wrong decisions despite the perfect ranking
  • The fix is to measure calibration separately (reliability diagram, Brier, ECE) and apply post-hoc calibration; AUC will never surface the problem
Calibration

When does calibration not matter, and when is it essential?

  • It does not matter when only the ranking or the top-k is used: search results, recommendation shortlists, lead prioritization — there a strong ROC-AUC or PR-AUC is sufficient
  • It is essential when the probability feeds a decision: expected-value thresholds, cost-sensitive actions, abstention/escalation gates, risk scores shown to humans, or probability averaging across an ensemble
  • It is also essential when probabilities from different models or time periods are compared, since miscalibration makes them non-comparable
  • Rule of thumb: if a human or a formula reads the number, calibrate it; if only the order is consumed, you may not need to
Calibration

Why is temperature scaling the default calibration method for neural networks?

  • Modern deep nets are systematically overconfident, and the miscalibration is largely a global scaling of the logits rather than a per-class distortion
  • Temperature scaling fits a single scalar T that divides the logits before softmax, which is exactly the right shape for that global overconfidence
  • Because it preserves the argmax, accuracy is unchanged — you fix probabilities without touching the model’s decisions
  • It needs only a small held-out set and one parameter, so it rarely overfits; the limitation is that it cannot repair calibration that varies by region or class
Calibration

When should you optimize precision vs recall, and how do you operationalize the choice?

Optimize precision when false positives are expensive: blocking legitimate users (and churning them), flooding a manual review queue with false alarms, or triggering expensive downstream actions (automated account lockouts). Optimize recall when misses are dangerous: fraud slipping through, safety violations in content moderation (regulatory exposure), or medical screening (missed diagnosis). Operationalize by setting a hard constraint on the priority metric (e.g., “recall ≥ 0.95”), then maximizing the other. Freeze the threshold and monitor both metrics daily with alerting on ≥5% drift.

Classification Evaluation

How do you pick a classification threshold in practice?

  1. Define the business constraint: “recall must be ≥ 0.95” or “FP rate must be ≤ 0.02.” 2. Plot the precision-recall curve on a held-out validation set. 3. Find the threshold that satisfies your constraint while maximizing the complementary metric. 4. Validate on a separate golden test set (not the validation set used for selection). 5. Freeze the threshold in production config (not hardcoded). 6. Set up monitoring: if the metric drops ≥5% on weekly golden-set runs, trigger re-evaluation. A static threshold degrades as the data distribution shifts, so schedule quarterly threshold reviews or move to dynamic thresholding with confidence calibration.
Classification Evaluation

When should you distrust a single evaluation metric?

  • When the metric does not encode the cost asymmetry of the decision, for example accuracy on imbalanced classes
  • When the test set does not represent production traffic, for example a random split on time-dependent data
  • When the metric looks good overall but fails on critical slices or cohorts
  • When the metric is a ranking metric but you need calibrated probabilities for downstream logic
Evaluation

Why is the offline–online gap the central risk in ML evaluation?

  • Offline metrics are computed on a frozen sample; production traffic shifts in distribution, user behavior, and edge-case mix that the sample never captured
  • A change can improve the offline number while leaving real outcomes flat — or reverse them — because the offline set rewards patterns that do not generalize
  • Leakage, non-representative splits, and metric-proxy mismatch all widen the gap silently
  • The mitigation is layered: offline metrics as a release gate, production monitoring to catch drift, and controlled experiments to confirm a real effect before trusting it
Evaluation

What does ROC-AUC measure?

  • It measures ranking quality across thresholds.
  • A higher ROC-AUC means positives usually get higher scores than negatives.
ROC-AUC and PR-AUC

When should I use PR-AUC instead of ROC-AUC?

  • Use PR-AUC when positives are rare, like fraud or anomaly detection.
  • PR-AUC shows how precision changes as recall increases, which is key when false positives are costly.
ROC-AUC and PR-AUC

Why can high accuracy be misleading on imbalanced data?

  • If negatives dominate, a model can predict mostly negatives and still get high accuracy.
  • Check PR-AUC, precision, and recall to understand real positive-class performance.
ROC-AUC and PR-AUC

Tooling

Why do hierarchical instruction files often outperform a single giant root file?

  • Rules stay close to the code they govern, reducing ambiguity in mixed-language or mixed-architecture repos
  • Subdirectory instructions can refine local constraints without polluting global context
  • Smaller scoped files usually improve relevance-to-token ratio in the agent prompt
  • The tradeoff is governance complexity: teams need clear precedence and periodic cleanup
Agent Instructions

What is the fastest way to detect that an instruction file is harming agent quality?

  • Repeated command failures from outdated scripts or wrong paths
  • Inconsistent code style across tasks despite explicit rules
  • Frequent edits that violate architecture constraints the file claims to enforce
  • Rapid fix is to trim to high-signal rules, remove contradictions, then re-add specifics incrementally
Agent Instructions

When should a team keep instructions minimal and rely more on repository inspection?

  • Early prototype phases where architecture and conventions are changing weekly
  • Small codebases where implicit conventions are obvious from a few files
  • Teams that cannot maintain rule quality yet; stale detail is worse than concise accurate guidance
  • As complexity grows, move from minimal to layered instructions before inconsistency becomes expensive
Agent Instructions

Why is an agentic coding tool not just "better autocomplete"?

  • Autocomplete predicts local tokens, while coding agents execute multi-step plans across files and tools
  • Agents can run verification commands and revise output based on failures, which autocomplete does not do autonomously
  • The mechanism changes risk: agent output must be governed with approvals, tests, and repository rules
  • Net benefit comes from closed-loop execution, not from raw text quality alone
Coding Agents

When should a team prefer terminal agents over IDE agents?

  • Prefer terminal agents when workflows are command-heavy, scriptable, and CI-first
  • Prefer IDE agents when developers rely on interactive navigation, visual diffing, and editor-native refactors
  • The deciding factor is operational fit with current engineering process, not vendor marketing claims
  • Teams can mix both if shared instruction files and validation gates keep behavior consistent
Coding Agents

What controls reduce production risk when adopting coding agents?

  • Repository instruction files to encode architecture and testing constraints
  • Hooks and policy checks to block unsafe operations automatically
  • Bounded execution: cost limits, step limits, and explicit approval points
  • Mandatory verification (build, tests, lint) before merge, regardless of which model produced the patch
Coding Agents

Why should destructive-operation controls live in PreToolUse rather than PostToolUse?

  • PreToolUse is the last decision point before side effects execute.
  • PostToolUse runs after execution and cannot reliably undo external side effects like file writes, shell commands, or API calls.
  • Preventive controls produce cleaner failure modes than post-hoc remediation.
  • The complementary pattern: deny in pre-hooks, then lint, format, and log in post-hooks.
  • This adds latency to every in-scope tool call, so reserve pre-hook gating for operations where an unblocked violation costs more than the delay.
Hooks

How do you design hook pipelines that improve quality without making the agent unusably slow?

  • Gate only high-risk actions synchronously — destructive shell commands, writes to protected paths.
  • Keep post-hooks deterministic and incremental — format changed files, not the whole repo.
  • Move expensive validation (full test suites, security scans) to commit and CI boundaries or async hook paths.
  • Track hook execution time and failure rate; split or prune hooks that dominate loop latency.
  • More hooks buy more safety but more friction; the win is narrow matching plus the cheapest check that still catches the violation.
Hooks

When should you use an LLM-based hook instead of a deterministic script?

  • Use deterministic scripts when the policy can be expressed as a clear rule — regex match, file path check, exit code.
  • Use LLM-based hooks when the decision requires judgment that rules cannot capture, such as whether a code change introduces a security risk.
  • LLM hooks add 1-5 seconds of inference latency and introduce non-determinism where the same input may produce different decisions.
  • Reserve LLM hooks for high-stakes, ambiguous decisions where a false negative is expensive.
  • Deterministic hooks are fast and predictable but rigid; LLM hooks handle nuance at the cost of latency and non-determinism, so spend them only where a missed violation outweighs occasional false positives.
Hooks

Why does MCP reduce integration complexity for agent tooling ecosystems?

  • Without a shared protocol, each client-service pair needs custom integration code, creating N x M scaling
  • MCP changes this to N + M: each client implements MCP once, each service exposes one MCP server
  • The shared primitives (tools, resources, prompts) standardize capability discovery and invocation
  • Tradeoff: protocol standardization simplifies interoperability but adds runtime/server management overhead
Plugins

When should a team intentionally limit plugin count even if more integrations are available?

  • When accuracy and latency degrade from too many tool schemas in context
  • When the security team cannot properly review or monitor additional server dependencies
  • When task scope is narrow and extra plugins create more selection noise than value
  • Good practice is progressive enablement: start minimal, add only tools that measurably improve outcomes
Plugins

Why do skills usually improve agent reliability more than repeating instructions in every prompt?

  • Skills are persistent and reusable, so important constraints are present every time instead of being forgotten in ad-hoc prompts
  • They standardize behavior across contributors and sessions, reducing output variance
  • Structured frontmatter plus explicit usage guidance improves skill selection and timing
  • Skills are version-controlled artifacts, so teams can review and evolve them like code
Skills

When should you choose project-scoped skills over user-global skills?

  • Choose project scope when conventions are repository-specific and must be shared by all contributors
  • Choose project scope when CI, linters, or architecture rules are tightly coupled to that codebase
  • Use user-global skills for personal productivity preferences that should not override team policy
  • If a rule would surprise teammates in a PR review, it should be project-scoped, not global
Skills

How do you choose between a terminal-first coding agent and an IDE-first coding agent?

  • Start with team workflow: terminal-first fits script-heavy and CI-centric teams, IDE-first fits interactive editing-heavy teams
  • Compare observability: terminal-first often exposes every command clearly, while IDE-first may optimize UX but hide low-level steps
  • Validate integration needs: if your process depends on PR workflows, issue trackers, and editor navigation, IDE integration can reduce friction
  • Check policy controls: hooks, rules files, and approval gates matter more than UI style in regulated or production-sensitive environments
Tooling

Why are agent instructions and hooks more important than model quality in long-running repos?

  • Instructions encode project constraints (architecture, naming, testing expectations) that the base model does not know
  • Hooks create automatic guardrails, catching policy violations before merge
  • Better models can still make wrong local decisions without repo-specific constraints
  • Reliable behavior over months depends on repeatable controls, not one-off prompt quality
Tooling

What is the practical difference between a coding agent and a code review agent?

  • Coding agents optimize change creation: plan, edit, run checks, iterate
  • Review agents optimize change evaluation: identify risk, gaps, regressions, and missing tests
  • They complement each other: one increases delivery speed, the other protects quality and maintainability
  • Mature teams use both to keep throughput high without lowering merge standards
Tooling

Security

When is blockchain justified over a traditional database?

Blockchain is justified when you need a shared ledger across mutually distrusting parties with no central authority and no single party can be trusted to maintain the record. If all parties trust a central authority, a traditional database with append-only audit logging is simpler, faster, and GDPR-compliant. The cost of blockchain — low throughput, immutability conflicts with erasure rights, consensus overhead — is only worth paying when decentralization is a hard requirement.

Block-chain

Why does PoW waste energy and what is the alternative?

Proof-of-Work requires miners to perform computationally expensive hash searches to earn the right to add a block. This energy expenditure is the security mechanism — attacking the chain requires outspending honest miners. Proof-of-Stake replaces energy expenditure with economic stake: validators lock up tokens as collateral and lose them if they act dishonestly. PoS achieves similar security guarantees at a fraction of the energy cost, which is why Ethereum migrated from PoW to PoS in 2022.

Block-chain

Why does signing use the private key to encrypt the hash, not the public key?

Signing does not generally encrypt a hash. A signature scheme uses the private key to create a value that anyone with the public key can verify for the exact message. The asymmetry makes creation exclusive to the private-key holder while leaving verification public.

Digital Signature

What is the difference between signing and encryption?

Signing proves authenticity and integrity under a trusted key — it does not hide the content. Encryption hides the content but does not identify the sender. In TLS, the client validates the certificate chain, validity period, hostname, key usage, and configured revocation policy; the handshake proves the server holds the corresponding private key, and the negotiated session keys encrypt traffic.

Digital Signature

How do you choose between ECDSA and RSA?

Follow the protocol’s allowed algorithms and encodings, then check every signer, verifier, HSM, and rotation path. ECDSA P-256 offers shorter keys and signatures; RSA often has broader compatibility with existing infrastructure. Neither should be selected by an untrusted message or treated as a universal default.

Digital Signature

When should you use symmetric vs asymmetric encryption?

Use symmetric authenticated encryption for bulk data. Use a public-key encryption scheme only for the small payload and protocol it was designed for, a signature scheme for public verification, and authenticated key agreement to establish fresh shared secrets. Real systems compose these through protocols such as TLS or envelope encryption rather than choosing one family for the whole job.

Encryption

What is envelope encryption and when is it used?

Envelope encryption uses two keys: a Data Encryption Key (DEK) encrypts the data; a Key Encryption Key (KEK) encrypts the DEK. The encrypted DEK is stored alongside the ciphertext. The KEK lives in a key management service (Azure Key Vault, AWS KMS) and never leaves it. This pattern is used in cloud storage (Azure Blob Storage, S3 server-side encryption) and allows key rotation without re-encrypting all data — you only re-encrypt the DEK.

Encryption

Why can't you use SHA-256 directly to store passwords?

SHA-256 is engineered to be fast, which is exactly wrong for passwords: an attacker who steals the hash database can compute billions of guesses per second and crack weak/common passwords quickly, and without a salt they can use precomputed rainbow tables. Password hashing needs a slow, memory-hard, salted function (Argon2id, bcrypt, scrypt, PBKDF2) with a tunable work factor so each guess is expensive and every user’s hash is unique.

Hashing

What's the difference between a hash, an HMAC, and a digital signature?

A hash is keyless and only detects accidental change — an attacker can alter data and recompute it. An HMAC mixes in a shared secret key, so it proves integrity and authenticity between parties who both hold the key. A digital signature hashes then signs with a private key, so anyone with the public key can verify, adding non-repudiation (the signer can’t deny it) that HMAC lacks because its key is shared. Escalating from hash → HMAC → signature trades simplicity for stronger guarantees.

Hashing

What does a salt protect against, and why must it be unique per user?

A salt is a per-password random value stored with the hash. It defeats rainbow tables (precomputed digest→password lookups can’t be built without knowing each salt) and ensures that two users who pick the same password produce different stored hashes, so a cracker can’t crack many accounts at once or spot shared passwords. A single global salt would still let one rainbow table target the whole database — uniqueness per user is what forces the attacker to attack each hash individually.

Hashing

What is a JWT token and why is it not encrypted by default?

  • JWT is a compact token format: header.payload.signature (base64url encoded).
  • Signed (JWS) means the signature proves integrity and authenticity under the selected trusted key; issuer validation binds that key and token to the expected issuer. The payload is still readable by anyone with the token.
  • Encryption (JWE) is a separate standard that wraps the JWT in an encrypted envelope.
  • Tradeoff: signed-only JWTs are simpler and faster to validate; JWE adds encryption overhead and key management complexity.
JWT Bearer

Why should JWTs have short expiry times?

  • A locally validated bearer token remains usable until expiry unless the resource server also checks revocation or session state.
  • Shorter lifetimes bound that replay window but increase renewal traffic and dependence on the issuer.
  • Refresh tokens or another renewal credential need stronger storage, rotation, replay detection, and revocation than access tokens.
  • Choose the lifetime from the operation’s impact and the system’s ability to detect and terminate compromise, not a universal minute value.
JWT Bearer

Why does passing an OWASP Top 10 checklist not prove an application is secure?

  • The Top 10 groups broad, common risk classes; it does not enumerate the application’s assets, trust boundaries, business rules, or attackers.
  • A system can prevent listed injection patterns and still let a valid customer refund another customer’s order through broken workflow authorization.
  • Use the list to seed threat models and verification, then add abuse cases, architecture-specific controls, dependency review, and incident exercises.
OWASP

How do you prevent SQL injection in a .NET application?

  • Use parameterized queries always — EF Core and Dapper with parameters are safe by default.
  • Never concatenate user input into SQL strings, even for dynamic ORDER BY or table names.
  • For dynamic SQL, use allowlists (validate column names against a known set) rather than sanitization.
  • Do not assume an ORM makes interpolated raw SQL safe; verify which APIs parameterize and which execute literal command text.
  • Raw SQL is appropriate for performance-sensitive paths when every value remains parameterized and dynamic identifiers come from a closed allowlist.
OWASP

A secret was accidentally committed and pushed. What's the correct response?

Treat it as compromised and rotate (revoke + reissue) it immediately — that’s the only real fix, because the value persists in Git history, clones, forks, and CI caches even after you delete it. Rewriting history (e.g. git filter-repo) is useful cleanup to stop further exposure, but it does not un-leak a secret that others may already have. Afterward, add push protection / secret scanning so it can’t recur.

Secrets Management

What is the "secret zero" (bootstrapping) problem and how is it solved?

If all your secrets live in a vault, the app still needs one credential to authenticate to the vault — secret zero. Storing it just moves the problem. The modern solution is platform-provided identity: a managed identity (Azure) / IAM role (AWS) / workload identity (Kubernetes/GCP) that the infrastructure vouches for, letting the workload obtain a short-lived token with no stored secret. CI pipelines do the same via OIDC federation.

Secrets Management

Why are environment variables a weak place to store secrets despite being common?

They keep secrets out of the image (good, and the 12-factor standard), but the whole process and any child processes can read them, they’re easy to leak accidentally (a crash dump, a printenv in a build log, an APM that captures env), they aren’t audited or versioned, and rotating them means redeploying. A dedicated secret store adds access control, auditing, versioning, and rotation that env vars lack — so env vars are fine for dev but a managed store is better for production.

Secrets Management

What is the practical difference between authentication and authorization, and why do bugs cluster at the boundary?

  • Authentication establishes identity (who); authorization enforces permission (what) — a system can authenticate perfectly and still leak data through a missing authz check
  • The classic failure is the insecure direct object reference: a valid, logged-in user changes an ID in a URL and reads someone else’s record because the code checked authn but not ownership
  • Authorization must be enforced server-side on every request, close to the data — never in the UI, never assumed from a prior step
  • Tokens (JWTs, sessions) carry both concerns, so validating a token is necessary but not sufficient; you still authorize the specific action
Security

How should secrets and cryptography be handled so they don't become the incident?

  • Never hand-roll crypto: use vetted libraries and standard algorithms — the failures are almost always in usage (nonce reuse, ECB mode, weak KDFs), not the primitives
  • Hash passwords with a slow, salted password hash (bcrypt/argon2/PBKDF2), never a plain hash; encrypt data in transit and at rest with managed keys
  • Keep secrets out of source, logs, and config: use a secrets manager, rotate them, and scope them narrowly
  • Assume any secret that reaches a log or an error message is compromised — design so it never does
Security

Why does parameterizing a query fully prevent SQL injection, where input filtering does not?

A parameter is transmitted to the database separately from the SQL text, and the query is parsed/planned before the value is bound — so the value can never change the statement’s structure, no matter what characters it contains. Filtering tries to anticipate every dangerous input and always misses encodings/edge cases. Parameterization removes the entire class of attack rather than playing whack-a-mole with payloads.

Web Vulnerabilities

What's the difference between XSS and CSRF?

XSS runs the attacker’s code in the victim’s browser within your site’s origin (so it can read the DOM, cookies, and act with full privilege) — caused by unescaped untrusted data in your output. CSRF runs no code in your origin; it tricks the victim’s browser into sending a request to your site using its existing cookies, and the attacker can’t even read the response — caused by browsers auto-attaching cookies. XSS is fixed by output encoding/CSP; CSRF by anti-forgery tokens and SameSite. Notably, an XSS hole can defeat CSRF tokens, so XSS is the more severe of the two.

Web Vulnerabilities

Why are header-token (JWT) APIs less exposed to CSRF than cookie-session apps?

CSRF works because browsers automatically include cookies on cross-site requests without the attacker’s page needing to read or set anything. A bearer token sent in the Authorization header is not auto-attached — JavaScript on the attacker’s origin can’t read your token (same-origin policy) and can’t set the header on a forged cross-site request. So pure header-token APIs sidestep CSRF; the risk returns the moment you store the token in a cookie, which is why cookie storage (good against XSS) must be paired with SameSite + anti-forgery.

Web Vulnerabilities

Authentication

Why is Basic Auth unsafe over HTTP?

Base64 is encoding, not encryption — it is trivially reversible. Over HTTP, the Authorization header is sent in plaintext and visible to any network observer. Over HTTPS, the header is encrypted by TLS, making Basic Auth safe to use.

Basic Auth

When is Basic Auth acceptable in production?

For machine-to-machine calls between trusted services on an internal network over HTTPS, Basic Auth is acceptable when simplicity matters and the credential is a service account (not a user password). For user-facing authentication, use OAuth 2.0 / JWT Bearer — Basic Auth requires sending credentials on every request, which increases exposure.

Basic Auth

Why can an access token not be used as an ID token?

The access token is issued for a resource server and carries delegated authority. Its format and claims are controlled by that API contract. The ID token is issued for the OIDC client and has explicit authentication claims and validation rules, including audience and nonce.

Oauth OIDC (OpenId Connect)

What do state, nonce, and PKCE each bind?

state binds the callback to the initiating browser session, nonce binds the ID token to the OIDC request, and PKCE binds the authorization code to the client instance that created the verifier. One does not substitute for the others.

Oauth OIDC (OpenId Connect)

What is the difference between role-based and resource-based authorization?

Role-based authorization checks what type of user you are (e.g., Admin, Editor). Resource-based authorization checks your relationship to a specific resource instance (e.g., are you the owner of this document?). Use role-based for coarse-grained access control; use resource-based when the decision depends on data.

Resource-based Auth

Why inject IAuthorizationService instead of checking ownership in the controller directly?

IAuthorizationService centralizes authorization logic in handlers, making it testable and reusable across controllers. Direct ownership checks in controllers scatter authorization logic, making it easy to miss a check or apply it inconsistently. The handler pattern also supports multiple requirements composing into a single policy.

Resource-based Auth

Why does SSO not mean one shared session?

The IdP and each relying application remain distinct session and security boundaries. The IdP session can make authentication at another application silent, but that application must validate a new federation result and issue its own session.

SSO (Single Sign-On)

Which identity should a relying party store?

Store the pair of configured issuer and stable sub claim. Email and display names can change or be reassigned; they are attributes, not durable federation keys.

SSO (Single Sign-On)

Why can the server not hash a TOTP secret like a password?

Verification requires the server to compute HMAC with the same secret as the authenticator. Store it encrypted with a separately protected key, restrict decrypt access, and rotate it after suspected exposure.

TOTP

Why remember the last accepted time step?

A code remains mathematically valid for its entire accepted window. Recording the step and updating it atomically turns a valid captured code into a single-use value at that verifier.

TOTP

Why does WebAuthn resist a real-time phishing proxy better than TOTP?

TOTP is a transferable number that a proxy can relay before it expires. WebAuthn signs data bound to the browser-observed origin and RP ID, so a credential for the real site will not produce a valid assertion for the phishing origin.

Two-Factor Auth

Is a synced passkey the same trust model as a hardware security key?

No. Both use WebAuthn origin-bound public-key credentials, but a synced passkey relies on a platform account and encrypted synchronization/recovery, while a device-bound security key keeps the private key on one authenticator and needs a separate backup path.

Two-Factor Auth

Cloud

How do you choose a compute abstraction — VMs, containers, or serverless?

  • Serverless functions minimize ops for spiky, event-driven work; billing usually combines request count with execution duration and allocated resources, while cold starts, duration limits, and runtime constraints reduce control
  • Containers fit custom runtimes and long-lived services, but the operating cost depends on the platform: a managed serverless container service can own orchestration and scale-to-zero mechanics, while managed Kubernetes still leaves cluster policy, upgrades, capacity, and workload operations to the platform team
  • VMs are the escape hatch for legacy workloads, specialized OS/kernel needs, or lift-and-shift: most control, most ops
  • Decide by workload shape, runtime limits, scaling model, and which layer the team is prepared to operate; the container image alone does not imply Kubernetes or VM-level operations
Cloud

What is the key operational difference between IaaS and PaaS?

With IaaS you manage the OS, runtime, and middleware — you patch the OS, configure security groups, and handle scaling policies. With PaaS the provider manages all of that; you only deploy application code and data. The tradeoff: IaaS gives full control (custom OS, GPU, specific runtime versions); PaaS eliminates operational overhead at the cost of flexibility. Start with PaaS for new web applications; move to IaaS only when PaaS cannot meet your requirements.

IaaS, PaaS, SaaS, CaaS

Where does CaaS fit between IaaS and PaaS?

CaaS (e.g., AKS, EKS) sits between them: you manage container images and deployments, but the provider manages the Kubernetes control plane and underlying OS. It gives more control than PaaS (you define your own container images, resource limits, and networking) while eliminating the Kubernetes control plane management burden of IaaS. Use CaaS when you need container portability or multi-service orchestration that PaaS cannot provide.

IaaS, PaaS, SaaS, CaaS

DevOps

What makes a CI pipeline 'good' vs 'fast but unreliable'?

A good CI pipeline returns feedback before developers stop attending to it; under ten minutes is a useful target for many change-validation paths, not a universal threshold. It quarantines and repairs flaky tests, does not print sensitive data, promotes the same verified artifact through environments, and produces failures that identify the broken boundary. Track queue time and execution time separately: a fast job behind a long runner queue is still slow feedback.

CI CD tools

What does DevOps actually optimize, beyond tooling?

  • The target is delivery throughput and instability. DORA’s current five metrics are change lead time, deployment frequency, failed deployment recovery time, change fail rate, and deployment rework rate.
  • Automation (CI/CD, infrastructure as code) exists to make releases boring and repeatable, removing the manual steps where humans introduce variance
  • Observability (logs, metrics, traces) exists so failures are visible before users report them and diagnosable without a redeploy
  • Ownership closes the loop: the team that ships a change runs it, so production feedback shapes the next change
DevOps

Why is fast, safe rollback more valuable than trying to avoid failure?

  • You cannot prevent all failures; you can make their blast radius and duration small — a one-click rollback turns an incident into a non-event
  • Progressive delivery (canary, blue-green, feature flags) limits how many users a bad change reaches and lets you revert without a full redeploy
  • Small, frequent deploys are easier to reason about and roll back than large batched ones — big-bang releases are where irreversible failures hide
  • Design the pipeline so the undo path is as tested as the deploy path
DevOps

Why use multi-stage builds for .NET applications?

  • The .NET SDK stage includes compilers and build tools that the running application usually does not need.
  • The runtime stage can start from the ASP.NET runtime image and copy only the published output.
  • This separates build credentials and tooling from the production filesystem and usually reduces transfer and scan surface; measure the actual result for the chosen tags and architecture.
  • Smaller images mean faster pulls, less attack surface, and lower registry storage costs.
  • Cost: slightly more complex Dockerfile; worth it for any production workload.
Docker

How do you prevent secrets from leaking into Docker images?

  • Never use ENV or ARG for secrets in Dockerfiles — they are visible in docker history.
  • Keep non-sensitive settings in environment variables; mount credentials at runtime from Docker secrets or a platform secret provider.
  • For build-time secrets (e.g., NuGet feeds), use RUN --mount=type=secret (BuildKit).
  • In Kubernetes, mount Secrets or external-secret-provider volumes as files and restrict access with RBAC.
  • Tradeoff: runtime injection adds operational complexity but is the only safe approach.
Docker

What is the difference between declarative and imperative IaC?

Declarative IaC describes the desired end state and computes a diff. Reapplying should be a no-op only when inputs and observed external state are unchanged and the provider implements the operation safely. Imperative IaC specifies commands; it can still be idempotent when the program checks and converges state explicitly. The practical advantage of declarative tooling is an inspectable model and plan, not an unconditional safety guarantee.

Infrastructure as Code

Why is the Terraform state file important, and how should it be managed?

The state file maps Terraform resource addresses to remote objects and stored attributes. If it is lost or corrupted, planning and ownership become unsafe. Use a remote backend with access control, versioning, and supported locking. For the S3 backend, enable use_lockfile; DynamoDB locking is deprecated. Never commit state to Git because it can contain secrets and full resource details.

Infrastructure as Code

What is configuration drift and how does IaC address it?

Drift is when observed infrastructure differs from the declared definition. Refresh and plan can surface drift for attributes the provider reads and manages; apply can reconcile those fields. Ignored attributes, external systems, provider defaults, and emergency changes still require explicit handling, so IaC bounds drift rather than proving none exists.

Infrastructure as Code

What is the difference between a Pod, Deployment, and Service?

  • Pod: the smallest unit; wraps one or more containers sharing a network namespace.
  • Deployment: manages a set of identical pods; handles rolling updates and scaling.
  • Service: stable network endpoint (DNS name + IP) that load-balances across pods.
  • Pods are ephemeral (new IP on restart); Services provide stable addressing.
  • A Deployment supports controlled rolling replacement, but does not guarantee zero downtime. Readiness, graceful shutdown, compatible versions, disruption budgets, and enough spare capacity must all hold.
Kubernetes

Why do Kubernetes Secrets need additional protection beyond base64 encoding?

  • Base64 is encoding, not encryption — anyone with etcd access can decode secrets trivially.
  • By default, etcd stores secrets in plaintext (base64 is just a transport format).
  • Mitigations: enable etcd encryption at rest, use Sealed Secrets (encrypted in git), or use Azure Key Vault with the Secrets Store CSI driver.
  • Tradeoff: external secret management adds operational complexity but is required for regulated workloads.
Kubernetes

What causes CrashLoopBackOff and how do you debug it?

  • CrashLoopBackOff means the container starts, crashes, and Kubernetes keeps restarting it with exponential backoff.
  • Common causes: app crashes on startup (missing config, DB connection failure), liveness probe failing before app is ready, OOMKilled (memory limit too low).
  • Debug: kubectl logs <pod> --previous (logs from the crashed instance), kubectl describe pod <pod> (events and exit codes), kubectl exec -it <pod> -- /bin/sh (if the container starts briefly).
  • Tradeoff: liveness probes are essential for self-healing but misconfigured probes cause the problem they are meant to solve.
Kubernetes

How do you diagnose an intermittent p95/p99 latency bottleneck in a multi-service system?

Start from traces and filter to the slow requests — the p99, not the average — to see which span dominates. Correlate with per-service RED metrics to tell whether the latency is isolated or systemic, then walk the dependency spans (DB, cache, external API) to find where it originates. Pull structured logs by the same trace ID to check for edge-case payloads or retries on those requests. Traces locate the delay, metrics size its effect, and logs expose the state around it.

Observability

When should you use RED vs USE?

RED — Rate, Errors, Duration — is for customer-facing endpoints and request pipelines: it tells you what users are experiencing. USE — Utilization, Saturation, Errors — is for the resources underneath: CPU, thread pools, queue depth, DB connection pools. You want both, because they pair causally: RED is the symptom at the service boundary, USE is the pressure source beneath it. A rising p99 traced to a saturated connection pool is the canonical chain.

Observability

Why instrument with OpenTelemetry from day one instead of adding observability later?

Instrumenting early establishes telemetry contracts and historical baselines before the system becomes difficult to change. Retrofitting during an incident requires invasive changes across several services and still cannot recover signals that were never emitted. OpenTelemetry also reduces the application changes needed to switch backends, although exporters, collector routing, queries, dashboards, and sampling policies may still change.

Observability

Deployment Strategies

When would you choose canary over blue-green deployment?

  • Blue-green gives instant rollback by switching traffic back to the old environment, but requires running two full environments simultaneously.
  • Canary gradually shifts traffic to the new version, catching issues in production with real users before full rollout.
  • Choose canary when you need production validation with real traffic patterns that staging cannot replicate.
  • Choose blue-green when you need atomic cutover and can afford double infrastructure cost during the switch.
  • Canary requires traffic splitting infrastructure (service mesh, load balancer rules, or a progressive delivery controller like Argo Rollouts).
  • Canary shrinks blast radius but stretches the deployment window and leans on monitoring automation to catch regressions; blue-green is simpler but doubles infrastructure cost during the switch.
Deployment Strategies

What failure mode makes rolling deployments unsafe for database schema changes?

  • Rolling deployments run old and new code side by side during the rollout window.
  • A destructive schema change (dropping a column, renaming a table, changing a constraint) breaks the old instances still serving traffic.
  • Safe approach: expand-and-contract migrations — add the new column first, deploy code that writes to both, then remove the old column in a later release.
  • If schema changes are not backward-compatible, rolling deployment causes partial failures, data corruption, or 500 errors from old instances.
  • Expand-and-contract spreads one change across several deploys and adds migration bookkeeping, but it is the only safe path to zero-downtime schema evolution under rolling deploys.
Deployment Strategies

How do you decide between A/B testing and canary when both are available?

  • Canary validates operational health — does the new version crash, timeout, or degrade throughput.
  • A/B testing validates user behavior — does the new version improve conversion, engagement, or other business metrics.
  • Use canary first to confirm the release is safe, then A/B test to measure business impact.
  • A/B testing requires statistically significant traffic volumes and longer observation windows than canary health checks.
  • Canary can auto-rollback on error rate spikes in minutes; A/B tests typically run days or weeks.
  • Running both gives the most confidence but demands traffic management, metric pipelines, and longer release cycles — justified for high-impact user-facing changes, overkill for an internal service.
Deployment Strategies

Version Control Systems

Why does GitFlow create integration problems for teams practicing continuous deployment?

GitFlow’s long-lived develop branch accumulates divergence from main over days or weeks. Feature branches branch off develop, so they also diverge. When multiple features merge back, conflicts compound. The release/* stabilization phase adds a manual gate that prevents continuous deployment. For web services that deploy multiple times per day, GitFlow’s overhead is pure cost with no benefit. Use trunk-based development or GitHub Flow instead.

Branching Stratagies

What does trunk-based development require that makes it unsuitable for all teams?

Trunk-based development needs small integrable changes, a protected and quickly repaired trunk, and feedback fast enough that developers do not stack work on an unknown result. Feature flags are one way to separate deployment from exposure, but branch-by-abstraction and dark code paths can serve the same purpose. A ten-minute CI target is a useful heuristic, not a definition. Teams with slow review or validation can use GitHub Flow while measuring and reducing that latency.

Branching Stratagies

SDLC

When does BDD overhead outweigh its benefits?

BDD adds overhead: Gherkin scenarios must be written and maintained, step definitions must be kept in sync, and the collaboration process takes time. The overhead is not worth it for: pure technical components with no business language (algorithms, infrastructure), small teams where developers and stakeholders communicate directly without formal scenarios, and rapidly changing requirements where maintaining .feature files becomes a burden. BDD earns its cost when: business rules are complex and misunderstanding them is expensive, cross-functional alignment is genuinely needed, and scenarios serve as living documentation.

BDD

How does BDD differ from TDD, and are they complementary?

TDD focuses on unit behavior: does the code work correctly? Tests are written by developers in code. BDD focuses on system behavior: does the system meet requirements from the user’s perspective? Scenarios are written in business language (Gherkin) collaboratively with stakeholders. They are complementary: use TDD for unit-level design and fast feedback loops; use BDD for acceptance criteria and cross-functional alignment. A well-tested system uses both — TDD for the inner loop, BDD for the outer loop.

BDD

What is 'specification by example' and how does it reduce requirement ambiguity?

Specification by example replaces abstract requirement statements (‘the system should handle invalid input’) with concrete examples (‘given a blank email field, when I submit the form, then I see the error message X’). Concrete examples are unambiguous: they can be automated as tests and verified against the running system. Abstract requirements are interpreted differently by developers, testers, and business stakeholders. The Three Amigos practice (developer + tester + business) produces examples collaboratively, surfacing misunderstandings before implementation begins.

BDD

Why are story points measured in relative complexity rather than hours?

Hours are absolute and vary by person, day, and context. A senior engineer estimates 2 hours; a junior estimates 8 hours for the same task. Story points measure relative complexity: a 5-point story is roughly twice as complex as a 2-point story, regardless of who does it. A team’s velocity (points per sprint) emerges from historical data and automatically accounts for team-specific factors like skill level, interruptions, and tech debt. Treating story points as hours destroys this abstraction and makes velocity meaningless.

Estimation Techniques

What is the most common estimation mistake and how do you avoid it?

Treating story points as hours (‘8 points = 8 hours’). This forces estimators to think in absolute time, reintroduces the person-dependency problem, and makes velocity comparisons between teams meaningless. The fix: calibrate story points against reference stories (‘this is a 2-point story because it’s similar to the login feature we built last sprint’), track velocity over 3-5 sprints to establish a baseline, and use that baseline for capacity planning — never convert points to hours.

Estimation Techniques

Graph View

Created with Quartz v5.0.0 © 2026

  • Credits
  • Email
  • LinkedIn
  • GitHub