Top .NET Interview Questions & Answers (115+) - For 2-6 Years Experience - IndianTechnoEra
Latest update Android YouTube

Top .NET Interview Questions & Answers (115+) - For 2-6 Years Experience

Prepared for interview preparation with professional, industry-level explanations and code examples where relevant.

For 2-6 Years Experience | .NET Framework, ASP.NET Core, C#, Entity Framework Core, Web API, Coding Round


Section 1: .NET Framework Basics

1. What is .NET Framework?

.NET Framework is a software development platform developed by Microsoft for building and running Windows applications. It provides a runtime environment called CLR (Common Language Runtime) and a large class library called BCL (Base Class Library). It supports multiple languages like C#, VB.NET, and F#, which all compile down to a common intermediate language (MSIL) and run on the CLR.

2. What are the main components of .NET Framework?

The two main components are:

  • CLR (Common Language Runtime): The execution engine that manages memory, security, exception handling, and JIT compilation.
  • BCL/FCL (Base Class Library / Framework Class Library): A large collection of reusable classes, interfaces, and value types (collections, file I/O, networking, etc.) used by all .NET applications.

Other supporting components include ADO.NET, ASP.NET, WPF, WCF, and the Common Type System (CTS).

3. What is CLR (Common Language Runtime)?

CLR is the heart of the .NET Framework — it's the virtual machine component that manages the execution of .NET programs. It handles JIT compilation of MSIL into native code, automatic memory management (Garbage Collection), type safety, exception handling, thread management, and security (Code Access Security). Because CLR is language-agnostic, code written in C# or VB.NET both run on the same runtime.

4. What is CTS (Common Type System)?

CTS defines how types are declared, used, and managed in the CLR. It ensures that data types used in one .NET language (e.g., C# int) are compatible with data types in another .NET language (e.g., VB.NET Integer), enabling cross-language interoperability. It defines two main categories: Value Types and Reference Types.

5. What is CLS (Common Language Specification)?

CLS is a subset of CTS — it's a set of basic rules and restrictions that all .NET languages must follow to ensure cross-language compatibility. If a library follows CLS rules, it can be consumed by any CLS-compliant .NET language without compatibility issues (e.g., avoiding unsigned integers in public APIs since some languages don't support them).

6. Difference between CLR, CTS, and CLS?

  • CLR is the runtime/execution engine that runs the code.
  • CTS defines the common data types across all .NET languages.
  • CLS is a subset of CTS defining rules for cross-language compatibility.

In short: CLR executes, CTS defines types, CLS defines compatibility rules.

7. What is Base Class Library (BCL)?

BCL is a standard set of class libraries shipped with .NET that provides fundamental functionality such as collections (List<T>, Dictionary<TKey,TValue>), file handling, string manipulation, exception types, and threading primitives. It is the foundation on top of which frameworks like ASP.NET and Entity Framework are built.

8. What is MSIL (Microsoft Intermediate Language)?

