Interview Q&A

Technical interview questions with detailed answers—organized by course, like Dot Net Tutorials interview sections. Original content for Toolliyo Academy.

Popular tracks

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

Review the concept and prepare a concise verbal explanation with a real project example.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

Review the concept and prepare a concise verbal explanation with a real project example.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

Review the concept and prepare a concise verbal explanation with a real project example.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Global state: Singleton can lead to hidden dependencies and make testing difficult.
  • Tight coupling: Other classes depend on the Singleton instance, reducing flexibility.
  • Concurrency issues: If not implemented thread-safe, it can cause race conditions.
  • Resource contention: Singleton might become a bottleneck if overused in

concurrent environments.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

container, though generally discouraged.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Eager Initialization: The Singleton instance is created at the time of class loading.

It's simple but can waste resources if the instance is never used.

  • Lazy Initialization: The instance is created only when it is first accessed. It saves

resources but requires careful implementation for thread safety.

Factory pattern Q&A

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Factory Method: Defines an interface for creating an object but lets subclasses

decide which class to instantiate. It uses inheritance and relies on subclass

overriding.

  • Abstract Factory: Provides an interface to create families of related or dependent

objects without specifying their concrete classes. It uses composition and is useful

when you need to create multiple related objects together.

Permalink

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

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Encapsulates object creation: Decouples client code from concrete classes.
  • Promotes code reuse: Centralizes object creation logic.
  • Enhances maintainability: Adding new types requires minimal changes to existing

code.

  • Supports polymorphism: Clients work with interfaces or base classes rather than

concrete types.

  • Improves testability: Can easily mock or swap factory implementations.
Permalink

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

Permalink

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.

Permalink

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

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • 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)
Permalink

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.

Permalink

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

Permalink

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).

Permalink

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();

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • 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
Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Add custom methods in a specialized repository interface (e.g.,

IProductRepository)

  • Use Specification pattern or LINQ expressions
  • Inject DbContext into repository if needed for advanced queries
  • Optionally, break out complex queries into Query objects or services

public interface IProductRepository : IRepository<Product>

Task<IEnumerable<Product>> GetProductsWithLowStockAsync(int

threshold);

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Over-abstraction: Can add unnecessary complexity for simple apps.
  • Duplication: May duplicate what EF Core already provides (since EF is already a

repository/unit-of-work pattern).

  • Hides EF Core features: May obscure advanced capabilities like eager loading or

projections.

  • Extra boilerplate: Especially with generic repositories, which may not add much

value.

Unit of Work

Permalink

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.

Permalink

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.

Permalink

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>();

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Transactional consistency: All changes across repositories are saved in a single

transaction.

  • Centralized commit: All changes are committed through one method

(Complete()), improving control and readability.

  • Reduced code duplication: Prevents repeated save logic across repositories.
  • Improved testability: Enables better unit testing by mocking a single Unit of Work

instead of multiple repositories.

  • Change tracking: Ensures that only modified entities are persisted, reducing

unnecessary database operations.

Permalink

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)

Permalink

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:

  • Improves modularity
  • Enables easier unit testing (via mock dependencies)
  • Promotes adherence to SOLID principles (especially the Dependency Inversion

Principle)

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

Review the concept and prepare a concise verbal explanation with a real project example.

Permalink

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.

Permalink

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>();

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Transient: A new instance is created every time it is requested.
  • Scoped: A single instance is created per request/HTTP request.
  • Singleton: A single instance is created and shared across the application lifetime.

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

Permalink

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:

  • Refactor the code to remove tight coupling.
  • Use interfaces or events to break the cycle.
  • Consider using property injection carefully (not recommended as a first choice).
  • Rethink your design – circular dependencies often indicate architectural issues.
Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Easier mocking of dependencies using frameworks like Moq or NSubstitute.
  • No need to instantiate real implementations (e.g., database access) for tests.
  • Improved isolation of the unit under test.
  • Faster, more reliable tests due to lack of external dependency reliance.
Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Over-injection (too many dependencies in one class – violates SRP)
  • Service locator anti-pattern
  • Incorrect lifetimes, leading to memory leaks or unintended reuse
  • Tight coupling to the DI container (e.g., using container APIs in business logic)
  • Circular dependencies due to poor design
  • Registering services in the wrong order or not at all
Permalink

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)

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

You can identify SRP violations by asking questions like:

  • Does this class perform more than one function (e.g., data access and business

logic)?

  • Does it change for different reasons (e.g., changes in UI and database)?
  • Does it have too many dependencies or too much code?
  • Are there “and”s in the class name or method descriptions? E.g.,

ReportGeneratorAndPrinter.

Common signs:

  • Long classes or large files
  • Many unrelated methods
  • Hard-to-test code
Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

SRP improves maintainability by:

  • Reducing complexity – Classes are smaller and easier to understand.
  • Easier testing – Each class can be tested in isolation.
  • Better separation of concerns – Business logic, data access, and infrastructure

code are kept apart.

  • Lower risk of bugs – Changes in one responsibility don’t affect others.

