Master technical and career interviews with structured answers—short definition, real examples, pitfalls, and how to answer in 60–90 seconds.
Short answer: Transactional consistency: All changes across repositories are saved in a single transaction. Explain a bit more Centralized commit: All changes are committed through one method (Complete()), improving cont…
Short answer: 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 effe…
Short answer: .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,…
Short answer: Services are registered in the Program.cs or Startup.cs file using the IServiceCollection interface. Example code builder.Services.AddTransient<IProductService, ProductService>(); builder.Services.Add…
Short answer: 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…
Short answer: 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, mor…
Short answer: How? Yes, DI can be implemented manually without using a DI container. You simply pass dependencies through constructors or methods: public class OrderService Example code { private readonly IOrderRepositor…
Short answer: Yes, DI can be implemented manually without using a DI container. Explain a bit more You simply pass dependencies through constructors or methods: public class OrderService { private readonly IOrderReposito…
Short answer: 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., usi…
Short answer: 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 Injectio…
Short answer: You can identify SRP violations by asking questions like: Does this class perform more than one function (e.g., data access and business logic)? Explain a bit more Does it change for different reasons (e.g.…
Short answer: Before (SRP Violation): public class Invoice Example code { public void GenerateInvoice() { /* logic */ } public void SaveToDatabase() { /* logic */ } public void SendEmail() { /* logic */ } } This class ha…
Short answer: 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, d…
Short answer: 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 t…
Short answer: 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…
Short answer: 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 Str…
Short answer: Interfaces and abstract classes provide a contract that other classes can implement or inherit. Explain a bit more They enable polymorphism, which allows behavior to be extended without altering existing co…
Short answer: 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…
Short answer: To ensure subclasses follow LSP: Subclasses should not override behavior in a way that breaks expected behavior. Explain a bit more Subclasses should preserve the invariants and preconditions/postconditions…
Short answer: 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…
Short answer: 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 reusab…
Short answer: 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 in…
Short answer: Violation Example: public interface IWorker Example code { void Work(); void Eat(); void Sleep(); } public class Robot : IWorker { public void Work() { /* logic */ } public void Eat() { throw new NotImpleme…
Short answer: Refactored Using ISP: public interface IWorkable Example code { void Work(); } public interface IFeedable { void Eat(); } public interface ISleepable { void Sleep(); } public class Human : IWorkable, IFeeda…
Short answer: 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 lo…
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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)
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: .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.
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: Services are registered in the Program.cs or Startup.cs file using the IServiceCollection interface.
builder.Services.AddTransient<IProductService, ProductService>(); builder.Services.AddScoped<IOrderService, OrderService>(); builder.Services.AddSingleton<ILoggingService, LoggingService>();
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: How? 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.
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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. 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.
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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)
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Open/Closed in ShopNest: add a new payment method by adding a class, not by editing a giant switch in CheckoutService.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Open/Closed in ShopNest: add a new payment method by adding a class, not by editing a giant switch in CheckoutService.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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)
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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)
Open/Closed in ShopNest: add a new payment method by adding a class, not by editing a giant switch in CheckoutService.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Open/Closed in ShopNest: add a new payment method by adding a class, not by editing a giant switch in CheckoutService.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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?
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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)
Open/Closed in ShopNest: add a new payment method by adding a class, not by editing a giant switch in CheckoutService.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Patterns in ShopNest should solve a real pain (swappable payments, test seams)—not be added for decoration.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Open/Closed in ShopNest: add a new payment method by adding a class, not by editing a giant switch in CheckoutService.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Open/Closed in ShopNest: add a new payment method by adding a class, not by editing a giant switch in CheckoutService.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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)
Open/Closed in ShopNest: add a new payment method by adding a class, not by editing a giant switch in CheckoutService.
Design Patterns & SOLID Design Patterns in C# · SOLID
Short answer: 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.
Open/Closed in ShopNest: add a new payment method by adding a class, not by editing a giant switch in CheckoutService.