20 C# Interview Questions Every Senior .NET Developer Should Know
If you have several years of C# experience, a senior .NET interview is rarely about remembering syntax.
Most interviewers are trying to understand something deeper:
Can you make good engineering decisions when the requirements are not obvious?
You may be asked about async and await, dependency injection, LINQ, generics, exception handling, or memory management. But the strongest candidates don't just explain what these features do.
They explain when to use them, when not to use them, and what trade-offs they introduce.
After years of working with C# and .NET, I've found that senior-level interviews tend to come back to the same concepts repeatedly.
Here are 20 questions worth preparing for.
1. What's the difference between IEnumerable<T>, IQueryable<T>, and ICollection<T>?
This is a classic question because it tests whether you understand how data is evaluated.
IEnumerable<T>
IEnumerable<T> is primarily used for iterating over data that is already available in memory.
IEnumerable<int> numbers = new[] { 1, 2, 3, 4, 5 };
var result = numbers
.Where(x => x > 2)
.ToList();
The filtering happens in the application process.
IQueryable<T>
IQueryable<T> is commonly used with data providers such as Entity Framework Core.
IQueryable<Customer> customers = dbContext.Customers;
var result = customers
.Where(x => x.IsActive)
.ToList();
The query can be translated by the provider into SQL and executed by the database.
ICollection<T>
ICollection<T> represents a collection that supports operations such as adding and removing elements.
ICollection<Customer> customers = new List<Customer>();
customers.Add(customer);
Senior-level answer
Don't simply say "IQueryable is faster."
The better answer is:
IQueryableallows a provider to translate the expression into another query language, such as SQL. That can reduce the amount of data transferred to the application. But using it carelessly can also generate inefficient queries or expose persistence concerns throughout the application.
What the interviewer is testing:
Whether you understand deferred execution, query providers, and where computation occurs.
2. What is deferred execution in LINQ?
Consider this:
var result = numbers.Where(x => x > 10);
At this point, the filtering may not have happened yet.
The query is executed when you enumerate it.
foreach (var number in result)
{
Console.WriteLine(number);
}
Or when you materialize it:
var list = numbers
.Where(x => x > 10)
.ToList();
This matters because the underlying data may change between creating the query and executing it.
For example:
var query = customers.Where(x => x.IsActive);
customers.Add(new Customer { IsActive = true });
var result = query.ToList();
The result can include the newly added customer depending on the underlying collection.
Senior-level consideration
A senior developer should understand that deferred execution can be useful, but accidental multiple enumeration can cause unnecessary work.
For example:
if (query.Any())
{
var items = query.ToList();
}
Depending on the provider, this can result in multiple executions.
3. What is the difference between async and await?
async marks a method as asynchronous.
await asynchronously waits for an operation to complete.
public async Task<Customer> GetCustomerAsync(int id)
{
return await repository.GetCustomerAsync(id);
}
The important part is that await does not mean "create a new thread."
For I/O-bound operations such as:
Database calls
HTTP requests
File operations
the asynchronous model allows the current thread to be released while the operation is waiting.
A common misconception
This:
await httpClient.GetAsync(url);
doesn't mean:
"Run this on another thread."
It means:
"Suspend this asynchronous method until the operation completes without blocking the current thread."
That distinction is important in ASP.NET Core applications.
4. Why should you avoid .Result and .Wait() in asynchronous code?
Consider:
var result = service.GetDataAsync().Result;
or:
service.GetDataAsync().Wait();
These calls synchronously block the current thread.
In modern ASP.NET Core applications, this can reduce scalability and create thread-pool pressure.
Prefer:
var result = await service.GetDataAsync();
And propagate asynchronous execution through the call chain.
Senior-level answer
A good interview answer should go beyond:
"
awaitis better."
Explain that blocking asynchronous operations can waste thread-pool threads and reduce throughput under load.
5. What is dependency injection and why is it useful?
Dependency injection means an object receives the dependencies it needs rather than constructing them itself.
Instead of:
public class OrderService
{
private readonly SqlOrderRepository _repository =
new SqlOrderRepository();
}
use:
public class OrderService
{
private readonly IOrderRepository _repository;
public OrderService(IOrderRepository repository)
{
_repository = repository;
}
}
Then register the dependency:
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
This provides:
Loose coupling
Easier testing
Replaceable implementations
Centralized configuration
Better separation of responsibilities
Senior-level consideration
Dependency injection isn't automatically good just because it is DI.
Overusing abstractions can make a codebase harder to understand.
A senior developer should know where abstraction provides real value.
6. Explain Singleton, Scoped, and Transient lifetimes.
ASP.NET Core provides three common dependency injection lifetimes.
Transient
A new instance is created each time it is requested.
services.AddTransient<IEmailService, EmailService>();
Useful for lightweight, stateless services.
Scoped
One instance is created per scope.
In a typical ASP.NET Core request, this means one instance per HTTP request.
services.AddScoped<IOrderService, OrderService>();
This is commonly used for services that depend on DbContext.
Singleton
One instance is created for the lifetime of the application.
services.AddSingleton<ICacheService, CacheService>();
Useful for genuinely application-wide, thread-safe state or services.
The important interview question
The interviewer may ask:
What happens if a singleton depends on a scoped service?
This creates a lifetime mismatch and should generally be avoided.
A singleton must not directly capture a scoped dependency.
7. What's the difference between a class and a struct?
A class is a reference type.
A struct is a value type.
Example:
public class Customer
{
public string Name { get; set; }
}
and:
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
}
When you assign a class variable:
var customer1 = customer2;
both variables can refer to the same object.
For a struct:
var point1 = point2;
the value is copied.
Senior-level consideration
Don't answer:
"Use structs because they are stored on the stack."
That's an oversimplification.
Value-type storage and allocation behavior depends on context. Structs can also be boxed and can appear inside heap-allocated objects.
The better rule is to use structs when they represent small, value-like data and their semantics make sense.
8. What's the difference between ref, out, and in?
These keywords control how arguments are passed.
ref
The variable must already be initialized.
void Increment(ref int value)
{
value++;
}
out
The method must assign a value before returning.
void GetValue(out int value)
{
value = 10;
}
in
The argument is passed by reference but intended to be read-only.
void Print(in LargeStruct value)
{
Console.WriteLine(value);
}
For most application-level code, these should be used only when their semantics provide a clear benefit.
9. What's the difference between ==, Equals(), and ReferenceEquals()?
This is a good question because equality can become surprisingly complicated.
a == b
uses the == operator, which can be overloaded.
a.Equals(b)
uses equality logic implemented by the type.
ReferenceEquals(a, b)
checks whether two references point to the same object.
For example:
var customer1 = new Customer { Name = "John" };
var customer2 = new Customer { Name = "John" };
They are two different object references.
So:
ReferenceEquals(customer1, customer2)
returns false.
Senior-level consideration
When implementing value-based equality, you should think about:
EqualsGetHashCode==!=IEquatable<T>
Incorrect equality implementations can cause subtle bugs in dictionaries, hash sets, caching, and domain models.
10. What are delegates and events?
A delegate represents a reference to a method.
public delegate void NotificationHandler(string message);
You can assign a compatible method:
NotificationHandler handler = SendNotification;
Events are built on delegates and are commonly used for publisher/subscriber patterns.
public event EventHandler? OrderCreated;
The publisher raises the event:
OrderCreated?.Invoke(this, EventArgs.Empty);
Consumers can subscribe:
orderService.OrderCreated += HandleOrderCreated;
Senior-level consideration
Events create relationships between publishers and subscribers.
If subscriptions aren't managed correctly, they can contribute to unexpected object lifetimes or memory retention.
11. What are generics and why are they important?
Generics allow you to write reusable, type-safe code.
Instead of:
public object GetValue()
{
return 10;
}
you can write:
public T GetValue<T>(T value)
{
return value;
}
Now:
var number = GetValue(10);
var name = GetValue("John");
Generics provide:
Compile-time type safety
Reusable code
Less casting
Better API design
Reduced need for boxing in many scenarios
A common example is:
List<int>
List<string>
Dictionary<string, Customer>
12. What is boxing and unboxing?
Boxing converts a value type into an object reference.
int number = 10;
object value = number;
The value is boxed.
Unboxing converts it back:
int result = (int)value;
Boxing can introduce allocations and performance overhead.
This is one reason generic collections such as:
List<int>
are preferable to non-generic collections such as older APIs that store values as object.
Senior-level consideration
Don't obsess over every boxing operation.
The important question is whether boxing occurs in a hot path where it actually affects performance.
13. How does garbage collection work in .NET?
.NET uses a garbage collector to automatically reclaim memory from objects that are no longer reachable.
The GC uses generations:
Generation 0
Generation 1
Generation 2
Short-lived objects generally begin in Generation 0.
Objects that survive collections can be promoted to older generations.
Why does this matter?
Creating excessive temporary objects can increase allocation pressure.
For example:
for (int i = 0; i < 1_000_000; i++)
{
var item = new SomeObject();
}
may create significant allocation pressure depending on what happens with those objects.
Senior-level answer
Don't simply say:
"Garbage collection deletes unused objects."
A stronger answer discusses:
Reachability
Generations
Allocation pressure
Large objects
GC pauses
Object lifetime
Measuring before optimizing
14. What's the difference between throw and throw ex?
Consider:
catch (Exception ex)
{
throw;
}
versus:
catch (Exception ex)
{
throw ex;
}
Use:
throw;
when rethrowing the current exception.
It preserves the original stack trace.
Using:
throw ex;
can reset the stack trace starting point and make debugging more difficult.
Better approach
Only catch exceptions when you can do something meaningful:
try
{
await service.ProcessAsync();
}
catch (ValidationException ex)
{
logger.LogWarning(ex, "Validation failed");
throw;
}
Don't wrap every method in:
try
{
// code
}
catch
{
// log
}
just because it feels safer.
15. What's the difference between Task, Thread, and ValueTask?
A Thread represents an operating-system thread.
A Task represents an asynchronous operation.
A ValueTask can reduce allocations in certain scenarios where operations frequently complete synchronously.
Example:
public async Task<Customer> GetCustomerAsync()
{
return await repository.GetAsync();
}
A Task is usually the default choice.
ValueTask should not be used everywhere simply because it sounds faster.
Senior-level answer
Use ValueTask only when profiling and the API's execution characteristics justify its complexity.
A senior engineer should understand that optimization should be evidence-driven.
16. What is the difference between const, readonly, and static readonly?
const
Compile-time constant:
public const int MaxRetries = 3;
readonly
Can be assigned during declaration or in a constructor:
public readonly string ConnectionName;
public Service(string connectionName)
{
ConnectionName = connectionName;
}
static readonly
One value belongs to the type rather than each instance:
public static readonly TimeSpan DefaultTimeout =
TimeSpan.FromSeconds(30);
A common mistake is treating const as simply "a value that never changes."
Constants have compile-time semantics and can affect binary compatibility when shared across assemblies.
17. What's the difference between an interface and an abstract class?
An interface describes a contract.
public interface IPaymentService
{
Task ProcessAsync();
}
An abstract class can provide both contracts and shared implementation.
public abstract class PaymentService
{
public abstract Task ProcessAsync();
protected void LogPayment()
{
// Shared behavior
}
}
When should you use each?
Use an interface when you primarily need a capability or contract.
Use an abstract class when related types genuinely share implementation or state.
Don't create an interface for every single class automatically.
For example:
IUserService
UserService
is not necessarily valuable just because dependency injection is being used.
18. Explain SOLID principles with a practical example.
SOLID consists of five principles:
S — Single Responsibility Principle
A class should have one primary responsibility.
O — Open/Closed Principle
Software should generally be open for extension but closed for modification.
L — Liskov Substitution Principle
Derived types should be usable wherever their base abstraction is expected without breaking correctness.
I — Interface Segregation Principle
Clients shouldn't be forced to depend on methods they don't need.
D — Dependency Inversion Principle
High-level code should depend on abstractions rather than concrete implementation details.
Senior-level answer
Don't simply memorize the five definitions.
Explain how you use them to prevent:
Giant classes
Tight coupling
Fragile inheritance hierarchies
Difficult testing
Excessive dependencies
For example, instead of a large payment service containing every payment provider:
public interface IPaymentProcessor
{
Task ProcessAsync(Payment payment);
}
you can create separate implementations:
StripePaymentProcessor
PayPalPaymentProcessor
BankPaymentProcessor
The architecture becomes easier to extend.
19. Composition vs inheritance — which one should you prefer?
This is one of the questions that separates experienced developers from developers who have simply memorized OOP definitions.
Inheritance creates an "is-a" relationship.
Composition creates a "has-a" relationship.
Instead of:
public class PremiumOrderService : OrderService
{
}
you might compose behavior:
public class OrderService
{
private readonly IDiscountCalculator _discountCalculator;
public OrderService(
IDiscountCalculator discountCalculator)
{
_discountCalculator = discountCalculator;
}
}
Now the behavior can be changed without creating a large inheritance hierarchy.
Does that mean inheritance is bad?
No.
Inheritance is appropriate when there is a genuine substitutable relationship and the base abstraction is designed for extension.
The senior-level answer is:
Prefer composition when behavior needs to vary independently or when inheritance would create tight coupling. Use inheritance when the domain relationship and substitutability are genuinely strong.
20. How would you investigate a slow ASP.NET Core API?
This is probably one of the most valuable senior-level questions.
A weak answer:
"I would optimize the code."
A strong answer starts with measurement.
I'd investigate several areas.
1. Database performance
Check:
SQL execution time
Missing indexes
Excessive joins
N+1 queries
Large result sets
2. External services
Measure:
HTTP calls
Timeouts
Retries
Third-party APIs
3. Application code
Look for:
Excessive allocations
Expensive LINQ
Synchronous blocking
Unnecessary serialization
Repeated calculations
4. Infrastructure
Check:
CPU
Memory
Network latency
Thread-pool behavior
Container/resource limits
5. Observability
Use:
Logs
Metrics
Distributed tracing
Profilers
Application performance monitoring
The key principle is:
Don't optimize what you haven't measured.
For example, if an API takes 2 seconds, don't immediately rewrite the C# code.
You may discover that 1.7 seconds are spent waiting for the database.
Changing the C# code won't solve the real problem.
Bonus: What Makes a Senior-Level C# Answer Different?
The biggest difference isn't the amount of syntax you know.
It's how you think about trade-offs.
A junior answer might be:
"Dependency injection makes code loosely coupled."
A senior answer is more like:
"Dependency injection reduces coupling and improves testability, but introducing abstractions everywhere can increase complexity. I'd introduce an abstraction where I need substitutability, isolation, or a meaningful architectural boundary."
That's the difference interviewers are often looking for.
5 Follow-Up Questions Interviewers May Ask
Once you've answered the basic question, expect the interviewer to go deeper.
For example:
After async/await
They may ask:
What happens to the thread while an I/O operation is waiting?
After dependency injection
They may ask:
Why shouldn't a singleton directly depend on a scoped service?
After LINQ
They may ask:
When does this query actually execute?
After garbage collection
They may ask:
What would you investigate if an API has high memory usage?
After SOLID
They may ask:
Can following SOLID principles too aggressively make a system worse?
These follow-up questions are where senior-level interviews often become interesting.
How I Would Prepare for a Senior C# Interview
I wouldn't try to memorize 200 questions.
Instead, I'd organize preparation around a few major areas:
C# Fundamentals
Understand:
Value vs reference types
Generics
Delegates
Events
Equality
Exceptions
Collections
Asynchronous Programming
Know:
TaskasyncawaitCancellation
Exception handling
Concurrency
Thread-pool behavior
ASP.NET Core
Understand:
Middleware
Dependency injection
Configuration
Logging
Authentication
Authorization
API performance
Data Access
Know:
EF Core
LINQ
Tracking vs no-tracking
Transactions
Query performance
Indexes
Architecture
Be comfortable discussing:
SOLID
Clean Architecture
Dependency inversion
Composition
Design patterns
Distributed systems
Performance
Know how to investigate:
High CPU
High memory
Slow database queries
Slow APIs
Excessive allocations
Latency
Final Thought
Senior .NET interviews aren't really about proving that you remember every C# keyword.
They're about demonstrating that you can make good technical decisions.
When answering an interview question, try to move beyond:
"What is it?"
and explain:
"Why would I use it?"
Then go one step further:
"When would I NOT use it?"
That final question is often where senior-level engineering judgment becomes visible.
If you can explain the trade-offs behind your decisions, support your answers with real-world examples, and demonstrate that you've actually dealt with production problems, you're no longer answering like someone who memorized interview questions.
You're answering like a senior engineer.
you are looking for interviewer or mock interviewer please comment your mail id will reach out.
Comments
Post a Comment