Design pattern questions at the architect level rarely ask "what is the Factory pattern" — they ask you to point at where a pattern already lives in code you use every day, or to choose between two plausible patterns for a scenario with real trade-offs. Interviewers are screening for engineers who reach for a pattern because it solves a recognized, recurring problem, not because a name sounds impressive on a design document. This page works through the patterns most likely to come up in a .NET architecture loop — where they already live in the BCL and ASP.NET Core, how to recognize when a pattern has been misapplied, and how to reason out loud when asked to design something from scratch.

Q1 Where in the .NET base class library and ASP.NET Core would I find the Strategy pattern already in use, without anyone calling it that?#

Short answer: Strategy is one of the most common patterns hiding in plain sight in .NET: IComparer<T> and IEqualityComparer<T> let you swap comparison behavior into Sort, OrderBy or a Dictionary without changing the algorithm that uses them, and Polly's v8 ResiliencePipeline composes retry, timeout and circuit-breaker strategies as interchangeable, independently configurable units behind one execution API.

The shape is always the same: an interface capturing one varying behavior, several interchangeable implementations, and a context class that holds a reference to the interface and delegates to it instead of hard-coding the behavior. List<T>.Sort(IComparer<T>) is the textbook case — the sort algorithm is fixed, but the ordering rule is injected. Polly v8's ResiliencePipelineBuilder is a more modern instance of the same idea, letting you compose named strategies into one pipeline and execute arbitrary code through it:

C#
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions())
    .AddTimeout(TimeSpan.FromSeconds(10))
    .Build();

await pipeline.ExecuteAsync(static async ct => await CallDownstreamAsync(ct), cancellationToken);

Recognizing Strategy in the BCL matters because it shows you understand the pattern as a shape, not a name you memorized from a catalog — the same shape appears in a custom IDiscountStrategy for pricing tiers or an IShippingCostCalculator per carrier.

What interviewers look for: at least one concrete BCL or framework example named without prompting, and the ability to connect it to the same shape you would design by hand.

Q2 Show how ASP.NET Core's middleware pipeline is really the Decorator pattern, and explain the difference between Decorator and Chain of Responsibility using it as the example.#

Short answer: Each ASP.NET Core middleware component wraps the RequestDelegate for everything after it, optionally doing work before and after calling next() — that "wrap the same interface and add behavior around a call to the wrapped thing" shape is Decorator; the pipeline is simultaneously Chain of Responsibility because each link can also choose not to call next() at all and short-circuit the request, which pure Decorator does not model.

The distinction that matters: a strict Decorator always delegates to the wrapped object and only adds behavior before or after — think of an ILogger decorator that times a call and always forwards it. Chain of Responsibility is about a sequence of handlers where each one decides whether to handle the request itself, pass it further down the chain, or stop the chain entirely — an authentication middleware that returns 401 and never calls next() is terminating the chain, which is not something a well-behaved decorator does.

C#
app.Use(async (context, next) =>
{
    var sw = Stopwatch.StartNew();
    await next(context);           // Decorator-style: always forwards, wraps around it
    logger.LogInformation("Request took {Elapsed}ms", sw.ElapsedMilliseconds);
});

app.Use(async (context, next) =>
{
    if (!context.Request.Headers.ContainsKey("X-Api-Key"))
    {
        context.Response.StatusCode = 401;
        return;                    // Chain of Responsibility: short-circuits, never calls next
    }
    await next(context);
});

What interviewers look for: identifying that ASP.NET Core middleware is architecturally closer to Chain of Responsibility than pure Decorator, because short-circuiting is a first-class, expected behavior, not an edge case.

Common mistakes: calling middleware "just Decorator" without acknowledging that unconditional forwarding is not guaranteed, which is the actual distinguishing feature between the two patterns.

Q3 Compare the classic GoF Factory Method and Abstract Factory patterns to how DI containers actually solve the same problem. When do you still need an explicit factory alongside DI?#

Short answer: IServiceProvider and constructor injection cover most of what Factory Method and Abstract Factory exist for — the container decides which concrete type to construct for a requested abstraction — but DI containers resolve dependencies at a fixed point (usually once per scope), so you still need an explicit factory whenever a dependency needs a runtime parameter the container cannot know in advance, or when you must create several related objects at once.