MSIL (also called CIL - Common Intermediate Language) is a CPU-independent, low-level language that .NET compilers (like csc.exe for C#) produce when you build your code. This IL code is not directly executed by the OS — instead, the CLR's JIT compiler converts it into native machine code at runtime, which is what allows different .NET languages to interoperate.

9. What is JIT (Just-In-Time) Compilation?

JIT is the process by which the CLR converts MSIL code into native machine code specific to the operating system and CPU architecture, right before execution. There are types of JIT: Normal JIT (compiles methods as they are called and caches them), Pre-JIT (compiles the whole assembly at once, e.g., via NGen), and Econo-JIT (compiles but doesn't cache, used in constrained environments).

10. What is Managed Code and Unmanaged Code?

  • Managed Code: Code executed under the control of the CLR — it gets automatic memory management, exception handling, and type safety (e.g., all C#/.NET code).
  • Unmanaged Code: Code executed directly by the OS outside the CLR's control (e.g., C/C++ compiled binaries), where the developer must manually manage memory.

.NET can call unmanaged code via interop mechanisms like P/Invoke.

11. What is Global Assembly Cache (GAC)?

GAC is a machine-wide code cache used to store assemblies that are meant to be shared by multiple applications on the same machine. Assemblies in the GAC must have a strong name (name + version + culture + public key token). It's mainly relevant to .NET Framework; .NET Core/.NET 5+ moved away from GAC in favor of NuGet packages and local deployment.

12. What is an Assembly?

An assembly is the fundamental unit of deployment in .NET — a compiled output (.dll or .exe) that contains MSIL code, metadata, manifest, and resources. It acts as a self-describing unit: the CLR uses the assembly's manifest to know its version, referenced assemblies, and exported types.

13. What are the different types of Assemblies?

  • Private Assembly: Used by a single application, deployed in the app's local folder.
  • Shared/Public Assembly: Stored in the GAC and shared across multiple applications; requires a strong name.
  • Satellite Assembly: Contains only resources (like localized strings) for a specific culture, used for localization.

14. What is Metadata in .NET?

Metadata is data about the code embedded within an assembly — it describes types, methods, properties, fields, and their attributes. The CLR uses metadata for type-checking, resolving method calls, and enabling features like Reflection, without needing separate header files (unlike C/C++).

15. What is Reflection in .NET?

Reflection is a mechanism that allows a program to inspect and interact with its own metadata at runtime — you can dynamically discover types, invoke methods, access properties, and even create instances, all without knowing them at compile time. It's commonly used in frameworks like ORMs (e.g., Entity Framework) and dependency injection containers.

Type t = typeof(string);
MethodInfo method = t.GetMethod("ToUpper", Type.EmptyTypes);
object result = method.Invoke("hello", null);
Console.WriteLine(result); // HELLO

16. What is Garbage Collection (GC)?

Garbage Collection is the CLR's automatic memory management system that reclaims memory occupied by objects that are no longer reachable/referenced by the application. It eliminates manual memory deallocation (unlike C/C++), reducing memory leaks and dangling pointer bugs. The GC runs on a background thread and uses a generational approach for efficiency.

17. What are Generations (Gen 0, Gen 1, Gen 2) in GC?

The GC divides the heap into generations based on object lifetime, since most objects are short-lived:

  • Gen 0: Newly created, short-lived objects. Collected most frequently and fastest.
  • Gen 1: Objects that survived a Gen 0 collection — acts as a buffer between short-lived and long-lived objects.
  • Gen 2: Long-lived objects (e.g., static data, cached objects). Collected least frequently, since full Gen 2 collection is expensive.

18. Difference between Dispose() and Finalize()?

  • Dispose(): Explicitly called by the developer (usually via using statement or manually) to release unmanaged resources deterministically, immediately.
  • Finalize(): Called by the GC non-deterministically before reclaiming an object's memory, as a safety net. It's slower since it requires two GC cycles.

Best practice is to implement the IDisposable pattern and call Dispose() explicitly, with Finalize() only as a backup for unmanaged resource cleanup.

19. What is Boxing and Unboxing?

  • Boxing: Converting a value type (e.g., int) into a reference type (object), which involves allocating memory on the heap.
  • Unboxing: Converting the boxed object back into a value type, which involves an explicit cast and unwrapping from the heap.
int num = 10;
object obj = num;        // Boxing
int num2 = (int)obj;     // Unboxing

Boxing/unboxing has a performance cost due to heap allocation, so it's avoided in performance-critical code (e.g., prefer generics like List<int> over non-generic ArrayList).

20. Difference between Value Types and Reference Types?

  • Value Types: Stored on the stack (usually), hold the actual data directly, copied by value on assignment. Examples: int, double, bool, struct, enum.
  • Reference Types: Stored on the heap, variable holds a reference (pointer) to the memory location, copied by reference on assignment. Examples: class, string, array, interface, delegate.

21. What are Namespaces in C#?

Namespaces are used to logically organize code and avoid naming collisions between classes with the same name. They act like a hierarchical container for classes, interfaces, and other types (e.g., System.Collections.Generic). You use the using directive to import them.

22. What is Exception Handling?

Exception handling is a mechanism to detect and handle runtime errors gracefully using try, catch, finally, and throw blocks, preventing application crashes and allowing controlled recovery or logging.

try
{
    int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
finally
{
    Console.WriteLine("Cleanup code runs here regardless of exception.");
}

23. Difference between throw and throw ex?

  • throw; (no argument) re-throws the current exception while preserving the original stack trace — this is the correct way to re-throw in a catch block.
  • throw ex; resets the stack trace to the current point, losing the original location where the exception actually occurred, which makes debugging harder.
catch (Exception ex)
{
    LogError(ex);
    throw;       // preserves original stack trace - recommended
    // throw ex; // resets stack trace - avoid in production code
}

24. Difference between String and StringBuilder?

  • String: Immutable — every modification (concatenation, replace) creates a new string object in memory, which is inefficient for heavy string manipulation in loops.
  • StringBuilder: Mutable — modifies the existing buffer in-place, making it much more efficient for scenarios with repeated string modifications (e.g., building large strings in a loop).
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i).Append(",");
}
string result = sb.ToString();

25. Difference between const, readonly, and static readonly?

  • const: Compile-time constant, value must be assigned at declaration, implicitly static, cannot be changed ever.
  • readonly: Runtime constant, can be assigned at declaration or in a constructor, value can differ per instance.
  • static readonly: Combines both — one shared value across all instances, but assigned at runtime (e.g., in a static constructor), useful when the value isn't known at compile time.
public const double Pi = 3.14159;           // fixed forever
public readonly int InstanceId;             // set in constructor, per-instance
public static readonly DateTime StartTime = DateTime.Now; // shared, set at runtime

Section 2: C# Basics

1. Difference between class and struct?

  • class: Reference type, stored on heap, supports inheritance, default to null if unassigned.
  • struct: Value type, stored on stack (typically), does not support inheritance (except from interfaces), better for small, immutable data models due to lower GC pressure.

Use struct for small, lightweight, immutable data (e.g., Point, DateTime), and class for complex objects with identity and behavior.

2. Difference between abstract class and interface?

  • Abstract class: Can have both implemented and abstract methods, fields, constructors, and access modifiers. Supports single inheritance only.
  • Interface: Traditionally only method signatures (from C# 8+ can have default implementations), no instance fields, no constructors. A class can implement multiple interfaces.

Use an abstract class when you want to share common code among closely related classes; use an interface when you want to define a contract that unrelated classes can implement.

3. What is Inheritance?

Inheritance allows a class (derived/child) to acquire properties and behavior from another class (base/parent), enabling code reuse and establishing an "is-a" relationship.

public class Employee { public string Name; }
public class Manager : Employee { public int TeamSize; }

4. What is Polymorphism?

Polymorphism means "many forms" — the ability of an object to take multiple forms. There are two types:

  • Compile-time (Static): Achieved via method overloading.
  • Runtime (Dynamic): Achieved via method overriding using virtual/override.

5. What is Encapsulation?

Encapsulation is the practice of bundling data (fields) and methods that operate on that data into a single unit (class), while restricting direct access to internal state using access modifiers (private fields exposed via public properties). It protects object integrity from unintended external modification.

6. What is Abstraction?

Abstraction means exposing only essential features of an object while hiding implementation details. In C#, it's achieved using abstract classes and interfaces — the consumer only knows "what" a method does, not "how".

7. What are Access Modifiers in C#?

  • public: Accessible from anywhere.
  • private: Accessible only within the same class.
  • protected: Accessible within the same class and derived classes.
  • internal: Accessible within the same assembly.
  • protected internal: Accessible within same assembly OR derived classes in other assemblies.
  • private protected: Accessible within same assembly AND derived class.

8. What is Method Overloading?

Overloading allows multiple methods with the same name but different parameter lists (number, type, or order) within the same class. It's resolved at compile time.

public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
public int Add(int a, int b, int c) => a + b + c;

9. What is Method Overriding?

Overriding allows a derived class to provide its own implementation of a method already defined in its base class, using virtual in the base and override in the derived class. It's resolved at runtime (dynamic polymorphism).

public class Animal { public virtual void Speak() => Console.WriteLine("Animal sound"); }
public class Dog : Animal { public override void Speak() => Console.WriteLine("Bark"); }

10. Difference between virtual, override, and new?

  • virtual: Marks a base class method as overridable.
  • override: Provides a new implementation of a virtual/abstract base method, maintaining polymorphic behavior (called based on runtime type).
  • new: Hides the base class member entirely (method hiding), rather than overriding it — the base version still gets called if accessed via a base class reference. This breaks polymorphism and should be used cautiously.

11. Difference between ref, out, and in?

  • ref: Passes a variable by reference; the variable must be initialized before being passed, and can be read/modified inside the method.
  • out: Passes by reference but the variable need not be initialized before the call; the method MUST assign it before returning. Commonly used for methods returning multiple values (e.g., TryParse).
  • in: Passes by reference but as read-only — the method cannot modify the value, used mainly for performance (avoiding copying large structs).
bool success = int.TryParse("123", out int result);

12. Difference between == and .Equals()?

  • ==: For value types compares values; for reference types, by default compares references (unless overloaded, as string does for value comparison).
  • .Equals(): Virtual method that can be overridden to define custom value-equality logic for reference types.

Best practice: override both Equals() and GetHashCode() together when defining custom equality for a class.

13. Difference between is and as?

  • is: Checks if an object is compatible with a given type, returns a boolean (in C# 7+, can also do pattern matching with a variable declaration).
  • as: Attempts to cast an object to a given type, returns null if the cast fails, instead of throwing an InvalidCastException.
if (obj is string str) { Console.WriteLine(str); }
string s = obj as string; // null if obj is not a string

14. Difference between Array and ArrayList?

  • Array: Fixed size, strongly typed (type-safe), better performance since no boxing/unboxing for value types.
  • ArrayList: Dynamically resizable, stores elements as object (non-generic), so value types get boxed/unboxed — slower and not type-safe. Largely obsolete in favor of generic collections.

15. Difference between List<T> and Array?

  • Array: Fixed size once created, slightly better raw performance for fixed-size data.
  • List<T>: Dynamically resizable generic collection, provides many built-in methods (Add, Remove, Find, Sort), preferred for most business scenarios due to flexibility.

16. What is IEnumerable?

IEnumerable<T> is an interface that supports simple forward-only iteration over a collection using GetEnumerator(). It's used for in-memory collections, and LINQ queries on IEnumerable execute in memory using deferred execution but process objects one at a time.

17. What is IQueryable?

IQueryable<T> extends IEnumerable<T> and is designed for querying data from an out-of-memory source (like a database via Entity Framework). It builds an expression tree that gets translated into the target query language (e.g., SQL), enabling filtering/sorting to happen at the data source rather than in application memory.

18. Difference between IEnumerable and IQueryable?

  • IEnumerable: Executes queries in memory (client-side); best for LINQ-to-Objects.
  • IQueryable: Executes queries at the data source (server-side, e.g., translated to SQL); best for LINQ-to-Entities/EF Core, since filtering happens in the database, reducing data transferred over the network.
// IQueryable - filter translated to SQL WHERE clause, runs in DB
IQueryable query = dbContext.Users.Where(u => u.Age > 18);

// IEnumerable - loads all data into memory FIRST, then filters in-app
IEnumerable list = dbContext.Users.ToList().Where(u => u.Age > 18);

19. What is LINQ (Language Integrated Query)?

LINQ is a set of features that adds native query capabilities into C#, allowing you to query collections, databases, XML, etc. using consistent syntax (either query syntax or method syntax) directly in code, with compile-time type checking and IntelliSense support.

var adults = employees.Where(e => e.Age >= 18).OrderBy(e => e.Name).ToList();

20. What is a Lambda Expression?

A lambda expression is a concise, anonymous function using the => syntax, commonly used with LINQ, delegates, and events, to define inline logic without a formal method declaration.

Func add = (a, b) => a + b;
Console.WriteLine(add(3, 4)); // 7

21. What is a Delegate?

A delegate is a type-safe function pointer — it holds a reference to a method with a matching signature and can be invoked like a method. It's the foundation for events, callbacks, and LINQ's Func/Action types.

public delegate int MathOp(int a, int b);
MathOp op = (a, b) => a + b;
Console.WriteLine(op(5, 3)); // 8

22. What is an Event in C#?

An event is a wrapper around a delegate that provides a publish-subscribe mechanism — it allows a class to notify subscribers when something happens, while restricting subscribers from invoking the event directly (only the publishing class can raise it).

public class Publisher
{
    public event Action OnCompleted;
    public void Finish() => OnCompleted?.Invoke();
}

23. What is an Anonymous Method?

An anonymous method is a method defined inline without a name, using the delegate keyword (predecessor to lambda expressions), often used for short-lived event handlers or callbacks.

Button.Click += delegate(object sender, EventArgs e)
{
    Console.WriteLine("Button clicked");
};

24. What is an Extension Method?

An extension method allows adding new methods to an existing type without modifying its source code or creating a derived type. It's defined as a static method in a static class, with the first parameter prefixed with this.

public static class StringExtensions
{
    public static bool IsValidEmail(this string str) 
        => str.Contains("@");
}
// Usage: "test@mail.com".IsValidEmail();

25. What is Async and Await?

async/await enable asynchronous programming — allowing long-running operations (I/O, network calls, DB queries) to run without blocking the calling thread. async marks a method as asynchronous, and await suspends execution until the awaited task completes, freeing up the thread in the meantime (important for scalability in Web APIs).

public async Task GetDataAsync()
{
    HttpClient client = new HttpClient();
    string result = await client.GetStringAsync("https://api.example.com/data");
    return result;
}

Section 3: Web API

1. What is a REST API?

REST (Representational State Transfer) is an architectural style for designing networked APIs based on stateless, resource-oriented communication over HTTP. Resources (e.g., /api/employees) are manipulated using standard HTTP methods, and responses are typically in JSON. Key principles: statelessness, uniform interface, resource-based URIs, and use of standard HTTP verbs/status codes.

2. What are HTTP Methods?

  • GET: Retrieve a resource.
  • POST: Create a new resource.
  • PUT: Update/replace an entire resource.
  • PATCH: Partially update a resource.
  • DELETE: Remove a resource.

3. Difference between GET and POST?

  • GET: Retrieves data, parameters sent in URL (query string), idempotent, cacheable, no request body, limited data length.
  • POST: Sends data in the request body, used to create resources, not idempotent (calling it twice can create two resources), not cached by default.

4. Difference between PUT and PATCH?

  • PUT: Replaces the entire resource with the provided data — any field not included is typically reset/removed.
  • PATCH: Applies a partial update — only the specified fields are modified, rest remain unchanged.

5. Difference between PUT and POST?

  • PUT: Idempotent — calling it multiple times with the same data results in the same state (used for update/create-at-specific-URI).
  • POST: Not idempotent — each call typically creates a new resource (e.g., calling POST /orders twice creates two orders).

6. What are common HTTP Status Codes?

  • 200 OK: Request succeeded.
  • 201 Created: Resource successfully created.
  • 204 No Content: Success, no response body.
  • 400 Bad Request: Invalid client request/data.
  • 401 Unauthorized: Authentication required/failed.
  • 403 Forbidden: Authenticated but not permitted.
  • 404 Not Found: Resource doesn't exist.
  • 409 Conflict: Conflicting state (e.g., duplicate record).
  • 500 Internal Server Error: Unhandled server-side error.

7. What is Content Negotiation?

Content negotiation is the mechanism by which a Web API decides the format of the response (JSON, XML, etc.) based on the Accept header sent by the client. ASP.NET Core supports this out of the box via formatters, defaulting to JSON unless configured otherwise.

8. What is Dependency Injection in Web API?

DI in Web API allows controllers to receive their dependencies (services, repositories, DbContext) via constructor injection rather than creating them manually, promoting loose coupling and testability. ASP.NET Core has a built-in DI container registered in Program.cs.

public class EmployeeController : ControllerBase
{
    private readonly IEmployeeService _service;
    public EmployeeController(IEmployeeService service) => _service = service;
}

9. What is Model Validation in Web API?

Model validation ensures incoming request data meets defined rules before it's processed, using Data Annotations ([Required], [Range], [StringLength]) on model properties. ASP.NET Core automatically checks ModelState.IsValid and can auto-return 400 Bad Request when [ApiController] attribute is used.

public class EmployeeDto
{
    [Required]
    public string Name { get; set; }

    [Range(18, 60)]
    public int Age { get; set; }
}

10. What is API Versioning?

API Versioning is the practice of managing changes to an API over time without breaking existing consumers. Common strategies: URI versioning (/api/v1/employees), query string versioning (?api-version=1.0), header versioning, or media-type versioning. ASP.NET Core supports this via the Microsoft.AspNetCore.Mvc.Versioning package.


Section 4: ASP.NET Core Basics

1. What is ASP.NET Core?

ASP.NET Core is a cross-platform, open-source, high-performance framework for building modern web applications and APIs. Unlike the older ASP.NET, it runs on Windows, Linux, and macOS, is built on .NET Core/.NET 5+, and is modular — you only include the packages/middleware you need.

2. Difference between ASP.NET and ASP.NET Core?

  • ASP.NET: Windows-only, runs on full .NET Framework, uses web.config, tightly coupled to IIS, monolithic (System.Web).
  • ASP.NET Core: Cross-platform, runs on .NET Core/.NET 5+, uses appsettings.json/Program.cs, can self-host with Kestrel, built for performance and modularity, built-in dependency injection.

3. What is Kestrel Server?

Kestrel is the default, cross-platform, lightweight web server built into ASP.NET Core, used to handle HTTP requests. It can run standalone or behind a reverse proxy (like IIS, Nginx, or Apache) for production deployments to add additional features like SSL termination and load balancing.

4. What is Middleware in ASP.NET Core?

Middleware are components assembled into a pipeline that handle requests and responses — each middleware can process an incoming request, pass it to the next middleware, and process the outgoing response. Examples: authentication, routing, exception handling, static files.

app.Use(async (context, next) =>
{
    Console.WriteLine("Before next middleware");
    await next.Invoke();
    Console.WriteLine("After next middleware");
});

5. What are Built-in Middleware components?

Common built-in middleware includes: UseRouting(), UseAuthentication(), UseAuthorization(), UseStaticFiles(), UseCors(), UseExceptionHandler(), UseHttpsRedirection(), and UseSwagger(). Order matters — e.g., authentication must run before authorization.

6. What is Dependency Injection (DI)?

DI is a design pattern where an object's dependencies are provided (injected) from an external source rather than the object creating them itself. ASP.NET Core has DI built into the framework — services are registered in Program.cs and resolved automatically via constructor injection.

7. What are Service Lifetimes: Singleton, Scoped, Transient?

  • Transient: A new instance is created every time it's requested — best for lightweight, stateless services.
  • Scoped: One instance per HTTP request — best for things like DbContext, ensuring consistency within a request.
  • Singleton: One instance for the entire application lifetime — best for shared, stateless, thread-safe services (e.g., configuration, caching).
builder.Services.AddTransient();
builder.Services.AddScoped();
builder.Services.AddSingleton();

8. What is Program.cs file?

Program.cs is the entry point of an ASP.NET Core application (from .NET 6+ using minimal hosting model). It combines the responsibilities of the older Program.cs + Startup.cs — configuring services (DI container), building the middleware pipeline, and starting the web host, all in one file.

9. What was the Startup class (earlier .NET Core versions)?

In .NET Core 3.1/5, the Startup.cs class had two key methods: ConfigureServices() (register DI services) and Configure() (build the middleware pipeline). From .NET 6 onward, this was merged into the simplified minimal hosting model in Program.cs, though the older pattern can still be used if preferred.

10. What is Configuration in ASP.NET Core?

ASP.NET Core uses a flexible configuration system that pulls settings from multiple sources — appsettings.json, environment variables, command-line arguments, user secrets, and Azure Key Vault — merged into a single IConfiguration object, with later sources overriding earlier ones.

11. What is appsettings.json?

appsettings.json is the default configuration file used to store application settings (connection strings, logging levels, custom app settings) in JSON format. It supports environment-specific overrides via files like appsettings.Development.json.

12. What is IConfiguration?

IConfiguration is the interface used to read configuration values at runtime from any registered configuration source (JSON, env variables, etc.), typically injected via DI into services or controllers.

public class MyService
{
    private readonly IConfiguration _config;
    public MyService(IConfiguration config) => _config = config;
    public string GetConnStr() => _config["ConnectionStrings:DefaultConnection"];
}

13. What is IOptions<T>?

IOptions<T> is a strongly-typed way to bind and access configuration sections as POCO classes, rather than accessing raw string keys via IConfiguration. It supports the Options pattern, promoting cleaner, type-safe configuration access.

public class SmtpSettings { public string Host { get; set; } public int Port { get; set; } }

builder.Services.Configure(builder.Configuration.GetSection("Smtp"));

public class EmailService
{
    private readonly SmtpSettings _settings;
    public EmailService(IOptions options) => _settings = options.Value;
}

14. What is Logging in ASP.NET Core?

ASP.NET Core has a built-in logging framework (ILogger<T>) that supports multiple providers (Console, Debug, EventLog, and third-party ones like Serilog or NLog). It supports log levels (Trace, Debug, Information, Warning, Error, Critical) to control verbosity.

public class OrderController
{
    private readonly ILogger _logger;
    public OrderController(ILogger logger) => _logger = logger;
    public void Process() => _logger.LogInformation("Processing order started.");
}

15. What is IHttpClientFactory?

IHttpClientFactory is a factory for creating HttpClient instances in a managed way, solving common problems like socket exhaustion (caused by creating a new HttpClient per request) and stale DNS issues. It supports named/typed clients and integrates with Polly for retry/circuit-breaker policies.

builder.Services.AddHttpClient();

16. What are Filters in ASP.NET Core?

Filters allow you to run custom logic at specific stages of the request pipeline within the MVC/API framework (before/after action execution), for cross-cutting concerns like logging, validation, authorization, or exception handling — without repeating code in every controller.

17. What are Action Filters?

Action Filters run code before and after an action method executes (e.g., logging execution time, modifying action arguments/results). Implemented via IActionFilter or attribute-based ActionFilterAttribute.

18. What are Authorization Filters?

Authorization Filters run first in the filter pipeline, before model binding, to determine if the current user is authorized to execute the requested action — e.g., the built-in [Authorize] attribute is implemented as an authorization filter.

19. What are Resource Filters?

Resource Filters run after authorization but before model binding, and are useful for short-circuiting most of the pipeline (e.g., serving a cached response) for performance, since they wrap the rest of the pipeline execution.

20. What are Exception Filters?

Exception Filters handle unhandled exceptions thrown during action execution, allowing you to log the error and return a custom error response, without needing try-catch in every action.

public class GlobalExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        context.Result = new ObjectResult(new { error = context.Exception.Message }) { StatusCode = 500 };
        context.ExceptionHandled = true;
    }
}

21. What is Model Binding?

Model Binding is the process by which ASP.NET Core automatically maps data from an HTTP request (route values, query strings, form data, JSON body) to action method parameters/objects, eliminating manual parsing.

22. What is Model Validation? (ASP.NET Core context)

Similar to Web API validation, ASP.NET Core validates incoming models against Data Annotations automatically when combined with the [ApiController] attribute, populating ModelState with any errors and short-circuiting the request with a 400 response if invalid.

23. What is Routing in ASP.NET Core?

Routing maps incoming HTTP request URLs to the appropriate controller action (or endpoint). ASP.NET Core uses endpoint routing (UseRouting()/UseEndpoints() or the simplified minimal API style), supporting both convention-based and attribute-based routing.

24. What is Attribute Routing?

Attribute Routing lets you define routes directly on controllers/actions using attributes like [Route], [HttpGet], [HttpPost], giving explicit and flexible control over URL structure, commonly used in Web API projects.

[Route("api/[controller]")]
public class EmployeeController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetById(int id) => Ok();
}

25. What is CORS (Cross-Origin Resource Sharing)?

CORS is a browser security feature that blocks web pages from making requests to a domain different from the one that served the page, unless explicitly allowed. ASP.NET Core lets you configure CORS policies to permit specific origins, headers, and methods for cross-domain API access (e.g., allowing a React frontend on a different port/domain to call the API).

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowFrontend", policy =>
        policy.WithOrigins("https://myfrontend.com").AllowAnyHeader().AllowAnyMethod());
});
app.UseCors("AllowFrontend");

