Q1 . What is the difference between Action
and Func
?
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.

Q2. Why is it recommended to use Task as return type over void in the async method?
The main difference between async void and async Task is that async
void is used for asynchronous methods that don’t return a value and can’t be
awaited, while async Task is used for asynchronous methods that return a Task object and can be awaited.
It’s generally recommended to use async Task instead of async void, unless you’re writing an event handler where the method needs to match a delegate signature that returns void.eturns the first element that matches the predicate, while SingleOrDefault() throws an exception if more than one element matches the predicate. If there are no matches, both methods return the default value for the type.
Q3. Can you explain the difference between ConfigureAwait(false) and ConfigureAwait(true)?
ConfigureAwait(false) and ConfigureAwait(true) is that ConfigureAwait(false) schedules the continuation of the execution of the method after the await statement to a different context (e.g., thread pool),
while ConfigureAwait(true) schedules the continuation to the same context as the calling method (e.g., UI thread or thread pool thread).
It’s generally recommended to use ConfigureAwait(false) in library code that doesn’t depend on the context, and use ConfigureAwait(true) in code that depends on the context, such as UI code
Q4. 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.
- Default Scheduler:
Task.Run
usesTaskScheduler.Default
, which is typically the ThreadPool.Task.Factory.StartNew
without specifying aTaskScheduler
usesTaskScheduler.Current
if there is one, otherwise, it also usesTaskScheduler.Default
.
- 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.
- Continuation Options:
Task.Run
automatically setsTaskCreationOptions.DenyChildAttach
to avoid the task being an attachment point for child tasks.Task.Factory.StartNew
does not setTaskCreationOptions.DenyChildAttach
by default.

Q5. 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.
Q6. How would you detect and resolve memory leaks in a production .NET application?
- Monitor memory usage trends (steady growth indicates a leak)
- Use performance counters to watch GC behavior and memory consumption
- Take memory dumps during high memory usage
- Analyze dumps with tools like WinDbg, dotMemory, or Visual Studio Memory Profiler
- Look for large object counts, unexpected references, or event handlers
- Implement proper IDisposable patterns and event unsubscription
- Consider using weak references for caches or callbacks
- Use tools like Application Insights or Dynatrace for production monitoring
Q7. What are Finalizers and Dispose patterns in C#? When do you need both?
Following code is a complete implementation of the Dispose Pattern with a Finalizer (destructor).
In this implementation, we have a public Dispose
method, which is intended to be called by the caller to clean up unmanaged resources. (Note: Managed resources generally do not need manual cleanup because the Garbage Collector (GC) handles them automatically.)
But imagine — what happens if the caller forgets to call the Dispose
method?
To prevent memory leaks in such cases, we provide a safety net in the form of a destructor (finalizer). (Note: Internally, the C# compiler converts the destructor into a Finalize
method, which you can observe by inspecting the CIL (Common Intermediate Language) code.)
When a class has a finalizer, the Garbage Collector places instances of that class into a finalization queue. Later, during a GC cycle, the finalizer is executed.
This ensures that the Dispose
method (or at least the unmanaged cleanup logic) is eventually called, and the unmanaged resources are properly released.
Important: We only clean up unmanaged resources in the finalizer, because managed objects may already have been collected by that time.
Also, notice that when we manually call the Dispose
method, we invoke GC.SuppressFinalize(this)
— telling the GC that the object no longer needs to be finalized, thus optimizing performance by removing it from the finalization queue.
public class ClassWithUnmanagedResource : IDisposable
{
// Detect redundant Dispose() calls.
private bool _isDisposed;
private IntPtr _fileHandle;
// Instantiate a disposable object owned by this class.
private Stream? _managedResource = new MemoryStream();
// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);// this will ensure finalize method(destructor) is not called.
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
// Dispose managed state.
_managedResource?.Dispose();
_managedResource = null;
}
//clear unmanaged resource here
// Free unmanaged resources (if any) //below is one example
if (_fileHandle != IntPtr.Zero)
{
CloseFile(_fileHandle);
_fileHandle = IntPtr.Zero;
}
}
}
~ClassWithUnmanagedResource () //destructor/finalize method.
{
Dispose(false);
}
}