A typed factory delegate, Func<string, IPaymentGateway> registered against a switch inside the composition root, or a dedicated IPaymentGatewayFactory.Create(string provider), covers the "pick an implementation based on a value only known at call time" case that plain constructor injection cannot express, since the container resolves the dependency graph before that runtime value exists. IServiceScopeFactory covers a related but distinct case: creating a new DI scope on demand, typically from a singleton background service that needs a fresh scoped DbContext per unit of work, which is Abstract Factory in spirit — one call produces a coherent family of related objects, the services registered for that scope.

C#
public interface IPaymentGatewayFactory
{
    IPaymentGateway Create(string provider);
}

public sealed class PaymentGatewayFactory(IServiceProvider services) : IPaymentGatewayFactory
{
    public IPaymentGateway Create(string provider) => provider switch
    {
        "stripe" => services.GetRequiredService<StripeGateway>(),
        "paypal" => services.GetRequiredService<PayPalGateway>(),
        _ => throw new NotSupportedException(provider),
    };
}

What interviewers look for: recognizing DI as having absorbed most of Factory Method's job, plus a specific, runtime-parameter-driven reason an explicit factory is still needed on top of it.

Q4 MediatR is often shorthand for "the Mediator pattern" in .NET architecture interviews. What problem does Mediator actually solve, and what has changed recently in the ecosystem?#

Short answer: Mediator decouples a set of senders and receivers that would otherwise need direct references to each other by routing all communication through one central object, so a request handler and the code that triggers it never need to know about one another directly; MediatR, the library most associated with the pattern in .NET, introduced commercial licensing requiring a paid key for many usage scenarios, which has pushed some architects to re-evaluate whether they need the library at all versus a much smaller hand-rolled dispatcher.

The pattern itself solves a real problem in codebases with many cross-cutting concerns per operation: instead of a controller directly calling a service, which calls validation, which calls logging, Mediator lets you register pipeline behaviors, such as validation and logging, that wrap every request uniformly, and it keeps the controller's only dependency as ISender, not a growing list of service interfaces. The risk architects should name unprompted is treating Mediator as the default way to call any method, even trivial ones — routing a single-line CRUD operation through a request, a handler class, and a pipeline adds real indirection for zero benefit over calling a method directly. With MediatR's licensing change, teams facing a straightforward in-process request/handler need increasingly implement a minimal IRequestHandler<TRequest, TResponse> dispatcher themselves, register handlers via IServiceCollection, and reserve the decision to adopt a full library for cases that actually need its pipeline behavior pipeline, not just the routing.

What interviewers look for: the decoupling-of-senders-and-receivers definition stated precisely, awareness of the MediatR licensing change as a current, practical fact rather than assuming it is still purely free and open source, and a clear-eyed view of when Mediator is overkill.

Common mistakes: treating "we use MediatR" as equivalent to "we have a clean architecture," when a mediator can just as easily hide an anemic, procedural design behind an extra layer of indirection.

Q5 Explain the Observer pattern and how it shows up in .NET beyond the event keyword.#

Short answer: Observer defines a one-to-many dependency where subject state changes are pushed to every registered observer without the subject knowing their concrete types; besides C#'s built-in event/delegate mechanism, it appears in IObservable<T>/IObserver<T> and Reactive Extensions, in INotifyPropertyChanged for MVVM data binding in Blazor, MAUI and WPF, and architecturally in domain events raised by an aggregate and handled by one or more subscribers.

IObservable<T> formalizes Observer with a contract the event keyword does not enforce on its own: Subscribe returns an IDisposable used to unsubscribe cleanly, and Rx operators let you compose, filter and throttle a stream of pushed notifications instead of wiring raw event handlers by hand — useful for something like debouncing rapid UI input or combining multiple sensor readings. INotifyPropertyChanged is Observer applied to a single object's properties: a view model raises PropertyChanged, and a data-bound UI component observes it to know when to re-render, without the view model holding any reference to the UI at all. Domain events are the same shape at the architecture level — an aggregate raises OrderShipped, and one or more handlers, such as a notification sender and an analytics recorder, react independently, which keeps the aggregate from needing to know who cares about its state changes.

What interviewers look for: naming at least two non-event manifestations of Observer, and the ability to explain why decoupling the subject from concrete observer types matters, not just that notifications get sent.

Follow-up questions:

  • How would you unsubscribe correctly from an IObservable<T> to avoid a memory leak in a long-lived subject?

Q6 When would you reach for the Adapter pattern in a .NET codebase, and how does it differ from a Facade?#