26. What is Authentication?

Authentication is the process of verifying who a user is (identity verification) — e.g., validating login credentials or a token. ASP.NET Core supports multiple authentication schemes: Cookie-based, JWT Bearer, OAuth, OpenID Connect.

27. What is Authorization?

Authorization determines what an authenticated user is allowed to do (permissions/roles) — it happens after authentication. ASP.NET Core supports role-based ([Authorize(Roles = "Admin")]) and policy-based authorization.

28. What is JWT Authentication?

JWT (JSON Web Token) is a compact, self-contained token format used for stateless authentication. After a successful login, the server issues a signed JWT containing claims (user id, roles, expiry) which the client sends in the Authorization: Bearer <token> header on subsequent requests. The server validates the token's signature without needing a session store, making it ideal for scalable APIs and microservices.

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
        };
    });

29. What is Swagger / OpenAPI?

Swagger (OpenAPI) is a specification and tooling ecosystem for documenting REST APIs. ASP.NET Core integrates with Swashbuckle to auto-generate interactive API documentation (Swagger UI) from your controllers/DTOs, letting developers and testers explore and test endpoints directly from a browser.

30. What is a Dependency Injection Container?

The DI Container is the built-in service responsible for registering, resolving, and managing the lifetime of dependencies (services) throughout the application. In ASP.NET Core, it's accessed via IServiceCollection (registration) and IServiceProvider (resolution), configured in Program.cs.


