C# and .NET Interview Questions 2026
C# interviews have a particular flavor, the language has been around long enough that it's picked up a lot of features (LINQ, async/await, pattern matching, nullable reference types), and interviewers like to probe whether you actually understand what's happening underneath them, not just whether you can write await in front of a method call. On the .NET side, the framework itself has changed a lot too, .NET Framework to .NET Core to just ".NET," and a good chunk of interview questions exist specifically to check you're not stuck thinking in outdated patterns.
This page covers C# the language first, OOP, memory management, LINQ, async, then moves into ASP.NET Core and Entity Framework Core, since most C# interviews for backend roles eventually get there regardless of what the job title says.
Why C# and .NET Questions Go Deeper Than "Do You Know the Syntax"
C# is a mature, feature-rich language, and .NET has a lot of built-in infrastructure, similar in spirit to Angular on the frontend side, so interviewers use these questions to check real understanding, not memorized definitions:
- Whether you understand value types versus reference types and where things actually live in memory
- Whether you know why async/await exists and what it's actually doing to your call stack, not just where to paste it
- Whether you understand EF Core well enough to avoid the classic N+1 query trap
- Whether you know the difference between DI lifetimes (Singleton, Scoped, Transient) well enough to avoid a real production bug
Who This Page Is For
- Backend and full stack developers interviewing for roles built on ASP.NET Core
- Developers with a Java or general OOP background moving into a C#/.NET codebase
- Anyone who's used C# for a while but never had to explain garbage collection or the middleware pipeline out loud
Related Resources
C# and .NET Interview Questions and Answers (2026)
C# Fundamentals
1. What's the actual difference between C# and .NET? People use these interchangeably all the time.
C# is a programming language, the syntax and rules you actually write code in. .NET is the broader platform and runtime, the CLR (Common Language Runtime), the base class library, and the tooling that C# code actually compiles down to and runs on. The distinction matters because .NET supports multiple languages, F# and VB.NET also compile to the same intermediate language and run on the same runtime, C# just happens to be the most widely used one.
2. What's the difference between the .NET Framework, .NET Core, and just ".NET" (5/6/7/8+)? Why did Microsoft consolidate everything?
.NET Framework was the original, Windows-only implementation, tightly coupled to Windows and eventually feeling dated for cross-platform and cloud-native development. .NET Core was a ground-up rewrite, open source, cross-platform (Windows, Linux, macOS), and significantly faster, built to be the modern direction. Starting with .NET 5, Microsoft dropped the "Core" branding and just calls it ".NET," unifying everything into one single, actively developed platform going forward, so ".NET Framework" is effectively legacy at this point, and any new project should be built on modern .NET, not .NET Framework.
3. What is the CLR, and what role does it actually play when your C# code runs?
The CLR (Common Language Runtime) is .NET's virtual machine, it's responsible for actually executing your compiled code, managing memory and garbage collection, handling exceptions, enforcing type safety, and JIT-compiling intermediate language into native machine code at runtime. It's the same role the JVM plays for Java, your C# source doesn't run directly, it's compiled to an intermediate form first, and the CLR is what actually executes that on a given machine.
4. What's the difference between compiling to IL and then to native code? What is JIT actually doing?
When you build a C# project, the compiler produces IL (Intermediate Language), a CPU-independent bytecode, not native machine code, packaged into an assembly (a .dll or .exe). When that assembly actually runs, the CLR's JIT (Just-In-Time) compiler translates that IL into native machine code for the specific CPU it's running on, method by method, as they're first called. This two-step process is what makes .NET assemblies portable across different machine architectures, the same compiled IL runs anywhere a compatible CLR is installed, with JIT handling the machine-specific translation at runtime.
5. What's the difference between var and dynamic? They both let you skip specifying a type explicitly.
var is resolved entirely at compile time, the compiler infers the actual concrete type from the right-hand side of the assignment, and from that point on it's statically typed exactly as if you'd written the type explicitly, full compile-time checking and IntelliSense apply. dynamic skips type checking entirely until runtime, the compiler doesn't verify member access at all, so you can call a method that doesn't exist and it'll compile fine, only to throw a RuntimeBinderException when it actually executes, dynamic is genuinely a different, much riskier feature, not just a longer way to write var.
6. What's the difference between const and readonly?
const values must be known and assigned at compile time, and they're effectively baked directly into any code that references them at the point of compilation. readonly fields can be assigned at declaration or inside a constructor, meaning their value can be computed at runtime, and they can differ per instance of a class, not just be a single fixed value shared everywhere. If a value genuinely never changes and is knowable at compile time (like Math.PI), const fits, if it depends on runtime logic or needs to vary per instance, readonly is the right tool.
OOP in C#
1. What's the difference between an abstract class and an interface in C#, and when would you pick one over the other?
An abstract class can contain actual implemented method bodies, fields, and constructors that derived classes inherit, alongside abstract methods that must be overridden, and a class can only inherit from one abstract (or any) base class. An interface (traditionally) only declared method signatures with no implementation, though modern C# now allows default interface implementations too, and a class can implement any number of interfaces. You'd pick an abstract class when there's genuinely shared implementation and state to inherit, and an interface when you're defining a contract that unrelated classes should be able to fulfill, especially when a class needs to satisfy multiple such contracts at once.
2. What's the difference between method overloading and method overriding?
Overloading means defining multiple methods with the same name but different parameter signatures within the same class, resolved entirely at compile time based on which arguments you pass. Overriding means a derived class provides its own implementation of a virtual (or abstract) method it inherited from a base class, using the override keyword, and which implementation actually runs is resolved at runtime based on the object's actual type, not the variable's declared type, that runtime resolution is what makes overriding the mechanism behind real polymorphism.
3. What does the virtual keyword do, and why do you need it on a base class method before you can override it?
Marking a base class method virtual tells the CLR that this method is allowed to be overridden by derived classes, and it changes how the method is dispatched, instead of always calling the exact method defined on the declared type, the runtime looks up the actual, most-derived override based on the object's real type. Without virtual, a method is statically bound, calling it always executes the base class's version, even through a derived class instance, which is exactly why C# requires you to opt in explicitly with virtual, unlike some languages where every method is implicitly overridable by default.
4. What does sealed do, and why would you deliberately prevent a class from being inherited?
sealed on a class prevents any other class from inheriting from it, and on an overridden method, it prevents any further derived class from overriding it again past that point. You'd use it when you want to guarantee a class's behavior can't be altered by inheritance, protecting invariants you rely on, or simply because inheritance wasn't part of the intended design and you want the compiler to enforce that boundary rather than leaving it as an unspoken convention, sealing a class can also let the JIT make small performance optimizations since it knows no override can exist.
5. What's the difference between a struct and a class in C#? This is a genuinely common interview trap.
A struct is a value type, allocated on the stack (when it's a local variable, not a field of a reference type) and copied by value whenever it's assigned or passed to a method, so two variables holding the "same" struct actually hold independent copies. A class is a reference type, allocated on the heap, and variables hold a reference to the same underlying object, so assigning or passing it around shares the same instance, not a copy. The trap people fall into is mutating a struct through a method or property that actually operates on a copy, silently having no effect on the original, which almost never happens with reference types.
6. What is boxing and unboxing, and why does it have a real performance cost?
Boxing is converting a value type (like an int) into a reference type (object), which requires allocating a new object on the heap to hold a copy of that value. Unboxing is the reverse, extracting the value type back out of that boxed object. Both operations cost real performance, boxing means an extra heap allocation (and eventual garbage collection) for something that would otherwise live cheaply on the stack, and this can sneak into code unexpectedly, for example passing an int into an older, non-generic collection like ArrayList, which stores everything as object, silently boxes every value you add.
Value Types, Reference Types and Memory Management
1. What's actually stored on the stack versus the heap in C#, and how does that relate to value types vs reference types?
Local variables and method parameters that are value types are typically stored directly on the stack, fast to allocate and automatically cleaned up when the method returns. Reference types are always allocated on the heap, and what actually lives on the stack (or wherever the variable is declared) is just a reference, a pointer, to that heap location. It's worth being precise here, a value type that's a field of a class instance lives on the heap too, as part of that object, it's really about where the variable itself is declared, not a blanket rule that "value types always live on the stack."
2. What is garbage collection in .NET, and how does the generational GC (Gen 0, 1, 2) actually work?
Garbage collection automatically reclaims memory occupied by objects that are no longer reachable from any active reference, so you don't manually free heap memory the way you would in C or C++. .NET's GC is generational, based on the observation that most objects die young, new objects start in Gen 0, which is collected frequently and cheaply since most Gen 0 objects are already garbage by the next collection. Objects that survive a Gen 0 collection get promoted to Gen 1, and if they survive further collections, eventually to Gen 2, which is collected far less often since long-lived objects are statistically likely to keep living, this generational approach means the GC doesn't have to scan the entire heap on every single collection, keeping most collections fast.
3. What's the difference between Dispose() and a finalizer? Why does IDisposable exist at all if GC already cleans up memory?
Garbage collection only manages managed memory, it has no idea how to clean up unmanaged resources, open file handles, database connections, network sockets, that your object might be holding onto. IDisposable's Dispose() method gives you a deterministic, explicit way to release those unmanaged resources exactly when you're done with them, rather than waiting for the GC to eventually get around to it. A finalizer (~ClassName()) is a backup safety net the GC calls before reclaiming an object if Dispose() was never called, but finalizers run at an unpredictable time and add real overhead, so Dispose() being called properly and promptly is always the preferred path, finalizers exist as a fallback, not the main mechanism.
4. What's the difference between a using statement and manually calling Dispose()?
A using statement (or the newer using declaration without braces) is syntactic sugar that wraps your code in a try/finally block, guaranteeing Dispose() gets called even if an exception is thrown partway through, without you writing that try/finally yourself. Manually calling Dispose() at the end of a method works in the happy path, but if an exception occurs before that line executes, Dispose() never gets called and the resource leaks, which is exactly the bug using exists to prevent automatically.
using var connection = new SqlConnection(connectionString);
connection.Open();
// connection.Dispose() is guaranteed to run when this scope ends,
// even if an exception is thrown
5. What is a memory leak in a garbage-collected language like C#? Doesn't GC prevent that by definition?
GC prevents the classic C-style leak of forgetting to free memory, but it can't save you from a different kind of leak: objects that are technically still reachable (so the GC correctly leaves them alone) but that you actually intended to be done with. This commonly happens with event subscriptions, if object A subscribes to an event on long-lived object B and never unsubscribes, B is now holding a reference to A forever through that event handler, keeping A alive indefinitely even if you thought you were done with it, which is a genuinely common, real-world .NET memory leak pattern, especially in long-running applications.
Collections and LINQ
1. What's the difference between an Array, a List<T>, and an IEnumerable<T>?
An Array has a fixed size set at creation, you can't add or remove elements, only replace existing ones. List<T> is a dynamically resizable collection built on top of an internal array, growing automatically as you add items, it's the go-to general-purpose collection for most scenarios. IEnumerable<T> is more abstract still, it's an interface representing "something you can iterate over," it doesn't guarantee a count, indexed access, or even that all the data already exists in memory, both Array and List<T> implement it, but so does a lazily-evaluated LINQ query.
2. What's the difference between IEnumerable and IQueryable? This comes up constantly with Entity Framework.
IEnumerable operations execute in memory, against objects you already have loaded, LINQ methods chained on an IEnumerable translate directly into normal C# code running in your process. IQueryable builds up an expression tree representing the query instead of executing anything immediately, and with a provider like Entity Framework Core, that expression tree gets translated into an actual SQL query and executed on the database, only pulling back the specific data your query asked for. The practical danger is calling .ToList() (or otherwise materializing an IQueryable into IEnumerable) too early, if you do, all subsequent filtering happens in memory in C# instead of being pushed down to the database as SQL, which can mean pulling back far more data than you actually needed.
3. What is deferred execution in LINQ, and why can it cause a confusing bug if you're not careful?
Most LINQ query operators don't actually execute anything when you write them, they just build up a description of the query, the actual execution happens later, when you iterate over the result (with foreach, or a method like .ToList() or .Count()). This can cause confusing bugs when the underlying data source changes between when you define the query and when you actually enumerate it, the query re-evaluates against the current state of the data at enumeration time, not a frozen snapshot from when you wrote the LINQ expression, so you can end up iterating over data that's different from what you expected if something modified the source collection in between.
4. What's the difference between First(), FirstOrDefault(), Single(), and SingleOrDefault()?
First() returns the first matching element, or throws an exception if there are none. FirstOrDefault() returns the first matching element, or the type's default value (like null for reference types, 0 for int) if there are none, instead of throwing. Single() asserts there's exactly one matching element and throws if there are zero or more than one, useful when finding more than one would itself indicate a bug. SingleOrDefault() is the same but returns the default value instead of throwing when there are zero matches, though it still throws if there's more than one.
5. What's the difference between a Dictionary<TKey,TValue> and a HashSet<T>?
A Dictionary stores key-value pairs, and you look things up by key to retrieve an associated value, both operations averaging O(1) thanks to its hash table implementation. A HashSet stores just values with no associated data, and its entire purpose is enforcing uniqueness and fast membership checking, "does this value already exist in the set," also averaging O(1), which makes it a much better fit than a List for deduplication or fast "contains" checks on a large collection, where a List's Contains would be a slower linear scan.
Exception Handling, Delegates and Events
1. What's the difference between throw and throw ex inside a catch block, and why does it actually matter?
A bare throw inside a catch block re-throws the caught exception while preserving its original stack trace, so when you're debugging later, you can see exactly where the exception first occurred. throw ex re-throws the same exception object but resets the stack trace to originate from that throw ex line instead, which erases genuinely useful information about where the problem actually started, making it noticeably harder to diagnose the root cause later, this is a small syntax difference with a real, practical debugging consequence.
catch (Exception ex)
{
LogError(ex);
throw; // preserves original stack trace
// throw ex; // resets it, avoid this
}
2. What's the difference between a checked and unchecked exception in C#? Does C# even have that distinction the way Java does?
No, and this is a genuine, meaningful difference from Java, C# doesn't have checked exceptions at all. In Java, certain exceptions must be either caught or declared in a method's signature, enforced by the compiler. In C#, every exception is effectively "unchecked," the compiler never forces you to catch or declare anything, which is a deliberate design choice, C#'s designers felt checked exceptions in practice often led to developers writing empty catch blocks just to satisfy the compiler rather than genuinely handling errors well.
3. What is a delegate, and how is it different from just calling a method directly?
A delegate is a type-safe reference to a method, essentially a variable that can hold a pointer to a method matching a specific signature, which you can then invoke, pass around as a parameter, or reassign to point to a different compatible method entirely. The difference from calling a method directly is flexibility, delegates let you decide at runtime which method actually gets invoked, and let you pass "a piece of behavior" as data, which is the foundation of callbacks, LINQ's method-based query syntax, and events.
4. What's the difference between a delegate and an event? Why not just expose the delegate directly as a public field?
An event is built on top of a delegate but adds restrictions specifically meant for the publish-subscribe pattern, external code can only subscribe (+=) or unsubscribe (-=) from an event, it can't directly invoke it or overwrite the entire delegate's subscriber list (=) from outside the declaring class. If you exposed a raw public delegate field instead, any external code could maliciously (or accidentally) call myDelegate = someOtherHandler, wiping out every other subscriber's registered handler, event exists specifically to prevent that kind of accidental or malicious tampering while still allowing controlled subscription.
5. What's the difference between Action, Func, and Predicate? They all seem to just be delegate types.
They're all built-in generic delegate types that save you from having to declare custom delegate types for common shapes. Action represents a method that takes some parameters and returns nothing (void). Func represents a method that takes some parameters and returns a value, with the last generic type parameter specifically being the return type. Predicate<T> is a more specific, semantically named version that always takes one argument of type T and returns a bool, functionally equivalent to Func<T, bool>, but reads more clearly at the call site for things like filtering conditions.
Async/Await and Multithreading
1. What's actually happening when you mark a method async and use await inside it? Does it create a new thread?
No, async/await doesn't inherently create a new thread at all, that's one of the most common misconceptions. When execution hits an await on an operation that isn't already complete, the method returns control to its caller immediately (freeing up the current thread to do other work), and the rest of the method is scheduled to resume as a continuation once the awaited operation actually finishes, potentially on a different thread from a pool, depending on the context. This is what makes async I/O so efficient, you're not blocking a thread while waiting, the thread is released to do other useful work during the wait.
2. What's the difference between Task and Thread? Why does modern C# code favor Task-based async over raw threads?
A Thread represents an actual OS-level thread, creating one directly is relatively expensive and gives you a dedicated, blocking unit of execution you manage yourself. A Task represents an abstraction over an asynchronous operation, which may or may not involve a dedicated thread at all, for I/O-bound work (network calls, file access, database queries), a Task typically doesn't occupy a thread while it's waiting, it uses the OS's async I/O completion mechanisms instead. Task-based async scales far better for I/O-heavy workloads specifically because you're not tying up an expensive OS thread just sitting idle waiting on something external.
3. What is a deadlock, and why does calling .Result or .Wait() on an async method in certain contexts cause one?
Calling .Result or .Wait() synchronously blocks the calling thread until the async operation completes. In contexts with a synchronization context that only allows one thread at a time to run (like older ASP.NET Framework's request context, or WPF/WinForms UI threads), that blocked thread is exactly the same thread the async continuation is trying to resume on once the awaited work finishes, so the continuation can never actually run because the one thread it needs is stuck blocking on .Result, waiting for that very continuation to complete, resulting in a genuine deadlock, this is a big part of why "always await, never block on async code" is such a strongly repeated rule.
4. What's the difference between Task.Run() and just calling an async method directly?
Calling an async method directly runs its synchronous portion (everything before the first await) on the current thread, only genuinely offloading work when it hits an actual asynchronous, I/O-bound operation. Task.Run() explicitly queues work to run on a thread pool thread, which is the right tool specifically for offloading genuinely CPU-bound work so it doesn't block the calling thread (like a UI thread), it's a mistake to wrap an I/O-bound async method in Task.Run() "just to be safe," since that just burns an extra thread pool thread for no real benefit, the I/O operation was already going to free up its thread through await on its own.
5. What is ConfigureAwait(false), and when would you actually want to use it?
By default, after an await completes, execution tries to resume on the original synchronization context it started on (important for UI frameworks, so code can safely touch UI elements after an await without a thread mismatch). ConfigureAwait(false) tells the runtime it doesn't need to capture and restore that original context, the continuation can resume on any available thread pool thread instead, which avoids a small amount of overhead and can prevent deadlocks in certain blocking scenarios. It's commonly recommended in library code that doesn't need to touch UI or context-specific state, since libraries shouldn't force a particular context onto whatever application consumes them, but it's less relevant in modern ASP.NET Core, which doesn't have that same restrictive synchronization context to begin with.
.NET Core / ASP.NET Core Fundamentals
1. What is ASP.NET Core, and how is it actually different from the older ASP.NET (Framework)?
ASP.NET Core is the modern, cross-platform, open-source web framework built on .NET, a ground-up redesign from the older, Windows-only ASP.NET (Framework), with a leaner, modular architecture, built-in dependency injection, and significantly better performance, it can run on Windows, Linux, and macOS, and it's what any new ASP.NET project should be built on today. The older ASP.NET (Framework) is effectively legacy at this point, still maintained for existing applications but not the target for new development.
2. What is the middleware pipeline in ASP.NET Core, and what does calling next() actually do?
The middleware pipeline is an ordered sequence of components that each request passes through, each middleware can inspect or modify the request, short-circuit and return a response immediately, or pass control to the next component in the pipeline by calling the next delegate. Calling that next delegate is what actually invokes the following middleware in the chain, if a middleware never calls it, everything registered after it in the pipeline simply never runs for that request, which is intentional, it's exactly how authentication middleware can short-circuit and reject a request before it ever reaches your actual endpoint logic.
app.Use(async (context, next) =>
{
Console.WriteLine("Before");
await next(); // passes control to the next middleware
Console.WriteLine("After");
});
3. What's the difference between the minimal hosting model in newer .NET versions and the old Startup.cs-based approach?
The older pattern split configuration across a Program.cs that just built and ran the host, and a separate Startup.cs with distinct ConfigureServices and Configure methods for registering services and setting up the middleware pipeline respectively. The minimal hosting model (default since .NET 6) collapses all of that into a single, top-level Program.cs, using a WebApplicationBuilder to register services and a WebApplication to configure the pipeline, in the same file, in a more linear, less ceremony-heavy style, both patterns are still supported, but minimal hosting is the modern default for new projects.
4. What's the difference between MVC and Minimal APIs in ASP.NET Core, and when would you pick one over the other?
MVC (Model-View-Controller) organizes endpoints into controller classes with action methods, following more structure and convention, and it's a natural fit for larger applications, especially ones serving views/pages, or that benefit from features like model binding attributes, filters, and a more formalized structure. Minimal APIs let you define an endpoint directly as a lambda mapped to a route, with far less boilerplate, which fits smaller APIs, microservices, or situations where you specifically want a lightweight, low-ceremony surface, the choice generally comes down to project size and how much structure you actually want versus how much overhead you're willing to accept for it.
5. What is model binding, and how does ASP.NET Core actually decide where to pull a parameter's value from?
Model binding is the process of automatically populating an action method's (or minimal API handler's) parameters from data in the incoming HTTP request, route values, query string, form data, request body, or headers, without you manually parsing the raw request yourself. By default, ASP.NET Core uses a set of conventions to figure out where to look, simple types often come from route values or the query string, complex types are typically expected from the request body (as JSON), but you can be explicit about it using attributes like [FromBody], [FromQuery], [FromRoute], or [FromHeader] when you want to override the default inference.
Entity Framework Core and Data Access
1. What is Entity Framework Core, and what's the actual tradeoff of using an ORM instead of writing raw SQL/ADO.NET?
EF Core is an ORM (Object-Relational Mapper) that lets you work with your database through C# classes and LINQ queries instead of writing raw SQL directly, translating your LINQ expressions into SQL behind the scenes and mapping query results back into objects automatically. The tradeoff is real productivity and maintainability gains, less boilerplate, strongly typed queries, easier refactoring, in exchange for somewhat less direct control over the exact SQL generated, and the very real risk of writing LINQ that generates inefficient SQL if you're not paying attention to how EF Core translates what you write.
2. What's the difference between eager loading, lazy loading, and explicit loading in EF Core?
Eager loading fetches related data upfront, in the same query, using .Include(), so everything you need is available immediately without extra round trips. Lazy loading fetches related data automatically, but only at the exact moment you actually access that related property, which is convenient but can silently trigger a separate database query at a point in your code you didn't obviously expect, if you're not careful. Explicit loading gives you manual control, you decide precisely when to trigger loading related data for an already-loaded entity, using .Entry().Collection().Load() or similar, which is useful when you want the predictability of eager loading but only conditionally, not for every query.
3. What is the N+1 query problem, and how does it actually happen with EF Core if you're not careful?
The N+1 problem happens when you fetch a list of N entities with one query, then, for each of those N entities, trigger a separate additional query to fetch related data, N additional queries instead of one efficient combined query. With EF Core, this commonly happens with lazy loading enabled, if you loop over a list of orders and access order.Customer inside the loop, each access can trigger its own separate database round trip for that specific order's customer, instead of one .Include(o => o.Customer) call that fetches everything together upfront.
4. What's the difference between AsNoTracking() and a normal tracked query, and why would you use it?
By default, EF Core's change tracker keeps track of every entity a query returns, monitoring it for changes so it knows what to update if you later call SaveChanges(), which has a real memory and performance cost. AsNoTracking() tells EF Core to skip that tracking entirely for a given query, which is a meaningful performance win for read-only scenarios, displaying data you have no intention of modifying and saving back, since there's no reason to pay the tracking overhead for data you're never going to update.
var products = await context.Products
.AsNoTracking()
.Where(p => p.IsActive)
.ToListAsync();
5. What's the difference between a migration and just modifying the database schema directly?
A migration is a code-first, version-controlled description of a schema change, generated by EF Core by comparing your current model against the last known schema state, and it can be applied (or rolled back) consistently across every environment, dev, staging, production, in the correct order. Modifying the schema directly (through a database GUI tool, for example) makes an immediate, one-off change with no record of what changed, why, or in what order relative to other changes, which becomes genuinely dangerous once you have multiple environments or a team, since there's no reliable, repeatable way to bring another environment's schema up to the same state.
Dependency Injection, Middleware and Production Practices
1. What's the difference between AddSingleton, AddScoped, and AddTransient in ASP.NET Core's built-in DI container?
AddSingleton creates exactly one instance for the entire lifetime of the application, shared by every request. AddScoped creates one instance per HTTP request (or more precisely, per DI scope), shared by everything within that single request but not across different requests. AddTransient creates a brand new instance every single time it's requested from the container, even multiple times within the same request. Choosing the right one matters, particularly for services holding state that shouldn't be shared inappropriately across requests or users.
2. What's a real bug that happens if you inject a scoped service into a singleton service? Why is that actually dangerous?
Since a singleton is created exactly once, at application startup, and lives for the entire application's lifetime, if it captures a reference to a scoped service at that point, it ends up holding onto whatever scoped instance existed during that very first resolution, forever, effectively turning a service that was meant to be request-specific (like something using DbContext, which is scoped) into something shared and stale across every subsequent request. This can cause genuinely confusing bugs, data from one user's request leaking into another's, or a DbContext being used across multiple concurrent requests in ways it was never designed to safely handle, ASP.NET Core actually detects and throws an exception for this specific misconfiguration by default in newer versions, precisely because it's such a common and dangerous mistake.
3. What is the options pattern in ASP.NET Core (IOptions<T>), and why is it preferred over just reading configuration values directly everywhere?
The options pattern binds a section of your configuration (from appsettings.json, environment variables, etc.) into a strongly typed C# class, which you then inject wherever it's needed via IOptions<T> (or IOptionsSnapshot<T>/IOptionsMonitor<T> for reload-aware scenarios), rather than sprinkling raw Configuration["SomeKey"] string lookups throughout your codebase. This gives you compile-time safety (typos in a hardcoded string key only fail at runtime, a missing property on a typed class fails to compile), centralizes what configuration a given feature actually depends on, and makes testing far easier, since you can just construct a plain instance of the options class instead of mocking a raw configuration object.
4. What's the difference between appsettings.json and environment variables for configuration, and how does ASP.NET Core decide which one wins?
appsettings.json (and its environment-specific variants like appsettings.Production.json) is file-based configuration checked into (or deployed alongside) your application, good for non-sensitive, environment-specific defaults. Environment variables are typically used for configuration that varies by deployment environment or that's sensitive (connection strings, API keys), and are commonly injected by the hosting platform itself rather than committed to source control. ASP.NET Core's default configuration system loads multiple sources in a defined order and later sources override earlier ones, environment variables are loaded after appsettings.json by default, which means an environment variable will override a matching key from the JSON file, letting you keep sensible defaults in the file while overriding specific values per deployment without touching code.
5. How would you structure a large ASP.NET Core application, layers, folders, so it doesn't become one giant tangled project?
A common approach splits the solution into layered projects, a Domain/Core layer with entities and business logic with no dependency on infrastructure, an Application layer with use cases and interfaces (following something like Clean Architecture principles), an Infrastructure layer implementing those interfaces (EF Core, external API clients), and a Presentation/API layer (the actual ASP.NET Core project with controllers or minimal API endpoints) that depends on the layers beneath it, not the other way around. This keeps business logic testable and decoupled from infrastructure details, so you could swap out EF Core for a different data access approach without rewriting your core business rules.
Struggling to Find a Job? Get Specific Batch Wise job Updates ✅ Check now