Top latest C# Interview questions to clear any Technical interviews (Level 2)

Q1. What is difference between IQueryable and IEnumerable?

1. IQueryable has deferred execution, while IEnumerable has immediate execution. With IQueryable, the query is not executed until the results are actually needed, such as when calling ToList() or FirstOrDefault(). With IEnumerable, the query is executed immediately when you call a method like ToList() or FirstOrDefault(), without the possibility of delaying the execution.

2. IQueryable supports server-side filtering, while IEnumerable supports client-side filtering.

With IQueryable, you can filter data on the server side by building a query that is executed against a database or other data source, resulting in faster and more efficient querying. With IEnumerable, filtering is done on the client side, which can be slower and less efficient, especially with large datasets.

3. IQueryable is generally more performant than IEnumerable for large datasets. Because IQueryable supports server-side filtering and deferred execution, it can be more efficient at processing large datasets by reducing the number of records that need to be loaded into memory.

In contrast, IEnumerable will load all records into memory before processing them, which can result in poor performance and high memory usage with large datasets.

Q2. What are Records in C# and when would you use them?

Answer: Records (introduced in C# 9) are reference types designed for immutable data models. They provide value based equality like struct, has built in immutability and has concise syntax.

Use records when modeling data that should be immutable and compared by value rather than reference, such as DTOs, value objects in DDD, or configuration settings

//Records can be simply created like below line. 
public record Person(string FirstName, string LastName);

This is equivalent to 
public class Person : IEquatable<Person>
{
    public string FirstName { get; init; }
    public string LastName { get; init; }

    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
//following is internal implementation . It helps to compare any two instances of the record type using the value. I hope you got the point. 

  public override bool Equals(object? obj)
    {
        return Equals(obj as Person);
    }

    public bool Equals(Person? other)
    {
        if (other is null) return false;
        if (ReferenceEquals(this, other)) return true;
        return FirstName == other.FirstName && LastName == other.LastName;
    }
}

Q3. Explain memory leaks in .NET and how to avoid them.

Answer: Despite automatic garbage collection, memory leaks can occur when:

  1. Objects remain referenced but unused (especially event handlers not unsubscribed)
  2. Unmanaged resources aren’t properly disposed
  3. Static collections grow indefinitely
  4. Long-lived objects reference short-lived ones

Best practices to avoid them include:

  • Implementing IDisposable properly
  • Events should be unsubscribed
  • Using weak references for caches
  • Avoiding static collections that grow unbounded
  • Using memory profiling tools

Q4. What is a Weak Reference in C#? When to use it?

A weak reference allows your code to reference an object without preventing it from being garbage collected.This is useful for caching, object pools, or managing large objects that you want to reuse if possible — but not at the cost of memory pressure.

  • Use it for Avoid memory leaks in caches
  • Keep large objects alive only if needed
  • Better control over memory in high-performance scenarios
  • Works well in scenarios where recreating the object is cheap
var bigData = new BigData();
var weakRef = new WeakReference<BigData>(bigData);

Q5.Explain how garbage collection works in .NET and its generations.

Answer: .NET uses a generational garbage collector that allocates objects on the managed heap. Objects are categorized into three generations:

  • Gen 0: New objects; collected most frequently
  • Gen 1: Objects that survive a Gen 0 collection
  • Gen 2: Long-lived objects that survive a Gen 1 collection

Garbage Collection performs following steps:

  1. Allocation: When objects are created, they’re allocated on the managed heap.
  2. Mark Phase: The GC identifies all “reachable” objects by starting from “root” objects (global/static variables, local variables on thread stacks, CPU registers) and following all references.
  3. Sweep Phase: Unreachable objects are marked for collection.
  4. Compaction: In some collections, memory is compacted by moving surviving objects together to eliminate fragmentation.

.NET uses a generational garbage collection model based on the observation that most objects have short lifetimes, while some live much longer. The heap is divided into three generations:

Generation 0 (Gen 0)

  • Newest, shortest-lived objects
  • Small and collected most frequently
  • Fast collection process
  • Initial location for all new objects

Generation 1 (Gen 1)

  • Objects that survived a Gen 0 collection
  • Acts as a buffer between short-lived and long-lived objects
  • Collected less frequently than Gen 0

Generation 2 (Gen 2)

  • Long-lived objects that survived Gen 1 collections
  • Largest part of the heap
  • Collected least frequently
  • Full GC (collecting all generations) is the most expensive

Large Object Heap (LOH) (Part of Gen2)

  • Special area for objects larger than 85KB
  • Bypasses Gen 0 and Gen 1, allocated directly in Gen 2
  • LOH objects are only collected during full garbage collections (Gen 2 collections).

Q6. Explain the Task Parallel Library (TPL) and when to use it.

Answer: The Task Parallel Library (TPL) provides high-level abstractions for parallel and concurrent programming. It includes Task and Task<T> for asynchronous operations, Parallel class for data and task parallelism, PLINQ for parallel queries, concurrent collections, and low-level synchronization primitives. Use TPL when:

  • Decomposing CPU-bound work across multiple cores
  • Managing multiple asynchronous operations
  • Implementing producer-consumer patterns
  • Coordinating parallel tasks with dependencies
  • Converting sequential algorithms to parallel

TPL handles thread management, workload balancing, and cancellation support.

Task<int> task = Task.Run(() => {
    // Some computation
    return 42;
});
//Parallel class usage
Parallel.For(0, 10, i => {
    Console.WriteLine($"Processing item {i}");
});

// Parallel ForEach
string[] items = { "item1", "item2", "item3" };
Parallel.ForEach(items, item => {
    Console.WriteLine($"Processing {item}");
});

Q7. What are the risks of using async/await or Why you should be careful with async/await?

  1. Deadlocks: Caused by blocking on async code in synchronization contexts. Mitigate with ConfigureAwait(false) and avoiding .Result or .Wait().
  2. Context capture overhead: By default, continuations capture and resume on the original context. Use ConfigureAwait(false) for non-UI code.
  3. Exception handling: Exceptions from tasks can be lost. Always observe task completion and use structured error handling.
  4. Cascading asynchrony: “Async all the way” requires significant refactoring. Plan architecture to handle asynchrony throughout.
  5. Cancellation handling: Unhandled cancellations cause exceptions. Always handle cancellation properly.
  6. Resource leaks: Long-running tasks can hold resources. Use proper disposal patterns and cancellation.

Q8. What are concurrent collections and when should they be used?

Answer: Concurrent collections in System.Collections.Concurrent are thread-safe collections optimized for concurrent access:

  • ConcurrentDictionary<TKey,TValue>: Thread-safe key-value pairs with atomic operations
  • ConcurrentQueue<T>: FIFO collection for multi-threaded access
  • ConcurrentStack<T>: LIFO collection for multi-threaded access
  • ConcurrentBag<T>: Unordered collection optimized for same-thread operations
  • BlockingCollection<T>: Blocks when empty/full, for producer-consumer patterns

Use them when multiple threads access a collection simultaneously instead of externally synchronizing regular collections, which causes contention. Each has performance characteristics suited to different access patterns.

Q9. Explain how mocking works in Moq?

Answer: Mocking creates substitute implementations of dependencies to isolate units under test. Moq uses expression trees and dynamic proxies to create mock objects at runtime:

  1. Create a mock for an interface/class: var mockService = new Mock<IService>()
  2. Set up behavior: mockService.Setup(m => m.GetData()).Returns("test data")
  3. Verify interactions: mockService.Verify(m => m.Process(It.IsAny<string>()), Times.Once)

Moq works by creating dynamic proxies that intercept method calls, comparing them to setup expressions, and executing configured behavior. It uses lambda expressions to provide a strongly-typed, refactoring-friendly API.

As it work by creating proxies, it can mock interfaces or virtual methods only.

Q10. Why don’t you use “using” block with all the classes you use. How do you know that you can wrap any type with “Using”?

This question is asked to understand your knowledge about Managed, Unmanaged resources and proper use of Dispose pattern.

Mostly devs would have used ‘Using’ block only with certain types like SQLConnection ,HttpClient or FileStream . The using block in C# is meant only for types that implement the IDisposable interface. These are classes that manage unmanaged resources that can’t be managed by the CLR (i.e. Garbage Collector) . Some examples are

  • File handles
  • Network sockets
  • Database connections
  • OS handles
  • System memory outside the CLR

Most regular C# classes don’t use these kinds of resources. They’re managed objects, and the .NET Garbage Collector (GC) will clean them up when they’re no longer needed. For these, using is unnecessary (and invalid — won’t even compile unless that class has IDisposable interface ).

Rule of thumb is ,if a class implements IDisposable, you should dispose it when you’re done — either with a using block or manually by calling .Dispose().

Q11. What is difference between Task.WhenAll and Task.WaitAll? Which is preferred approach.

Task.WhenAll is an asynchronous method that returns a Task which completes when all the provided tasks have completed.

Task.WaitAll is a synchronous blocking method that waits for all the provided tasks to complete before continuing execution. It returns bool value indicating success.

async Task ProcessAsync()
{
    Task task1 = DoWorkAsync(1);
    Task task2 = DoWorkAsync(2);
    Task task3 = DoWorkAsync(3);
    
    // This doesn't block the thread
    await Task.WhenAll(task1, task2, task3);
    
    Console.WriteLine("All tasks completed");
}


Understand this, since Task.WhenAll returns Task object you can use this in async method. And as usual Async/Await method, the task is free to call other code outside ProcessAsync . The code after await will be obviously called all the three tasks (task1,task2 and task3) are completed.

    // This blocks the current thread until all tasks complete
   bool completed= Task.WaitAll(new[] { task1, task2, task3 });
    
    Console.WriteLine("All tasks completed");

If you use WaitAll, the task will not be free to perform any other operation and will be blocked until all three tasks get completed. Not to mention all three tasks would run asynchronously as these are task object as in case of WhenAll.

In modern C# development, Task.WhenAll is preferred in most scenarios as it maintains the asynchronous flow of your application, preventing potential thread starvation and deadlocks.

Q12 . What is the difference between Action and Func in C#?

Action and Func are built in delegates in .Net Library that can be used instead of creating new custom delegate in the code. Prior to the introduction of Action and Func, developers had to create custom delegate types for methods with specific signatures. This led to the creation of numerous delegate types, increasing the amount of boilerplate code.

Action and Func (or any other delegate for that matter) allows developers to pass functions as arguments, return functions from other functions, and use lambda expressions more effectively.

Action is a delegate type that represents a method that takes zero or more parameters but does not return a value (void).

  • Func is a delegate type that represents a method that takes zero or more parameters and returns a value.

Q13. What is the difference between Task.Run and Task.Factory.StartNew?

both Task.Run and Task.Factory.StartNew are used to start a new Task for concurrent execution. However, there are some differences between them in terms of default behavior and recommended usage.

  1. Default Scheduler:
    • Task.Run uses TaskScheduler.Default, which is typically the ThreadPool.
    • Task.Factory.StartNew without specifying a TaskScheduler uses TaskScheduler.Current if there is one, otherwise, it also uses TaskScheduler.Default.
  2. Exception Handling:
    • Task.Run has better exception handling. It captures and marshals exceptions on the calling context.
    • Task.Factory.StartNew requires more careful handling of exceptions, and you need to ensure that exceptions are observed.
  3. Continuation Options:
    • Task.Run automatically sets TaskCreationOptions.DenyChildAttach to avoid the task being an attachment point for child tasks.
    • Task.Factory.StartNew does not set TaskCreationOptions.DenyChildAttach by default.

Q14. What is the difference between multi-threaded , Parallel and Asynchronous programming?

Multi-threading is a programming paradigm where multiple threads within a process execute independently and concurrently(simultaneously). It is important to understand the multi-threading may or may not be parallel.

Multi-threading can be parallel or interleaved (different thread takes turn to execute) depending upon the number of CPUs or Cores and how OS schedules the thread . Parallel” execution means that multiple threads are truly executing simultaneously, each on its own processor core or hardware thread. In case If there are more threads than available processor cores, or if the operating system’s scheduler decides to switch between threads for other reasons, the threads may be interleaved i.e. the threads would take turn running on a single processor core. Usually the switching happens so fast that it appears that they are running parallely.

Asynchronous programming allows you to work on multiple tasks concurrently without blocking the execution of the calling thread. Does it sounds like Multi-thread of Parallel programming? It is important to understand that it is different from them as it doesn’t require you to run the other task on the separate thread to run concurrently. For e.g. When you do I/O-bound operations like file-system accesses, HTTP requests, API calls, or database queries the thread hands over the processing to the other hardware and just wait for the execution to complete in case of synchronous call. If you make asynchronous calls using Async method , the thread will not wait for I/O operation to finish and will perform other task just appearing to be parallel or multi threaded.

Leave a Reply

Your email address will not be published. Required fields are marked *