Section 5: Entity Framework Core

1. What is Entity Framework Core?

EF Core is a lightweight, cross-platform, open-source Object-Relational Mapper (ORM) for .NET, that allows developers to work with a database using .NET objects/classes instead of writing raw SQL, while still supporting raw SQL and stored procedures when needed for performance.

2. What is Code First approach?

In Code First, you define your domain model as C# classes (POCOs), and EF Core generates the database schema from these classes using Migrations. This approach gives developers full control via code and works well with version-controlled schema evolution.

3. What is Database First approach?

In Database First, you start with an existing database, and EF Core scaffolds/generates the C# entity classes and DbContext automatically by reverse-engineering the schema (using Scaffold-DbContext command), useful when working with legacy databases.

4. Difference between DB First and Code First?

  • Code First: Classes drive the database schema; suited for new projects, easy version control via migrations.
  • DB First: Database drives the classes; suited for existing/legacy databases, or when DB is managed by DBAs separately.

5. What are Migrations in EF Core?

Migrations are a version-control mechanism for your database schema — they track incremental changes to your model (adding a column, table, etc.) as code, and apply them to the database in a controlled, repeatable way, keeping the schema in sync with your model across environments.

6. How do you create a Migration?

Using the EF Core CLI tools:

dotnet ef migrations add InitialCreate
dotnet ef database update