Ultimately, it leads to more modular, flexible, and robust code.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

If a class has multiple responsibilities:

  • It becomes tightly coupled and harder to change without affecting other parts.
  • Changes are more error-prone and often introduce bugs.
  • Testing becomes harder since the class relies on multiple behaviors.
  • Code reusability and readability suffer due to mixed concerns.
  • You violate SRP, which makes code harder to maintain in the long run.

Open/Closed Principle (OCP)

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

To design classes that follow OCP:

  • Use abstraction (interfaces or abstract classes).
  • Rely on polymorphism and inheritance.
  • Apply composition over inheritance when suitable.
  • Follow design principles like Strategy, Decorator, or Template Method patterns.

✅ Extend behavior through new classes rather than modifying existing code.

Permalink

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.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Interfaces and abstract classes provide a contract that other classes can

implement or inherit.

  • They enable polymorphism, which allows behavior to be extended without altering

existing code.

  • By programming to abstractions (not concrete implementations), you can easily

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)

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

Violating LSP can lead to:

  • Unexpected behavior when a subclass does not honor the contract of the base

class.

  • Code that breaks at runtime when substituting a derived class.
  • Tightly coupled code that depends on specific implementations rather than

abstractions.

  • Unit tests failing when testing subclasses in place of base classes.

Essentially, it defeats the purpose of polymorphism.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

To ensure subclasses follow LSP:

  • Subclasses should not override behavior in a way that breaks expected behavior.
  • Subclasses should preserve the invariants and preconditions/postconditions of the

base class.

  • Avoid overriding methods to throw exceptions for valid base class behavior.
  • Use composition over inheritance if a subclass doesn’t strictly conform to the base

class behavior.

  • Write unit tests to verify that the subclass behaves identically to the base class in all

valid scenarios.

✅ Ask yourself: Can this subclass be used anywhere the base class is used — without

surprises?

Permalink

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)

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

Small, specific interfaces:

  • Promote separation of concerns
  • Make classes easier to implement and test
  • Reduce the risk of breaking changes
  • Avoid forcing classes to implement irrelevant methods
  • Increase reusability and readability

In contrast, large interfaces force classes to implement methods they may not need —

leading to fragile and cluttered code.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

ISP improves code flexibility by:

  • Allowing classes to only depend on what they actually use
  • Making it easier to extend or replace functionality without affecting unrelated parts
  • Enabling composition over inheritance
  • Making interfaces easier to mock or stub in unit tests
  • Encouraging clean, modular design

Smaller interfaces result in lower coupling and better maintainability.

Permalink

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.

Permalink

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)

Permalink

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:

  • High-level business logic shouldn't depend on concrete implementations.
  • Instead, both high- and low-level components should depend on interfaces or

abstract classes.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

Abstractions (e.g., interfaces or abstract classes):

  • Decouple components so changes in one don’t ripple through others
  • Enable substitution of different implementations easily
  • Allow for easier unit testing with mocks/stubs
  • Promote extensibility and maintainability
  • Serve as contracts that both high- and low-level modules depend on
Permalink

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.");

  • OrderService is tightly coupled to FileLogger.

✅ 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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

✅ Key Benefits:

  • Decouples components — changes in low-level modules won’t affect high-level

ones

  • Improves testability — you can easily inject mocks/stubs
  • Enhances flexibility — swap implementations without touching core logic
  • Promotes reuse — abstractions can be used across different modules
  • Supports SOLID architecture — especially when combined with DI and IoC

containers

Advanced & Scenario-Based

Questions

Permalink

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.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

Testing singletons/statics is tricky due to global state. Best practices include:

  • Refactor to use interfaces and DI instead of static/singletons.
  • Wrap static calls behind interfaces so you can mock them.
  • Use specialized mocking tools (e.g., Microsoft Fakes) if refactoring isn't possible.
  • Avoid static state to improve testability.
Permalink

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.

Permalink

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.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Identify classes violating SRP and break them down.
  • Introduce abstractions and interfaces to decouple components (DIP).
  • Replace conditional logic with polymorphism to respect OCP.
  • Split large interfaces (ISP).
  • Check inheritance hierarchies to maintain LSP.
  • Inject dependencies instead of direct instantiation.
  • Incrementally refactor with unit tests to ensure behavior remains consistent.
Permalink

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.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Define plugin contracts with interfaces (DIP).
  • Load plugins dynamically using reflection or MEF.
  • Use DI to inject dependencies into plugins.
  • Ensure plugins follow SRP with focused responsibilities.
  • Use Factory or Strategy patterns to instantiate plugins.
  • Keep core system closed for modification but open for extension (OCP).
  • Separate cross-cutting concerns externally.

Practical .NET Questions

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

ASP.NET Core MVC has built-in support for Dependency Injection.

  • Register services in Startup.cs within ConfigureServices method using

IServiceCollection:

public void ConfigureServices(IServiceCollection services)

services.AddControllersWithViews();

services.AddScoped<IProductService, ProductService>(); //

