A topic-grouped index of interview and review questions across .NET, computer science, architecture, AI, data, networks, security, and engineering practice. Each answer links back to the note that establishes the underlying mechanism and tradeoffs.
A topic-grouped index of interview and review questions across .NET, computer science, architecture, AI, data, networks, security, and engineering practice. Each answer links back to the note that establishes the underlying mechanism and tradeoffs.
Total questions: 300
When is targeting .NET Standard still justified?
The decision starts with the supported consumers.
netstandard2.0is still justified when a library must work with .NET Framework or another runtime that cannot consume a modernnetX.0target. Multi-targeting can keep that compatibility asset while giving current .NET applications a modern build. If every supported consumer runs current .NET, targeting only the modern TFM keeps the package simpler and exposes the newer platform APIs.
What are the main layers of .NET, and why does it help to separate them?
The main layers are the language and compiler, the runtime and base libraries, and the application frameworks and SDK tooling. Separating them makes diagnosis more precise. A C# allocation decision can create pressure that appears in the GC, while an ASP.NET Core middleware decision can tie up thread-pool workers. Identifying the layer that owns the behavior narrows where to measure and fix it.
What does
ValidateIssuerSigningKey = truecontrol during JWT validation?For a signed token, the handler uses a resolved key to verify the signature. This flag tells the default validation path to also validate that signing key itself. It does not enable issuer, audience, lifetime, or algorithm validation, and a custom
IssuerSigningKeyValidatorruns regardless of the flag. The rest of the JWT validation settings still have to form one coherent trust configuration.
What is a captive dependency and why is it dangerous?
A captive dependency appears when a long-lived service holds a dependency that was meant to live for less time. For example, a singleton that captures a scoped
DbContextkeeps it beyond one request and may share that non-thread-safe object across concurrent operations. A transient captured by a singleton also lives as long as the singleton, which is safe only if it can handle that lifetime and concurrency.
How can a scoped service be used safely from a singleton?
The singleton should not keep the scoped service. Inject
IServiceScopeFactory, create a new scope for each operation, resolve and use the service inside that scope, then dispose the scope. A background worker normally repeats this for every iteration instead of keeping one scope for the worker’s lifetime.
What is the difference between
GetService<T>andGetRequiredService<T>?
GetService<T>returnsnullwhen no registration exists.GetRequiredService<T>throwsInvalidOperationExceptionat the point of resolution. Startup validation can move some failures earlier, but callingGetRequiredServicealone does not guarantee startup-time failure.
What is the execution order of ASP.NET Core filter types?
Authorization filters run first. Resource filters then wrap the rest of the MVC pipeline, action filters wrap the action, and result filters wrap execution of the selected result. The normal before path is therefore authorization, resource, action, then result, with the wrapping filters running their after logic in reverse.
Exception filters are conditional, not another before-and-after stage. They run only when an unhandled exception comes from controller creation, model binding, an action filter, or the action method. They do not catch failures from authorization filters, resource filters, result filters, or result execution. If an exception filter handles the failure and supplies a result, result processing continues; otherwise the exception leaves the MVC pipeline.
Within one filter type, lower
IOrderedFilter.Ordervalues run earlier on the way in and later on the way out. When order values are equal, scope normally nests global, controller, then action.
How does middleware differ from an MVC action filter?
Middleware runs around the broader HTTP pipeline, so it can handle requests before an endpoint is selected or even when no controller is involved. An action filter runs inside MVC around a controller action, where it can work with bound arguments, model state, and action results. Middleware fits application-wide HTTP concerns; an action filter fits behavior that specifically needs MVC context.
How are unhandled exceptions handled consistently across an ASP.NET Core application?
Exception-handling middleware is placed near the start of the pipeline so it wraps the components registered after it. When downstream code throws,
UseExceptionHandlercan log the failure and produce one safe response format, usually Problem Details. The developer exception page is useful during development, but it should not expose stack traces in production.
How does a request move through the ASP.NET Core middleware pipeline?
The request enters each registered middleware in order. A middleware can handle it immediately or call the next component; routing selects an endpoint, authorization may stop the request, and the endpoint eventually creates the response. Control then returns through the earlier middleware in reverse order, which allows work such as response headers, logging, or cleanup after the endpoint runs.
What is the difference between asynchrony and multithreading?
Asynchrony is about not blocking while work is waiting. During asynchronous I/O, the method can pause without keeping a worker thread idle, then continue later, possibly on the same thread. Multithreading is about executing work on multiple threads at the same time, usually for CPU-bound work. An application can be asynchronous while using only one thread, but CPU-bound work still needs a thread to execute it.
What is the difference between
awaitand usingTask.Result?Both return the task’s result, but they wait differently. If the task is not complete,
awaitpauses the method without blocking the current thread. The method continues when the task finishes, using the capturedSynchronizationContextwhen one exists.Task.Resultblocks the current thread until the task completes. This can cause a deadlock when the continuation needs to return to that same thread. It also wraps errors inAggregateException, whileawaitthrows the original exception.
When is
ConfigureAwait(false)appropriate?It is mainly useful in reusable library code when the continuation does not need the caller’s scheduling context. The continuation no longer has to return through a captured
SynchronizationContextor non-defaultTaskScheduler. UI code usually keeps context capture when it needs to update controls. ASP.NET Core normally has no customSynchronizationContext, so usingConfigureAwait(false)there usually changes little, andHttpContextdoes not depend on that context.
Why can asynchronous I/O improve server scalability without using extra threads?
While I/O is pending, the asynchronous operation pauses and returns its worker thread to the pool. That thread can process another request instead of sitting idle. A server with 100 threads can therefore keep thousands of I/O-bound requests in progress, as long as those threads are released during each wait. This improves concurrency for I/O-bound work; it does not make CPU-bound work require fewer threads.
When is
Task.Runuseful in async code?
Task.Runis useful for CPU-bound work that should run on a thread-pool thread, for example to keep a UI thread responsive. It should not wrap an I/O API that is already asynchronous. That adds another scheduling step without making the I/O finish sooner.
What does a bounded
Channel<T>provide thatSemaphoreSlimdoes not?A bounded channel stores queued work and lets producers wait asynchronously when the buffer is full. Consumers receive items in accepted FIFO order.
SemaphoreSlimonly limits how many callers may enter at once; it does not store work and provides no fairness guarantee. A channel therefore fits producer-consumer handoff, while a semaphore fits throttling access to an operation.
Why is
Channel.CreateUnbounded<T>()a risky default?An unbounded channel never slows a producer because of capacity. If producers stay faster than consumers, queued items keep accumulating and memory use can grow until the process is under pressure. A bounded channel forces an overload policy: either producers wait for space or the channel drops items according to an explicit rule.
When is
BoundedChannelFullMode.DropOldesta reasonable policy?It is reasonable when the newest value replaces older state, such as a progress update or sampled metric. When the buffer is full, the oldest queued value is discarded so a newer one can be accepted, and that loss should be observable. It is not suitable when every item represents separate work or a business obligation that must be processed.
What is the difference between concurrency and parallelism in practice?
Concurrency means several operations are in progress during the same period, even if one thread takes turns running them. Parallelism means operations execute at the same time on multiple cores. Asynchronous I/O uses concurrency so a thread is not blocked while an external operation is pending. CPU-bound work uses parallelism when splitting the calculation across cores reduces its elapsed time.
What should be checked before choosing
Task,lock,Parallel, orChannel?First check what the work spends time doing and who is responsible for finishing it. I/O-bound work usually needs asynchronous APIs so threads are not blocked. CPU-bound work may benefit from measured parallelism. Shared mutable state needs synchronization or a single owner, while background work needs a queue with a clear lifetime and failure policy. The primitive follows from those requirements.
What are the four Coffman conditions and which is easiest to break in practice?
The four conditions are mutual exclusion, hold-and-wait, no preemption, and circular wait. In practice, circular wait is usually the easiest to remove by defining one lock order and following it everywhere. If every code path takes the locks in the same order, the cycle cannot form.
Why can calling
.Resulton aTaskdeadlock in a UI app but usually not in a console app?A deadlock can happen when the task is still incomplete and its continuation needs the UI thread.
.Resultblocks that thread, while the continuation waits to get back onto it, so neither can finish. Console applications and ASP.NET Core normally run continuations on thread-pool threads, so this specific cycle is usually absent. Blocking is still harmful because it ties up a thread and can cause thread-pool starvation under load.
What steps help diagnose a deadlock in a production .NET service?
Start with a process dump. Inspect thread stacks for waits in
Monitor.Enter,.Result, or.Wait(), then check which thread owns each monitor;syncblkshows that ownership. For an async deadlock, find the continuation that cannot run and the context or scheduler it is waiting for. Together, those waits reveal the cycle.
What does
lock (obj) { ... }compile to?
Monitor.Enter(obj, ref lockTaken)surrounds the body with atry/finally, and thefinallycallsMonitor.Exitonly whenlockTakenis true. This preserves release on every normal or exceptional exit without releasing an unowned monitor.
Why can't a
lockblock containawait?
Monitorownership belongs to the acquiring thread. Since an async continuation may resume elsewhere, the compiler rejectsawaitin alockblock with CS1996.SemaphoreSlim.WaitAsyncmodels asynchronous waiting without monitor ownership.
What does
System.Threading.Lockimprove over locking on a plainobjectin .NET 9+?A dedicated
Lockfield makes its purpose clear and avoids reusing an arbitrary object that other code may also lock or expose. The compiler recognizes the type and lowers the block to its scopedEnterScopeAPI instead of the usualMonitor.EnterandMonitor.Exitpattern. It is still a synchronous lock, so it does not makeawaitvalid inside the block.
How does reentrancy differ between
lock/MonitorandSemaphoreSlim?
lockandMonitortrack the owning thread and a recursion count, so the same thread can enter the same lock again and must exit it the same number of times.SemaphoreSlimtracks permits, not an owner or recursion count. If code holding the only permit waits on that semaphore again, the second wait blocks and can deadlock the operation.
When is a named
Mutexappropriate in .NET?A named
Mutexfits mutual exclusion between processes on the same machine, for example a single-instance application or several processes writing the same local file. The thread that acquires it owns it and must release it. For coordination inside one process,lockis usually lighter; for distributed coordination across machines, a named mutex is not enough.
What does
AbandonedMutexExceptionsignal?It means the previous owning thread ended without releasing the mutex. The waiting thread receives the exception but also acquires the mutex, so it must treat the protected state as possibly incomplete or corrupted. Recovery may require validating or rebuilding that state before the mutex is released in a
finallyblock.
Why can adding more parallel workers reduce performance?
More workers help only while useful work can run independently and the machine still has capacity. After that point, workers compete for CPU time, memory bandwidth, cache lines, and shared locks, while scheduling and coordination add more overhead. Throughput can then fall even though more tasks are running, so the useful degree of parallelism has to be measured for the actual workload.
How should
MaxDegreeOfParallelismbe chosen?The starting point comes from the bottleneck. For CPU-bound work,
Environment.ProcessorCountis a reasonable first value, but memory-heavy work may reach its limit earlier. Work that calls a database or remote service should also respect that dependency’s safe concurrency. The final value comes from measuring throughput, latency, and resource pressure under a realistic load.
When is PLINQ a poor fit?
PLINQ works best when each item can be processed independently and does enough CPU work to repay the parallel overhead. It is a poor fit when correctness depends on side-effect order, strict source ordering is required, or each item is so cheap that partitioning and merging cost more than the work. A sequential query is also easier to reason about, so parallel execution should be kept only when measurement shows a useful gain.
Why can a parallel query be slower than a sequential query for small inputs?
Parallel execution has fixed costs: the input is partitioned, work is scheduled, and partial results are merged. With only a few items or very little work per item, the sequential query can finish before those costs are recovered. Parallelism becomes useful only when the amount of independent work is large enough to outweigh that setup and coordination.
When is
SemaphoreSlima better fit thanlock?
SemaphoreSlimfits asynchronous work or a resource that may allow more than one operation at a time.WaitAsynclets a caller wait for a permit without blocking a thread, and the permit can be released after anawait. Alockis better for a short synchronous critical section that allows only one thread at a time. Because a semaphore has no owner, its acquire and release still need one cleartry/finallyboundary.
Why is
Tasknot equivalent to a thread?A
Taskrepresents an operation that may finish later and records whether it completed, failed, or was canceled. A thread is a resource that executes code. A task for asynchronous I/O can remain incomplete while no managed thread is assigned to it; a thread is needed only when code starts the operation or resumes after completion.
When is
Task.Runappropriate in ASP.NET Core?
Task.Runcan move CPU-bound work to a thread-pool thread, but it does not add CPU capacity. Under load, those extra work items compete with request processing and can reduce throughput. It should not wrap I/O APIs that are already asynchronous. Long or expensive work usually belongs behind a bounded background queue or in a separate service instead of running inside the request.
Why can
Task.WhenAllreduce the elapsed time for independent async calls?If the operations are started before they are awaited, their waiting time can overlap.
Task.WhenAllthen completes when all of those tasks finish, so the elapsed time is closer to the slowest call than to the sum of every call. It only coordinates tasks that have already started; it does not make synchronous work parallel, and the fan-out must still respect connection and downstream limits.
When is
ValueTaskworth using instead ofTask?
ValueTaskis useful in a measured hot path where operations often complete synchronously and allocating a completedTaskis a real cost. Its consumption rules are stricter: depending on what backs it, the value may be safe to await only once and should not be cached or shared directly.Taskremains the simpler choice when the result needs to be stored, shared, or awaited more than once.
What is ThreadPool starvation and how does it usually start?
ThreadPool starvation means queued callbacks and continuations wait too long because too few worker threads are available, even though the CPU is not fully busy doing useful work. A common cause is pool workers blocking on
.Result,.Wait(), synchronous I/O, or similar waits. The work needed to complete those waits may also depend on the pool, so latency rises while the runtime adds workers. Raising the minimum can reduce that ramp-up delay, but it does not remove the blocking and may increase contention.
When is it appropriate to call
ThreadPool.SetMinThreads?
ThreadPool.SetMinThreadsis worth testing when measurements show that work is waiting for the pool to add workers during a burst. It is not a fix for CPU saturation, a slow downstream service, or workers blocked by sync-over-async code.Change it under a representative load test and watch queue length, worker count, CPU, memory, and tail latency. The value is a threshold below which the pool can add workers without its normal delay; it does not create that many threads in advance.
What is the difference between
throw;andthrow ex;inside acatchblock?
throw;rethrows the current exception without changing its original stack trace.throw ex;throws the same exception object from the current line and resets the stack trace, which hides the calls that led to the original failure. Usethrow;when the same exception is rethrown from itscatchblock.
When should an exception be wrapped instead of rethrown directly?
Wrap an exception when code crosses a boundary and the caller needs context that the original failure does not provide. For example, a repository can translate a database exception into an order-saving exception that the application layer understands. The original exception should remain in
InnerExceptionso its stack trace and details are still available. If the wrapper adds no useful meaning,throw;is clearer.
Why is throwing from
finallyconsidered dangerous?If the
tryblock throws one exception andfinallythrows another, the exception fromfinallycan replace the original failure. The caller then sees the cleanup error while the real cause is hidden. Afinallyblock should normally perform cleanup that is safe to run during stack unwinding, while failure handling stays incatchor at a higher boundary.
When might
finallynot execute?
finallynormally runs when control leaves the protected block, including during exception-driven stack unwinding. It may not run when the process stops without normal unwinding, such as an operating-system kill,Environment.FailFast(), a severe runtime failure, or aStackOverflowException. It is an in-process cleanup guarantee, not a crash-recovery mechanism.
What must a type provide to work with
foreach?A type does not have to implement
IEnumerable. The compiler can use the enumerable pattern: a public parameterless instanceGetEnumeratormethod, or a supported extensionGetEnumeratorwhen instance lookup does not provide one. That method must return an enumerator with a publicCurrentproperty and a public parameterlessbool MoveNext()method. ImplementingIEnumerable<T>is the usual reusable contract. Arrays receive dedicated compiler handling, whileSpan<T>works through its enumerator pattern.
How does
foreachwork under the hood?The compiler rewrites the loop according to the source type. In the general case it obtains an enumerator, calls
MoveNext, readsCurrent, and disposes the enumerator in afinallyblock when disposal is required. Arrays use a simpler indexed loop, so the exact generated shape is not identical for every collection.
How does
yield returnimplement an iterator?The compiler turns the method into a state machine that stores its current position and local state. Calling the method creates the iterator, but the body normally starts only when enumeration begins. Each
yield returnproduces one value and pauses execution; the nextMoveNextcall resumes from that point. This also means work and exceptions occur during enumeration rather than when the iterator is created.
When is
yield returnbetter than returning a materialized collection such asList<T>?
yield returnis useful when values can be produced one at a time, the sequence is large, or the caller may stop early. It avoids building the whole collection first, but the generation logic runs again on each enumeration and can observe changing state. A materialized collection is better when a stable snapshot, random access, a cheapCount, or repeated enumeration is required.
Why does
IEnumerable<string>assign toIEnumerable<object>, butList<string>does not assign toList<object>?
IEnumerable<out T>only produces values, so treating a sequence of strings as a sequence of objects is safe.List<T>also accepts values. If aList<string>could masquerade asList<object>, a caller could insert a non-string and break the original list’s contract.
When should a generic interface type parameter be marked as
outorin?Mark a type parameter as
outwhen the interface only produces values of that type. This is why a producer of strings can safely be used where a producer of objects is expected. Mark it asinwhen the interface only consumes the type, so a consumer that accepts any object can also accept strings.If the interface both accepts and returns the type, it must remain invariant because either conversion could make a read or write unsafe. Variance applies only to reference-type substitutions.
A generic method uses
default(T)as a fallback value. Why can this be dangerous in production code?
default(T)may be0,DateTime.MinValue, a zeroed struct, ornull. Those values can be valid domain data, so a failed lookup becomes indistinguishable from a real result. ATry*contract, nullable result, or explicit result type keeps the distinction visible.
Why would a method need
refwhen the argument is already a reference type?A reference-type argument still passes the reference itself by value. The method can use that copied reference to mutate the same object, but assigning a different object changes only the method’s local copy. With
ref, the method receives an alias to the caller’s variable and can replace the reference stored there.
What does an
inparameter do, and when is it useful?An
inparameter passes an argument by readonly reference. The method can read the value but cannot assign through that parameter. This can avoid copying a large struct on a measured hot path, although conversions and non-variable arguments may still create a temporary copy. It rarely helps for small values.
How do optional parameters work in C#?
An optional parameter has a default value, so the caller may leave that argument out. The compiler inserts the default into the calling code at compile time. If a library later changes the default, already compiled callers keep using the old value until they are recompiled.
With
Animal a = new Dog();, how cana.Category()call the derived implementation, and what does that mean for the base API?Mark
Animal.Category()asvirtualand implement it withoverrideinDog. The runtime then chooses the method from the actual object type, even though the variable is typed asAnimal. Declaring the base method virtual also makes derived replacement part of the API’s intended extension model.
When is hiding a method with
newappropriate instead of overriding it?Use
newonly when the base member cannot or should not participate in runtime polymorphism and the result is intentionally allowed to depend on the variable’s compile-time type. This sometimes preserves compatibility with an existing API. If derived behavior should still appear through a base reference, the member needsvirtualandoverrideinstead.
What can be done when a base method is not virtual but derived behavior is needed?
If the base type is under control and derived substitution is intended, make the method
virtualand override it. If the base API cannot change,newcan hide the member, but calls through the base type will still use the base implementation. Composition is usually clearer when the varying behavior does not naturally belong to the inheritance hierarchy.
When is
extern aliasnecessary instead of an ordinaryusingalias?Use an ordinary alias when two types have different fully qualified names.
extern aliasis for conflicting types whose assembly-qualified identities differ but whose fully qualified type names are identical.
Why is reflection often a bad default in performance-critical code?
Reflection moves member discovery, argument validation, and dispatch to runtime. That cost is often irrelevant during startup, but repeated lookup and invocation can dominate a small hot-path operation. Measure first, then cache metadata or bind a delegate when the same member is reused.
How do attributes become behavior at runtime?
An attribute only stores metadata on a type, member, or other program element. A framework uses reflection to find that metadata and decide what code to run, such as registering a test or mapping an HTTP route. The attribute itself does nothing until runtime code interprets it. A source generator can perform a similar interpretation during the build and emit direct code instead.
How should code choose between reflection, interfaces or generics, and source generators?
Reflection fits cases where the types or members are genuinely unknown until runtime, such as loading plugins or inspecting external models. Interfaces and generics are simpler when the variation can be expressed as a compile-time contract. A source generator fits repeated metadata-driven work whose shapes are known during the build, especially when startup cost, trimming, or Native AOT makes runtime discovery a problem.
What is the difference between an abstract class and an interface with default members, and when is an abstract class a better fit?
Both can contain implemented members. An abstract class can also store instance state, define constructors, and expose protected members, but a class can inherit from only one base class. An interface has no instance fields, and a class can implement several interfaces. An abstract class fits when derived types must share state or initialization rules. An interface fits when callers only need a common capability and the implementations do not belong in one inheritance hierarchy.
Why can't a static class implement an interface?
An interface normally describes behavior on an object. A static class has no instances, so no object can be assigned to an interface variable. When an implementation needs to be injected or replaced, use a regular class and choose its lifetime through dependency injection. A generic algorithm that needs operations on the type itself can use static abstract interface members with a constrained type parameter.
A
sealed overridestops further overriding, but can a derived class usenewto hide the sealed method? What happens at runtime?Yes,
newcompiles 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 dispatchThe hidden method is selected only when the compile-time receiver exposes
Bottom.Do. Calls throughMiddleorBasestay on the sealed virtual slot and invokeMiddle.Do. Hiding therefore does not replace polymorphic behavior. It creates a second member with type-dependent call semantics.
Can a C# type be both abstract and sealed, and how are static classes represented in metadata?
C# rejects that modifier pair on an ordinary class. A C# static class is represented in metadata with both flags, which prevents construction and inheritance. Reflection therefore reports
typeof(Math).IsAbstract && typeof(Math).IsSealedastrue.
Why can
partialbe dangerous with source generators? Give a concrete scenario.The generated part is the same type, so it can add interface implementations or members that are absent from the handwritten file. A generator and handwritten code that both declare
Validate()cause a compile-time collision. An optional partial-method hook may disappear entirely when no implementing declaration is generated. Generated output should be inspectable, and tests should cover the behavior that depends on it.
Why do two separately created class instances with equal fields compare unequal with
==, and how should value equality be added?A class uses reference equality for
==unless it overloads the operator, so two separately created instances are different references. A value-like conventional class should implementIEquatable<T>, overrideEqualsandGetHashCode, and overload==/!=if operator equality belongs to its API. Arecord classis the shorter choice when generated value equality matches the domain.
How is a delegate represented at runtime?
A delegate declaration creates a sealed type derived from
System.MulticastDelegate. ItsInvokemethod describes the signature that compatible methods must match. A delegate instance stores a method pointer and, for an instance method, the target object; a multicast delegate also stores an invocation list. The type still hasBeginInvokeandEndInvokemetadata, but calling those methods on modern .NET (6+) throwsPlatformNotSupportedException.
What is the difference between an event and a public delegate field?
Outside code can only subscribe to or unsubscribe from an event. The type that declares the event keeps control over when it is raised. A public delegate field also lets outside code invoke the delegate, replace its handlers, or set it to
null, which can break the publisher’s notification logic.
Why can event subscriptions cause memory leaks, and how can they be prevented?
The publisher stores each handler, and the handler normally holds a strong reference to its subscriber. If the publisher lives longer, that reference keeps the subscriber alive even when the rest of the application no longer uses it. The subscription should be removed when the subscriber’s lifetime ends, commonly through
Dispose. Weak-event patterns or scoped subscription helpers are alternatives when explicit ownership is difficult.
In
record Wrapper(List<int> Items), ifvar b = a with { };and an item is added tob.Items, doesaobserve the change, and why?Yes. The copy is shallow, so both properties hold the same
List<int>reference. Record equality also delegates to the list’s equality, which remains reference-based before and after the mutation. A model that needs structural collection equality must choose a suitable immutable value or implement that equality explicitly.
When is
record classa better choice thanreadonly record struct?A
record classis a reference type, so assignment copies a reference instead of the whole value. It is a better fit when inheritance or naturalnullsemantics are needed, or when copying a large value would be expensive.A
readonly record structfits a small logical value when whole-value copies are cheap and inheritance is unnecessary. Boxing and frequent copies can remove its allocation advantage, so the real call path still needs measurement. Reference members do not decide the choice by themselves; either form can contain references to separately allocated objects.
If
Equalson a positional record is overridden to ignore one property, doesGetHashCodestill include that property, and what breaks?Yes, unless
GetHashCodeis overridden as well. Two records can then be equal according toEqualsbut produce different hash codes because the ignored property still affects the synthesized hash. Hash-based collections may fail to find an equal key or place equal values in different buckets. C# reports CS8851 for this mismatch, so both methods should use the same equality components.
Can a record struct be used safely as a
Dictionarykey, and what can make it unsafe?Yes. A dictionary copies a struct key when it is inserted, so changing the caller’s local variable later does not change the stored key. The stored key is safe only while every value observed by its equality and hash code remains stable.
A reference field is the main risk because the stored copy and the caller’s copy still point to the same object. If custom equality or a comparer hashes that object’s mutable contents, changing the object changes the hash seen for the stored key and can make the entry unreachable in its original bucket. A comparer whose results change over time causes the same problem. A
readonly record structprevents direct field reassignment, but it does not freeze referenced objects. Stable value-semantic members and a stable comparer make the type safe to use as a key.
When is
StringBuildera better choice than string concatenation?
StringBuilderis useful when text is built through many appends, especially in a loop, because it avoids allocating a new string for every intermediate result. For one expression with a small number of values, interpolation or concatenation is usually clearer and the compiler can optimize it well. In a hot path, the choice should be measured; whenStringBuilderwins and the final size is roughly known, setting its capacity reduces buffer growth and copying.
Why can
ReferenceEquals(a, b)befalsewhena == bistruefor strings?String equality with
==compares the characters, whileReferenceEqualschecks whether both variables point to the same object. Two separately created strings can contain the same text and therefore be equal without sharing an object. Business comparisons should use string equality with the requiredStringComparison; reference equality is mainly useful when object identity or allocation behavior is being inspected.
Why might changing a struct inside
foreachnot update the collection, and how can it be fixed?A
foreachvariable normally contains a copy of the struct, so changing that copy does not change the element stored in the collection. Properties and ordinary indexers can return the same kind of copy. A safe fix is to create the changed value and assign the whole element back. A ref-returning API is appropriate only when the collection deliberately supports in-place mutation.
Where does boxing commonly happen, and how can it be reduced?
Boxing commonly happens when a value type is converted to
objector an interface, stored in a non-generic collection, or passed throughparams object[]. Each box creates a heap object and copies the value into it. Generic APIs such asList<T>and constrained generic calls can keep the value in its concrete type. Profiling should confirm that the boxing occurs often enough to matter before the API is made more complex.
When should a type be a
struct,class, orrecord class?Start with assignment and equality semantics. A
structfits a small logical value that should be copied as a whole. A conventional class fits an entity whose identity stays the same while its state changes. Arecord classfits reference-typed data whose contents define equality. After that, size, mutation, boxing, and measured allocation cost can rule out a choice that looked correct from the data model alone.
How do managed and unmanaged code differ, and why does interop require careful lifetime management?
Managed code runs under the CLR, which uses metadata to provide services such as garbage collection, type checks, and exception handling. Unmanaged code runs as native code and follows the platform’s ABI. A P/Invoke or other interop signature must match that ABI, including the calling convention, data layout, and encoding. A mismatch can corrupt the arguments, returned data, or call stack.
Resource lifetime is a separate contract defined by the native API: which side allocates the resource, which side owns it, and which function releases it. The GC can track a managed wrapper, but it does not know that native ownership contract. An owned handle should normally be placed in
SafeHandleand released through deterministic disposal. The wrapper then keeps the handle valid during native calls and releases it once with the correct native operation.
How does the CLR run IL, and when does JIT or Native AOT make more sense?
Most .NET builds store IL together with type metadata in an assembly. The CLR loads that assembly, and a JIT deployment compiles each method when it is first used. Tiered compilation can later replace frequently executed methods with more optimized code. This adds first-use work, but it allows runtime optimization and supports dynamic-code features.
Native AOT compiles the application at publish time, so the deployed process has no JIT compilation step. It can be a better fit when measured startup time or footprint matters, but dependencies must survive trimming and AOT analysis, and features that require runtime code generation may not work.
Why can generation 0 be collected without scanning every generation 2 object?
The collector starts from normal GC roots, but it also needs to find references from old objects to young ones. A write barrier records the older heap ranges where those references may have been written. During a generation 0 collection, the GC scans those recorded ranges, called dirty cards, instead of walking every generation 2 object. Young objects referenced by older ones are still preserved without paying for a full old-generation scan.
Why can process memory remain high after a generation 2 collection?
A generation 2 collection removes objects that are no longer reachable; it cannot remove live object graphs held by caches, static fields, or other roots. Pinned objects and swept heap regions can also leave gaps that are free to the GC but difficult to reuse. Finally, the runtime may keep committed heap memory for later allocations instead of returning it to the operating system immediately. A completed collection therefore does not guarantee that the process working set will shrink.
Can a .NET application leak memory even though it has garbage collection?
Yes. The GC reclaims managed objects only after they become unreachable. If a static cache, event subscription, or long-lived collection still points to an object that is no longer useful, the object and everything it references remain alive. The collector is doing its job, but the application is keeping the object alive for too long.
The GC does not own native memory or operating-system handles. They leak when their owner never releases them, usually through
Dispose()or a safe wrapper such asSafeHandle. Repeated process growth is a reason to investigate, but a managed root path or an unreleased native allocation is what proves the leak.
Why is
usingneeded when .NET already has garbage collection?The GC manages managed memory and decides for itself when to run. Resources such as file handles, sockets, operating-system handles, and unmanaged buffers often need to be released as soon as their owner is finished with them.
A
usingstatement gives that cleanup a clear scope. It hastry/finallysemantics, soDispose()is called when control leaves the scope, including when an exception is thrown.
How do
IDisposableand a finalizer differ?
IDisposableprovides explicit cleanup throughDispose(). The owner can call it directly or useusing, so the resource is released at a known point.A finalizer is a fallback that the runtime may run after the object becomes unreachable. Its timing is unpredictable, and it is not guaranteed during abrupt process termination. A custom finalizer is normally needed only when a type directly owns an unmanaged resource and no suitable
SafeHandleexists. After explicit cleanup succeeds,Dispose()callsGC.SuppressFinalize(this)to avoid the extra finalization work.
How does the .NET dispose pattern work?
Dispose()releases the resources owned by the object and must be safe to call more than once. In an inheritable type, the public method normally calls a protectedDispose(bool disposing)method and then suppresses finalization. Thedisposingflag istrueduring explicit cleanup, when owned managed disposables can also be released.A finalizer is added only when the type directly owns an unmanaged resource that cannot be delegated to a safe wrapper. Its path calls
Dispose(false), which releases only that unmanaged state because other managed objects may already have been finalized.
What does the CLR do when an application starts, and why does startup behavior matter?
A native host selects and starts the runtime, then the loader resolves the entry assembly and dependencies. Executed methods use ReadyToRun code when available or are JIT-compiled. Tiering can replace those bodies later. Assembly loading, static initialization, JIT work, and dependency setup can all appear in cold-start latency, so traces must separate them.
Why does Big O drop constant factors and lower-order terms?
Big O describes growth as
n → ∞, where the fastest-growing term dominates.n² + 100n + 500isO(n²)because the quadratic term eventually outweighs the rest.
What is the difference between average-case and amortised complexity?
Average-case complexity takes an expectation over a distribution of inputs. A hash lookup is
O(1)average when keys spread across buckets. Amortised complexity spreads expensive operations across a sequence on one structure. Dynamic-array append isO(1)amortised because many cheap appends pay for the occasionalO(n)resize.
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.Sortuses insertion sort for small subarrays inside its introspective sort implementation.
What determines whether a performance problem needs a different data structure or a different algorithm?
The dominant operation is the starting point. A
HashSet<T>removes repeated membership scans, while aSortedSet<T>keeps the set ordered as items are added or removed. If a change affects an item’s sort order, remove it and add it again. The algorithm becomes the next target when the representation is fixed by an external format or the chosen structure still leaves too much work in each operation.
What turns brute-force enumeration into backtracking?
A feasibility test on the partial candidate. Brute force waits until a configuration is complete. Backtracking checks after each choice and discards every completion that shares a rejected prefix.
What changes between memoization and tabulation if the recurrence is the same?
Evaluation order and control flow. Memoization starts at the target, follows recursive dependencies, and stores states on demand. Tabulation starts at the base cases and fills states in a fixed order. Their asymptotic work matches when they visit the same states. Tabulation avoids call-stack cost, while memoization may skip states the target never reaches.
What is the relationship between memoization and dynamic programming?
Memoization is DP’s top-down implementation: write the recurrence and cache each subproblem’s result. Bottom-up tabulation solves the same dependency graph iteratively. Memoization evaluates only states reached by recursion, while tabulation usually fills a planned table in dependency order. Repeated states make the cache useful. Optimal substructure is a separate requirement for optimization problems.
Why must a memoised function be pure, and what breaks if it isn't?
A cache hit returns a stored result without running the function again. If the output also depends on a global value, the clock, or an I/O read, the stored result may describe conditions that no longer hold. Any expected side effect is skipped as well. Safe memoization therefore requires same-input-same-output behavior and no observable side effects.
What is the most common correctness bug when memoising a recurrence?
An incomplete cache key. If a
(i, capacity)knapsack state is cached onialone, two different subproblems map to the same entry and the second lookup can return the wrong value. The key must represent the full state, which is the same requirement DP calls state design.
What determines the isolation level needed for a read-modify-write transaction?
The required protection depends on the invariant and on every row or predicate used to make the decision. Repeatable Read stabilizes repeated reads and may protect an update to the same row under the database engine’s rules, but it does not generally prevent write skew. A financial or inventory decision that spans rows or a predicate needs Serializable isolation or explicit locks over the full set. Optimistic row-version checks reduce contention only when the transaction validates every record that influenced the decision.
Why is a bigger connection pool often worse, not better?
Each connection consumes server resources. After the useful database concurrency is saturated, additional queries wait on CPU, I/O, or locks inside the engine, so latency rises without a matching throughput gain. Size the whole fleet against measured database capacity and the server connection limit, then divide that budget across instances.
What factors determine whether a new service should use SQL or a NoSQL store?
The decision starts with the data model, required guarantees, and main access patterns. Relational storage fits when constraints, multi-record transactions, joins, or changing queries matter. A document, key-value, graph, or other specialized store fits when its access pattern clearly dominates and its consistency and query limits are acceptable.
Scale alone does not settle the choice. A well-indexed relational database handles more load than most services need, so a specialized store should solve a measured problem. A relational source of truth with one specialized read or hot path is often safer than forcing every workload into one model.
Why use both
textandkeywordfor one field?
textis analyzed into terms for relevance-ranked full-text matching.keywordkeeps the exact value for equality filters, sorting, and aggregations. One representation cannot efficiently provide both semantics.
When is NoSQL a bad idea?
It is a poor trade when the core model depends on relational constraints, multi-entity transactions, or queries that change faster than the storage model can be redesigned. If the selected engine cannot enforce those guarantees, application code inherits them. Keeping SQL and adding a cache, replica, or purpose-built read model is often cheaper.
How does EF Core change tracking work, and when is a no-tracking query appropriate?
A tracking query attaches its entity instances to the context and reuses the same instance when the same entity key appears again. With the default snapshot strategy, EF Core compares current values with the tracked original values when change detection runs, normally before
SaveChanges()..AsNoTracking()fits a read-only result that will not be updated through that context. If a disconnected entity is attached later, the application must state which properties changed and how concurrency will be checked.
What is the N+1 query problem, and how can it be detected?
N+1 means one query loads parent rows and later navigation access issues another query for each parent. Detect it by counting database commands per operation and inspecting generated SQL in logs or tracing. Fix the query shape with a projection, an explicit include, or a deliberate second query. The choice depends on result size and consistency needs. There is no universal collection-size threshold for split queries.
When is raw SQL a better boundary than an ORM query?
Raw SQL fits a provider-specific query, bulk operation, or execution-plan requirement that the ORM cannot express predictably. It should be parameterized, tested against the production database engine, and kept behind a narrow data-access boundary.
Why can eager loading several collections be expensive?
Sibling collection joins can multiply rows, duplicating parent data across the result. Split queries or purpose-built projections avoid that multiplication, but split queries add commands and may need an explicit consistency boundary.
What is normalization and why do most systems stop at 3NF/BCNF?
Normalization decomposes relations according to their dependencies so that one update cannot leave conflicting versions of the same fact. 3NF and BCNF cover ordinary functional dependencies. 4NF and 5NF matter when the domain contains independent multivalued facts or a genuine join dependency. 6NF is mainly a temporal modeling tool. The stopping point follows the domain’s dependencies, not a universal target number.
What conditions justify denormalizing a table, and what risks does it introduce?
Denormalization is justified when a specific read path misses its latency or resource target and storing the result is cheaper than rebuilding it from source facts on every read. The duplicate adds another update path and can introduce lag, write contention, and repair work. The design must identify the source of truth, acceptable freshness, how updates are applied, and how the duplicate is reconciled when it drifts.
How can 2NF and 3NF violations be distinguished?
2NF removes partial dependencies of a non-prime attribute on part of a composite candidate key. A make-level discount repeated in rows keyed by
{Make, Model}violates it. 3NF additionally constrains dependencies whose determinant is not a superkey. A store phone determined byStoreinside a table keyed byModelis the usual transitive shape. A repair separates the facts only when the decomposition is lossless and preserves the constraints the system needs.
What is the difference between WHERE and HAVING?
WHEREfilters source rows before grouping and cannot use aggregate results.HAVINGfilters groups afterGROUP BYand can test aggregates such asCOUNT(*). A non-aggregate predicate belongs inWHEREwhen it can reduce the rows entering the grouping step without changing semantics.
What is the difference between a stored procedure and a function?
A stored procedure runs as a separate database operation. It can execute several statements, change data, and return result sets or output parameters. A function returns a scalar or table value and can be used inside a query, so the database limits what it can do. SQL Server may inline an eligible scalar UDF. Otherwise, the function may run once per row and limit plan choices such as parallelism.
What is a Common Table Expression (CTE), and when is a temp table a better fit?
A CTE gives a name to a query result for one statement. It does not automatically store the result or guarantee that repeated references reuse the same work. A temp table is usually better when the intermediate result must be inspected, indexed, reused across statements, or optimized with its own statistics.
What are SQL Server transaction isolation levels?
SQL Server provides
READ UNCOMMITTED,READ COMMITTED,REPEATABLE READ,SERIALIZABLE, andSNAPSHOT. Enabling Read Committed Snapshot Isolation changesREAD COMMITTEDreads to statement-level row versions.NOLOCKis not a general performance switch: it permits observations of rolled-back work and can return missing or duplicate rows while data changes concurrently.
What evidence justifies sharding a database?
Scale-driven sharding is justified after evidence shows that write throughput or storage exceeds one ownership domain and simpler measures cannot remove the ceiling. Hard tenant isolation, locality, or data-placement constraints can justify it earlier. Read replicas help reads, caches remove repeated reads, and in-engine partitioning improves manageability without creating cross-database transactions.
What's the practical difference between a Layer 4 and a Layer 7 load balancer?
An L4 balancer selects a backend at the transport-connection boundary, so it cannot independently distribute streams inside one HTTP/2 connection. An L7 proxy terminates and parses HTTP, which enables request- or stream-aware routing, retries, and header policy. The extra capability also adds application-protocol state and processing.
How does the OSI model map onto the actual TCP/IP stack?
The common four-layer view maps application protocols to OSI 5–7, TCP and UDP to transport, IP to the Internet layer, and local network access to the link layer. It is an approximate conceptual mapping because real protocols can cross the reference boundaries.
At which layer do IP addresses, ports, and MAC addresses each operate?
A link-layer address identifies an interface within the current link’s delivery scope. An IP address is routed across networks. A transport port selects an endpoint within the host’s transport namespace. For TCP, the local and remote IP addresses and ports identify a connection within the relevant network namespace.
When can HTTP/1.1 be necessary or perform better than HTTP/2?
HTTP/1.1 remains necessary across a server or intermediary that cannot negotiate HTTP/2. On a lossy path without HTTP/3, several HTTP/1.1 connections can also isolate TCP loss better than one HTTP/2 connection, though extra connections add handshake and congestion-control cost. The choice should follow measurements across the real path.
Why is an idempotent HTTP method not automatically safe to retry?
Idempotency constrains the intended effect of repeating the method. A request can still carry a one-time credential, trigger downstream work, consume deadline budget, or have an unknown first outcome. Automatic retry needs an end-to-end replay contract, not the method label alone.
What conditions make Server-Sent Events a better fit than WebSockets?
SSE fits ordered server-to-browser events when ordinary HTTP requests already cover client commands.
EventSourcereconnects and sendsLast-Event-ID, giving the server a natural replay cursor. WebSocket fits low-latency bidirectional messages, but the application must define its own resume contract.
What factors determine whether a system should use a monolith, a modular monolith, or microservices?
A monolith keeps one deployment and local transactions, so it usually fits a small team while domain boundaries are still changing. A modular monolith adds enforced module boundaries without introducing network failures or separate operations. It is a safer default when one deployment is not slowing delivery.
Microservices fit stable boundaries that repeatedly need independent deployment, scaling, or ownership. That independence comes with remote calls, separate data ownership, eventual-consistency workflows, and distributed observability. Strong module boundaries make a later extraction safer, but they do not remove those costs.
How should a small CRUD service with simple domain rules be structured if growth is expected?
Expected growth alone is not enough reason to start with the full ceremony of Clean Architecture. A layered structure is usually sufficient, provided domain logic does not leak into controllers or infrastructure code. Stricter inward dependency rules become useful when valuable business policy is repeatedly coupled to a framework, database, or external service. Ports and adapters should earn their cost through faster tests or clearer change isolation, not through the possibility that the service may become complex later.
How does Clean Architecture differ from traditional N Layer, and when does the extra indirection pay off?
Traditional N Layer commonly points dependencies from UI through business logic to data access. Clean Architecture points source dependencies toward policy instead, so inner layers own the contracts that outer adapters implement. The extra boundary pays off when business rules are valuable, isolated tests matter, or infrastructure is likely to change. A short-lived service with shallow rules usually pays the wiring cost without receiving much protection.
What is the difference between traditional layered and Onion/Clean Architecture?
Traditional layering points from UI to Business Logic to Data Access, so the business layer consumes the lower data API. Onion and Clean move the persistence contract inward and make Infrastructure implement it. Both separate responsibilities. Only the inward form prevents source dependencies from pulling infrastructure types into policy.
How can plug-ins use different versions of the same dependency without breaking the host contract?
Each plug-in can load its private dependencies through its own
AssemblyLoadContext. Plug-in A may then resolveNewtonsoft.Json12.x while plug-in B resolves 13.x. The extension contract must still come from the host’s default context. If a plug-in loads another copy of that contract assembly, its types have a different identity even when the name and source code match. Values should cross the boundary through the shared contract types and plain data.
When is plug-in architecture the wrong choice?
It is the wrong choice when one team owns all features and releases them with the host. Dynamic loading and contract compatibility add failure modes without creating independent delivery. A modular monolith with feature flags handles that case with fewer moving parts. Plug-ins earn their cost when an extension must evolve without changing or rebuilding the core.
What is the key difference between MVC and MVVM?
MVC uses a controller to handle a request and select a response view. MVVM exposes observable state and commands to a long-lived bound view. MVC keeps a stateless request path explicit. MVVM accepts binding and notification machinery in exchange for persistent screen state without direct view manipulation.
Why is 2PC problematic in microservices?
Prepared participants can hold locks while waiting for the coordinator’s durable decision, so network faults reduce availability and throughput. Many common service boundaries do not support XA, which prevents them from joining the protocol.
How does the Outbox pattern support at-least-once event delivery?
The message commits in the same local transaction as the domain change. A relay retries publication until acknowledged. A crash between broker acknowledgement and marking the row processed can publish twice, so consumers remain idempotent.
How do Kafka and RabbitMQ fit different messaging workloads in a .NET service?
Kafka fits high-throughput event streams that must be retained and replayed. RabbitMQ fits low-latency work queues and messages that need flexible routing. For either broker, the design still needs a clear ordering boundary, such as a Kafka partition or a RabbitMQ queue, and the decision must include operating cost and the team’s experience.
What architectural prerequisites must be met before horizontal scaling works?
Work must be divisible, routing must reach an eligible owner, and state must have a sharing or partition-ownership rule. Stateless handlers satisfy those constraints by externalizing shared state. A load balancer can distribute requests, while stateful services also need replication, ownership, and failover coordination.
Why can horizontal scaling fail even with many instances?
More replicas can move saturation to the database, a connection pool, or an external quota. Local state can make replicas inconsistent, while sticky sessions or hot keys create uneven load. Startup delay also means new replicas may arrive after the overload has already caused failures.
Why does CQS make code easier to reason about?
The contract separates observation from mutation. Queries can be repeated or cached only when their no-side-effect promise holds. Commands expose the paths that need authorization, transaction boundaries, and retry analysis. The distinction reduces the amount of implementation detail required to judge a call site.
When is it pragmatic to violate CQS?
A combined operation is justified when separation breaks a coherent transition or adds a wasteful round trip.
Stack.Pop()is the classic single-threaded shape;ConcurrentStack<T>.TryPop()supplies the atomic concurrent form. A create command returning its generated identifier is another. The exception should keep mutation, retry safety, and returned data explicit.
Explain
Transient,Scoped, andSingletonlifetimes with a safe production example each.The lifetime controls how long the container reuses an instance:
- Transient: one instance per resolution, suitable for a lightweight stateless mapper.
- Scoped: one instance per scope. In ASP.NET Core, a scoped
DbContextgives one request a coherent unit of work.- Singleton: one instance for the application lifetime, suitable for a thread-safe cache or
IClock. Lifetime is a shared-state decision, not a performance setting. A singleton cannot safely capture a scoped service.
What is a captive dependency, and how can it be fixed?
A captive dependency appears when a longer-lived service stores a shorter-lived one, classically an
IHostedServiceholding a scopedDbContext. The scoped object escapes its boundary, so request state may leak across work and disposal happens too late. Scope validation catches this configuration when enabled. The repair is to injectIServiceScopeFactory, create a short scope for the operation, resolve the scoped service inside it, and dispose the scope when the operation ends.
What evidence justifies introducing a pattern?
A recurring variation, responsibility boundary, or failure mode must be visible in the code. The pattern should make that pressure cheaper to handle than the direct design. Without that evidence, the extra indirection is speculative complexity.
Why does EF Core's DbContext already implement the Unit of Work pattern?
DbContexttracks entity changes and sends the pending work throughSaveChangesAsync(), which uses a transaction when the provider supports it. Several repositories participate in one unit only when they share the same context instance. A singleton context is unsafe, while separate transient contexts split the commit boundary.
When is a generic
IRepository<T>an anti-pattern?It becomes an anti-pattern when the generic CRUD surface replaces aggregate-specific access rules.
GetAll()may be meaningless for a large aggregate, and unrestricted updates can bypass invariants. A generic implementation can remain inside infrastructure. The domain-facing interface should describe the operations the aggregate actually supports.
When is CQRS useful, and when does it add unnecessary complexity?
CQRS is useful when the write side and the read side have clearly different needs. For example, writes may enforce order and payment rules, while reads need data prepared for fast searches and reports.
It is not useful when the same model already handles both sides without difficulty. In that case, maintaining a separate read model and keeping it synchronized adds complexity without enough benefit.
When is full DDD the wrong choice?
A system with straightforward CRUD and little business behavior gains little from aggregates, repositories, and context mapping. Strategic boundaries may still help a large organization, but tactical machinery should follow real invariants rather than project size alone.
How is a GoF category chosen?
Creational patterns vary how objects come into existence. Structural patterns vary how types or objects are assembled. Behavioral patterns vary responsibility and communication. The category is a recall aid. The pattern’s intent still decides whether it fits.
When does using a design pattern become an anti-pattern?
When its indirection costs more than the variation it isolates. A Factory Method with one permanent product, a Builder around two independent values, or a Singleton hiding request state adds vocabulary without removing design pressure. Concrete evidence of variation should pay for the abstraction.
How can two patterns with the same wrapper shape be distinguished?
Name the responsibility that the wrapper owns. A Decorator adds behavior, a Proxy controls access, and an Adapter translates a contract. Class shape alone cannot identify the pattern because intent and collaboration are part of its definition.
How does Information Expert reduce coupling, and when should another principle override it?
It places behavior beside the information it needs, so another object does not have to pull data out and reproduce the rule. Low Coupling or Pure Fabrication should override it when that placement would import infrastructure, coordinate other aggregates, or give the object unrelated responsibilities.
How does GRASP differ from SOLID?
GRASP focuses on assigning responsibilities among collaborating objects. SOLID describes broader properties of class and dependency design. They overlap around cohesion, coupling, and variation, but GRASP starts with ownership: who should do the work?
How does ASP.NET Core Middleware differ from a classical Chain of Responsibility?
Classical CoR usually links handler objects directly. ASP.NET Core composes middleware delegates into one request pipeline at startup. That pipeline is fixed after the application is built, while a conventional object chain may be rearranged at runtime. The practical boundary is startup composition versus runtime mutation.
How does MediatR implement the Command pattern, and what does it add?
PlaceOrderCommandcarries the request, whilePlaceOrderCommandHandlerowns execution. MediatR routes between them and can wrap handlers with pipeline behaviors. That indirection is useful when dispatch and shared policies matter across many requests. A direct service call remains clearer for a small, fixed interaction.
How does EF Core use LINQ Expression Trees as an Interpreter?
The
Queryable.Whereoverload accepts anExpression<Func<Order, bool>>, so the compiler represents the lambda as a tree instead of only emitting an executable delegate. EF Core examines supported nodes, builds a database query, and parameterizes captured values. A custom method fails when no translator handles its expression node.
How does Interpreter differ from Strategy when rules are selected at runtime?
Strategy selects an algorithm that already exists in code. Interpreter evaluates a rule represented as data. Strategy fits a known set of implementations. Interpreter fits rules that need to be combined or changed without writing and compiling another strategy class.
What should determine whether an API returns
IEnumerable<T>,IReadOnlyList<T>, orIAsyncEnumerable<T>?The return type should describe what the caller can safely rely on.
IReadOnlyList<T>guaranteesCountand indexed access, but it does not say whether values are computed lazily or represent a snapshot. If the method returns a materialized snapshot, that should be stated separately in the method’s contract.IEnumerable<T>promises only synchronous enumeration, which may be lazy and repeat the underlying work when enumerated again.IAsyncEnumerable<T>fits a source that waits between items and should stream them instead of buffering the full result.
What does the compiler generate for a
yield returnmethod?The compiler generates a state-machine type that implements the enumeration contracts. Each
yield returnbecomes a suspension point.MoveNext()resumes execution andCurrentexposes the yielded value. Locals that must survive suspension become fields on the generated object.
What are the signs that a mediator is adding indirection without reducing coupling?
A mediator should route work, not become the place where business rules accumulate. Moving a 300-line workflow from a controller into one handler changes its location without reducing its complexity or dependencies. For a simple, stable interaction, a direct dependency is usually clearer.
How do MediatR pipeline behaviors implement the Chain of Responsibility pattern?
Each
IPipelineBehavior<TRequest, TResponse>wraps the next delegate. It callsnext()to continue or returns its own response to stop. DI registration supplies the order, so the chain is less visible than explicitSetNext()calls but centralized in application composition.
How can memento history be kept from growing without limit?
Keep a fixed number of snapshots or set a memory budget. Long-lived recovery may need only the latest snapshot, while an editor can keep a limited undo window. A full audit history should store durable events or changes instead of keeping an unlimited stack of object copies.
When is Memento overkill compared to simpler approaches?
A full snapshot is wasteful when undo can be represented as one small delta. If restoring a removed item needs only that item and its position, store those values. Memento earns the extra copy when state is interdependent or restoration must survive across sessions.
How should event subscriptions be managed to avoid retaining subscribers after their lifetime ends?
A subscription should be removed when its owner is disposed or otherwise reaches the end of its lifetime. Weak events help when the publisher must live longer than its subscribers and there is no clear owner that can unsubscribe. Matching DI scopes helps only if the publisher stays in the same scope and the subscription does not escape into a singleton, static event, or another longer-lived object.
How does a compiler-generated async state machine relate to the State pattern?
The compiler emits an
IAsyncStateMachineimplementation with a numeric state field.MoveNext()branches on that field, saves a continuation when an awaiter is incomplete, and later resumes from the saved point. Locals that must survive suspension become fields. This is a compiler state machine, but it does not use the pattern’s usual family of state objects.
What determines whether a strategy should use an interface or a
Func<T, TResult>delegate?A delegate is enough when the variation is one operation and its inputs contain everything it needs. An interface becomes useful when the algorithm has several related operations, owns a lifecycle, or has dependencies that should be visible in dependency injection. State alone does not require an interface because a delegate can close over state, although doing that may hide who owns the state and how long it lives.
How should strategy selection work when several strategies can handle the same request?
Precedence has to be part of the contract. A first-match registry needs stable ordering, explicit selection needs a validated key, and a composite needs a rule for combining results. If two strategies apply with the same priority and the contract does not say what happens, that is a domain bug. Dependency-injection registration order should not decide it by accident.
What should determine whether algorithm variation uses Template Method or Strategy?
Template Method fits a stable workflow whose hooks make sense only inside one base class. Strategy fits a behavior that is useful on its own or must be replaced at runtime. Template Method couples variants through inheritance. Strategy adds another dependency and needs a clear selection rule.
What is the "Hollywood Principle," and how does Template Method apply it?
The principle means that the framework controls the flow and calls application code at defined extension points. Template Method applies that rule inside a class: the base method fixes the order, then calls subclass hooks for the steps that may vary. This protects the sequence, but every subtype becomes coupled to that calling protocol.
What is double dispatch and why does Visitor need it?
Normal virtual dispatch selects a method from the runtime type of the receiver. Overload resolution still uses the argument’s compile-time type.
Acceptfirst dispatches to the concrete element, wherethishas that concrete type.visitor.Visit(this)can then select the corresponding overload and dispatch to the concrete visitor implementation. The two calls encode both dimensions.
When does EF Core use ExpressionVisitor, and what does it do?
EF Core receives a LINQ expression tree and passes it through multiple visitor-based phases that normalize, expand, translate, and shape the query. A method or member fails translation when the provider has no supported server-side mapping for that expression in its current context. The failure is broader than a missing
Visitoverload because providers often visit the node successfully but cannot translate its semantics.
When is pattern matching a better fit than Visitor?
Pattern matching is usually clearer for a small hierarchy or a short operation because it needs no
Acceptmethod or visitor interface. Its exhaustiveness depends on the type shape: an interface hierarchy with a discard arm does not warn when a new implementation appears. Visitor earns its ceremony when the element hierarchy is stable and compile-time pressure to update every operation is an important part of the design.
What should happen when an Abstract Factory gains a new product type such as
IFraudDetector?If every provider family must supply fraud detection, add it to the factory interface and update every concrete factory. The compile-time failures are useful because they expose incomplete families. A separate factory fits only when fraud detection varies independently. A default no-op keeps existing factories compiling, but it can make a missing security control look valid.
When is Abstract Factory overkill compared to a simpler approach?
It is unnecessary when only one product varies or the products do not share a compatibility boundary. In that case, inject the product interface directly. Abstract Factory earns its indirection only when a family choice must move together.
How does Abstract Factory relate to the DI container in modern .NET?
A DI container can construct the same object graph, but it is a general registry rather than a domain-specific family contract. Grouped registration methods can keep a provider family together at the composition root. A typed factory makes that boundary visible to consumers, yet ordinary interface return types still do not prove concrete-family compatibility.
When is a Builder worth using instead of a constructor?
A Builder is useful when inputs arrive across several steps and the complete object must be checked before it is created.
Build()can validate relationships between those inputs and calculate values that callers should not supply. A constructor is still better when all required values fit in one clear call and it can enforce the same invariants directly. Async initialization usually belongs in an asynchronous factory because a conventionalBuild()cannot be awaited.
Why does
WebApplicationBuilderuse a builder instead of a constructor with parameters?Hosting configuration arrives from several extension points before the application can be assembled. The builder gives those registrations one mutable setup phase, then
Build()creates the service provider and host. Most dependency completeness remains a runtime property, so startup validation still matters.
How do Factory Method and Abstract Factory differ?
Factory Method uses inheritance: a creator subclass chooses one product implementation for a step in its workflow. Abstract Factory uses composition: an injected factory creates several related products as one family. Factory Method fits when one creation step varies. Abstract Factory fits when a whole compatible set must change together.
When does Factory Method become the wrong choice?
It is the wrong fit when no creator algorithm needs an overridable construction hook. A static factory or DI registration is smaller for simple selection. When several product types must vary together, an Abstract Factory makes that family boundary explicit.
How does Factory Method support the Open/Closed Principle?
The shared creator algorithm can remain unchanged while a new subtype supplies another product. This protects only the creation variation anticipated by the abstraction. A change to the workflow or product contract still modifies existing code. If subtypes exist solely to return different constructors, a registry or DI registration may express the variation with fewer classes.
When is Prototype useful instead of constructing an object directly?
Prototype fits when the new object starts as a variant of an existing configured instance. It also fits when code knows only an abstraction and the runtime object must copy its own concrete type. Direct construction is clearer when there is no useful template and the runtime type is already known.
Where is the boundary between a shallow and a deep copy?
A shallow copy duplicates the outer object’s fields and preserves references to nested objects. A deep-copy policy replaces the mutable nested state that must evolve independently. Copying every reachable object is rarely the real requirement. Ownership and identity decide where copying stops.
How does a DI singleton differ from the classical Singleton pattern?
A classical Singleton type controls construction and exposes a global access point. A DI singleton is a container lifetime: one root provider reuses one registered instance, while consumers receive it through declared dependencies. Other providers or direct construction can still produce more instances.
What must be true before choosing singleton lifetime for mutable state?
The state must be intentionally shared across all callers in that provider, safe under concurrent access, bounded in memory, and independent of scoped data. If any condition fails, a scoped or transient lifetime is usually safer.
How can an Adapter be distinguished from a Facade when both wrap another system?
An Adapter translates an incompatible interface into the contract a client expects. A Facade gives clients a smaller, workflow-oriented API and may coordinate several subsystem calls. For example, a wrapper that exposes three business operations over twenty legacy calls is acting as a Facade, even if some translation also happens inside it.
When does payment-provider selection need Bridge rather than Strategy?
Strategy swaps one behavior behind an interface. Bridge connects two independently changing models. Provider selection alone needs Strategy. Provider selection combined with a growing set of payment operations may justify Bridge.
What makes the payment example Bridge rather than ordinary provider polymorphism?
Both sides vary.
PaymentOperationhas charge, subscription, or refund variants, whileIPaymentGatewayhas Stripe, PayPal, or bank implementations. Provider implementations can grow independently, and operation variants can grow independently while they compose existing gateway primitives. A new primitive still changes the gateway contract and every provider. Injecting only one gateway behind one service interface would be ordinary polymorphism or Strategy.
How does Bridge differ from dependency injection?
Dependency injection supplies an object with its dependencies. Bridge is the design decision to keep an abstraction and its implementation as separate models that can vary independently and connect through composition. DI can wire an
IPaymentGatewayinto aPaymentOperation, but that wiring does not create the two dimensions or prove that they need to evolve separately.
How does Composite relate to the Visitor pattern?
Composite gives leaves and groups one interface, and each group can recurse through its children. Visitor adds an operation across the node types without putting that operation on every node. They work well together when the tree structure belongs to Composite, the set of node types is stable, and operations change more often than the nodes.
When does a Composite tree become a performance problem?
Cost grows with the number of visited nodes. A bundle containing 10,000 SKUs makes
GetPrice()visit those nodes on each uncached call. Profiling should decide whether to cache totals or update an aggregate during mutation. Either choice introduces an invalidation rule.
How does ASP.NET Core Middleware implement the Decorator pattern?
Each middleware receives a
RequestDelegatefor the remaining pipeline. It can run logic before delegation, after delegation, or stop the chain. Startup composition fixes the wrapper order, so ordering mistakes appear as runtime behavior rather than type errors.
When should Decorator replace inheritance for added behavior?
Decorator suits optional behavior assembled at composition time, especially around sealed or third-party types. Inheritance suits a stable subtype relationship. A decorator avoids coupling behavior to a base-class implementation, but introduces another object and call boundary.
What's the performance cost of a deep decorator chain?
Each decorator adds a call boundary and may add asynchronous state-machine work if its method awaits. I/O usually dominates that cost. A CPU-bound hot path still deserves measurement. If wrapper overhead appears in profiles, collapsing layers on that path may be reasonable.
When does a Facade become a "god class" anti-pattern?
The shift happens when orchestration becomes ownership of business rules or mutable domain state.
OrderFacademay call pricing and validation services in sequence. It should not become the place where those rules are implemented. Difficulty testing the facade without reproducing the whole domain is a stronger signal than a line-count threshold.
When is Flyweight not worth the complexity?
It is not worth using when duplicate state is a small part of the process heap or object counts stay low. A memory profiler should show many equal, retained objects before the model is split. The factory and lookup path otherwise add complexity without relieving a measured constraint.
What's the difference between a Proxy and a Decorator in terms of intent?
A proxy stands in for the subject to control access to it. A decorator attaches another responsibility while preserving the component contract. Their class diagrams can look identical, so the design intent and the wrapper’s reason for existing settle the classification.
Decorator and Proxy can have the same class structure. What determines which pattern is present?
Intent. A Decorator adds composable behavior to an object. A Proxy controls access to another object, perhaps by deferring creation or enforcing authorization. The wrapper’s responsibility and the reason it exists matter more than the diagram.
What determines whether an event-driven workflow uses orchestration or choreography?
The main question is whether one component must own the process from start to finish. An ordered checkout with compensation steps fits orchestration because the current step and recovery state need one visible owner. Independent reactions to
OrderPlaced, such as email, analytics, or indexing, fit choreography because no subscriber controls the others. Asynchronous messaging supports both styles; it does not force choreography.
Why do microservices create distributed data consistency problems, and what keeps a cross-service workflow reliable?
Each service commits its own data, so a cross-service workflow cannot rely on one local ACID transaction. One service may commit successfully and a later step may fail. The workflow is usually split into local transactions connected by a saga. Outbox and inbox patterns make message transfer recoverable, and idempotent handlers make redelivery safe.
If a completed step must be reversed, the saga runs a business compensation where that is possible. Its state must remain visible so the system can keep retrying, finish later, or wait for manual repair instead of leaving partial work hidden.
What architecture usually fits a new product, and what evidence would justify moving to microservices?
The starting point is the release and ownership constraint, not a preferred topology. One team still discovering the domain usually gets the fastest feedback from a monolith. A modular monolith keeps changes and transactions in-process while enforcing domain boundaries.
Microservices become justified when a stable boundary repeatedly needs independent deployment or scaling, and the team can own the added operational cost. Until that pressure is measured, distribution adds network and data-consistency problems without buying real independence.
What evidence shows that an extracted service is independent rather than part of a distributed monolith?
- Its team can change, deploy, roll back, and operate it without a paired release.
- It owns its writable data and exposes a versioned contract. Dependency failure behavior is defined and exercised.
- Traces and reconciliation prove that synchronous calls and later messages can be followed across the boundary.
When is a modular monolith a better fit than microservices, and what signals justify extracting a module?
A modular monolith fits while business domains stay cleanly separated into modules and one deployment remains reliable. Extraction becomes worthwhile when one module repeatedly needs its own scaling, release cadence, or isolation policy. The domain contract can remain familiar, but the remote interaction must be redesigned around deadlines, retries, observability, and local transactions.
What evidence justifies extracting a service?
A stable boundary should already exist, and the module should repeatedly need an independent release cadence, runtime isolation, or scaling profile. Extraction without those pressures usually trades visible code coupling for harder operational coupling.
Which part of SOA remains useful without SOAP, WSDL, or an ESB?
The durable part is the explicit service contract around a business capability. Consumers depend on that contract instead of importing the service’s internal code or writing directly to its database. This allows systems built on different technology stacks to change independently as long as the contract stays compatible. SOAP, WSDL, and ESBs were common ways to implement or govern that boundary, but the boundary still matters when the transport is HTTP, gRPC, or messaging.
What makes a function "pure" and why does purity matter for testing?
For inputs in its defined domain, its observable result depends only on explicit inputs, and evaluating it does not change external state. Tests therefore pass values and assert values instead of arranging clocks, databases, or global configuration. A throwing input makes the function partial rather than supplying a substitutable result; expected invalid inputs can be modeled as returned values when totality matters. Real systems keep I/O and external state in a surrounding shell and pass their results into the pure core.
How can TDD improve design beyond increasing test coverage?
The test is the first concrete caller, so construction, inputs, outputs, and failure semantics must be expressed before implementation details dominate. Difficult setup is a design signal worth investigating, not proof that a class must be split. The benefit comes from selecting the next useful example and refactoring after it passes. A test that begins green may document existing behavior, but it does not prove that the intended production change was necessary.
When is TDD not worth the overhead?
The loop has little leverage when the work is a disposable experiment, a mechanical declaration already guaranteed by a framework, or a visual exploration whose useful feedback comes from rendering rather than a code-level example. That does not remove the need for verification before the behavior becomes durable. There is no universal percentage cost: the tradeoff depends on domain familiarity, test level, tooling, and how much change the code will absorb.
What is the difference between a stub and a mock?
A stub supplies answers needed to reach the behavior. A mock is configured with interaction expectations and verifies them. A spy records calls for later assertions, while a fake provides a working simplified implementation. Frameworks often let one object play several roles, so the distinction is about how the test uses the double. Prefer result assertions when they expose the behavior, and interaction assertions when the command itself is the observable contract.
When is a unit test the wrong testing layer?
A unit test adds little value when it only repeats language or framework behavior, such as an uncustomized property accessor. Failures in visual rendering, dependency registration, configuration binding, or database-provider behavior need a component or integration test that can observe that boundary. Exploratory code may start without tests, but behavior kept in production still needs evidence that matches its risk. The best layer is the cheapest one that can catch the realistic failure.
When is classic machine learning a better fit than an LLM?
Classic ML fits a well-defined prediction problem with representative labeled data, such as classification, regression, or ranking. It usually has lower latency and unit cost, and its behavior is easier to measure at scale. An LLM fits open-ended language work or early product discovery, where a prompt can describe the task before a training set exists. That choice can change as traffic grows or output variability becomes a problem. At that point, a rule-based system or a smaller trained model may be the better option.
How do Matryoshka embeddings reduce vector storage, and how should the smaller size be validated?
Matryoshka training makes shorter prefixes of the full vector useful on their own, so a model with a
dimensionsoption can return 256 or 512 values instead of the full size. Indexing 256 rather than 1536 float values cuts raw ANN vector storage by about six times and also reduces similarity work. Recall is not guaranteed, so the same labeled queries must be compared at several dimensions using Recall@k and latency. If full vectors are used for reranking, retaining or recomputing them adds storage, API cost, or latency outside the smaller index.
Why can switching to a higher-scoring embedding model cause recall to drop on existing queries?
Each model defines its own vector space. Comparing a query from the new model with stored vectors from the old one produces meaningless distances and broken rankings. Build the new corpus index before switching query traffic, and include the model name and version in embedding-cache keys.
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.
Why is adjusting temperature and top_p simultaneously discouraged?
Both reshape the token distribution through different mechanisms. Temperature sharpens or flattens logits, while top_p truncates candidates at a cumulative-mass threshold. Changing both makes an observed output shift harder to attribute. Tuning one and leaving the other at its default keeps evaluation legible.
Why can a grounded response still contain unsupported claims despite citation tags?
Citation markers do not verify entailment. A cited passage may be related to the topic without supporting the exact claim. Grounding therefore needs a separate claim-to-source check, using NLI or another verifier, before the citation can be trusted.
What does graph engineering add beyond loop engineering and workflow patterns?
Loop engineering controls progress, verification, budgets, and termination across repeated work. Workflow patterns name reusable shapes such as routing or evaluator-optimizer. Graph engineering makes the chosen shape executable by defining nodes, legal transitions, shared state, merge rules, scheduling assumptions, checkpoints, and recovery behavior. A loop can remain inside one node or appear as a cycle across several nodes.
Why does an explicit graph not make an agentic system deterministic?
Fixed edges constrain which paths are legal, and deterministic guards can make some transitions reproducible. Nodes may still contain model calls, external APIs, mutable data, or concurrent work whose results vary. The graph makes control flow more inspectable; it does not guarantee the quality or repeatability of the work inside each node.
Why does checkpointing require idempotent node design?
A runtime normally checkpoints at a step boundary rather than after every instruction inside a node. If a process stops after performing a side effect but before committing the checkpoint, resume can execute that side effect again. Idempotency keys, upserts, or read-before-write checks make re-execution safe; a checkpoint alone does not provide exactly-once delivery.
How should prompting, RAG, and fine-tuning be chosen for an LLM system?
The choice starts with the type of gap shown by evaluation. Prompting is the simplest option when clearer instructions or examples can stabilize the task. RAG fits gaps in current or private knowledge and cases where answers need source evidence. Fine-tuning fits a stable behavior gap, such as format, policy, style, or a narrow task, after prompting has been tested and is still inconsistent. The techniques can be combined: fine-tuning can shape behavior while RAG supplies facts that change.
What matters when choosing an LLM?
Model size and labels such as “frontier” are only starting points. A frontier model may produce better answers but miss the latency or cost target. A smaller model may look cheap until retries and human escalation are included. Candidates should be tested on the same representative workload for task quality, safety, reliability, latency, and total cost per successful task. The best choice is the least expensive model that meets all required targets.
How do classifier routing and cascading differ, and when is each useful?
A classifier chooses a model before generation based on the predicted task type or difficulty. It is useful when those signals are reliable and generating a second answer would cost too much or take too long. Its main risk is sending a hard request to a model that cannot handle it. A cascade starts with one model and decides whether to escalate after checking its answer. It works well when failures can be detected with a cheap, reliable signal, such as schema validation or a groundedness check. Failed first attempts pay for a second generation and add latency. The patterns can also work together: a classifier can choose the initial route, while a cascade handles answers that fail a quality gate. The complete route should be evaluated for end-to-end quality, cost, and tail latency.
When does a workflow fit better than an autonomous agent?
A workflow fits predictable steps with explicit inputs and outputs. Its fixed control flow is cheaper to run and easier to debug. Autonomy is justified when the steps are not known in advance and the system has a checkable success signal that can catch drift.
How does an autonomous agent accumulate error, and what bounds it?
Each step consumes state produced by earlier steps, so one bad assumption can shape the rest of the run. Iteration caps bound cost, validation gates reject invalid progress, and an escalation path stops the loop when recovery needs outside input. Decision and tool traces make the original divergence visible.
What makes a task a good fit for an autonomous agent?
The control flow must be genuinely open-ended, and progress must still be checkable. Tests, resolution criteria, or source-backed claims give the loop feedback. Vague or delayed outcomes do not. The agent can keep moving while getting further from the goal.
How do the five workflow patterns differ in who controls the next step?
The difference is how much of the control flow is fixed by the application. Prompt chaining fixes the sequence, routing chooses among predefined branches, and parallelization runs predefined work at the same time. With orchestrator-workers, the model decides the tasks and worker count at runtime. Evaluator-optimizer adds a loop that continues until an acceptance test passes or a limit is reached. The simplest pattern that matches the task is usually easier to test because every extra model-owned decision adds variable cost and makes failures harder to reproduce.
Why are orchestrator-workers harder to operate than ordinary parallelization even when their diagrams look similar?
Parallelization starts a known set of calls, so coverage, cost, and aggregation can be tested ahead of time. An orchestrator chooses the decomposition and worker count from the input. That flexibility creates variable cost and new failure modes: missing work, overlapping assignments, or a synthesis that cannot reconcile the results. It also makes the pattern a bridge into Multi-Agentic Systems, because the model controls the shape of the work.
Why can one base model produce very different results on the same benchmark?
The benchmark runs a model-plus-scaffold system. Planning logic, retry limits, prompts, tool descriptions, and context handling all affect the outcome. Scores from different scaffolds cannot isolate model quality. A fair model comparison keeps the scaffold fixed, then reruns the candidates on the same tasks.
Why should tool selection and argument accuracy be measured separately?
They fail for different reasons. Choosing the wrong tool usually points to routing or unclear tool descriptions. Choosing the right tool but passing the wrong order ID, filter, or date points to grounding or schema problems. Separate metrics make a result such as 95% selection accuracy and 70% argument accuracy actionable instead of hiding it inside one average. Identifiers and enums can usually be checked against exact references, while free-text arguments often need a semantic check.
When is reference matching a better trajectory scorer than an LLM judge?
Reference matching fits tasks with a small set of knowable procedures. It is cheap, objective, and can enforce hard boundaries: subset mode proves the agent stayed within an allowed tool set, while superset mode proves required calls occurred. A judge becomes necessary when valid decompositions are too numerous to enumerate, but it adds cost and long-context bias.
Why does adding more context often make answers worse, not better?
Attention is uneven across a long window, so evidence buried in the middle may be underused. Added tokens also compete with the evidence already present, even when they are loosely relevant. Larger inputs cost more and take longer. The fix is signal density: keep fewer complete chunks, order them deliberately, and measure whether each increase in context improves the answer.
What are the main techniques for keeping a long-running agent's context under control?
Context control starts with tracking the token budget on every iteration instead of waiting for the window to fill. Keep a small set of strong evidence and place the best material where the model is likely to use it. Older turns can be compacted into decisions, constraints, and pending work. Tool results should contain only the fields needed for the next step, while bulky state can live outside the window and be loaded through a reference when needed.
Why should retrieval cache keys be based on processed query text instead of raw embeddings?
Processed query text and its transformation version are readable, deterministic inputs. Raw embedding bytes change with the model and hide why two entries differ. A translation-version change should produce a new key, and the embedding model version still belongs in the retrieval key because it affects ranking.
Why is response caching riskier than embedding caching?
A fixed text-and-model pair produces the same embedding. A response also depends on the prompt template, retrieved evidence, permissions, and generation model. Any of those can change independently, so a response key is easier to under-specify and harder to invalidate safely.
When is semantic caching safe to deploy, and when should it be avoided?
Semantic caching is defensible when the domain is narrow, queries repeat, false positives have low cost, and a held-out set supports a stable threshold. It should be avoided when a wrong answer can cause harm, conversation state changes meaning, or no threshold separates safe reuse from false hits.
Why does parent-child chunking often improve answer completeness over child-only retrieval?
Small children give the retriever a precise target, but a child may omit an adjacent exception or prerequisite. Expanding a match to its parent restores that surrounding evidence before generation. The price is more context and possibly more noise, so parent size still needs evaluation.
When should a team move from recursive to structure-aware chunking?
Move when failed queries repeatedly trace to broken tables, code blocks, or clause-and-exception pairs. Recursive splitting understands separators, not the source format’s actual structure. A parser is justified once that blind spot costs more than maintaining it.
Why is semantic chunking not always superior to simpler rule-based approaches?
It embeds spans during ingestion and relies on a threshold whose distribution changes with the model and corpus. Reliable headings already provide cheaper boundaries. Semantic splitting is useful when topic changes are real but structural markers are absent.
How does monitoring differ from evaluation in a RAG system, and why are both needed?
Evaluation gates a candidate pipeline against a controlled dataset before release. Monitoring observes live traffic after release, where query distribution and source data keep changing. Production failures found through monitoring should become new evaluation cases. Without that loop, evaluation misses new incidents and monitoring keeps rediscovering old ones.
Why does query translation often improve recall but sometimes hurt precision, and how can the tradeoff be detected?
Query translation raises recall by searching more phrasings, but it lowers precision when a rewrite adds concepts or changes constraints. Measure Recall@k and Precision@k before and after translation, split by query type. If Recall@20 rises while Precision@5 falls, coverage improved but the generator receives worse evidence. Keep the original query in the candidate set and reject variants that change material constraints.
When is decomposition a better choice than multi-query, and when does it hurt?
Decomposition fits distinct sub-problems that need different evidence, such as a comparison or a timeline assembled from several sources. Multi-query fits one intent expressed with uncertain vocabulary. Decomposition hurts when the split removes the constraints connecting the pieces. It fits only when each sub-question can be answered independently and those answers can support the original request. It also adds a synthesis call, while multi-query adds only retrieval work.
When is GraphRAG a better fit than plain vector retrieval?
GraphRAG earns its cost when answers depend on explicit entity relations or paths across many documents. Compliance tracing and architecture impact analysis are examples. Plain vector retrieval remains better for independent fact lookup because it avoids a graph extraction pipeline.
Why should advanced RAG patterns be introduced incrementally instead of all at once?
Every added stage creates another place for quality, latency, or cost to regress. Introduce one pattern against a measured baseline, then keep it only if it fixes a frequent failure. Shipping several together makes attribution difficult and often leaves expensive machinery with no proven benefit.
How can retrieval and generation failures be separated when a RAG answer is wrong?
The first check is the context that reached the model. If the relevant evidence is missing, the problem is in retrieval, such as chunking, filtering, or ranking. If the evidence is present but the answer ignores or contradicts it, the problem is in generation and faithfulness. These stages need separate metrics because their fixes are different, while one end-to-end score only shows that the final answer failed.
Why can reranking improve offline nDCG without visible quality improvement for end users?
The changed positions may sit outside the generator’s context. When generation uses only three chunks, a better ordering at positions 4–5 cannot affect the answer. Compare the actual top-k composition as well as overall nDCG, and check whether the evaluation queries resemble production traffic.
When does reranking hurt retrieval quality instead of helping?
A reranker trained on short web passages may misjudge long technical documents or unfamiliar terminology, then demote the relevant evidence. A small candidate set creates a different failure: low first-stage recall leaves only noise to reorder. Compare recall and precision before and after reranking on domain queries.
When does hybrid retrieval perform worse than single-mode retrieval?
It loses when the weaker path adds more noise than useful evidence. A homogeneous scientific corpus may already suit vector search, while keyword results pull marginal matches into the fused list. The extra system earns its cost only if it beats both single-mode baselines on real queries.
Why do vector databases use approximate nearest-neighbor search instead of exact search?
Exact search scores the query against every stored vector, so its work grows linearly with the collection. An ANN index organizes the vectors so a query visits only the most promising parts of the search space, which cuts latency and compute. The tradeoff is that it can miss some true nearest neighbors, so recall must be compared with brute-force ground truth at the required latency.
Why can aggregate retrieval metrics improve while individual user segments degrade?
An average hides which queries improved and which regressed. A gain on a large, easy segment can outweigh a severe drop for one tenant or language. Slice metrics along dimensions that change the retrieval distribution, and treat a material segment drop as a failure. The useful granularity is the smallest one with enough labeled examples to produce a stable signal.
Given high Faithfulness (0.91) and low Context Recall (0.54), which pipeline layer should be fixed first, and why?
Faithfulness of 0.91 says the model usually uses the context it receives. Context Recall of 0.54 says retrieval omits much of the required evidence, so retrieval is the first bottleneck to test. Review filters, hybrid search, k, and embedding fit before changing generation prompts. Re-measure both scores afterward because higher recall can add noise and lower faithfulness, which may then justify re-ranking.
Why decompose RAG evaluation into separate retrieval, generation, and end-to-end layers?
The layers fail for different reasons and need different fixes. Retrieval scoring shows whether relevant evidence arrived. Generation scoring checks whether the answer follows that evidence, while the end-to-end score records whether the task was solved. This separates a model that ignores a good context from one that faithfully summarizes irrelevant chunks. A single score makes both failures look the same.
What belongs in RAG evaluation specifically versus general LLM evaluation?
RAG adds retrieval relevance, ranking quality, and faithfulness to the evidence placed in context. It also needs labels for queries with several acceptable chunks. Golden sets, deterministic checks, semantic judges, and online experiments remain general LLM evaluation machinery. Reusing that shared layer prevents every RAG pipeline from inventing its own evaluation system.
When several chunks are relevant to one query, how should the retrieval metric be chosen?
Classify the evidence relationship first. Substitutable chunks need HitRate@k and often MRR because any early hit succeeds. Complementary chunks need Recall@k or Context Recall because the whole required set matters. If usefulness varies, graded labels and nDCG@k preserve that distinction. The cutoff must match what generation consumes and cannot be smaller than a complementary ground-truth set.
Why do synthetically generated retrieval eval sets often report worse recall than the system delivers in production?
Chunk-anchored generation initially labels only its source, even when duplicated or summary chunks also answer the question. Retrieval can return valid evidence that the labels call wrong, depressing the score and even reversing model rankings. Retrieve a candidate set, judge other valid answers, and expand the qrels or discard ambiguous cases. Lexical leakage creates the opposite bias by copying rare source terms and making exact match unrealistically easy.
When should a team invest in a human-annotated golden set versus relying on synthetic generation?
Synthetic cases are enough to bootstrap breadth and test the harness. Human annotation becomes necessary when labels require domain judgment, the failure cost is high, or synthetic phrasing no longer matches production traffic. A practical set combines generated coverage with reviewed incidents and a curated regression subset in Golden Test Set and Regression Runs.
What is the minimum useful set of deterministic checks for a tool-using agent?
Start with the contracts that can prevent irreversible harm: tool permission, argument validation, and output schema. Add data-loss prevention, citation, rendering, or length rules only when the product contract requires them. Each rule needs an explicit failure policy.
When does LLM-as-a-judge fit better than a deterministic metric, and what evidence makes the judge trustworthy?
Use a judge when acceptance depends on meaning that fixed rules cannot capture. Keep exact constraints in deterministic checks. Trust comes from measured agreement with human labels, stability under answer-order swaps, and repeated calibration after the judge changes.
What does harness engineering control that context engineering does not?
Context engineering decides what information the model receives. Harness engineering decides what the runtime allows the model to do with that information: which tools are exposed, what permissions they have, how calls are validated, and where execution is sandboxed or stopped for approval. Tool schemas and results connect the two areas because they consume context, but capability and permission decisions still belong to the harness. Loop engineering then controls whether another turn is allowed.
Why does harness quality deserve as much investment as prompt quality for agent reliability?
Agents reuse the harness on every iteration. An ambiguous tool or vague error can redirect one step and then contaminate every later step. A precise contract helps the model recover, and repairing it improves every run that shares the surface.
When should a tool remain an in-process function instead of becoming an MCP server?
Function calling and MCP are not direct alternatives, because an MCP tool can still be presented to the model as a function. The real decision is whether the tool needs a separate reusable server boundary. Keeping it in process is simpler when it belongs to one application, depends on private application state, or would expose sensitive business logic through a broader interface.
What additional security risks appear when a model uses tools from an MCP server?
An MCP server still has the usual API risks, but its tool names and descriptions also become model context, and the model may request an operation after reading untrusted content. With HTTP, MCP authorization can restrict access to the server and enforce OAuth audience and scopes; insufficient scope can trigger another authorization step. Those scopes do not automatically define whether a caller may run each business operation or access a particular tenant’s data. The server still needs per-tool authorization, input validation, tenant isolation, rate limits, and audit logging.
Why do hierarchical instruction files often outperform a single giant root file?
The root file can hold rules that apply to the whole repository, while a local file adds the build command or dependency rule for one subtree when the runtime supports path-based loading. This keeps unrelated guidance out of the model’s context and makes the active instructions more specific to the files being changed. The hierarchy works only when precedence is clear and local rules refine the root instead of silently contradicting it.
What controls reduce production risk when adopting coding agents?
Start by limiting the task, the tools the agent can call, and the number of iterations it can run. Destructive or external actions need code-enforced approval before execution, while hooks and CI should run deterministic checks. Repository instructions capture architectural rules that tests cannot enforce. Before merge, the diff still needs review and the claimed behavior needs fresh test or build evidence, regardless of which model produced the change.
Why is tool design often more impactful than prompt engineering in agentic systems?
A tool contract is reused at every call site and every loop iteration. Ambiguous selection, malformed arguments, or an opaque error can redirect all later steps. Fixing the interface removes that failure mode across prompts, while a prompt workaround depends on the model remembering an exception each time.
What determines whether related operations should use one broad tool or several narrow tools?
Separate tools make sense when operations need different descriptions, input schemas, or permissions. Closely related actions can stay together when one clear contract covers them without a long list of unrelated optional parameters. Narrow tools make arguments easier to generate, but a large catalog makes tool selection harder. Routing or filtering can limit the tools shown for each request, and selection and argument accuracy should be measured separately.
What does ReAct add to chain-of-thought reasoning when a task needs external knowledge?
Chain-of-thought reasoning still works with information already available to the model. ReAct can pause that reasoning to search, call an API, or run a calculation, then use the observed result in the next decision. This helps when the answer depends on current or missing evidence, but it does not guarantee a better result. Each action adds another model round trip, grows the context, and creates another place for a tool or interpretation error.
Which safeguards must be enforced outside the model, and what failure does each contain?
A per-request iteration cap contains non-terminating behavior. Token and cost budgets stop context growth before it becomes an outage or an unexpected bill. Tool validation blocks unknown functions and malformed arguments before they reach a side-effecting boundary. Prompt instructions can help, but none of these controls can depend on the model obeying them.
What does loop engineering add on top of a model with tools, and why is it its own discipline?
A model with tools can still be a single call. Loop engineering adds the runtime that repeats observe → decide → act until a checkable stop condition. It owns budgets and verification between turns, which determines whether an early mistake is corrected or amplified.
What are the main ways to bound a loop, and why are prompt-level stop instructions not enough?
The runtime needs a hard iteration cap, a cumulative token or cost budget, and a stop condition it can verify. Prompt instructions are advisory, so they cannot replace those limits. At the cap, return an explicit partial result or escalate instead of truncating silently. The unbounded run documented in Agent Loop shows the failure mode: 369 repeated tool calls consumed 9.7M tokens without converging.
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 and what a summary must preserve. Loop engineering decides when to compact, offload artifacts, or stop. Durable state such as a plan file bridges them by surviving compaction and keeping later iterations tied to the original goal.
When is multi-agent coordination worth the added complexity?
Multi-agent coordination makes sense when separate contexts keep unrelated work apart, independent tasks can run in parallel, or specialists need tools or instructions that would conflict inside one agent. The extra agents also mean more tokens, handoffs, and traces to debug, so the design should show a clear improvement in quality or latency over a single agent.
What is the benefit of keeping related context with one agent?
Keeping related evidence and decisions in one working context lets the agent complete connected work without rebuilding the same background after each handoff. Splitting that work between agents creates more transfers and more chances to lose details. The tradeoff is that each agent owns a broader part of the work.
What makes multi-agent failures difficult to trace?
A bad output can pass through several agents before the failure becomes visible. A handoff may contain a false claim that looks valid, and parallel agents may repeat that claim in several places. Debugging therefore needs the full handoff history, the source of each artifact, and limits such as token or turn budgets for the whole run, not just separate agent logs.
How should a task move from zero-shot to one-shot or few-shot prompting?
Zero-shot is the baseline when instructions and labels define the task clearly. One example is useful when the main failure is the output shape. Few-shot prompting earns its extra tokens when examples express decision boundaries better than prose. The final prompt should keep the smallest demonstration set that wins on a representative evaluation set, because every example adds cost and another artifact that can drift.
What are the main failure modes of few-shot prompting?
Few-shot behavior can shift when examples are reordered, reformatted, or run on a different model. Long demonstrations also consume context, while examples that do not match production traffic create false confidence. Examples cannot reliably supply missing external facts or control a long multi-step process. Those failures call for retrieval, decomposition, or training rather than simply adding more shots.
When is prompt chaining a better fit than one large prompt?
Chaining fits when a task has real stages and an intermediate result changes what should happen next. Each stage should have a clear output contract, so invalid data can be stopped before it reaches the following call and failures can be traced to one step. That control has to justify the extra latency, token cost, and risk of errors passing between calls. If the task is simple and no intermediate result needs separate validation, one prompt is usually easier to operate.
What is a practical meta prompting workflow for improving a weak prompt?
A useful workflow starts with real failures rather than a general request to make the prompt better. Group the failures by cause, then ask for a revision that addresses those causes and states a clear output contract. Compare the candidate with the current prompt on held-out cases, not only on the examples used to create it. Version the prompt and evaluator together, and keep a rollback threshold in case the revision fixes known failures but hurts normal inputs.
Why must prompt structure and model settings be designed together?
The prompt defines the task, context, constraints, and expected output, while model settings control sampling and output limits. Clear instructions can still produce unstable results when sampling is too random for the task. Conservative settings cannot repair an ambiguous instruction or a missing output contract. Both parts should be tested together on the same task-specific evaluation set because changing either one can change quality, consistency, latency, and cost.
When is few-shot prompting a better fit than pure instruction prompting?
Few-shot prompting is useful when the expected meaning, label boundary, or output convention is easier to demonstrate than to describe. A small set of representative examples can show how close cases should be handled and make repeated outputs more consistent. Examples do not replace a schema when the shape must be strict. Start with the smallest set that improves held-out results, then add edge cases only when evaluation shows a real gap because every example consumes context.
How can an accurate but verbose and expensive prompt be tightened?
First identify whether the cost comes from repeated instructions, unnecessary examples, oversized context, or a verbose output contract. Remove duplicated input and state the required length and shape directly.
max_tokensis a safety cap rather than the main way to request a concise answer because a low cap can truncate a valid response; stop sequences help only when the output has a reliable delimiter. Compare token usage, task quality, and truncation or failure rates after each change so a cheaper prompt does not quietly become less accurate.
When does Chain-of-Thought help, and when can it make a result worse?
Chain-of-Thought can help when a task has intermediate state that can be expressed as a sequence of smaller decisions. It adds little to simple retrieval or extraction, where the extra tokens mainly create more room for drift. A wrong assumption near the start can shape the rest of the trace, and a fluent explanation still does not prove the answer is correct. The technique earns its cost when the intermediate steps improve measured results or expose something that can be checked independently.
When does Tree of Thoughts justify its cost over Chain-of-Thought or self-consistency?
Tree of Thoughts fits a real search problem where candidate states can be generated, evaluated, revisited, and abandoned when they lead to a dead end. Planning and combinatorial search can have that shape; extraction usually does not. Its cost grows with the branching factor and depth, and a weak evaluator can keep the wrong branches. If a direct call or deterministic tool already meets the evaluation target, the tree adds work without improving the result.
What is the minimum useful guardrail set for a production LLM application?
Start with task-scoped tools, authorization outside the model, strict output parsing, request budgets, and a refusal or escalation path. Add policy classifiers and human approval where the harm warrants their latency. Prompt-injection detection is useful telemetry, but it cannot replace the permission boundary.
Why can an LLM still hallucinate when using RAG?
RAG gives the model context, but it does not prove that the answer follows from it. The source may not contain the fact, retrieval may miss the right passage, or the model may ignore the passage and add unsupported details. Retrieval quality and answer faithfulness should be measured separately so the failing stage is clear.
Why can preference tuning make an answer sound better without making it more factual?
Preference data may reward agreement, confidence, or style without checking whether a claim is supported. The model can then produce a polished answer that accepts a false premise. Factual accuracy and calibration still need separate evaluation from preference scores.
Why is prompt injection fundamentally harder to prevent than SQL injection?
Parameterized SQL gives the database a deterministic split between code and data. A language model interprets both through the same learned behavior, so a delimiter cannot provide the same guarantee. The practical defense contains successful injections with narrow permissions and validated downstream operations.
Why should system prompts be treated as public rather than secret?
Prompt extraction cannot be ruled out, so secrecy is an unsafe dependency. A disclosed prompt should reveal no credential and grant no permission. RBAC and tool authorization remain effective even when every instruction is known.
How do security failures and reliability failures differ, and why does the distinction matter?
The OWASP Top 10 describes conditions an adversary can exploit, so isolation and least privilege limit their impact. The same controls can contain accidentally triggered excessive agency or data disclosure. Hallucinations need no attacker. They arise when likely text outruns the available evidence. Grounding and verification address that reliability problem, though one incident may cross both categories.
Why are prompt-level guardrails insufficient on their own?
A model instruction is advisory and can fail when untrusted data is interpreted as guidance. Durable controls live outside the model: input validation, content isolation, sandboxing, permission gates, and output checks. Harness Engineering places critical action gates where a prompt cannot bypass them.
What is the difference between data drift and concept drift?
Data drift changes P(X), such as a new language appearing in support traffic. Concept drift changes P(Y|X), such as yesterday’s fraud cues becoming normal behavior. The first can leave the model useful. The second means its learned mapping no longer describes current outcomes. Recent labeled performance separates a harmless input shift from a model that needs updating.
What does ROC-AUC show about a classifier?
ROC-AUC shows how well the classifier ranks positive cases above negative cases across all thresholds. For example, an ROC-AUC of 0.8 means a randomly chosen positive receives a higher score than a randomly chosen negative about 80% of the time, with a tie counted as half. It does not choose a production threshold or show whether the predicted scores are calibrated probabilities.
What types of classification problems are better evaluated with PR-AUC than ROC-AUC?
PR-AUC is usually more useful when the positive class is rare and the system acts on positive predictions, as with fraud alerts or anomaly review. In these cases, false positives fill the review queue, and precision shows that cost directly. The score must still be compared with the positive rate in the evaluation data because the PR-AUC baseline changes with prevalence.
What learning signal distinguishes supervised, self-supervised, and reinforcement learning even when all three use a neural network?
Supervised learning receives an external target for each example. Self-supervised learning derives a proxy target from the input itself, such as a hidden or next token. Reinforcement learning receives rewards from actions in an environment and optimizes return across a trajectory. The architecture does not determine which learning problem is being solved.
How are symmetric and asymmetric cryptography used together in a real system?
Symmetric authenticated encryption handles bulk data because it is efficient and protects both confidentiality and integrity. Public-key encryption is limited to the small payloads and protocols it was designed for. Signatures provide public verification, while authenticated key agreement establishes a fresh shared secret. Protocols such as TLS and envelope encryption combine these roles instead of choosing one family for the whole job.
Why is SHA-256 alone unsuitable for password storage?
SHA-256 is fast and gives an offline attacker a cheap guess test. Password verifiers need a unique salt and an adaptive password-hashing function whose CPU and memory costs are tuned for the deployment and raised as hardware improves.
How do a hash, an HMAC, and a digital signature differ?
A hash is keyless. Adversarial integrity depends on authenticating the expected digest. HMAC authenticates within a shared-secret group, where every verifier can also create tags. A digital signature gives one private-key holder the signing capability while allowing public verification, provided the verification key is trusted.
What does a salt protect against, and why must it be unique per user?
A salt prevents one precomputed table or one computed guess from being reused across many stored verifiers. It is public and stored with the verifier. Each credential needs a distinct random salt so equal passwords do not produce equal stored values.
How should a .NET application prevent SQL injection?
Keep untrusted values out of SQL text by using parameterized APIs. Dynamic identifiers such as a sort column cannot normally be parameters, so map them from a closed allowlist. ORM safety depends on the exact API: LINQ and parameterizing raw-SQL methods differ from APIs that accept already constructed SQL text. Least-privilege database permissions reduce impact but do not repair concatenation.
What protection does a pepper add, and what does it cost?
A pepper kept outside the password table can prevent a database-only thief from testing guesses. It creates another high-value secret and a rotation problem: replacing it for an existing verifier normally requires the user’s password or a reset. It cannot compensate for a fast KDF.
What should happen when a secret is accidentally committed and pushed?
Treat the secret as compromised and revoke or rotate it immediately. Update the affected workloads through the normal secret-delivery path, then verify that the old value is rejected. Check access logs and usage for signs of abuse. Removing the value from the current file and rewriting repository history can reduce future exposure, but neither action replaces revocation because copies may already exist in clones, caches, logs, or alerts.
How do authentication and authorization work together when handling a request?
Authentication establishes who or what is making the request and produces a principal. Authorization then checks whether that principal may perform the requested action on the specific resource in the current context. A valid identity does not grant access to every resource, so both checks are required.
Why is Basic Auth unsafe over HTTP?
Base64 is reversible encoding. Without TLS, every observer on the path can recover the reusable password. TLS protects it only to the termination point. The credential remains exposed to the client, verifier, trusted proxies, and any system that records the header.
When is Basic Auth acceptable in production?
It can be acceptable for a constrained integration that requires Basic and supports TLS across every hop, a unique vaulted credential, narrow authorization, rotation, rate limiting, and header redaction. A shared password across many clients or an interactive user’s primary password creates an unnecessarily large compromise boundary.
What is the difference between role-based and resource-based authorization?
Role-based authorization evaluates assigned roles without needing the target instance. Resource-based authorization includes the loaded object’s attributes or relationships in the decision. Roles work well as coarse gates. Resource checks enforce ownership, tenancy, or object state.
What separates continuous delivery from continuous deployment?
Both approaches keep a tested artifact ready for production. With continuous delivery, releasing that artifact is still a separate decision, often an approval or a business-controlled step. With continuous deployment, every change that passes the required automated checks is released automatically.
What is the difference between a Pod, Deployment, and Service?
A Pod is the scheduled execution unit. A Deployment keeps the desired number and revision of replaceable Pods present. A Service supplies stable discovery and routes to selected ready Pods. They solve execution, reconciliation, and discovery separately.
Why should request or user IDs not be metric labels?
Each unique label combination creates another time series. Unbounded identifiers can multiply storage and query work with traffic volume. Keep metric dimensions bounded and use the trace ID to reach access-controlled logs or traces when an individual request must be inspected.
When does canary fit better than blue-green deployment, and what does each approach cost?
Canary fits when a small but representative production cohort can reveal regressions and its results can be measured separately. Blue-green fits when a complete candidate environment must be tested before one traffic switch. Canary adds gradual routing, cohort analysis, and a longer overlap between versions. Blue-green mainly adds the cost of running duplicate capacity.