The first command generates a migration file capturing the model changes; the second applies it to the actual database.

7. What is DbContext?

DbContext is the primary class in EF Core that represents a session with the database — it's used to query and save data, manages the connection, and tracks changes to entities via its DbSet<T> properties.

public class AppDbContext : DbContext
{
    public DbSet Employees { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder) { }
}

8. What is DbSet?

DbSet<T> represents a collection/table of a specific entity type within the DbContext, used to query (via LINQ) and perform CRUD operations against that table.

9. What is Change Tracking in EF Core?

Change Tracking is EF Core's mechanism for monitoring entities retrieved via the DbContext, detecting modifications made to them, and generating the appropriate SQL (INSERT/UPDATE/DELETE) when SaveChanges() is called. Entities can be in states like Added, Modified, Deleted, or Unchanged.

10. What is Lazy Loading?

Lazy Loading defers loading of related/navigation entities until they are actually accessed for the first time, reducing initial query cost but potentially causing multiple round-trips to the database (the "N+1 query problem") if not used carefully. Requires navigation properties to be marked virtual and the lazy-loading proxies package enabled.

11. What is Eager Loading?

Eager Loading loads related entities upfront, in the same query, using the Include() method — reducing the number of database round-trips at the cost of a potentially larger single query.

var employees = dbContext.Employees.Include(e => e.Department).ToList();

12. What is Explicit Loading?

Explicit Loading is when related data is loaded on-demand, explicitly, via code (context.Entry(entity).Reference()/Collection().Load()), giving fine-grained control over exactly when navigation properties are populated.

var employee = dbContext.Employees.First();
dbContext.Entry(employee).Collection(e => e.Projects).Load();

13. Difference between Find() and FirstOrDefault()?

  • Find(): Looks up an entity by its primary key, checks the DbContext's in-memory change tracker first before hitting the database — faster for primary key lookups.
  • FirstOrDefault(): A general LINQ method that queries the database (always hits DB unless already materialized) based on any condition and returns the first match or null/default.
var emp1 = dbContext.Employees.Find(5);
var emp2 = dbContext.Employees.FirstOrDefault(e => e.Id == 5);

Section 6: Frequently Asked Coding Questions

1. Predict the output: object vs .Equals()

object a = 100;
object b = 100;
Console.WriteLine(a == b);        // False - reference comparison for boxed objects
Console.WriteLine(a.Equals(b));   // True  - Equals() does value comparison for int

Explanation: When boxed into object, == compares references (two separate boxed instances), so it's False. But int.Equals() is overridden to compare actual values, so it returns True.

2. Predict the output: Reference Type example

