Technical interview questions with detailed answers—organized by course, like Dot Net Tutorials interview sections. Original content for Toolliyo Academy.
Design Patterns & SOLID Design Patterns in C# · SOLID
The Singleton pattern ensures a class has only one instance and provides a global point of
access to it. It’s commonly used when exactly one object is needed to coordinate actions
across the system, such as configuration settings, logging, or caching.
Design Patterns & SOLID Design Patterns in C# · SOLID
Review the concept and prepare a concise verbal explanation with a real project example.
Design Patterns & SOLID Design Patterns in C# · SOLID
One common thread-safe implementation uses lazy initialization with Lazy<T>:
public sealed class Singleton
private static readonly Lazy<Singleton> instance = new
Lazy<Singleton>(() => new Singleton());
private Singleton() { }
public static Singleton Instance => instance.Value;
This approach ensures thread safety and lazy initialization without locks.
Design Patterns & SOLID Design Patterns in C# · SOLID
Review the concept and prepare a concise verbal explanation with a real project example.
Design Patterns & SOLID Design Patterns in C# · SOLID
Review the concept and prepare a concise verbal explanation with a real project example.
Design Patterns & SOLID Design Patterns in C# · SOLID
concurrent environments.
Design Patterns & SOLID Design Patterns in C# · SOLID
container, though generally discouraged.
Design Patterns & SOLID Design Patterns in C# · SOLID
Singletons can hinder unit testing because they introduce global state, making tests
dependent on a shared instance. This can cause tests to be flaky or order-dependent. To
mitigate this, use interfaces and dependency injection, or design the Singleton to allow
resetting its state for tests.
Design Patterns & SOLID Design Patterns in C# · SOLID
It's simple but can waste resources if the instance is never used.
resources but requires careful implementation for thread safety.
Factory pattern Q&A
Design Patterns & SOLID Design Patterns in C# · SOLID
The Factory pattern is a creational design pattern that provides an interface for creating
objects but allows subclasses or implementations to decide which class to instantiate. It’s
used to encapsulate object creation, promoting loose coupling and flexibility when the exact
types of objects aren’t known until runtime.
Use case: When a class can’t anticipate the class of objects it needs to create, or when you
want to delegate responsibility for object creation to subclasses.
Design Patterns & SOLID Design Patterns in C# · SOLID
decide which class to instantiate. It uses inheritance and relies on subclass
overriding.
objects without specifying their concrete classes. It uses composition and is useful
when you need to create multiple related objects together.
Design Patterns & SOLID Design Patterns in C# · SOLID
A simple example of Factory Method:
// Product interface
public interface IAnimal
void Speak();
// Concrete Products
public class Dog : IAnimal
public void Speak() => Console.WriteLine("Woof");
public class Cat : IAnimal
public void Speak() => Console.WriteLine("Meow");
// Factory
public class AnimalFactory
public static IAnimal CreateAnimal(string animalType)
return animalType.ToLower() switch
"dog" => new Dog(),
"cat" => new Cat(),
_ => throw new ArgumentException("Invalid animal type")
Usage:
var dog = AnimalFactory.CreateAnimal("dog");
dog.Speak(); // Outputs: Woof
Design Patterns & SOLID Design Patterns in C# · SOLID
code.
concrete types.
Design Patterns & SOLID Design Patterns in C# · SOLID
Yes! Factory pattern can complement Dependency Injection (DI) by abstracting complex
object creation logic, especially when the creation involves runtime parameters or complex
setup that DI containers can’t handle easily.
Example: Imagine a service that needs different data repositories based on a runtime
parameter.
public interface IRepository { void Save(); }
public class SqlRepository : IRepository { public void Save() =>
Console.WriteLine("Saving to SQL DB"); }
public class InMemoryRepository : IRepository { public void Save()
=> Console.WriteLine("Saving in Memory"); }
public interface IRepositoryFactory
IRepository CreateRepository(string repoType);
public class RepositoryFactory : IRepositoryFactory
public IRepository CreateRepository(string repoType)
return repoType.ToLower() switch
"sql" => new SqlRepository(),
"memory" => new InMemoryRepository(),
_ => throw new ArgumentException("Invalid repository
type")
// Consumer class with DI
public class Service
private readonly IRepositoryFactory _repositoryFactory;
public Service(IRepositoryFactory repositoryFactory)
_repositoryFactory = repositoryFactory;
public void SaveData(string repoType)
var repo = _repositoryFactory.CreateRepository(repoType);
repo.Save();
Here, DI injects the IRepositoryFactory while the factory manages object creation
based on runtime input. This promotes loose coupling and flexibility.
Strategy Design Pattern
Design Patterns & SOLID Design Patterns in C# · SOLID
The Strategy pattern is a behavioral design pattern that defines a family of algorithms,
encapsulates each one, and makes them interchangeable at runtime.
It allows the algorithm to vary independently from clients that use it.
Problem it solves:
Avoids large if-else or switch statements when selecting behavior and promotes
flexibility by decoupling the algorithm from the client using it.
Design Patterns & SOLID Design Patterns in C# · SOLID
Example: Payment strategy selection
// Strategy Interface
public interface IPaymentStrategy
void Pay(decimal amount);
// Concrete Strategies
public class CreditCardPayment : IPaymentStrategy
public void Pay(decimal amount) => Console.WriteLine($"Paid
{amount} using Credit Card");
public class PayPalPayment : IPaymentStrategy
public void Pay(decimal amount) => Console.WriteLine($"Paid
{amount} using PayPal");
// Context
public class PaymentContext
private IPaymentStrategy _paymentStrategy;
public PaymentContext(IPaymentStrategy paymentStrategy)
_paymentStrategy = paymentStrategy;
public void ExecutePayment(decimal amount)
_paymentStrategy.Pay(amount);
Usage:
var context = new PaymentContext(new PayPalPayment());
context.ExecutePayment(200); // Outputs: Paid 200 using PayPal
Design Patterns & SOLID Design Patterns in C# · SOLID
Design Patterns & SOLID Design Patterns in C# · SOLID
It promotes the Open/Closed Principle by allowing you to add new strategies (algorithms
or behaviors) without modifying the existing code.
The context class uses an interface for the strategy, so new behavior can be added just by
creating a new class that implements the interface—no need to touch existing logic.
Design Patterns & SOLID Design Patterns in C# · SOLID
Aspect Strategy Pattern State Pattern
Purpose Encapsulates interchangeable
behaviors (algorithms).
Encapsulates states and transitions
between them.
Client
Control
Client decides which strategy to use. Object changes its own state
internally.
Behavior
Switch
Switched externally (e.g., passed as
a parameter).
Switched internally (e.g., via
method call).
Example Payment method selection. Document lifecycle (Draft →
Published → Archived).
Repository Design Pattern
Design Patterns & SOLID Design Patterns in C# · SOLID
The Repository pattern abstracts the data access layer from the business logic by providing
a collection-like interface to access domain objects.
It helps keep data access logic centralized and makes the codebase easier to maintain,
test, and swap out data sources (e.g., switching from EF Core to Dapper or an API).
Design Patterns & SOLID Design Patterns in C# · SOLID
Here's a basic example:
// Entity
public class Product
public int Id { get; set; }
public string Name { get; set; }
// Generic Repository Interface
public interface IRepository<T> where T : class
Task<IEnumerable<T>> GetAllAsync();
Task<T> GetByIdAsync(int id);
Task AddAsync(T entity);
void Update(T entity);
void Delete(T entity);
Task SaveAsync();
// EF Core implementation
public class Repository<T> : IRepository<T> where T : class
private readonly DbContext _context;
private readonly DbSet<T> _dbSet;
public Repository(DbContext context)
_context = context;
_dbSet = context.Set<T>();
public async Task<IEnumerable<T>> GetAllAsync() => await
_dbSet.ToListAsync();
public async Task<T> GetByIdAsync(int id) => await
_dbSet.FindAsync(id);
public async Task AddAsync(T entity) => await
_dbSet.AddAsync(entity);
public void Update(T entity) => _dbSet.Update(entity);
public void Delete(T entity) => _dbSet.Remove(entity);
public async Task SaveAsync() => await
_context.SaveChangesAsync();
Design Patterns & SOLID Design Patterns in C# · SOLID
Design Patterns & SOLID Design Patterns in C# · SOLID
IProductRepository)
public interface IProductRepository : IRepository<Product>
Task<IEnumerable<Product>> GetProductsWithLowStockAsync(int
threshold);
Design Patterns & SOLID Design Patterns in C# · SOLID
repository/unit-of-work pattern).
projections.
value.
Unit of Work
Design Patterns & SOLID Design Patterns in C# · SOLID
The Unit of Work pattern is a design pattern used to maintain a list of operations to be
performed within a single transaction. It ensures that all operations either succeed or fail
together, providing consistency and managing changes to multiple business objects during a
transaction. It coordinates the writing out of changes and resolves potential concurrency
issues.
Design Patterns & SOLID Design Patterns in C# · SOLID
The Repository pattern abstracts the data access layer, providing a simplified interface to
data operations. The Unit of Work pattern complements it by managing multiple repositories
and ensuring that all changes made through these repositories are committed in a single
transaction. This combination separates concerns, promotes clean architecture, and
maintains transactional integrity across multiple operations.
Design Patterns & SOLID Design Patterns in C# · SOLID
In a .NET application (especially using Entity Framework), the Unit of Work is typically
implemented around the DbContext, as it already tracks changes and handles
transactions. Here's a simplified example:
// IUnitOfWork.cs
public interface IUnitOfWork : IDisposable
IProductRepository Products { get; }
ICustomerRepository Customers { get; }
int Complete();
// UnitOfWork.cs
public class UnitOfWork : IUnitOfWork
private readonly AppDbContext _context;
public IProductRepository Products { get; private set; }
public ICustomerRepository Customers { get; private set; }
public UnitOfWork(AppDbContext context)
_context = context;
Products = new ProductRepository(_context);
Customers = new CustomerRepository(_context);
public int Complete()
return _context.SaveChanges(); // All changes in one
transaction
public void Dispose()
_context.Dispose();
Then register it using Dependency Injection in Startup.cs or Program.cs:
services.AddScoped<IUnitOfWork, UnitOfWork>();
Design Patterns & SOLID Design Patterns in C# · SOLID
transaction.
(Complete()), improving control and readability.
instead of multiple repositories.
unnecessary database operations.
Design Patterns & SOLID Design Patterns in C# · SOLID
Yes, combining Unit of Work with Dependency Injection (DI) is a best practice in modern
.NET applications. DI allows the Unit of Work to be injected into services or controllers,
managing its lifecycle effectively. This improves modularity, promotes loose coupling, and
makes the application easier to maintain and test.
Example using constructor injection in a service:
public class OrderService
private readonly IUnitOfWork _unitOfWork;
public OrderService(IUnitOfWork unitOfWork)
_unitOfWork = unitOfWork;
public void PlaceOrder(Order order)
_unitOfWork.Orders.Add(order);
_unitOfWork.Complete();
Dependency Injection (DI)
Design Patterns & SOLID Design Patterns in C# · SOLID
Dependency Injection (DI) is a design pattern that allows an object to receive its
dependencies from an external source rather than creating them itself. This promotes loose
coupling, enhances testability, and simplifies code maintenance.
Benefits:
Principle)
Design Patterns & SOLID Design Patterns in C# · SOLID
Review the concept and prepare a concise verbal explanation with a real project example.
Design Patterns & SOLID Design Patterns in C# · SOLID
.NET Core has a built-in DI container that's tightly integrated into the framework. When
you create a new ASP.NET Core project, DI is configured automatically and used in
controllers, middleware, services, etc. You register dependencies in the Program.cs or
Startup.cs file using IServiceCollection.
Design Patterns & SOLID Design Patterns in C# · SOLID
Services are registered in the Program.cs or Startup.cs file using the
IServiceCollection interface. Example:
builder.Services.AddTransient<IProductService, ProductService>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddSingleton<ILoggingService, LoggingService>();
Design Patterns & SOLID Design Patterns in C# · SOLID
Lifetime Created Per Use Case
Transient Every injection Lightweight, stateless services
Scoped HTTP Request Web services handling per-request
data
Singleton App lifetime Logging, configuration, cache
Design Patterns & SOLID Design Patterns in C# · SOLID
Circular dependencies occur when two or more services depend on each other, creating an
infinite loop. To handle them:
Design Patterns & SOLID Design Patterns in C# · SOLID
Design Patterns & SOLID Design Patterns in C# · SOLID
Yes, DI can be implemented manually without using a DI container. You simply pass
dependencies through constructors or methods:
public class OrderService
private readonly IOrderRepository _repo;
public OrderService(IOrderRepository repo)
_repo = repo;
// Manual injection
var repo = new OrderRepository();
var service = new OrderService(repo);
This is suitable for small or simple applications, though not scalable for large apps.
Design Patterns & SOLID Design Patterns in C# · SOLID
Design Patterns & SOLID Design Patterns in C# · SOLID
Inversion of Control (IoC) is a broader principle where control over object creation and
dependency resolution is transferred from the class itself to an external framework or
container.
Dependency Injection is a specific implementation of IoC, where the dependencies are
injected into a class rather than the class creating them internally. So, DI is one way to
achieve IoC.
Single Responsibility Principle
(SRP)
Design Patterns & SOLID Design Patterns in C# · SOLID
The Single Responsibility Principle (SRP) states that a class should have only one
reason to change. In other words, it should have only one responsibility or job.
Each class should do one thing, and do it well.
For example, a class that handles user authentication should not also be responsible for
logging or sending emails.
Design Patterns & SOLID Design Patterns in C# · SOLID
You can identify SRP violations by asking questions like:
logic)?
ReportGeneratorAndPrinter.
Common signs:
Design Patterns & SOLID Design Patterns in C# · SOLID
Before (SRP Violation):
public class Invoice
public void GenerateInvoice() { /* logic */ }
public void SaveToDatabase() { /* logic */ }
public void SendEmail() { /* logic */ }
This class has 3 responsibilities: generating, saving, and emailing.
After (SRP-compliant):
public class InvoiceGenerator
public void Generate() { /* logic */ }
public class InvoiceRepository
public void Save(Invoice invoice) { /* logic */ }
public class EmailService
public void Send(Invoice invoice) { /* logic */ }
Now each class has a single reason to change.
Design Patterns & SOLID Design Patterns in C# · SOLID
SRP improves maintainability by:
code are kept apart.
Ultimately, it leads to more modular, flexible, and robust code.
Design Patterns & SOLID Design Patterns in C# · SOLID
If a class has multiple responsibilities:
Open/Closed Principle (OCP)
Design Patterns & SOLID Design Patterns in C# · SOLID
The Open/Closed Principle (OCP) states that:
Software entities (classes, modules, functions) should be open for
extension but closed for modification.
This means you should be able to add new behavior to a class without changing its
existing code, which helps avoid breaking existing functionality.
Design Patterns & SOLID Design Patterns in C# · SOLID
To design classes that follow OCP:
✅ Extend behavior through new classes rather than modifying existing code.
Design Patterns & SOLID Design Patterns in C# · SOLID
Several design patterns are built around the idea of making systems extensible without
modifying core logic:
Pattern How It Helps With OCP
Strategy Allows changing behavior by swapping strategies.
Decorator Adds new responsibilities dynamically without changing original code.
Template Method Allows subclasses to override certain steps in an algorithm.
Factory Method Makes it easy to introduce new types without altering existing logic.
Observer Extends behavior in reaction to events without altering the source.
Design Patterns & SOLID Design Patterns in C# · SOLID
Before (OCP Violation):
public class DiscountCalculator
public decimal CalculateDiscount(string customerType)
if (customerType == "Regular") return 10;
if (customerType == "Premium") return 20;
if (customerType == "VIP") return 30;
return 0;
If you need to support a new customer type, you must modify this method — violating OCP.
After (OCP Compliant):
public interface IDiscountStrategy
decimal GetDiscount();
public class RegularCustomerDiscount : IDiscountStrategy
public decimal GetDiscount() => 10;
public class PremiumCustomerDiscount : IDiscountStrategy
public decimal GetDiscount() => 20;
public class VipCustomerDiscount : IDiscountStrategy
public decimal GetDiscount() => 30;
Now you can add new customer types without modifying existing code — just create a new
strategy.
Design Patterns & SOLID Design Patterns in C# · SOLID
implement or inherit.
existing code.
introduce new behavior (via new implementations) while keeping the core logic
unchanged.
✅ OCP encourages extending via new subclasses or interface implementations, not
modifying existing ones.
Liskov Substitution Principle (LSP)
Design Patterns & SOLID Design Patterns in C# · SOLID
The Liskov Substitution Principle (LSP) states that:
Subtypes must be substitutable for their base types without altering the
correctness of the program.
In other words, if class S is a subclass of class T, then objects of type T should be
replaceable with objects of type S without breaking the application.
This ensures that inheritance models is-a relationships correctly.
Design Patterns & SOLID Design Patterns in C# · SOLID
Violating LSP can lead to:
class.
abstractions.
Essentially, it defeats the purpose of polymorphism.
Design Patterns & SOLID Design Patterns in C# · SOLID
Example (Violation of LSP):
public class Rectangle
public virtual int Width { get; set; }
public virtual int Height { get; set; }
public int Area() => Width * Height;
public class Square : Rectangle
public override int Width
set { base.Width = base.Height = value; }
public override int Height
set { base.Width = base.Height = value; }
Now if you substitute Rectangle with Square:
Rectangle rect = new Square();
rect.Width = 5;
rect.Height = 10;
Console.WriteLine(rect.Area()); // Outputs 100, but logically
expected 50
❌ The behavior is incorrect — this is a violation of LSP.
Design Patterns & SOLID Design Patterns in C# · SOLID
To ensure subclasses follow LSP:
base class.
class behavior.
valid scenarios.
✅ Ask yourself: Can this subclass be used anywhere the base class is used — without
surprises?
Design Patterns & SOLID Design Patterns in C# · SOLID
LSP is fundamentally about correct use of inheritance. While inheritance allows code
reuse, LSP ensures that the behavior of subclasses remains consistent with that of the
base class.
If a subclass changes the meaning or violates the expectations of the base class’s
behavior, it's misusing inheritance.
Interface Segregation Principle (ISP)
Design Patterns & SOLID Design Patterns in C# · SOLID
The Interface Segregation Principle (ISP) states that:
Clients should not be forced to depend on interfaces they do not use.
This means that interfaces should be small and focused, containing only the methods that
are relevant to the implementing class. It prevents "fat" or "bloated" interfaces.
Design Patterns & SOLID Design Patterns in C# · SOLID
Small, specific interfaces:
In contrast, large interfaces force classes to implement methods they may not need —
leading to fragile and cluttered code.
Design Patterns & SOLID Design Patterns in C# · SOLID
ISP improves code flexibility by:
Smaller interfaces result in lower coupling and better maintainability.
Design Patterns & SOLID Design Patterns in C# · SOLID
Violation Example:
public interface IWorker
void Work();
void Eat();
void Sleep();
public class Robot : IWorker
public void Work() { /* logic */ }
public void Eat() { throw new NotImplementedException(); }
public void Sleep() { throw new NotImplementedException(); }
❌ Robot is forced to implement Eat() and Sleep(), which don’t make sense for it.
Design Patterns & SOLID Design Patterns in C# · SOLID
Refactored Using ISP:
public interface IWorkable
void Work();
public interface IFeedable
void Eat();
public interface ISleepable
void Sleep();
public class Human : IWorkable, IFeedable, ISleepable
public void Work() { }
public void Eat() { }
public void Sleep() { }
public class Robot : IWorkable
public void Work() { }
✅ Now each class implements only the interfaces it needs — in line with ISP.
Inversion Principle (DIP)
Design Patterns & SOLID Design Patterns in C# · SOLID
The Dependency Inversion Principle (DIP) states that:
High-level modules should not depend on low-level modules. Both should
depend on abstractions.
Abstractions should not depend on details. Details should depend on
abstractions.
In other words:
abstract classes.
Design Patterns & SOLID Design Patterns in C# · SOLID
Aspect Dependency Inversion Principle (DIP) Dependency Injection (DI)
Definitio
A design principle about depending on
abstractions
A technique for passing
dependencies
Goal Decouple high-level logic from low-level
details
Provide dependencies to
objects
Relation DIP motivates the need for DI DI is a way to implement DIP
Focus What to depend on (abstractions) How dependencies are supplied
✅ DIP is a design principle, while DI is a design pattern/technique to implement that
principle.
Design Patterns & SOLID Design Patterns in C# · SOLID
Abstractions (e.g., interfaces or abstract classes):
Design Patterns & SOLID Design Patterns in C# · SOLID
❌ Without DIP (Tightly Coupled):
public class FileLogger
public void Log(string message) => Console.WriteLine("File log:
" + message);
public class OrderService
private readonly FileLogger _logger = new FileLogger();
public void ProcessOrder()
// Logic
_logger.Log("Order processed.");
✅ With DIP (Loosely Coupled via Abstraction):
public interface ILogger
void Log(string message);
public class FileLogger : ILogger
public void Log(string message) => Console.WriteLine("File log:
" + message);
public class OrderService
private readonly ILogger _logger;
public OrderService(ILogger logger)
_logger = logger;
public void ProcessOrder()
// Logic
_logger.Log("Order processed.");
Now OrderService depends on the abstraction (ILogger), not the concrete
FileLogger. You can easily substitute with DatabaseLogger, ConsoleLogger, or a
mock in tests.
Design Patterns & SOLID Design Patterns in C# · SOLID
✅ Key Benefits:
ones
containers
Advanced & Scenario-Based
Questions
Design Patterns & SOLID Design Patterns in C# · SOLID
Design patterns provide structured, reusable solutions that embody SOLID principles. For
example, the Strategy pattern supports OCP by allowing behavior extension without
modifying existing code; Repository separates data access (SRP); Dependency Injection
supports DIP by decoupling high- and low-level modules. Using patterns helps keep code
clean, modular, and maintainable.
Design Patterns & SOLID Design Patterns in C# · SOLID
The Unit of Work coordinates multiple Repositories and commits changes in a single
transaction. For example:
public interface IUnitOfWork : IDisposable
IProductRepository Products { get; }
IOrderRepository Orders { get; }
int Complete();
public class UnitOfWork : IUnitOfWork
private readonly DbContext _context;
public IProductRepository Products { get; }
public IOrderRepository Orders { get; }
public UnitOfWork(DbContext context)
_context = context;
Products = new ProductRepository(_context);
Orders = new OrderRepository(_context);
public int Complete() => _context.SaveChanges();
public void Dispose() => _context.Dispose();
This ensures atomic commits across repositories.
Design Patterns & SOLID Design Patterns in C# · SOLID
Testing singletons/statics is tricky due to global state. Best practices include:
Design Patterns & SOLID Design Patterns in C# · SOLID
The Mediator Pattern centralizes communication between components, preventing direct
dependencies and reducing complexity. It promotes loose coupling and simplifies
interactions. Alternatively, the Observer Pattern enables event-driven decoupling, and
Facade Pattern provides a simplified interface to complex subsystems.
Design Patterns & SOLID Design Patterns in C# · SOLID
Define a common interface, e.g., IPaymentStrategy with a method Pay(). Implement
concrete strategies for each payment method (CreditCard, PayPal, etc.). Use a context class
to invoke the selected strategy at runtime, enabling easy extension without modifying
existing code.
Design Patterns & SOLID Design Patterns in C# · SOLID
DAO (Data Access Object) focuses on low-level database operations and CRUD, typically
mapping tables to objects.
Repository abstracts data access at the domain level, working with aggregates/entities and
encapsulating business logic. Repository often uses DAO internally but is more aligned with
domain-driven design.
Design Patterns & SOLID Design Patterns in C# · SOLID
Design Patterns & SOLID Design Patterns in C# · SOLID
Use AOP (Aspect-Oriented Programming) techniques or design patterns like Decorator
to separate cross-cutting concerns from business logic. In .NET, middleware, filters, or
interceptors can manage concerns like logging or authorization, keeping SRP intact.
Design Patterns & SOLID Design Patterns in C# · SOLID
DI containers use reflection to inspect constructors of requested services, resolve
dependencies recursively from their registrations, apply lifecycle scopes (Singleton, Scoped,
Transient), and build the full object graph to return fully constructed instances.
Design Patterns & SOLID Design Patterns in C# · SOLID
Practical .NET Questions
Design Patterns & SOLID Design Patterns in C# · SOLID
ASP.NET Core MVC has built-in support for Dependency Injection.
IServiceCollection:
public void ConfigureServices(IServiceCollection services)
services.AddControllersWithViews();
services.AddScoped<IProductService, ProductService>(); //
Example
public class HomeController : Controller
private readonly IProductService _productService;
public HomeController(IProductService productService)
_productService = productService;
public IActionResult Index()
var products = _productService.GetAll();
return View(products);
The framework resolves and injects dependencies automatically.
Design Patterns & SOLID Design Patterns in C# · SOLID
their lifetimes (Scoped, Transient, Singleton). It acts as a service registry.
registered services at runtime. It uses the registrations to create and inject
dependencies.
Design Patterns & SOLID Design Patterns in C# · SOLID
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConne
ction")));
on the DbContext:
public interface IUnitOfWork : IDisposable
IProductRepository Products { get; }
int Complete();
public class UnitOfWork : IUnitOfWork
private readonly AppDbContext _context;
public IProductRepository Products { get; private set; }
public UnitOfWork(AppDbContext context)
_context = context;
Products = new ProductRepository(_context);
public int Complete() => _context.SaveChanges();
public void Dispose() => _context.Dispose();
Register UnitOfWork in DI container as Scoped.
Design Patterns & SOLID Design Patterns in C# · SOLID
var mockRepo = new Mock<IProductRepository>();
mockRepo.Setup(repo => repo.GetAll()).Returns(new List<Product> {
... });
var service = new ProductService(mockRepo.Object);
// Act & Assert
Design Patterns & SOLID Design Patterns in C# · SOLID
decides to pass control or handle the request.
or after.
pipeline setup.
Design Patterns & SOLID Design Patterns in C# · SOLID
public interface ICacheStrategy
void Cache(string key, object value);
object Retrieve(string key);
DistributedCacheStrategy.
public class CacheContext
private readonly ICacheStrategy _cacheStrategy;
public CacheContext(ICacheStrategy cacheStrategy)
_cacheStrategy = cacheStrategy;
public void Cache(string key, object value) =>
_cacheStrategy.Cache(key, value);
This enables switching caching mechanisms without code changes.
Design Patterns & SOLID Design Patterns in C# · SOLID
without specifying concrete classes.
Example: Creating UI components for different OS (Windows, Mac).
representations.
Example: Building a complex House with various parts (walls, doors, roof).
Summary: Abstract Factory is about families of products, Builder is about complex
construction process.
Design Patterns & SOLID Design Patterns in C# · SOLID
classes.
Design Patterns & SOLID Design Patterns in C# · SOLID
Design Patterns & SOLID Design Patterns in C# · SOLID
DIP by reducing direct dependencies.
modifying existing components.
Behavioral / Conceptual Questions
Design Patterns & SOLID Design Patterns in C# · SOLID
In a recent project, we had a monolithic service class handling multiple responsibilities,
making it hard to maintain and extend. By applying Single Responsibility Principle (SRP),
we split the class into focused services, each with a clear purpose. This drastically improved
readability, reduced bugs, and made it easier to add new features without risking
regressions. The project became more testable because each small class could be unit
tested independently.
Design Patterns & SOLID Design Patterns in C# · SOLID
Yes. In one project, a singleton was used without thread safety, causing race conditions
when accessed concurrently. This led to inconsistent state and application crashes. We
resolved it by implementing thread-safe lazy initialization using Lazy<T> in .NET, ensuring
the singleton instance was created safely once, even under heavy parallel access.
Design Patterns & SOLID Design Patterns in C# · SOLID
I prioritize YAGNI (You Aren’t Gonna Need It) to avoid over-engineering. SOLID principles
guide design for flexibility and maintainability, but I apply them pragmatically: start with
simple solutions and refactor as requirements evolve. Writing tests early helps identify pain
points justifying additional abstractions. Communication with the team ensures we don’t add
complexity prematurely but keep the codebase adaptable.
Design Patterns & SOLID Design Patterns in C# · SOLID
Challenges include:
performant.
Design Patterns & SOLID Design Patterns in C# · SOLID
I use a combination of:
Patterns”.
maintainability.
Bonus / Miscellaneous
Design Patterns & SOLID Design Patterns in C# · SOLID
properties from a parent class. It can lead to tight coupling and fragile hierarchies if
overused.
other classes and delegates behavior to them. It’s more flexible and promotes loose
coupling.
Design Patterns & SOLID Design Patterns in C# · SOLID
their interface. It wraps the original object to extend behavior.
or logging, without changing its interface.
Design Patterns & SOLID Design Patterns in C# · SOLID
Adapter converts the interface of a class into another interface clients expect, allowing
incompatible interfaces to work together. It promotes Open/Closed Principle (OCP) by
enabling new integrations without modifying existing code.
Design Patterns & SOLID Design Patterns in C# · SOLID
Use the Proxy or Virtual Proxy pattern where a placeholder object controls access to the
real object and defers its creation until needed. In .NET, Lazy<T> provides built-in lazy
loading.
Design Patterns & SOLID Design Patterns in C# · SOLID
explicitly.
Design Patterns & SOLID Design Patterns in C# · SOLID
Template Method defines the skeleton of an algorithm in a base class, deferring some steps
to subclasses. It allows subclasses to redefine parts of the algorithm without changing its
structure. It supports OCP by enabling extensions through inheritance.
Design Patterns & SOLID Design Patterns in C# · SOLID
SOLID promotes small, single-responsibility services (SRP), clear interfaces (ISP), loose
coupling (DIP), and extendable design (OCP). This aligns well with microservices by
encouraging modular, maintainable, and testable service boundaries.
Design Patterns & SOLID Design Patterns in C# · SOLID
The Command Pattern encapsulates requests as objects, allowing operations to be stored,
undone, or redone by maintaining command history.
Design Patterns & SOLID Design Patterns in C# · SOLID
Design Patterns & SOLID Design Patterns in C# · SOLID
Encapsulates a request as an object with methods to execute and possibly undo the
operation. The invoker calls commands without knowing the action details, supporting
decoupling and flexible request handling.
Design Patterns & SOLID Design Patterns in C# · SOLID
means focused, well-defined responsibilities.
modules are independent and changes in one don’t affect others.
Design Patterns & SOLID Design Patterns in C# · SOLID
coupling.
Design Patterns & SOLID Design Patterns in C# · SOLID
allowing flexible implementations.
Template Method pattern and partial abstraction.
Design Patterns & SOLID Design Patterns in C# · SOLID
Builder separates complex object construction from its representation. For example, building
an HttpRequest with optional headers, query params, and body:
public class HttpRequestBuilder
private HttpRequestMessage _request = new HttpRequestMessage();
public HttpRequestBuilder SetMethod(HttpMethod method)
_request.Method = method;
return this;
public HttpRequestBuilder AddHeader(string key, string value)
_request.Headers.Add(key, value);
return this;
public HttpRequestMessage Build() => _request;
Allows building requests step-by-step fluently.
Design Patterns & SOLID Design Patterns in C# · SOLID
Observer supports SRP by separating notification logic from core business logic and DIP by
depending on abstractions (observers). It fits DI because observers can be injected, allowing
flexible runtime subscriptions and loose coupling.