Master technical and career interviews with structured answers—short definition, real examples, pitfalls, and how to answer in 60–90 seconds.
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…
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…
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…
.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 regis…
Services are registered in the Program.cs or Startup.cs file using the IServiceCollection interface. Example: builder.Services.AddTransient<IProductService, ProductService>(); builder.Services.AddScoped<IOrderSe…
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…
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 usi…
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 tes…
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(IOr…
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 A…
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 specifi…
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 exam…
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)? Do…
Before (SRP Violation): public class Invoice { public void GenerateInvoice() { /* logic */ } public void SaveToDatabase() { /* logic */ } public void SendEmail() { /* logic */ } } This class has 3 responsibilities: gener…
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, an…
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 relie…
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…
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, Decorat…
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 resp…
Before (OCP Violation): public class DiscountCalculator { public decimal CalculateDiscount(string customerType) { if (customerType == "Regular") return 10; if (customerType == "Premium") return 20; if (customerType == "V…
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 abstraction…
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…
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 i…
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 overr…
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 overrid…
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?