class Person { public string Name; }

Person p1 = new Person { Name = "Amit" };
Person p2 = p1;
p2.Name = "Rahul";
Console.WriteLine(p1.Name); // Rahul

Explanation: Since Person is a reference type, p2 = p1 copies the reference, not the object. Both variables point to the same object in memory, so modifying via p2 also reflects when accessed via p1.

3. What happens if you modify a collection inside a foreach loop?

List nums = new List { 1, 2, 3 };
foreach (var n in nums)
{
    nums.Remove(n); // throws InvalidOperationException
}

Explanation: Modifying a collection (Add/Remove) while iterating with foreach throws InvalidOperationException: Collection was modified, because the enumerator detects the version change. To safely remove items, iterate over a copy (e.g., nums.ToList()) or use a for loop from the end, or use RemoveAll().

4. What happens when FirstOrDefault() returns null?

var user = users.FirstOrDefault(u => u.Id == 999);
if (user == null)
{
    Console.WriteLine("No user found.");
}
Console.WriteLine(user.Name); // NullReferenceException if not null-checked!

Explanation: If no element matches the condition, FirstOrDefault() returns the default value for the type (null for reference types, 0 for value types). Always null-check before accessing members to avoid a NullReferenceException.

5. Difference between Any() and Count()?

  • Any(): Returns as soon as it finds the first matching element (short-circuits) — O(1) best case, efficient for existence checks.
  • Count(): Iterates through the entire collection to count all elements — always O(n).
// Prefer this for existence checks:
if (employees.Any(e => e.Department == "IT")) { }

// Avoid this - wastes time counting all matches just to check existence:
if (employees.Count(e => e.Department == "IT") > 0) { }

6. Difference between First(), FirstOrDefault(), Single(), SingleOrDefault()?

  • First(): Returns the first matching element; throws an exception if none found.
  • FirstOrDefault(): Returns the first matching element, or default value (e.g., null) if none found.
  • Single(): Expects exactly one matching element; throws an exception if zero or more than one match.
  • SingleOrDefault(): Returns the single matching element, or default if none; still throws if more than one match.

7. Reverse a string (without built-in Reverse method)

public static string ReverseString(string input)
{
    char[] chars = input.ToCharArray();
    int left = 0, right = chars.Length - 1;
    while (left < right)
    {
        (chars[left], chars[right]) = (chars[right], chars[left]);
        left++;
        right--;
    }
    return new string(chars);
}
// ReverseString("hello") => "olleh"

8. Check if a string is a palindrome

public static bool IsPalindrome(string input)
{
    int left = 0, right = input.Length - 1;
    while (left < right)
    {
        if (input[left] != input[right]) return false;
        left++;
        right--;
    }
    return true;
}
// IsPalindrome("madam") => true

9. Find duplicate characters in a string

public static void FindDuplicates(string input)
{
    var charCount = new Dictionary();
    foreach (char c in input)
    {
        if (charCount.ContainsKey(c)) charCount[c]++;
        else charCount[c] = 1;
    }
    foreach (var kvp in charCount)
    {
        if (kvp.Value > 1)
            Console.WriteLine($"{kvp.Key}: {kvp.Value}");
    }
}
// FindDuplicates("programming") => r:2, g:2, m:2

10. Find the second largest number in an array

public static int SecondLargest(int[] arr)
{
    int max = int.MinValue, secondMax = int.MinValue;
    foreach (int num in arr)
    {
        if (num > max)
        {
            secondMax = max;
            max = num;
        }
        else if (num > secondMax && num != max)
        {
            secondMax = num;
        }
    }
    return secondMax;
}
// SecondLargest(new[] {10, 5, 20, 8}) => 10

11. Remove duplicates from an array (without built-in Distinct)

public static int[] RemoveDuplicates(int[] arr)
{
    var seen = new HashSet();
    var result = new List();
    foreach (int num in arr)
    {
        if (seen.Add(num)) // Add() returns false if already present
        {
            result.Add(num);
        }
    }
    return result.ToArray();
}
// RemoveDuplicates(new[] {1,2,2,3,3,3,4}) => {1,2,3,4}

12. Sort a list without using built-in Sort methods (Bubble Sort example)

public static void BubbleSort(int[] arr)
{
    int n = arr.Length;
    for (int i = 0; i < n - 1; i++)
    {
        for (int j = 0; j < n - i - 1; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                (arr[j], arr[j + 1]) = (arr[j + 1], arr[j]);
            }
        }
    }
}
// BubbleSort(new[] {5,2,9,1,5}) => {1,2,5,5,9}

Interview tip: Bubble Sort is O(n²) — good enough to demonstrate logic, but in a real interview, also mention that you'd typically use Array.Sort() or List.Sort() (which use an efficient introspective/quicksort internally) in production code, and only implement manual sorting when specifically asked to test DSA fundamentals.


Final Interview Tips (Based on Your Profile)

  • Since your resume highlights ownership of a 100+ endpoint API and a VB6-to-.NET migration, be ready to explain why you chose specific patterns (RBAC, JWT, idempotency) — interviewers will dig into decisions, not just definitions.
  • Be ready to relate EF Core, DI lifetimes, and async/await concepts back to real examples from FFI HCM or Wealthmaker — concrete stories score higher than textbook answers.
  • Since your skills list SOLID and Design Patterns, expect at least one question asking you to identify a SOLID violation in a code snippet and refactor it live.
  • Practice explaining IEnumerable vs IQueryable and Service Lifetimes out loud — these are among the most commonly asked "trap" questions for 2-6 YOE .NET roles.

Section 7: Advanced .NET Questions (Deep-Dive / Senior-Level)

1. Why can Task.WhenAll() improve performance, and when can it actually make things worse?

Task.WhenAll() lets you kick off multiple independent asynchronous operations concurrently and await all of them together, instead of awaiting each one sequentially. Since I/O-bound operations (DB calls, HTTP calls) spend most of their time waiting rather than using CPU, running them concurrently means the total wait time becomes roughly the duration of the slowest operation, instead of the sum of all durations.

// Sequential - slow: total time = sum of all calls
var user = await GetUserAsync();
var orders = await GetOrdersAsync();
var invoices = await GetInvoicesAsync();
 
// Concurrent - fast: total time = slowest call
var userTask = GetUserAsync();
var ordersTask = GetOrdersAsync();
var invoicesTask = GetInvoicesAsync();
await Task.WhenAll(userTask, ordersTask, invoicesTask);

When it makes things worse:

  • If the tasks are CPU-bound rather than I/O-bound, firing many at once can overload the ThreadPool/CPU cores, causing context-switching overhead and actually slowing things down.
  • If all tasks hit the same downstream resource (e.g., same DB connection pool, same rate-limited API), you can exhaust connections or trigger throttling/429 errors.
  • If one task throws early, WhenAll still waits for all others to complete before surfacing the exception (wrapped in an AggregateException if you inspect Task.Exception), which can delay error handling compared to failing fast.
  • Unbounded fan-out (e.g., firing 10,000 tasks in a loop) can exhaust memory or ThreadPool threads — should be throttled using constructs like SemaphoreSlim or Parallel.ForEachAsync.

