Design Patterns Interview Questions for Senior Developers — Creational, Structural, Behavioral & When NOT to Use Them (Deep Answers)
30 senior design patterns interview questions with deep C# answers: creational, structural, behavioral patterns, CQRS, and when NOT to use a pattern.
- Author
- Randhir Jassal
- Published
- Reading time
- 19 min read
- Views
- 6 views
Junior design-pattern interviews ask "name the Gang of Four patterns." Senior interviews ask why you wrapped EF Core in a repository that adds nothing, why that Singleton is really global mutable state you can't test, and which pattern you'd delete because the abstraction costs more than it saves. Patterns are a shared vocabulary for solving recurring design problems — but a senior knows they're a means to uphold SOLID, not a checklist to apply everywhere. Over-patterning is as much a smell as no patterns at all. This guide is 30 questions across creational, structural, behavioral, and architectural/enterprise patterns — plus when NOT to use them.
Each answer is a short answer you could say in one breath, a deep dive into the mechanism (with C# code), and the senior signal the interviewer is listening for. Every example is from Mattrx — a real multi-tenant marketing-analytics platform on .NET 9, Clean Architecture + CQRS (MediatR), where patterns show up as production code (middleware, MediatR, caching decorators), not textbook diagrams. No Animal/Dog/Cat.
How to use this guide
Read the deep dive even when you know the pattern — seniority is in the trade-off, the when-not-to, and the real code it maps to. The blocks build on each other. If you can name the SOLID principle each pattern serves and its cost, you're ready.
Block A — foundations & creational patterns
1. What is a design pattern — and when should you NOT use one?
Short answer: A named, reusable solution to a recurring design problem — a shared vocabulary, not a library. You should NOT reach for one when the simple code is already clear; a pattern adds indirection, and indirection you don't need is overengineering. Apply a pattern when it removes real duplication or coupling, not to show you know it.
Deep dive: The senior instinct is "what's the simplest thing that stays open to the change I actually expect?" A pattern earns its keep by absorbing a likely change; speculative patterns just add layers.
Senior signal: You treat patterns as trade-offs (indirection for flexibility) and can say "here I'd keep it simple" — you don't pattern-match for its own sake.
2. How do design patterns relate to SOLID?
Short answer: Patterns are concrete ways to satisfy SOLID. Strategy/Decorator serve Open-Closed (extend without modifying); Dependency Injection serves Dependency Inversion; most patterns push toward Single Responsibility and interface segregation. SOLID is the why; patterns are the how.
Deep dive: If you can't name the principle a pattern upholds, you're probably cargo-culting it. Decorator exists so you can add behavior without editing the class (OCP).
Senior signal: You justify a pattern by the SOLID principle it protects, not by its GoF name.
3. Singleton — pattern or anti-pattern?
Short answer: Both. The intent (one shared instance) is fine; the classic implementation (a static Instance property) is usually an anti-pattern — global mutable state, a hidden dependency, and hard to test/mock. Prefer a DI container with singleton lifetime: one instance, but injected and swappable.
Deep dive:
// Anti-pattern: global state, hidden dependency, can't substitute in tests.
public sealed class Config { public static Config Instance { get; } = new(); }
// Better: same single instance, but injected via an interface -> testable, explicit.
services.AddSingleton<IConfig, Config>();
Senior signal: You separate the intent from the static implementation, and reach for DI singleton lifetime instead of a global.
4. Factory Method vs Abstract Factory?
Short answer: Factory Method creates one product, deferring which concrete type to a runtime decision — decoupling callers from new. Abstract Factory creates families of related products behind one interface (e.g., a whole themed UI kit). Factory Method = one thing; Abstract Factory = a related set.
Deep dive: Mattrx picks a notifier by channel — a factory keeps the domain free of concrete types.
public interface INotifier { Task SendAsync(Message m, CancellationToken ct); }
public sealed class NotifierFactory(IServiceProvider sp)
{
public INotifier For(Channel channel) => channel switch
{
Channel.Email => sp.GetRequiredService<EmailNotifier>(),
Channel.Sms => sp.GetRequiredService<SmsNotifier>(),
Channel.Push => sp.GetRequiredService<PushNotifier>(),
_ => throw new ArgumentOutOfRangeException(nameof(channel)),
};
}
Senior signal: You distinguish "one product" (Factory Method) from "a family of products" (Abstract Factory) and use a factory to keep concretes out of the callers.
5. When does the Builder pattern beat a constructor?
Short answer: When an object has many optional parameters, needs step-by-step assembly, or should be immutable at the end. Builder avoids telescoping constructors and unreadable positional args. In C#, object initializers and records cover simple cases — reach for Builder when construction has real logic or ordering.
Deep dive: A fluent builder shines for complex, validated construction (e.g., building a report definition); for a plain DTO, a record with init properties is simpler.
Senior signal: You know C# object initializers/records replace Builder for simple cases and reserve Builder for multi-step, validated, or conditional construction.
6. What is the Prototype pattern, and its modern form?
Short answer: Create new objects by cloning an existing one instead of constructing from scratch — useful when construction is expensive or you want a configured template. Modern languages give you this directly: structuredClone in JS, record with-expressions and copy constructors in C#.
Deep dive: Mattrx clones a base report config and tweaks it per tenant via a record with — Prototype without the ceremony.
Senior signal: You recognize Prototype in record with/clone APIs and use it when copying a configured template beats rebuilding it.
7. Is Dependency Injection a design pattern?
Short answer: Yes — it's the concrete technique for Inversion of Control: dependencies are supplied from outside (constructor injection) rather than created inside. It's the backbone of testability and the Dependency Inversion principle. Watch the anti-pattern cousin: Service Locator (pulling dependencies from a container inside the class) hides dependencies instead of declaring them.
Deep dive: Constructor injection makes every dependency explicit and mockable; a service locator makes them invisible and the class lie about what it needs.
Senior signal: You prefer constructor injection, name Service Locator as the anti-pattern, and tie DI to Dependency Inversion + testability.
8. When is an Object Pool worth it?
Short answer: When object creation/destruction is expensive and objects are reused rapidly — connections, large buffers, threads. Pooling amortizes allocation and reduces GC pressure. Don't pool cheap objects; the bookkeeping costs more than the allocation it saves.
Deep dive: Mattrx uses ArrayPool<byte> for large report buffers and connection pooling for the DB — pooling where allocation genuinely hurts, nowhere else.
Senior signal: You reserve pooling for expensive, high-churn objects (buffers, connections) and know pooling cheap objects is a pessimization.
Block B — structural patterns
9. What problem does the Adapter pattern solve?
Short answer: It wraps an incompatible interface so two things that weren't designed to work together can. You write an adapter that implements the interface your code expects and translates calls to the foreign API — great for isolating a third-party SDK behind your own abstraction.
Deep dive: Mattrx wraps the Stripe SDK in an IPaymentGateway adapter, so the domain talks to its interface and the vendor stays swappable.
Senior signal: You use Adapter to isolate third-party/legacy APIs behind an interface you own, keeping vendors at the edge.
10. Decorator — what does it give you over inheritance?
Short answer: It adds behavior by wrapping, not subclassing — you compose responsibilities at runtime instead of exploding a class hierarchy. Same interface in and out, so decorators stack (caching + logging + retry) in any order. It's the Open-Closed principle in action.
Deep dive: Mattrx adds caching around any repository without touching it:
// Wrap ANY IKpiRepository with caching -> no subclass, no edit to the original.
public sealed class CachingKpiRepository(IKpiRepository inner, ICache cache) : IKpiRepository
{
public Task<Kpis> GetAsync(Guid tenantId, CancellationToken ct)
=> cache.GetOrSetAsync($"kpis:{tenantId}",
() => inner.GetAsync(tenantId, ct), TimeSpan.FromSeconds(60));
}
request -> [ Logging ] -> [ Caching ] -> [ Real repository ]
(each wraps the next, same interface, composable in any order)
Senior signal: You reach for Decorator to compose cross-cutting behavior (caching/logging/retry) without subclass explosion, and note the stacking order matters.
11. What is the Facade pattern?
Short answer: A simplified interface over a complex subsystem — one entry point that hides the coordination of several classes. It reduces coupling between callers and the subsystem's internals. An application service that orchestrates several domain operations is essentially a facade.
Deep dive: Mattrx's ReportService.Generate is a facade over rendering, storage, and delivery — callers see one method, not five collaborators.
Senior signal: You use Facade to give clients one coherent entry point and keep subsystem complexity hidden and swappable.
12. What are the uses of the Proxy pattern?
Short answer: A stand-in with the same interface as the real object, controlling access to it: virtual proxy (lazy-create/load an expensive object), protection proxy (access checks), caching proxy, or remote proxy (a local stand-in for a remote service). EF Core's lazy-loading proxies are a real example.
Deep dive: Proxy and Decorator share the "same interface, wrap the target" shape; the intent differs — Proxy controls access, Decorator adds behavior.
Senior signal: You name the proxy variants and distinguish Proxy (controls access) from Decorator (adds behavior) despite the similar structure.
13. When do you use the Composite pattern?
Short answer: For tree structures where you want to treat a single object and a group of objects uniformly — a leaf and a branch implement the same interface, so client code recurses without special-casing. Think file systems, UI component trees, nested menus.
Deep dive: Mattrx models nested audience segments as a composite: a segment and a group-of-segments both answer Matches(user), so evaluation recurses naturally.
Senior signal: You reach for Composite when clients should treat one and many the same, and you know it trades a uniform interface for a slightly leaky one (leaf ops on branches).
14. What does the Bridge pattern decouple?
Short answer: It separates an abstraction from its implementation so the two vary independently — instead of a combinatorial class explosion (ShapeRedCircle, ShapeBlueSquare…), you have Shape × Color as two dimensions bridged by composition. "Prefer composition over inheritance," formalized.
Deep dive: When you feel a class hierarchy multiplying along two axes, Bridge splits them into separate hierarchies joined by a reference.
Senior signal: You spot the two-dimensional inheritance explosion and split it with Bridge (composition), so each axis evolves alone.
15. What is the Flyweight pattern?
Short answer: Share immutable, common state across many objects to save memory instead of duplicating it — store the shared ("intrinsic") state once and pass the varying ("extrinsic") state in. Classic for rendering thousands of glyphs/particles/tiles.
Deep dive: .NET string interning is a flyweight; Mattrx interns a small set of repeated event-type strings across 180M events rather than holding millions of duplicate strings.
Senior signal: You use Flyweight for massive counts of objects with heavy shared state, and know it splits intrinsic (shared) from extrinsic (passed-in) state.
16. Decorator vs Proxy vs Adapter — how do you tell them apart?
Short answer: All three wrap another object, but the intent differs: Adapter changes the interface (make incompatible fit), Decorator keeps the interface and adds behavior, Proxy keeps the interface and controls access. Same structure, three different reasons.
Deep dive: Ask "what is the wrapper doing?" — translating (Adapter), enhancing (Decorator), or gatekeeping/deferring (Proxy).
Senior signal: You classify a wrapper by intent (adapt / enhance / control), not by its shape, because the structure alone doesn't tell them apart.
Block C — behavioral patterns
17. What problem does the Strategy pattern solve?
Short answer: It lets you select an algorithm at runtime by encapsulating each variant behind a common interface — eliminating a growing if/else/switch on "what kind." Add a new behavior by adding a class, not editing the chooser (Open-Closed).
Deep dive: Mattrx prices usage with pluggable strategies keyed by plan:
public interface IPricingStrategy { decimal Price(Usage u); }
// FlatPricing, TieredPricing, VolumePricing each implement it; resolved by plan key.
// The billing service picks a strategy -> no switch on plan type in the domain logic.
Senior signal: You reach for Strategy when a switch on type keeps growing, and you add behaviors as classes rather than editing a central conditional.
18. Observer — where does it show up in real code?
Short answer: It defines a one-to-many dependency: when one object changes, its subscribers are notified. It's the basis of C# events, domain events, and reactive streams (IObservable). Publishers don't know their subscribers — that's the decoupling.
Deep dive: Mattrx raises domain events (CampaignPublished) that multiple handlers observe (analytics, webhooks, cache invalidation) without the publisher knowing them.
Senior signal: You recognize Observer behind events/domain-events/reactive, and value that the publisher stays ignorant of subscribers.
19. What is the Command pattern good for?
Short answer: It encapsulates a request as an object — turning "do X" into a Command with its data. That enables queuing, logging, retry, and undo/redo (store the inverse). MediatR requests and message-queue commands are this pattern.
Deep dive: Mattrx enqueues report generation as a command object onto Service Bus — the request becomes data that a worker executes later.
Senior signal: You use Command to make requests first-class (queue/log/undo), and connect it to CQRS commands and message queues.
20. Template Method vs Strategy?
Short answer: Template Method defines an algorithm's skeleton in a base class and lets subclasses override specific steps (inheritance, compile-time). Strategy injects the whole algorithm as a collaborator (composition, runtime). Same goal (vary part of a behavior), different mechanism — favor Strategy (composition) unless the skeleton is truly fixed.
Deep dive: Mattrx's import pipeline uses Template Method for the fixed validate -> transform -> persist skeleton with per-source overrides; the transform itself is a Strategy.
Senior signal: You contrast inheritance (Template Method) vs composition (Strategy) and default to Strategy unless the overall flow is genuinely invariant.
21. When do you use the State pattern?
Short answer: When an object's behavior depends on its state and you'd otherwise have sprawling switch (status) blocks in every method. Each state becomes a class that knows its allowed transitions and behavior — a state machine as objects. It replaces conditional soup with polymorphism.
Deep dive: A campaign's Draft/Scheduled/Active/Archived transitions live in state objects, so an illegal transition (Archived -> Active) is impossible by construction rather than caught by an if.
Senior signal: You reach for State when status-based conditionals spread across methods, and you value making illegal transitions unrepresentable.
22. What everyday framework is Chain of Responsibility?
Short answer: ASP.NET Core middleware. Each handler gets the request, does its part, and either passes to the next link or short-circuits. The sender doesn't know which handler will handle it. Pipelines, filters, and MediatR behaviors are all Chain of Responsibility.
Deep dive:
// Each middleware is a link: do work, then call next() -> or short-circuit and don't.
app.Use(async (ctx, next) => { /* authenticate */ await next(); });
app.Use(async (ctx, next) => { /* resolve tenant */ await next(); });
// A link can stop the chain (e.g., return 401) without calling next.
request -> [ auth ] -> [ tenant ] -> [ rate-limit ] -> [ endpoint ]
each link handles or passes on; any link can short-circuit
Senior signal: You recognize CoR in middleware/pipeline/MediatR-behaviors and note each link can handle or pass, sender-agnostic.
23. What does the Mediator pattern decouple, and where do you use it?
Short answer: It centralizes communication so components talk through a mediator instead of referencing each other directly — reducing many-to-many coupling to many-to-one. MediatR is the canonical .NET example: controllers send requests to IMediator, which routes to a handler they never reference.
Deep dive:
// Controller depends on IMediator, not on the handler -> decoupled request flow (CQRS).
public record PublishCampaign(Guid Id) : IRequest<Result>;
public sealed class PublishCampaignHandler : IRequestHandler<PublishCampaign, Result> { /* ... */ }
await _mediator.Send(new PublishCampaign(id), ct);
Senior signal: You use Mediator (MediatR) to decouple senders from handlers and know it can hide too much if overused (the "where is this handled?" cost).
24. What pattern powers foreach / IEnumerable?
Short answer: Iterator. It provides sequential access to a collection's elements without exposing its internal structure. In C#, IEnumerable/IEnumerator (and yield return, which generates an iterator for you) are the pattern built into the language.
Deep dive: yield return lets Mattrx stream a large export lazily — the Iterator pattern with zero boilerplate.
Senior signal: You know IEnumerable/yield is Iterator, and that it enables lazy, structure-agnostic traversal.
25. What is the Visitor pattern, and its trade-off?
Short answer: It lets you add new operations over an object structure without modifying the classes — you move the operation into a visitor and the elements "accept" it. The trade-off: easy to add operations, but hard to add new element types (you must touch every visitor). It's the inverse trade-off of normal polymorphism.
Deep dive: Mattrx walks a query-filter AST with visitors (one to compile to SQL, one to render a human-readable summary) without changing the node classes.
Senior signal: You use Visitor when operations change more than the type hierarchy, and you can state its inverse trade-off (cheap new ops, costly new types).
Block D — architectural & enterprise patterns (and anti-patterns)
26. Repository & Unit of Work — and when are they redundant?
Short answer: Repository abstracts data access behind a collection-like interface; Unit of Work tracks changes and commits them atomically. The catch on modern ORMs: EF Core's DbSet<T> is already a repository and DbContext is already a Unit of Work — a generic IRepository<T> on top often just hides EF and adds no value. Use a repository for a real domain-shaped interface, not to wrap EF one-to-one.
Deep dive: Mattrx uses purpose-built repositories (e.g., IKpiRepository with domain queries) but doesn't wrap everything in a generic repository over EF.
Senior signal: You know EF Core already is repository + unit of work, and add a repository only for a genuine domain abstraction — not reflexively.
27. What is CQRS and when is it worth it?
Short answer: Command Query Responsibility Segregation — separate the model that writes (commands) from the model that reads (queries), because their needs differ (normalized writes vs denormalized fast reads). Worth it for read-heavy, complex domains; overkill for simple CRUD, where it just doubles the code.
Deep dive: Mattrx runs CQRS via MediatR: commands mutate the domain, queries hit read models/projections built for the dashboard.
public record GetDashboard(Guid TenantId) : IRequest<DashboardDto>; // query -> read model
public record ArchiveCampaign(Guid Id) : IRequest; // command -> write model
Senior signal: You apply CQRS to read-heavy/complex domains, keep it out of plain CRUD, and don't conflate it with event sourcing (separate choice).
28. How do you replace null and exceptions with patterns?
Short answer: Null Object (a do-nothing implementation instead of null) removes null checks; the Result/Option pattern returns success-or-failure as a value instead of throwing for expected failures. Both make control flow explicit and push errors into the type system.
Deep dive: Mattrx returns Result<T> from command handlers for expected failures (validation, not-found) and reserves exceptions for the truly exceptional — so callers must handle both outcomes.
Senior signal: You use Result/Option for expected failures (not exceptions) and Null Object to kill null checks, keeping control flow in the type system.
29. What are the design anti-patterns you watch for?
Short answer: God object (one class doing everything), Singleton abuse (global state everywhere), anemic domain model (data bags + all logic in services), service locator (hidden dependencies), and over-patterning (layers of indirection nobody needs). Most legacy pain is one of these.
Deep dive: The subtle one is over-patterning — a factory-of-factories and five interfaces where a function would do. Abstraction has a carrying cost; unused flexibility is debt.
Senior signal: You name concrete anti-patterns including over-abstraction, and treat needless indirection as a smell equal to no structure.
30. How do you actually choose a pattern?
Short answer: Start from the problem and the change you expect, not the catalog. Identify what varies, isolate it behind an interface, and pick the pattern whose intent matches — or write the simple code if nothing varies yet. Refactor to patterns when duplication/coupling appears; don't design from patterns speculatively.
Deep dive: "What is likely to change, and how do I make that change cheap without paying for changes that won't happen?" is the whole decision. Patterns are the answers, not the question.
Senior signal: You reason from the expected axis of change and refactor toward patterns as needs emerge — you don't front-load abstraction.
Bonus — what makes an answer "senior" in design patterns?
Short answer: A junior lists patterns and applies them eagerly; a senior names the trade-off and knows when not to. "Should we add a repository layer?" gets an automatic "yes, best practice!" from a junior and "not over EF Core — DbSet already is one; we'd add indirection with no gain" from a senior.
Deep dive: The recurring senior lens across all 30: every pattern buys flexibility with indirection. Apply it when a real, expected change makes the indirection pay; otherwise the simple code wins. Patterns serve SOLID; they don't replace judgment.
Senior signal: You lead with the cost and the "when-not-to," map patterns to real framework code (middleware, MediatR, EF), and refactor toward them rather than starting from them.
The model to carry forward
Design patterns are a vocabulary for making expected change cheap — Strategy and Decorator for Open-Closed, DI for Dependency Inversion, Mediator/CQRS for decoupling request flow, Adapter for isolating vendors. But every pattern trades simplicity for indirection, so the senior move is to apply one only when it absorbs a change you actually expect, name the SOLID principle it serves, and delete the abstraction that doesn't earn its keep. Answer each question with the pattern's intent, the framework code it maps to, and the cost — and you'll sound like someone who has maintained a large codebase, which is the signal.
Further reading
- C# Interview Questions for Senior Developers — Async, Memory, LINQ & Concurrency (Deep Answers)
- Saga Pattern for Microservices — A Complete Guide
- Outbox Pattern — A Complete Guide with Order Processing Example
Prepping for a senior .NET/architecture interview and want these drilled live, or a mock focused on patterns and SOLID trade-offs? I'm always happy to help — reach me at randhir.jassal@gmail.com.
Get the next issue
A short, curated email with the newest posts and questions.