Understanding Design and Architectural Patterns
Understanding Design and Architectural Patterns Interview with follow-up questions
1. Can you explain what design patterns are and why they are important in .NET Framework?
Design patterns are reusable, named solutions to commonly occurring problems in software design. They are not code you copy-paste — they are templates or blueprints that describe how to structure code to solve a particular class of problem effectively.
They were popularized by the "Gang of Four" (GoF) book Design Patterns: Elements of Reusable Object-Oriented Software (1994) and are organized into three categories:
1. Creational patterns — how objects are created:
- Singleton, Factory Method, Abstract Factory, Builder, Prototype
2. Structural patterns — how objects are composed:
- Adapter, Decorator, Facade, Proxy, Composite, Bridge, Flyweight
3. Behavioral patterns — how objects communicate and assign responsibility:
- Observer, Strategy, Command, Iterator, Template Method, Chain of Responsibility, State, Visitor, Mediator, Memento, Interpreter
Why design patterns matter in .NET:
Shared vocabulary: Saying "use the Strategy pattern here" communicates intent instantly to teammates without lengthy explanation.
Proven solutions: Patterns encode the collective experience of practitioners — they solve problems that have tripped up many developers before.
Framework alignment: ASP.NET Core itself is built around patterns:
- Middleware pipeline — Chain of Responsibility
- Built-in DI container — Inversion of Control / Dependency Injection
- MVC — Model-View-Controller
IOptions— Options patternIHostedService— Background service pattern- Repository pattern — often used with EF Core to abstract data access
Testability: Patterns like Dependency Injection and Repository make code loosely coupled and easy to unit test by substituting mock implementations.
Maintainability and extensibility: Patterns like Open/Closed Principle (OCP) and Strategy allow adding new behaviors without modifying existing code.
Understanding design patterns and recognizing when (and when not) to apply them is a hallmark of an experienced .NET developer.
Follow-up 1
Can you provide an example of a design pattern?
Sure! One example of a design pattern is the Singleton pattern. The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This can be useful in scenarios where you want to limit the number of instances of a class or when you want to provide a shared resource across multiple parts of your application.
Follow-up 2
How does the use of design patterns improve code quality?
The use of design patterns improves code quality in several ways. Firstly, design patterns promote code reusability, allowing developers to leverage existing solutions to common problems. This reduces the amount of code duplication and leads to more maintainable and scalable code. Secondly, design patterns provide a common language and structure for discussing and documenting software designs. This makes it easier for developers to understand and collaborate on complex systems. Finally, design patterns often encapsulate best practices and proven solutions, which can help developers avoid common pitfalls and improve the overall quality of their code.
Follow-up 3
What are some common design patterns used in .NET Framework?
There are several common design patterns used in the .NET Framework. Some examples include:
Singleton Pattern: Ensures a class has only one instance and provides a global point of access to it.
Factory Pattern: Provides an interface for creating objects, but allows subclasses to decide which class to instantiate.
Observer Pattern: Defines a one-to-many dependency between objects, so that when one object changes state, all its dependents are notified and updated automatically.
Strategy Pattern: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.
These are just a few examples, and there are many more design patterns available in the .NET Framework and other software development platforms.
2. What is the difference between design patterns and architectural patterns?
Design patterns and architectural patterns both provide reusable solutions to recurring problems, but they operate at different levels of abstraction.
Design patterns:
- Operate at the class and object level within a module or component.
- Solve specific, localized design problems: how to create an object, how two classes should communicate, how to add behavior without subclassing.
- Scope: a few classes or objects.
- Examples:
- Singleton – ensure a class has only one instance.
- Observer – notify dependents when state changes.
- Strategy – swap algorithms at runtime.
- Factory Method – delegate object creation to subclasses.
- Decorator – add behavior to an object without modifying it.
Architectural patterns:
- Operate at the system or subsystem level — the high-level structure of an entire application.
- Define how major components of a system are organized, how data flows, and how responsibilities are divided across layers or services.
- Scope: an entire application, service, or system.
- Examples:
- MVC / MVVM / MVP – separate presentation from business logic and data.
- Layered (N-Tier) – organize code into layers (Presentation, Application, Domain, Infrastructure).
- Clean Architecture / Hexagonal (Ports and Adapters) – domain at the center, outer layers depend inward.
- Microservices – decompose an application into independently deployable services.
- Event-Driven Architecture – components communicate via events/messages.
- CQRS (Command Query Responsibility Segregation) – separate read and write models.
Key distinction:
| Dimension | Design patterns | Architectural patterns |
|---|---|---|
| Level | Class / object | System / subsystem |
| Scope | A few classes | Entire application or service |
| Concern | How objects interact | How the system is structured |
| Visibility | Internal implementation | Visible to all developers on the team |
In .NET, you often apply both together: you might build an ASP.NET Core application using Clean Architecture (architectural pattern) with Repository, Strategy, and Decorator (design patterns) throughout.
Follow-up 1
Can you provide an example of an architectural pattern?
Sure! One example of an architectural pattern is the Model-View-Controller (MVC) pattern. The MVC pattern is commonly used in web development to separate the concerns of data, presentation, and user interaction.
In the MVC pattern, the Model represents the data and business logic of the application. It encapsulates the data and provides methods for manipulating and accessing it. The View is responsible for presenting the data to the user and handling user input. It displays the data and provides a user interface for interacting with it. The Controller acts as an intermediary between the Model and the View. It receives user input from the View, updates the Model accordingly, and updates the View to reflect any changes in the Model.
The MVC pattern helps to decouple the different components of a web application, making it easier to maintain and modify. It also promotes reusability and testability, as each component can be developed and tested independently.
Follow-up 2
How do architectural patterns influence the structure of a system?
Architectural patterns provide a set of guidelines and principles for designing the structure of a software system. They influence the structure of a system in several ways:
Component organization: Architectural patterns define how components should be organized and structured within a system. They specify the relationships between components, modules, and subsystems, and help ensure that the system is modular, scalable, and maintainable.
Separation of concerns: Architectural patterns promote the separation of concerns by dividing the system into different layers or modules, each responsible for a specific aspect of the system. This separation helps to improve the modularity, reusability, and testability of the system.
Quality attributes: Architectural patterns consider the desired quality attributes of a system, such as performance, scalability, security, and maintainability. They provide guidelines for designing the system in a way that meets these attributes.
By following architectural patterns, developers can create systems that are well-structured, modular, and meet the desired quality attributes.
Follow-up 3
What are some common architectural patterns used in .NET Framework?
The .NET Framework provides several architectural patterns that are commonly used in software development. Some of the common architectural patterns used in .NET Framework are:
Layered architecture: The Layered architecture pattern divides the system into layers, such as presentation layer, business logic layer, and data access layer. Each layer has a specific responsibility and communicates with the adjacent layers through well-defined interfaces.
Client-Server architecture: The Client-Server architecture pattern separates the client and server components of a system. The client component is responsible for user interaction, while the server component handles the processing and storage of data.
Microservices architecture: The Microservices architecture pattern decomposes a system into small, independent services that can be developed, deployed, and scaled independently. Each service is responsible for a specific business capability and communicates with other services through lightweight protocols.
These are just a few examples of the architectural patterns used in the .NET Framework. There are many other patterns available, each suited for different types of systems and requirements.
3. Can you explain the Model-View-Controller (MVC) architectural pattern and its implementation in .NET Framework?
MVC (Model-View-Controller) is an architectural pattern that separates an application into three interconnected components, each with a distinct responsibility:
- Model: Represents the data and business logic. Encapsulates domain entities, validation rules, and data access. Should be independent of the UI.
- View: The presentation layer. Renders data from the model to the user. In web apps, this is typically HTML generated from Razor templates.
- Controller: Handles incoming requests, invokes the appropriate model operations, and selects the view to render. Acts as the coordinator between model and view.
MVC in ASP.NET Core:
ASP.NET Core MVC is the current implementation and runs on .NET 8/9 cross-platform.
// Controller
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _service;
public ProductsController(IProductService service)
=> _service = service;
[HttpGet("{id:int}")]
public async Task> GetProduct(int id)
{
var product = await _service.GetByIdAsync(id);
return product is null ? NotFound() : Ok(product);
}
[HttpPost]
public async Task> CreateProduct(CreateProductRequest request)
{
var product = await _service.CreateAsync(request);
return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}
}
Routing: ASP.NET Core uses attribute routing ([Route], [HttpGet], etc.) or conventional routing. The routing system maps incoming URLs to controller actions.
Model binding: ASP.NET Core automatically binds request data (route values, query strings, request body JSON) to action method parameters.
Validation: [ApiController] automatically validates model state using Data Annotations and returns 400 responses for invalid input.
Alternatives within ASP.NET Core:
- Razor Pages: Page-centric model where each page handles its own GET/POST. Cleaner than MVC for page-focused web UI; avoids the indirection of controllers for simple CRUD pages.
- Minimal APIs: Define endpoints inline without controllers — best for lightweight APIs and microservices.
- Blazor: Component-based model for interactive UI (server-side or WebAssembly), analogous to React/Angular but in C#.
All three approaches coexist in a single ASP.NET Core application and are registered through the middleware pipeline in Program.cs.
Follow-up 1
What are the benefits of using MVC?
There are several benefits of using the Model-View-Controller (MVC) architectural pattern:
Separation of concerns: MVC separates the concerns of an application into distinct components, making it easier to manage and maintain the codebase. The model handles the data and business logic, the view handles the user interface, and the controller handles the interaction between the model and the view.
Code reusability: With MVC, the components can be reused across different parts of the application. For example, the same model can be used by multiple views, and the same controller can handle different user interactions.
Testability: MVC promotes testability by separating the concerns and dependencies. Each component can be tested independently, making it easier to write unit tests and ensure the correctness of the application.
Flexibility: MVC allows for flexibility in the development process. The components can be developed and modified independently, making it easier to adapt to changing requirements or add new features to the application.
Follow-up 2
How does MVC improve the separation of concerns in an application?
The Model-View-Controller (MVC) architectural pattern improves the separation of concerns in an application by dividing it into three main components: the model, the view, and the controller.
The model is responsible for handling the data and business logic of the application. It encapsulates the data and provides methods to manipulate and access it.
The view is responsible for presenting the data to the user. It defines the user interface and displays the data from the model.
The controller acts as an intermediary between the model and the view. It receives user input from the view, updates the model accordingly, and updates the view to reflect the changes.
By separating the concerns into distinct components, MVC allows for better organization and maintainability of the codebase. Each component has a specific responsibility, making it easier to understand and modify the code. Changes in one component do not affect the others, promoting code reusability and flexibility.
Follow-up 3
Can you describe a scenario where MVC would be the most appropriate architectural pattern to use?
The Model-View-Controller (MVC) architectural pattern is most appropriate to use in scenarios where there is a need to separate the concerns of an application and provide a structured approach to development. Some scenarios where MVC would be a good fit include:
Web applications: MVC is commonly used in web application development as it provides a clear separation between the data, user interface, and user interactions. It allows for easy management of the application's logic and presentation layer.
Large-scale applications: MVC is beneficial for large-scale applications where there are multiple developers working on different parts of the application. The separation of concerns in MVC allows for better collaboration and code organization.
Applications with changing requirements: MVC's flexibility makes it suitable for applications with changing requirements. The components can be developed and modified independently, making it easier to adapt to new features or changes in the application's functionality.
4. What is the Singleton design pattern and how is it used in .NET Framework?
The Singleton pattern ensures that a class has only one instance throughout the application's lifetime and provides a global access point to that instance.
Classic implementation (thread-safe with Lazy):
public sealed class AppConfiguration
{
private static readonly Lazy _instance =
new(() => new AppConfiguration());
private AppConfiguration()
{
// Private constructor prevents external instantiation
}
public static AppConfiguration Instance => _instance.Value;
public string ConnectionString { get; set; } = "";
}
// Usage
var config = AppConfiguration.Instance;
Lazy is thread-safe by default and defers construction until first access.
Preferred approach in ASP.NET Core — DI container singleton:
In modern .NET applications, the built-in Dependency Injection (DI) container manages object lifetimes. You rarely implement the Singleton pattern manually; instead, you register a service as a singleton:
// Register as singleton in Program.cs
builder.Services.AddSingleton();
builder.Services.AddSingleton();
// The DI container creates one instance and reuses it for the application's lifetime
The DI container approach is preferred because:
- The class itself does not need to manage its own instance.
- It is easy to replace with a mock in unit tests.
- Lifetime is explicit and managed centrally.
Thread safety considerations:
Singleton instances are shared across all threads. Any mutable state must be protected:
public class CounterService
{
private long _count = 0;
// Thread-safe increment
public long Increment() => Interlocked.Increment(ref _count);
public long Value => Interlocked.Read(ref _count);
}
Lifetime pitfall — "captive dependency":
A singleton should never depend on a scoped or transient service. Doing so "captures" a short-lived service inside a long-lived one, causing bugs:
// BAD: Singleton captures a scoped DbContext
builder.Services.AddSingleton(); // MyService injects IMyDbContext (scoped) — bug!
// FIX: Inject IServiceScopeFactory and create a scope when needed
public class MyService
{
private readonly IServiceScopeFactory _scopeFactory;
public MyService(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;
public async Task DoWorkAsync()
{
using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService();
// use db
}
}
Follow-up 1
What are the benefits and drawbacks of using the Singleton pattern?
Benefits of using the Singleton pattern include:
- Ensures a single instance of a class throughout the application, avoiding unnecessary object creation.
- Provides a global point of access to the instance, making it easy to use and share.
Drawbacks of using the Singleton pattern include:
- Can introduce tight coupling and make the code harder to test and maintain.
- Can lead to potential thread-safety issues if not implemented correctly.
- Can make it difficult to extend or modify the behavior of the class.
Follow-up 2
Can you provide an example of a scenario where the Singleton pattern would be useful?
Sure! One example of a scenario where the Singleton pattern would be useful is in a logging system. Instead of creating multiple instances of a logger throughout the application, a Singleton logger can be used to ensure that all log messages are written to the same log file. This allows for centralized logging and makes it easier to manage and analyze the logs.
Follow-up 3
How would you implement the Singleton pattern in .NET?
There are several ways to implement the Singleton pattern in .NET. One common approach is to use a private constructor, a static field to hold the instance, and a static method to provide access to the instance. Here's an example implementation in C#:
public class Singleton
{
private static Singleton instance;
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
In this implementation, the Singleton class has a private constructor to prevent direct instantiation. The static Instance property provides access to the single instance of the class. If the instance is null, it is created and returned. Subsequent calls to Instance will return the existing instance.
5. Can you explain the Factory design pattern and its use cases in .NET Framework?
The Factory pattern is a creational design pattern that provides an abstraction for object creation. Instead of calling new ConcreteClass() directly in client code, a factory method or class handles the instantiation. This decouples the client from the concrete types being created.
Three related patterns:
1. Simple Factory (not a GoF pattern, but common):
A static or instance method that creates objects based on a parameter:
public static class ShapeFactory
{
public static IShape Create(string type) => type switch
{
"circle" => new Circle(),
"square" => new Square(),
_ => throw new ArgumentException($"Unknown shape: {type}")
};
}
IShape shape = ShapeFactory.Create("circle");
2. Factory Method (GoF pattern):
Defines an interface for creating an object, but lets subclasses decide which class to instantiate:
public abstract class Notifier
{
public abstract IMessageSender CreateSender(); // Factory method
public void Send(string message)
{
var sender = CreateSender();
sender.Send(message);
}
}
public class EmailNotifier : Notifier
{
public override IMessageSender CreateSender() => new EmailSender();
}
public class SmsNotifier : Notifier
{
public override IMessageSender CreateSender() => new SmsSender();
}
3. Abstract Factory (GoF pattern):
Provides an interface for creating a family of related objects:
public interface IDbFactory
{
IDbConnection CreateConnection();
IDbCommand CreateCommand();
}
public class SqlServerFactory : IDbFactory
{
public IDbConnection CreateConnection() => new SqlConnection(...);
public IDbCommand CreateCommand() => new SqlCommand();
}
public class PostgreSqlFactory : IDbFactory
{
public IDbConnection CreateConnection() => new NpgsqlConnection(...);
public IDbCommand CreateCommand() => new NpgsqlCommand();
}
Factory pattern in .NET and ASP.NET Core:
The factory pattern is pervasive in .NET:
IHttpClientFactory: Creates and managesHttpClientinstances (avoids socket exhaustion, handles DNS refresh):builder.Services.AddHttpClient();ILoggerFactory: CreatesILoggerinstances for specific categories.IServiceProvideras a factory: The DI container itself is a factory —serviceProvider.GetRequiredService().DbProviderFactory: ADO.NET abstract factory for database-agnostic code.Keyed services (.NET 8+): Register multiple implementations of the same interface under a key, enabling factory-like lookup:
builder.Services.AddKeyedSingleton("stripe"); builder.Services.AddKeyedSingleton("paypal");
When to use factories:
- The exact type to create depends on runtime conditions or configuration.
- You want to decouple client code from concrete implementations.
- Object creation is complex and should be centralized.
- You need to return different subtypes based on input.
Follow-up 1
What are the advantages of using the Factory pattern?
The advantages of using the Factory pattern include:
- Encapsulation: The client code only needs to know about the factory interface and does not need to be aware of the concrete classes being instantiated.
- Loose coupling: The client code is decoupled from the concrete classes, as it only depends on the factory interface.
- Flexibility: The factory can decide which class to instantiate based on some condition or configuration, allowing for easy extensibility and customization.
- Testability: The factory can be easily mocked or replaced with a different implementation for testing purposes.
Follow-up 2
Can you provide an example of a scenario where the Factory pattern would be beneficial?
Sure! Let's say we have an application that needs to generate reports in different formats, such as PDF, Excel, and CSV. Instead of having the client code directly instantiate the report classes, we can use the Factory pattern. The factory can take a parameter indicating the desired report format and return an instance of the corresponding report class. This way, the client code does not need to be aware of the specific report classes and can easily switch between different formats without modifying its code.
Follow-up 3
How would you implement the Factory pattern in .NET?
In .NET, the Factory pattern can be implemented using an abstract factory class or an interface. The factory class or interface defines a method or methods for creating objects of different types. The concrete factory classes implement the factory interface and provide the logic for creating the objects. The client code depends on the factory interface and uses it to create objects, without being aware of the concrete classes being instantiated. Here's an example:
public interface IReportFactory
{
IReport CreateReport();
}
public class PdfReportFactory : IReportFactory
{
public IReport CreateReport()
{
return new PdfReport();
}
}
public class ExcelReportFactory : IReportFactory
{
public IReport CreateReport()
{
return new ExcelReport();
}
}
public class ReportClient
{
private readonly IReportFactory _reportFactory;
public ReportClient(IReportFactory reportFactory)
{
_reportFactory = reportFactory;
}
public void GenerateReport()
{
IReport report = _reportFactory.CreateReport();
// Use the report...
}
}
Live mock interview
Mock interview: Understanding Design and Architectural Patterns
- Read your scene and goals
- Talk it out; goals tick off live
- Get a score and stronger lines
Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.