2. What happens internally when you write await? Explain the generated state machine.

When the compiler encounters an async method, it rewrites the method body into a compiler-generated state machine (a struct or class implementing IAsyncStateMachine) rather than executing it as regular sequential code. Each await point becomes a "state" boundary.

Step by step, when execution hits await someTask:

  • The compiler checks if the awaited task is already completed. If yes, execution continues synchronously (fast path, no state machine suspension needed).
  • If not completed, the state machine registers a continuation with the task's awaiter (via OnCompleted), records the current state (an integer field tracking "where to resume"), and returns control to the caller — freeing up the calling thread.
  • The ExecutionContext and SynchronizationContext (if any) are captured so that when the task completes, the continuation runs with the correct context (e.g., resuming on the UI thread in WPF, or losing context and running on a ThreadPool thread in ASP.NET Core by default).
  • When the awaited task completes, the continuation callback fires, which calls MoveNext() on the state machine, jumping to the saved state and resuming execution right after the await.

This is why async/await doesn't create new threads by itself — it's about efficiently suspending and resuming logical execution flow without blocking a thread while waiting.

3. How does the ThreadPool decide whether to create a new thread?

The .NET ThreadPool maintains a pool of worker threads that are reused across many short-lived tasks, avoiding the overhead of creating a new OS thread for every unit of work. It uses a combination of a minimum thread count, a maximum thread count, and a "hill-climbing" algorithm to decide scaling.

  • Minimum threads: The pool keeps at least this many threads "warm" and ready; below this threshold, new threads are created almost immediately when work is queued.
  • Above minimum: Once the minimum is exceeded, the ThreadPool injects new threads slowly (roughly one new thread every ~500ms–1s by default) rather than instantly, to avoid over-provisioning threads for short bursts of work.
  • Starvation detection: If queued work items aren't being picked up fast enough (all existing threads busy, work queue growing), the ThreadPool's starvation-avoidance logic adds more threads faster.
  • Maximum threads: A ceiling exists (configurable, defaults to a large number based on available cores) beyond which no more threads are added, to avoid excessive context switching and memory overhead.

This is also why blocking calls (e.g., Task.Result, .Wait()) inside async code are dangerous in high-throughput apps like ASP.NET Core APIs — they tie up ThreadPool threads while waiting, and since new threads are injected slowly, this can cause thread-pool starvation under load.

4. What is ExecutionContext, and how does it flow across asynchronous operations?

ExecutionContext is a mechanism that captures ambient information associated with the currently executing thread — such as security context, AsyncLocal<T> values, and culture info — so that this information can flow correctly even when execution hops across different threads during asynchronous operations.

Without ExecutionContext flowing, information like a request's correlation ID (stored via AsyncLocal<T>) would be lost the moment an await resumes on a different ThreadPool thread than the one it started on. The CLR automatically captures the ExecutionContext at the point of an await and restores it when the continuation runs — this is largely transparent to developers, but is exactly the mechanism that lets logging frameworks (e.g., Serilog's LogContext) and distributed tracing correctly attach the right metadata to logs even across async hops.

private static readonly AsyncLocal _correlationId = new AsyncLocal();
 
public static async Task ProcessRequestAsync()
{
    _correlationId.Value = Guid.NewGuid().ToString();
    await SomeAsyncWork(); // even after resuming on a different thread,
    Console.WriteLine(_correlationId.Value); // this still prints the same value
}

Note: This is distinct from SynchronizationContext, which specifically deals with "which thread to resume execution on" (e.g., UI thread marshaling) — ExecutionContext deals with "what ambient data travels with the logical flow", regardless of thread.

5. What causes Large Object Heap (LOH) allocations, and why can they impact application performance?

The Large Object Heap is a special heap segment in the .NET GC used for objects that are 85,000 bytes or larger (e.g., large arrays, large strings, big byte buffers from file/network reads). Unlike the normal Gen 0/1/2 heaps, the LOH is collected only as part of a Gen 2 collection, and historically was not compacted by default (meaning it could get fragmented, since freed LOH space isn't automatically defragmented).

Performance impact:

  • Fragmentation: Repeatedly allocating and freeing large objects of varying sizes can leave "holes" in the LOH, since it wasn't compacted, wasting memory and potentially causing OutOfMemoryException even when total free memory seems sufficient.
  • Expensive collections: Because LOH is only collected with Gen 2 (the most expensive, full GC), frequent large allocations trigger more frequent full GCs, which pause the application longer than typical Gen 0/1 collections.
  • Common causes in real applications: loading large files/images into byte arrays, building huge strings via concatenation instead of StringBuilder, large List<T>/array buffers in data processing pipelines, or large JSON payloads being deserialized in one shot.

Mitigation: Use streaming/chunked processing instead of loading everything into memory at once, use ArrayPool<T> to reuse large buffers instead of allocating new ones repeatedly, and in .NET Core 3.0+, you can enable GCSettings.LargeObjectHeapCompactionMode to explicitly request LOH compaction when needed.

6. What are Tiered Compilation and Dynamic PGO in modern .NET, and how do they improve runtime performance?

Tiered Compilation: Instead of the JIT immediately generating fully-optimized native code for every method (which is slow to produce and delays startup), .NET compiles methods in two tiers:

  • Tier 0: A quick, minimally-optimized version is JIT-compiled first, so the application starts up faster and methods become runnable sooner.
  • Tier 1: If a method is called frequently ("hot" method, detected via call-count instrumentation), the JIT recompiles it in the background with full optimizations, and the runtime seamlessly swaps in the optimized version — giving you fast startup AND strong steady-state performance.

Dynamic PGO (Profile-Guided Optimization): Introduced in .NET 7/8, Dynamic PGO goes a step further — while a method runs in Tier 0, the runtime instruments it to collect real, live profiling data (which branches are actually taken most, which types actually flow through a call site). When recompiling to Tier 1, the JIT uses this real runtime profile data to make smarter optimization decisions (better branch ordering, devirtualization of interface/virtual calls, inlining decisions) than static heuristics alone could achieve — resulting in noticeably faster hot-path code without any code changes from the developer, simply by upgrading the .NET runtime version.

7. Why is ValueTask not always a better replacement for Task?

ValueTask<T> is a struct designed to avoid the heap allocation that a Task<T> incurs, specifically useful when an async method very frequently completes synchronously (e.g., returning a cached value most of the time), since allocating a full Task object every time would be wasteful.

However, it's not a drop-in "always better" replacement, because:

  • Single consumption only: A ValueTask must only be awaited once. Awaiting it twice, calling .Result after already awaiting it, or storing it and awaiting later can lead to undefined behavior or exceptions, unlike Task which can safely be awaited multiple times or cached.
  • No caching/sharing: You can't easily store a ValueTask in a field and await it from multiple call sites (e.g., can't do Task.WhenAll style fan-out patterns as easily without first converting via .AsTask(), which then allocates anyway).
  • Harder to use correctly: Because of the above restrictions, using ValueTask incorrectly can introduce subtle bugs that are hard to catch in code review, whereas Task is more forgiving and "safe by default".
  • Marginal benefit for I/O-bound work: If the async operation almost always completes asynchronously anyway (e.g., real network/DB calls), the allocation savings of ValueTask are negligible, so the added complexity isn't worth it.