Short answer: Adapter translates one interface into another interface that calling code already expects, typically to make a third-party SDK conform to an abstraction your own domain defines; Facade instead simplifies a subsystem's own already-consistent API surface by exposing one smaller, easier entry point, without pretending to be a different interface the subsystem was never designed to have.

A concrete Adapter case: your domain defines INotificationSender.SendAsync(Notification n, CancellationToken ct), and a third-party SMS SDK exposes a completely different shape, SmsClient.Dispatch(string to, string body, SmsOptions options) — an adapter class implements INotificationSender and internally calls the SDK, translating your domain type into the SDK's expected parameters. This is the same shape as an anti-corruption layer at integration boundaries: it protects the rest of the codebase from being written directly against a vendor's API shape, so swapping SMS providers later only means writing a new adapter. Facade, by contrast, does not translate between two different expected interfaces — it just reduces a subsystem's own several-step API (open a connection, begin a transaction, execute three calls, commit) into one method, without any interface mismatch to bridge.

C#
public sealed class SmsNotificationAdapter(SmsClient client) : INotificationSender
{
    public Task SendAsync(Notification n, CancellationToken ct) =>
        client.Dispatch(n.RecipientPhoneNumber, n.Body, new SmsOptions());
}

What interviewers look for: the translation-versus-simplification distinction stated precisely, since candidates frequently use "wrapper" for both without noticing they solve different problems.

Q7 Walk through a real use of the Builder pattern in .NET. Where does WebApplicationBuilder fit, and when is a builder overkill for a POCO?#

Short answer: Builder separates the step-by-step construction of a complex object from its final representation, which is exactly what WebApplicationBuilder does — it accumulates configuration, services and logging setup across many calls before Build() produces an immutable, fully assembled WebApplication; for a simple data-transfer object with two or three properties and no construction-order dependencies, a builder is unnecessary ceremony that an object initializer, a record, or a primary constructor already handles more simply.

Builder earns its cost when construction genuinely has ordering constraints, optional steps that interact with each other, or validation that only makes sense once several pieces are in place — WebApplicationBuilder fits because registering services, configuring the host, and setting up logging must all happen before the immutable pipeline is built, and getting the order wrong (using a service before it is registered) is a real class of bug the pattern helps prevent by making Build() a single, explicit finalization step. A record with a handful of init properties or a primary constructor accomplishes the same "construct once, then treat as immutable" goal for simple shapes without needing a separate builder class, a fluent chain of With... methods, or a Build() step — reaching for Builder there just adds a parallel type to maintain for no ordering or validation benefit the language's own constructors don't already provide.

What interviewers look for: citing WebApplicationBuilder or an equivalent fluent host/options builder unprompted, and drawing a clear line for when records or primary constructors are the better, simpler tool.

Q8 What's the Specification pattern, and how do you use it to avoid duplicating LINQ query logic across a codebase using EF Core?#

Short answer: Specification encapsulates a reusable, composable business rule or query predicate as an object rather than an inline lambda scattered across the codebase, typically exposing an Expression<Func<T, bool>> that EF Core can translate to SQL, plus optional includes and ordering — so "active, non-deleted customers in a given region" is defined once and reused everywhere it is needed instead of being retyped, and potentially drifting, in five different query methods.

The value shows up once the same filter needs to combine with different other filters in different call sites: a specification exposing its criteria as an Expression<Func<T, bool>> lets you compose two specifications together with AndAlso/OrElse via an expression visitor, or a small library such as Ardalis.Specification, instead of duplicating the combined boolean logic inline in each query. It also creates one place to update a business rule — if "active" later needs to also exclude suspended accounts, updating the specification updates every query that uses it, instead of hunting down every inline Where clause across the codebase that encoded the same rule slightly differently.

C#
public sealed class ActiveCustomersInRegion(string region) : Specification<Customer>
{
    public override Expression<Func<Customer, bool>> ToExpression() =>
        c => c.IsActive && !c.IsDeleted && c.Region == region;
}

var customers = await dbContext.Customers
    .Where(new ActiveCustomersInRegion("EU").ToExpression())
    .ToListAsync(cancellationToken);

What interviewers look for: recognizing Specification as a response to duplicated, drifting query logic specifically, not a pattern to apply to every single query regardless of reuse.

Q9 Name three design-pattern anti-patterns you've seen misapplied in enterprise .NET codebases, and how you'd recognize them in a code review.#

