Master technical and career interviews with structured answers—short definition, real examples, pitfalls, and how to answer in 60–90 seconds.
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…
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…
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…
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…
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…
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…
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 read…
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 Maki…
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 v…
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 vo…
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 Pro…
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/stub…
❌ 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 FileLogg…
✅ 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…
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…
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…
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, ena…
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 grap…
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
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
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
Violating LSP can lead to:
class.
bstractions.
Essentially, it defeats the purpose of polymorphism.
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
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
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
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
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
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.