Rule of thumb: Use ValueTask only in hot, performance-critical paths where the method frequently completes synchronously (e.g., a caching layer, a buffer reader), and stick to Task/Task<T> as the default for general application and API code.

8. What are the differences between Span<T>, Memory<T>, and ReadOnlySpan<T>, and when should you use each?

  • Span<T>: A stack-only (ref struct), type-safe view over a contiguous block of memory (array, stack-allocated memory, or a slice of either), allowing you to work with sub-sections of memory without copying data. Because it's a ref struct, it cannot be stored on the heap, used in async methods, boxed, or used as a field in a class.
  • ReadOnlySpan<T>: Same as Span<T> but read-only, commonly used for parsing/processing string or array data without allocating new copies (e.g., string internally exposes data via ReadOnlySpan<char> for high-performance parsing).
  • Memory<T>: Similar concept to Span<T>, but it's a normal (non-ref) struct, so it CAN be stored on the heap, used as a class field, and passed across async method boundaries (unlike Span<T>). You call .Span on a Memory<T> to get a usable Span<T> when you actually need to operate on the data synchronously.
// Spa<T>: great for synchronous, high-performance parsing, no heap allocation
ReadOnlySpan<char> span = "Hello,World".AsSpan();
int commaIndex = span.IndexOf(',');
ReadOnlySpan<char> firstPart = span.Slice(0, commaIndex); // "Hello" - no new string allocated
 
// Memory<T>: needed when data must cross an async boundary or be stored in a field
public async Task ProcessAsync(Memory<byte> buffer)
{
    await SomeAsyncIO(buffer);
    Span<byte> span = buffer.Span; // convert to Span only when doing sync work
}

When to use each: Use Span<T>/ReadOnlySpan<T> for synchronous, allocation-free, high-performance operations (parsing, string manipulation, buffer slicing). Use Memory<T> when the same capability is needed but the data must be passed into an async method, stored in a field, or captured in a closure.

9. How would you investigate a memory leak in a production .NET application?

A structured, methodical approach:

  • Confirm it's a real leak: Monitor memory over time (e.g., via Application Insights, Prometheus/Grafana, or Windows Performance Counters) to distinguish a genuine leak (steadily rising memory that never comes back down after GC) from normal GC sawtooth behavior.
  • Capture memory dumps: Take a memory dump of the running process at a point of high memory usage using tools like dotnet-dump collect (cross-platform) or ProcDump, ideally capturing two dumps at different times to compare growth.
  • Analyze the dump: Use dotnet-dump analyze or Visual Studio's memory profiler / WinDbg with SOS extension to run commands like dumpheap -stat to see which object types have unusually high counts/sizes, and gcroot to trace what's still holding a reference to those objects (preventing GC from collecting them).
  • Common root causes to check: Static/singleton collections that grow unbounded (e.g., a static Dictionary used as a cache with no eviction), event handlers that are subscribed but never unsubscribed (a classic .NET memory leak pattern, since the publisher keeps a strong reference to the subscriber), IDisposable objects (like DbContext, HttpClient, streams) not being disposed properly, and captured variables in long-lived closures/delegates holding onto large objects.
  • Live diagnostics tools: Use dotnet-trace and dotnet-counters for real-time GC and memory metrics in production without needing a full dump, and Application Insights/APM tools to correlate memory growth with specific deployments or traffic patterns.
  • Fix and verify: After identifying the root cause (e.g., unsubscribed event handler), fix it, then re-monitor memory over an extended period under similar load to confirm the leak is resolved.

10. What happens inside the ASP.NET Core request pipeline from the moment a request reaches the server until a response is returned?

End-to-end flow:

  • 1. Kestrel receives the request: The Kestrel web server accepts the raw HTTP connection/request and parses it into an HttpContext object representing the request and response.
  • 2. Reverse proxy (if any): If deployed behind IIS/Nginx, the request first passes through the reverse proxy (handling things like SSL termination) before being forwarded to Kestrel.
  • 3. Middleware pipeline (request path): The HttpContext flows through the middleware pipeline configured in Program.cs, in the exact order they were registered — typically: exception handling middleware, HTTPS redirection, static files, routing (UseRouting()), CORS, authentication (UseAuthentication()), authorization (UseAuthorization()), then custom middleware.
  • 4. Routing: UseRouting() matches the incoming URL/HTTP method against the registered endpoints (controller actions or minimal API routes) and selects the matching endpoint, storing it on the HttpContext without executing it yet.
  • 5. Authentication & Authorization: Authentication middleware identifies the caller (validates JWT/cookie and populates HttpContext.User); authorization middleware then checks whether that identity is permitted to access the selected endpoint (based on [Authorize] attributes/policies).
  • 6. Endpoint execution (MVC/Web API pipeline): If using controllers, the request enters the MVC pipeline — model binding maps request data (route/query/body) into action parameters, model validation runs (ModelState), any Action/Resource/Exception filters execute in their defined order, and finally the controller action method itself runs, typically calling into services/repositories (often awaiting async DB or external calls).
  • 7. Result execution: The action's return value (e.g., IActionResult, Ok(data)) is converted into an actual HTTP response — content negotiation determines the response format (JSON by default) and the response is serialized.
  • 8. Middleware pipeline (response path): The response then flows back out through the same middleware pipeline in reverse order, allowing middleware like exception handling or logging to inspect/modify the outgoing response before it's sent.
  • 9. Kestrel sends the response: Finally, Kestrel writes the HTTP response back to the client over the network connection, completing the request lifecycle.

Understanding this pipeline deeply is especially valuable for you since you own a 100+ endpoint API — it directly explains where to plug in cross-cutting concerns like logging, rate-limiting, or global error handling correctly.

Post a Comment

Feel free to ask your query...
Cookie Consent
We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.
Oops!
It seems there is something wrong with your internet connection. Please connect to the internet and start browsing again.
AdBlock Detected!
We have detected that you are using adblocking plugin in your browser.
The revenue we earn by the advertisements is used to manage this website, we request you to whitelist our website in your adblocking plugin.
Site is Blocked
Sorry! This site is not available in your country.