1. 🔴 What is the difference between an interface and a concrete class?
An interface defines a contract specifying what members an implementing class must provide.
A concrete class provides implementations and can be instantiated to create objects.
Example
// Interface
public interface IEmployee
{
void Display();
}
// Concrete class
public class Employee : IEmployee
{
public void Display()
{
Console.WriteLine("Employee Details");
}
}
Usage:
IEmployee emp = new Employee();
emp.Display(); // Employee Details
Comparison
|
Interface |
Concrete class |
|---|---|
|
Defines a contract |
Provides implementation |
|
Cannot be instantiated directly |
Can be instantiated |
|
Supports multiple interface inheritance |
Supports single class inheritance |
|
Cannot have instance fields |
Can have instance fields |
|
Can contain default implementations |
Contains implemented methods |
|
Used for abstraction |
Used for actual behavior |
Key point: An interface defines what a class must support, while a concrete class defines how the behavior is implemented.
2. 🔴 How does interface-based programming support loose coupling?
Interface-based programming means writing code that depends on interfaces rather than concrete implementations.
This reduces dependencies between classes and makes applications easier to maintain, test, and extend.
Without interface – Tight coupling
public class SqlLogger
{
public void Log(string message)
{
Console.WriteLine("SQL: " + message);
}
}
public class OrderService
{
private readonly SqlLogger _logger = new SqlLogger();
public void PlaceOrder()
{
_logger.Log("Order placed");
}
}
Problem: OrderService directly depends on SqlLogger. Replacing it with a file logger requires modifying the service.
With interface – Loose coupling
public interface ILoggerService
{
void Log(string message);
}
public class SqlLogger : ILoggerService
{
public void Log(string message)
{
Console.WriteLine("SQL: " + message);
}
}
public class FileLogger : ILoggerService
{
public void Log(string message)
{
Console.WriteLine("File: " + message);
}
}
public class OrderService
{
private readonly ILoggerService _logger;
public OrderService(ILoggerService logger)
{
_logger = logger;
}
public void PlaceOrder()
{
_logger.Log("Order placed");
}
}
Usage:
OrderService service = new OrderService(new SqlLogger());
service.PlaceOrder();
To switch implementation:
OrderService service = new OrderService(new FileLogger());
Advantages:
-
Reduces dependencies between classes.
-
Makes unit testing easier through mocks.
-
Supports multiple implementations.
-
Follows the Dependency Inversion Principle.
Key point: Depend on abstractions, not concrete implementations.
3. 🔴 What is Dependency Injection, and how does it relate to OOP?
Dependency Injection (DI) is a design pattern in which a class receives its dependencies from an external source instead of creating them internally.
It supports loose coupling, abstraction, testability, and maintainability.
Example
public interface IEmailService
{
void SendEmail();
}
public class EmailService : IEmailService
{
public void SendEmail()
{
Console.WriteLine("Email Sent");
}
}
public class NotificationService
{
private readonly IEmailService _emailService;
public NotificationService(IEmailService emailService)
{
_emailService = emailService;
}
public void Notify()
{
_emailService.SendEmail();
}
}
Register in ASP.NET Core
builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddScoped<NotificationService>();
Usage:
public class NotificationController : ControllerBase
{
private readonly NotificationService _service;
public NotificationController(NotificationService service)
{
_service = service;
}
}
ASP.NET Core automatically resolves and injects the registered dependencies.
Types of Dependency Injection
|
Type |
Explanation |
|---|---|
|
Constructor Injection |
Dependencies supplied through constructor |
|
Property Injection |
Dependencies assigned through properties |
|
Method Injection |
Dependencies supplied through method parameters |
Constructor injection is the most commonly recommended approach for required dependencies.
DI service lifetimes in ASP.NET Core
|
Lifetime |
Behavior |
|---|---|
|
Transient |
New instance each time requested |
|
Scoped |
One instance per request scope |
|
Singleton |
One instance for the application's service provider lifetime |
Key point: DI is a technique for implementing Inversion of Control (IoC) and supports the Dependency Inversion Principle (DIP).
4. 🔴 What is the difference between tight coupling and loose coupling?
Tight coupling means one class directly depends on another concrete class.
Loose coupling means classes interact through abstractions, reducing dependencies on specific implementations.
Tight coupling
class PaymentService
{
private readonly SqlRepository _repository = new SqlRepository();
}
PaymentService directly creates SqlRepository.
Loose coupling
class PaymentService
{
private readonly IRepository _repository;
public PaymentService(IRepository repository)
{
_repository = repository;
}
}
Comparison
|
Tight coupling |
Loose coupling |
|---|---|
|
Depends on concrete classes |
Depends on abstractions |
|
Harder to replace implementations |
Easier to replace implementations |
|
Unit testing is more difficult |
Supports mocking |
|
Less flexible |
More flexible |
|
Changes can affect dependent classes |
Reduces impact of implementation changes |
Real-world example: In ASP.NET Core Clean Architecture, the Application layer can depend on IRepository, while Infrastructure provides SqlRepository.
Key point: Loose coupling improves maintainability, flexibility, and unit testability.
5. 🔴 How do you implement multiple interfaces in a single class?
C# allows a class to implement multiple interfaces by separating their names with commas.
This supports multiple contracts without multiple class inheritance.
Example
public interface IPrint
{
void Print();
}
public interface IScan
{
void Scan();
}
public class Printer : IPrint, IScan
{
public void Print()
{
Console.WriteLine("Printing...");
}
public void Scan()
{
Console.WriteLine("Scanning...");
}
}
Usage:
Printer printer = new Printer();
printer.Print();
printer.Scan();
What if two interfaces have the same method?
Use explicit interface implementation when separate implementations are required.
interface IFirst
{
void Show();
}
interface ISecond
{
void Show();
}
class Demo : IFirst, ISecond
{
void IFirst.Show()
{
Console.WriteLine("First");
}
void ISecond.Show()
{
Console.WriteLine("Second");
}
}
Usage:
Demo obj = new Demo();
((IFirst)obj).Show(); // First
((ISecond)obj).Show(); // Second
Key point: C# does not support multiple class inheritance, but a class can implement multiple interfaces.
6. 🟡 What is interface segregation, and why should large interfaces be avoided?
Interface Segregation Principle (ISP) is one of the SOLID principles.
It states that clients should not be forced to depend on methods they do not use.
Bad design – Large interface
public interface IWorker
{
void Work();
void Eat();
}
public class Robot : IWorker
{
public void Work()
{
Console.WriteLine("Working");
}
public void Eat()
{
throw new NotSupportedException();
}
}
Problem: A robot does not need an Eat() method.
Good design – Segregated interfaces
public interface IWorkable
{
void Work();
}
public interface IEatable
{
void Eat();
}
public class Robot : IWorkable
{
public void Work()
{
Console.WriteLine("Working");
}
}
public class Human : IWorkable, IEatable
{
public void Work() { }
public void Eat() { }
}
Advantages:
-
Avoids unnecessary method implementations.
-
Reduces coupling.
-
Improves maintainability.
-
Makes interfaces easier to implement and test.
Real-world example: Instead of one large repository interface containing every possible operation, separate read and write contracts when different consumers need different capabilities.
Key point: Prefer small, focused interfaces based on client requirements.
7. 🟡 What is covariance and contravariance in C#?
Covariance and contravariance allow compatible generic interface and delegate types to be assigned to one another when their type parameters have the appropriate variance.
-
Covariance (
out) – Supports assignment from a more derived type to a less derived type. -
Contravariance (
in) – Supports assignment in the opposite direction.
Covariance (out)
class Animal { }
class Dog : Animal { }
IEnumerable<Dog> dogs = new List<Dog>();
IEnumerable<Animal> animals = dogs;
Explanation: IEnumerable<out T> is covariant, so a sequence of dogs can be treated as a sequence of animals.
Contravariance (in)
Action<Animal> animalAction = animal =>
{
Console.WriteLine("Animal");
};
Action<Dog> dogAction = animalAction;
dogAction(new Dog());
Explanation: A method that accepts any Animal can also handle a Dog.
Comparison
|
Covariance |
Contravariance |
|---|---|
|
Uses |
Uses |
|
Supports derived-to-base assignment |
Supports base-to-derived assignment |
|
Typically produces values |
Typically consumes values |
|
Example: |
Example: |
Important: Generic variance applies to eligible interfaces and delegates, using reference-type conversions. List<Dog> itself is not assignable to List<Animal>.
Key point: Covariance is commonly associated with output types; contravariance with input types.
8. 🟡 What is the difference between is, as, and explicit casting?
C# provides is, as, and explicit casts for type checking and conversion.
Example
class Animal { }
class Dog : Animal
{
public void Bark()
{
Console.WriteLine("Barking");
}
}
Animal animal = new Dog();
is keyword
Checks whether an object is compatible with a specified type.
if (animal is Dog)
{
Console.WriteLine("Animal is Dog");
}
Modern pattern matching:
if (animal is Dog dog)
{
dog.Bark();
}
as keyword
Attempts a reference or nullable conversion and returns null when it fails.
Dog? dog = animal as Dog;
if (dog != null)
{
dog.Bark();
}
Explicit casting
Attempts conversion and throws an exception if an incompatible reference conversion is performed.
Dog dog = (Dog)animal;
dog.Bark();
Comparison
|
|
|
Explicit cast |
|---|---|---|
|
Checks type compatibility |
Attempts conversion |
Performs conversion |
|
Returns boolean or matches pattern |
Returns converted value or null |
Returns converted value |
|
No exception for failed type test |
No exception for failed compatible reference conversion |
May throw |
|
Useful for checking |
Useful for safe nullable casts |
Useful when type is known |
Key point: Prefer is with pattern matching when you need both type checking and access to the converted value.
9. 🟡 What are generic classes and generic interfaces?
Generics allow classes, interfaces, and methods to work with different data types while maintaining compile-time type safety.
Generic class
public class Repository<T>
{
public void Save(T entity)
{
Console.WriteLine("Saved: " + entity);
}
}
Usage:
Repository<string> repo1 = new Repository<string>();
repo1.Save("Employee");
Repository<int> repo2 = new Repository<int>();
repo2.Save(100);
Generic interface
public interface IRepository<T>
{
void Add(T entity);
T? GetById(int id);
}
Implementation
public class EmployeeRepository : IRepository<Employee>
{
public void Add(Employee entity)
{
Console.WriteLine("Employee Added");
}
public Employee? GetById(int id)
{
return null; // Example only
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
Advantages
-
Compile-time type safety.
-
Reusable code.
-
Reduces explicit casting.
-
Avoids boxing for many value-type operations.
-
Commonly used in collections, repositories, and services.
Generic constraints
public class Repository<T> where T : class
{
}
The where T : class constraint restricts T to reference types.
Real-world example: Generic repositories such as IRepository<Customer> and IRepository<Order> can share common persistence contracts.
Key point: Generics provide type-safe, reusable components without duplicating code for every data type.
10. 🟢 What are default interface methods, and what problems do they solve?
Default interface methods, introduced in C# 8.0, allow interfaces to provide method implementations.
Previously, adding a new abstract member to an interface generally required every implementing class to implement that member.
Default interface methods help evolve interfaces while reducing the need to modify existing implementations.
Example
public interface ILogger
{
void Log(string message);
void LogError(string message)
{
Log("ERROR: " + message);
}
}
Implementation
public class FileLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
FileLogger does not need to implement LogError() because the interface provides a default implementation.
Usage:
ILogger logger = new FileLogger();
logger.Log("Application Started");
logger.LogError("Database Connection Failed");
Output:
Application Started
ERROR: Database Connection Failed
Important points
-
Interfaces can provide default method implementations.
-
Implementing classes can supply their own implementations.
-
Default interface methods help maintain compatibility when extending interfaces.
-
A default interface method is not automatically available through a concrete class reference.
For example:
FileLogger logger = new FileLogger();
// logger.LogError("Error"); // Compilation error
((ILogger)logger).LogError("Error"); // Valid
Key point: Default interface methods help extend existing interfaces without forcing all implementations to change, although interface design and compatibility still require care.
Quick Interview Revision
|
Concept |
Key point |
|---|---|
|
Interface |
Defines a contract |
|
Concrete class |
Provides actual implementation |
|
Loose coupling |
Depend on abstractions |
|
Tight coupling |
Direct dependency on implementation |
|
Dependency Injection |
Dependencies supplied externally |
|
Constructor injection |
Most common DI approach |
|
Multiple interfaces |
One class can implement many interfaces |
|
Explicit interface implementation |
Resolves overlapping interface members |
|
Interface Segregation |
Small, focused interfaces |
|
Covariance ( |
Derived → Base |
|
Contravariance ( |
Base → Derived |
|
|
Checks type compatibility |
|
|
Safe nullable conversion |
|
Explicit cast |
Conversion may throw |
|
Generic class |
Type-safe reusable class |
|
Generic interface |
Type-safe reusable contract |
|
Default interface method |
Interface method with implementation |
Interview Priority – Senior .NET Developer / Technical Lead
Interfaces & loose coupling
Dependency Injection and lifetimes
Interface Segregation Principle
Generic classes and interfaces
is, as, and explicit casting
Covariance and contravariance
Default interface methods
For a .NET Technical Lead interview, be prepared to explain how interfaces, DI, and generic repositories work together in Clean Architecture, particularly why the Application layer depends on abstractions while Infrastructure supplies their implementations.