Technical interview Q&A plus 100+ career & HR questions—notice period, salary negotiation, resume, LinkedIn, freelancing, AI careers, and behavioral interviews with detailed, real-world answers.
Design Patterns & SOLID Design Patterns in C# · SOLID
The Singleton pattern ensures a class has only one instance and provides a global point of
ccess to it. It’s commonly used when exactly one object is needed to coordinate actions
cross the system, such as configuration settings, logging, or caching.
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
concurrent environments.
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
container, though generally discouraged.
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
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:
voids 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
Answer: Payment gateways (Credit Card, PayPal, UPI, etc.) Sorting algorithms (QuickSort, MergeSort, BubbleSort) Authentication strategies (OAuth, JWT, LDAP) Compression algorithms (ZIP, RAR, TAR) Loggers (FileLogger, ConsoleLogger, DatabaseLogger)
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
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
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
pplications?
The Repository pattern abstracts the data access layer from the business logic by providing
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
Answer: Separation of concerns between business and data access layers Improved testability (can mock repositories) Centralized query logic for maintainability Easier to switch persistence implementations Promotes cleaner and more organized code
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
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
nd 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
.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
chieve 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.
fter (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.
fter (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.
bstractions.
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
Answer: No client should be forced to implement methods it does not use. Break large interfaces into smaller, focused interfaces.
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
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.
bstractions should not depend on details. Details should depend on
bstractions.
In other words:
bstract classes.
Design Patterns & SOLID Design Patterns in C# · SOLID
Aspect Dependency Inversion Principle (DIP) Dependency Injection (DI)
Definitio
design principle about depending on
bstractions
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
dvanced & 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
nd why?
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
pplications?
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
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
Answer: 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.
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
Design Patterns & SOLID Design Patterns in C# · SOLID
Answer: 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.
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
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
Answer: The Command Pattern encapsulates requests as objects, allowing operations to be stored, undone, or redone by maintaining command history.
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
Design Patterns & SOLID Design Patterns in C# · SOLID
Answer: Use interfaces and abstractions (DIP). Apply Dependency Injection. Modularize code into bounded contexts or separate projects. Use events or messaging for decoupled communication. Avoid static state and global variables.
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
Design Patterns & SOLID Design Patterns in C# · SOLID
Answer: 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.
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
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
Answer: Use API contracts and versioning. Employ service discovery and load balancing. Prefer asynchronous messaging/event-driven architecture to reduce tight coupling. Apply circuit breakers and retries for fault tolerance.
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
Design Patterns & SOLID Design Patterns in C# · SOLID
Answer: Interfaces define contracts with no implementation, supporting ISP and DIP by llowing flexible implementations. Abstract classes provide a base implementation and shared code, useful for Template Method pattern and partial abstraction.
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.
Design Patterns & SOLID Design Patterns in C# · SOLID
Builder separates complex object construction from its representation. For example, building
n 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;
}
llows building requests step-by-step fluently.
Design Patterns & SOLID Design Patterns in C# · SOLID
Answer: 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.
In a production Design Patterns & SOLID application, teams apply this when handling user-facing features or integration boundaries. For example, you might use it during a sprint where reliability and observability matter—logging metrics, validating edge cases, and documenting the decision in an ADR so future developers understand why the approach was chosen.
Tip: Practice aloud on Toolliyo mock interview or the Interview Q&A section before your real interview.