Example

  • Inject dependencies via constructor injection in controllers or services:

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • IServiceCollection: A container used during app startup to register services and

their lifetimes (Scoped, Transient, Singleton). It acts as a service registry.

  • IServiceProvider: The built container that resolves and provides instances of

registered services at runtime. It uses the registrations to create and inject

dependencies.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Register DbContext with DI in Startup.cs:

services.AddDbContext<AppDbContext>(options =>

options.UseSqlServer(Configuration.GetConnectionString("DefaultConne

ction")));

  • Implement Repositories for entities injecting AppDbContext.
  • Implement Unit of Work which holds multiple repositories and calls SaveChanges()

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Use mocking libraries like Moq, NSubstitute, or FakeItEasy.
  • Create mocks of interfaces and inject them into the class under test:

var mockRepo = new Mock<IProductRepository>();

mockRepo.Setup(repo => repo.GetAll()).Returns(new List<Product> {

... });

var service = new ProductService(mockRepo.Object);

// Act & Assert

  • This allows testing in isolation without hitting real databases or external services.
Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Chain of Responsibility: Middleware components form a pipeline where each

decides to pass control or handle the request.

  • Decorator: Middleware wraps around the next component, adding behavior before

or after.

  • Factory: Middleware components can be created via factories for configurable

pipeline setup.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Define a caching interface:

public interface ICacheStrategy

void Cache(string key, object value);

object Retrieve(string key);

  • Implement strategies like MemoryCacheStrategy,

DistributedCacheStrategy.

  • Use DI or factory to inject the chosen strategy at runtime:

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Abstract Factory: Provides an interface for creating families of related objects

without specifying concrete classes.

Example: Creating UI components for different OS (Windows, Mac).

  • Builder: Focuses on step-by-step construction of a complex object, allowing different

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Apply Single Responsibility Principle (SRP) by splitting responsibilities into smaller

classes.

  • Use composition instead of inheritance to delegate behavior.
  • Extract business logic into services or helpers.
  • Introduce abstractions to isolate concerns.
  • Continuously refactor large classes and add unit tests.
Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Resharper: Provides code analysis and refactoring hints.
  • SonarQube / SonarCloud: Analyzes code quality and reports SOLID violations.
  • FxCop / Roslyn analyzers: Provide static analysis with custom rules.
  • NDepend: Deep architecture and dependency analysis tool.
  • StyleCop: Enforces coding style which indirectly helps maintain SOLID code.
Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Mediator decouples components by centralizing communication, supporting SRP and

DIP by reducing direct dependencies.

  • It fits DI because the mediator itself can be injected where needed.
  • It supports OCP by allowing new communication routes or handlers without

modifying existing components.

  • Helps avoid tight coupling in complex workflows or CQRS patterns.

Behavioral / Conceptual Questions

Permalink

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.

Permalink

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.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

Challenges include:

  • Legacy code with tight coupling, making decomposition hard.
  • Risk of introducing bugs while splitting responsibilities or introducing abstractions.
  • Managing dependencies and lifetimes correctly when injecting dependencies.
  • Convincing stakeholders that refactoring time is valuable.
  • Balancing between adhering strictly to SOLID vs. keeping code understandable and

performant.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

I use a combination of:

  • Simple examples showing before/after code refactoring.
  • Pair programming sessions to explain thought processes.
  • Encouraging reading and discussing classic books like “Clean Code” and “Design

Patterns”.

  • Practical coding exercises and code reviews focused on SOLID principles.
  • Showing real project scenarios where principles improved code quality and

maintainability.

  • Promoting a culture of continuous learning and curiosity.

Bonus / Miscellaneous

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Inheritance creates an “is-a” relationship, where a subclass inherits behavior and

properties from a parent class. It can lead to tight coupling and fragile hierarchies if

overused.

  • Composition creates a “has-a” relationship, where a class contains instances of

other classes and delegates behavior to them. It’s more flexible and promotes loose

coupling.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Decorator adds additional responsibilities to objects dynamically without altering

their interface. It wraps the original object to extend behavior.

  • Proxy controls access to an object, possibly adding lazy initialization, access control,

or logging, without changing its interface.

Permalink

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.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Service Locator anti-pattern: Hides dependencies instead of injecting them

explicitly.

  • Overusing Singleton: Leads to hidden global state and testing difficulties.
  • Improper Singleton thread safety: Causes race conditions.
  • Injecting concrete implementations: Violates DIP.
Permalink

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.

Permalink

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.

Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • 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.
Permalink

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.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Cohesion: Degree to which elements of a module belong together. High cohesion

means focused, well-defined responsibilities.

  • Coupling: Degree of interdependence between modules. Low coupling means

modules are independent and changes in one don’t affect others.

Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • 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.
Permalink

Design Patterns & SOLID Design Patterns in C# · SOLID

  • Interfaces define contracts with no implementation, supporting ISP and DIP by

allowing flexible implementations.

  • Abstract classes provide a base implementation and shared code, useful for

Template Method pattern and partial abstraction.

Permalink

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.

Permalink

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.

Permalink