Short answer: Three recurring ones: a generic repository wrapped around an already-abstracted DbContext, which duplicates EF Core's existing Unit of Work and query abstraction for no new benefit; Singleton misused as a global mutable state container instead of a genuinely stateless, thread-safe shared resource; and "mediator-for-everything," where trivial single-line operations get routed through a request/handler pair purely because the codebase has standardized on MediatR.

The generic repository case is worth calling out specifically: IRepository<T> with GetById, GetAll, Add, Update, Delete sitting on top of DbSet<T>, which already is a repository, and DbContext, which already is a Unit of Work, typically adds an interface with exactly one real implementation and no actual abstraction benefit, while losing EF Core-specific capabilities like Include, compiled queries, or AsNoTracking unless the repository interface is expanded to leak them back through anyway. Singleton misuse shows up as a static-like class registered as a DI singleton that holds mutable request-scoped state, which then either leaks data across unrelated requests under load or requires defensive locking that erases the performance benefit a singleton was meant to provide. Recognizing these in review is mostly about asking "what does this abstraction let us do that the thing underneath it doesn't already do" — when the honest answer is nothing, the pattern was applied for its own sake.

What interviewers look for: naming specific, common anti-patterns rather than a generic "over-engineering," and a repeatable review question that surfaces them.

Q10 You're designing a plug-in system where third parties can add pricing rules at runtime. Which patterns would you combine, and why?#

Short answer: Strategy for the pricing rule contract itself, Factory (or a plugin catalog built from reflection or AssemblyLoadContext) to construct the right implementation from configuration, and Composite to aggregate multiple applicable rules into one combined discount — layered only if the domain genuinely has more than a couple of rule types and a real need for third parties to add new ones without redeploying the host application.

IPricingRule defines decimal Apply(decimal subtotal, PricingContext context); a plugin loader discovers assemblies at a configured path, loads each into its own AssemblyLoadContext for isolation, and uses reflection to find and instantiate types implementing IPricingRule, registering them into a catalog keyed by a rule identifier from configuration. A CompositePricingRule implementing the same interface holds an ordered collection of individual rules and applies them in sequence or picks the best one per a defined precedence, so the caller that needs "the final price" depends on one IPricingRule, unaware whether it is talking to one rule or twelve. The trade-off worth stating unprompted: this is real infrastructure — assembly isolation, versioning the plugin contract, and validating third-party code before loading it are all non-trivial costs — and a switch statement or a configuration-driven percentage table is the right answer if the actual requirement is "a handful of promotions our own team configures," not genuine third-party extensibility.

What interviewers look for: combining patterns coherently around a stated real requirement (third-party, runtime-loaded rules) rather than reciting a pattern list, and naming the YAGNI alternative when the requirement turns out to be smaller than "a plugin system."

Quick-Fire Round#

QuestionAnswer
Which pattern does IComparer<T> implement?Strategy — the sorting algorithm stays fixed, the comparison behavior is injected.
Is ASP.NET Core middleware Decorator or Chain of Responsibility?Both in spirit, but short-circuiting via not calling next() makes it closer to Chain of Responsibility.
What changed about MediatR that architects should track?It introduced commercial licensing requiring a paid key for many usage scenarios.
What's the key difference between Adapter and Facade?Adapter translates between two mismatched interfaces; Facade simplifies one subsystem's own consistent API.
When is WebApplicationBuilder's Builder-pattern shape justified?When construction has real ordering constraints and validation across many steps.
What does the Specification pattern prevent?Duplicated, drifting query predicates scattered across multiple LINQ queries.
Why is a generic IRepository<T> over DbContext often an anti-pattern?DbSet<T> and DbContext are already a repository and Unit of Work; the wrapper adds no new capability.
What three patterns combine well for a runtime plugin pricing system?Strategy for the rule contract, Factory for construction, Composite for aggregation.

How to Prepare#

  • Be able to point at a real Strategy, Decorator and Observer implementation in the BCL or ASP.NET Core without hesitating.
  • Practice explaining ASP.NET Core middleware as Decorator versus Chain of Responsibility precisely, since interviewers use it to test whether you understand the patterns or just their names.
  • Know the current state of the MediatR ecosystem so you don't cite it as purely free and open source by default.
  • Rehearse the generic-repository-over-EF Core anti-pattern argument, since it is one of the most common architecture debates in senior interviews.
  • Prepare one plugin- or extensibility-style design question end to end, combining at least two patterns and naming the simpler alternative you rejected.