{"id":1154,"date":"2026-07-23T21:30:00","date_gmt":"2026-07-23T16:00:00","guid":{"rendered":"https:\/\/lastroundai.com\/blog\/?post_type=iq&#038;p=1154"},"modified":"2026-07-21T22:32:44","modified_gmt":"2026-07-21T17:02:44","slug":"dotnet-developer","status":"publish","type":"iq","link":"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer","title":{"rendered":".NET Developer Interview Questions (2026): Must-Know C# &#038; ASP.NET Q&#038;A"},"content":{"rendered":"<p>A .NET developer interviewing for a mid-level role at a Chicago insurance company last winter got a live-coding prompt most candidates don&#8217;t expect anymore. Fix a deadlocking ASP.NET Core endpoint, on a shared screen, with the interviewer watching every keystroke. She&#8217;d read a dozen blog posts on async in C#. She&#8217;d never actually reproduced a deadlock by hand. She froze for four minutes before finding a blocking <code>.Result<\/code> call buried three layers deep in a repository method.<\/p>\n<p>That kind of question, not &#8220;what is dependency injection&#8221; but &#8220;here&#8217;s a bug, find it live&#8221;, is where .NET interview questions have moved by 2026. C# sits at 27.1% usage among all developers in the <a href=\"https:\/\/survey.stackoverflow.co\/2025\/technology\" target=\"_blank\" rel=\"noopener noreferrer\">2025 Stack Overflow Developer Survey<\/a>, comfortably inside the top ten languages, most of it clustered around ASP.NET Core, EF Core, and Azure work. Syntax questions are searchable in ten seconds, so interviewers ask about the CLR, the garbage collector, and async internals instead, the kind of thing you only get right once you&#8217;ve actually debugged it.<\/p>\n<p>My honest opinion: most .NET interview prep lists spend too long on trivia like <code>var<\/code> versus <code>dynamic<\/code> and not nearly enough time on garbage collector generations or why <code>async void<\/code> quietly breaks your error handling. Those are the questions that separate a 2-year candidate from a 6-year one. This page skips the trivia and works through the eight areas that keep showing up in real 2026 loops, with two coding exercises folded in where they fit naturally.<\/p>\n<div class=\"iq-stats not-prose\"><div class=\"iq-stat\"><span class=\"iq-stat__value\">8<\/span><span class=\"iq-stat__label\">Core Topics<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Easy-Hard<\/span><span class=\"iq-stat__label\">Difficulty Range<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">C#, ASP.NET Core, EF Core<\/span><span class=\"iq-stat__label\">Focus<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Mid to Senior<\/span><span class=\"iq-stat__label\">Best For<\/span><\/div><\/div>\n<h2>C# Fundamentals and Object-Oriented Programming<\/h2>\n<p>Every .NET loop still opens with a warm-up on language basics. Nobody fails an interview here, but a shaky answer sets a bad tone for the 45 minutes after it.<\/p>\n<div class=\"iq-dsec iq-dsec--easy\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"easy\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Easy questions<\/h2><span class=\"iq-dsec__n\">15<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are the four pillars of object-oriented programming, and how does C# actually implement each one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">OOP<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Encapsulation means hiding internal state behind private fields and exposing it through properties or methods. Abstraction means exposing only what a consumer needs, usually through an interface or abstract class. Inheritance lets a class extend another with the <code>: BaseClass<\/code> syntax, single inheritance only. Polymorphism shows up two ways: compile-time (method overloading) and runtime (virtual methods overridden in a derived class, resolved through the vtable at the moment of the call).<\/p>\n<p>Interviewers rarely stop at the definitions. Expect a quick follow-up like &#8220;write a small example&#8221; on a shared screen, and a question about why C# doesn&#8217;t allow multiple class inheritance, the diamond problem, and how interfaces sidestep it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the practical difference between a value type and a reference type in C#?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Memory Model<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Value types (<code>struct<\/code>, <code>int<\/code>, <code>bool<\/code>, <code>enum<\/code>, <code>DateTime<\/code>) hold their data directly, so assigning one copies the entire value. Reference types (<code>class<\/code>, arrays, delegates) hold a pointer to data on the heap, so assigning one copies the pointer, not the object itself, meaning both variables end up pointing at the same data.<\/p>\n<p>Interviewers often ask about <code>string<\/code> specifically, since it&#8217;s technically a reference type but behaves like a value type in practice because it&#8217;s immutable. Every &#8220;modification&#8221; actually creates a new string object, which is exactly why repeated concatenation in a loop should use <code>StringBuilder<\/code> instead.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\npublic struct PointStruct\n\n{\n\n    public int X;\n\n    public int Y;\n\n}\npublic class PointClass\n\n{\n\n    public int X;\n\n    public int Y;\n\n}\nPointStruct s1 = new PointStruct { X = 1, Y = 2 };\n\nPointStruct s2 = s1; \/\/ copies the values\n\ns2.X = 99;\n\nConsole.WriteLine(s1.X); \/\/ still 1\nPointClass c1 = new PointClass { X = 1, Y = 2 };\n\nPointClass c2 = c1; \/\/ copies the reference\n\nc2.X = 99;\n\nConsole.WriteLine(c1.X); \/\/ 99, both variables point at the same object\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">List&lt;T&gt;, Dictionary&lt;TKey,TValue&gt;, or HashSet&lt;T&gt;, how do you choose?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Collections<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>List&lt;T&gt;<\/code> keeps insertion order and allows duplicates, but a lookup by value is O(n). <code>Dictionary&lt;TKey,TValue&gt;<\/code> gives O(1) average lookup by key through hashing. <code>HashSet&lt;T&gt;<\/code> gives O(1) average membership checks and rejects duplicates, with no guaranteed ordering.<\/p>\n<p>A fair follow-up: &#8220;how would you find duplicates in a list of a million items efficiently?&#8221; The answer is a <code>HashSet&lt;T&gt;<\/code> to track what you&#8217;ve already seen, one pass, O(n) total, instead of a nested loop.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Select vs. SelectMany, what&#039;s actually different?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">LINQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>Select<\/code> projects each element into a new form, one in, one out. <code>SelectMany<\/code> flattens a sequence of sequences into a single flat sequence, one in, zero or more out per element.<\/p>\n<p>The usual example: a list of orders, each with a list of items. <code>orders.Select(o => o.Items)<\/code> gives you a list of lists. <code>orders.SelectMany(o => o.Items)<\/code> gives you a single flat list of every item across every order.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When does a finally block NOT run?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Exceptions<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>finally<\/code> runs even if the try or catch block returns, and even if an exception propagates out unhandled. It does not run if the process itself dies abruptly, a <code>StackOverflowException<\/code>, a call to <code>Environment.FailFast<\/code>, the process getting killed externally, or the machine losing power.<\/p>\n<p>Candidates who answer &#8220;always&#8221; without that caveat usually haven&#8217;t thought about the difference between an exception unwinding the stack (finally runs) and the process itself terminating (finally doesn&#8217;t get the chance).<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you build a custom exception, and when is it actually worth the extra class?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Exceptions<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Subclass <code>Exception<\/code> (or a more specific existing type), match the standard constructor overloads, and add whatever custom context the caller actually needs, like an account ID or an error code.<\/p>\n<p>It&#8217;s worth doing when calling code needs to programmatically distinguish and handle a specific failure mode, catching <code>InsufficientCreditException<\/code> to trigger a specific retry path, for instance. It&#8217;s not worth doing just because a generic <code>Exception<\/code> with a custom message &#8220;feels&#8221; less professional.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\npublic class InsufficientCreditException : Exception\n\n{\n\n    public int AccountId { get; }\n    public InsufficientCreditException(int accountId, string message)\n\n        : base(message)\n\n    {\n\n        AccountId = accountId;\n\n    }\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What actually gets stored where when you run an EF Core migration?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">EF Core<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Running <code>dotnet ef migrations add<\/code> generates a C# migration class with <code>Up()<\/code> and <code>Down()<\/code> methods, plus an updated snapshot of your current model shape. Applying it with <code>dotnet ef database update<\/code> executes the <code>Up()<\/code> method&#8217;s SQL, and EF Core records which migrations have run in a <code>__EFMigrationsHistory<\/code> table inside the target database itself.<\/p>\n<p>A decent follow-up: two developers create migrations on separate branches at the same time, what happens? A merge conflict in the model snapshot, usually solved by regenerating one migration after merging rather than hand-editing the conflict away.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are the most common .NET interview questions for a fresher role?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Expect OOP basics (encapsulation, inheritance, polymorphism), value vs. reference types, a couple of collection questions (List vs. Dictionary), basic exception handling, and simple LINQ queries. System design and deep CLR internals mostly show up at mid-level and above, not for freshers.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Do 2026 .NET interviews still test the old .NET Framework, or is it all .NET 8 and later now?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Almost entirely modern .NET now (.NET 6 through .NET 9 and beyond), especially for anything greenfield. Legacy .NET Framework knowledge still matters for roles maintaining older enterprise systems, banking and insurance backends show up here often, so it&#8217;s worth asking the recruiter which stack the team actually runs before you prep.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Is knowing C# enough, or do I need hands-on ASP.NET Core and EF Core experience too?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>C# alone gets you through the language-fundamentals portion, not the whole loop. Most .NET developer roles expect at least working familiarity with ASP.NET Core (controllers or minimal APIs, middleware, DI) and EF Core (migrations, LINQ queries, the N+1 problem), since that&#8217;s what the actual job looks like day to day.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How many rounds does a typical mid-level .NET developer loop have?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Usually 3 to 4: a recruiter screen, one or two technical rounds, and a final round with the hiring manager. Smaller companies compress this to 2; larger enterprises stretch it to 5.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">const, readonly, and static readonly in C#, what actually separates them?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Const vs readonly<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>const is a compile-time constant. The value gets baked directly into the IL at every call site, so if you change a const value in a referenced assembly and don&#8217;t recompile the code that consumes it, that code keeps using the old baked-in value until it&#8217;s rebuilt. const also only works with primitive types, strings, and enums, since the compiler needs to know the value while it&#8217;s compiling.<\/p>\n<p>readonly is a runtime constant. It can only be assigned in the field initializer or inside a constructor of that same class, but the value itself is computed at runtime, so it can hold the result of a method call, a database lookup, or anything else that isn&#8217;t known ahead of time. Two instances of the same class can also end up with different readonly values if it&#8217;s an instance field.<\/p>\n<p>static readonly is readonly scoped to the type instead of the object, one shared value across every instance, still assigned at runtime, often in a static constructor, without the versioning trap that const carries. If a value might ever need to change without forcing every consumer to recompile, static readonly is the safer default over const in production code.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the real difference between ==, .Equals(), and ReferenceEquals() in C#?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Equality checks<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>For reference types, == by default and ReferenceEquals do the same thing unless a class overloads the == operator: they check whether two variables point at the same object in memory. .Equals() is virtual and can be overridden, so a class can define what counts as equal for its own data, like two Point objects with matching X and Y values being equal even though they&#8217;re two separate objects on the heap.<\/p>\n<p>string is the type that trips people up. string overloads == to do a value comparison instead of a reference comparison, so two separately built strings with the same characters compare equal with ==, thanks to that overload plus string interning caching identical literals. But build a string at runtime, say with StringBuilder, and ReferenceEquals against a matching literal comes back false even though == is true, because interning only applies to values known at compile time.<\/p>\n<p>For your own classes, overriding Equals() without also overriding GetHashCode() consistently is a real bug generator, the object behaves fine on its own but breaks the moment it goes into a Dictionary or a HashSet, since those rely on GetHashCode() to even find the right bucket before Equals() ever gets called.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Array or List&lt;T&gt;, what&#039;s actually different between them?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Arrays vs lists<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An array has a fixed size decided at creation. Once you allocate one, that block can&#8217;t grow, if you need more room you allocate a brand-new array and copy everything across yourself. Arrays are stored as one contiguous block, which is why they&#8217;re fast for tight numeric loops and interop with unmanaged code.<\/p>\n<p>List<T> wraps a growable array internally. When it runs out of room it doubles its internal capacity and copies the old elements over, which is why Add() is amortized constant time but occasionally spikes when a resize happens, and it exposes methods like Insert, Remove, and Contains that a plain array doesn&#8217;t have. If you know the exact size upfront and never need to resize, and the extra bit of allocation and cache locality actually matters, an array is the right call. For everything else, List<T> is the sane default.<\/p>\n<p>One gotcha worth knowing: object[] arr = new string[3] compiles and runs fine right up until you try arr[0] = 5, which throws an ArrayTypeMismatchException at runtime. List<T> doesn&#8217;t have that hole, because generics are invariant by default and the compiler catches the mismatch before it ever runs.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is a nullable value type like int?, and how is it actually implemented?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Nullable value types<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>int is a value type, it can never be null on its own, it always has a default like 0. int? (shorthand for Nullable<int>) is a small struct with two fields under the hood, a value of type T and a bool called HasValue, so it can represent &#8220;no value at all&#8221; on top of a value type without boxing it into a reference type.<\/p>\n<p>You read it with .Value, which throws an InvalidOperationException if HasValue is false, or with .GetValueOrDefault(), and the null-coalescing operator (someNullableInt ?? 0) is the idiomatic way to unwrap it safely in one line. It shows up constantly with database columns that allow NULL, since a plain int can&#8217;t represent that but int? maps cleanly onto a nullable SQL column through EF Core.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--medium\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"medium\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Medium questions<\/h2><span class=\"iq-dsec__n\">25<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Interface or abstract class, when do you actually reach for each one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">OOP<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An abstract class can hold shared state, constructor logic, and partial implementation, but a class can only inherit from one. An interface is a pure contract (no state until default interface methods arrived in C# 8, and even those are meant for narrow backward-compatibility cases), but a class can implement as many interfaces as it needs.<\/p>\n<p>The follow-up interviewers like: &#8220;why not just always use interfaces, then?&#8221; The honest answer is that abstract classes save you from repeating constructor logic and shared fields across every implementation, which interfaces still can&#8217;t do cleanly even with default methods.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain boxing and unboxing. Why does a loop full of boxed integers hurt performance?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">C# Basics<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Boxing wraps a value type in a heap-allocated object so it can be treated as a reference type (assigned to an <code>object<\/code> variable, stored in a non-generic collection, and so on). Unboxing extracts the value back out, with a runtime type check. Every boxing operation is a real heap allocation with object-header overhead, not a free cast.<\/p>\n<p>In a loop that runs a few million times, boxed values mean a few million extra heap allocations, which turns into extra Gen0 garbage collections and measurable slowdown. Generic collections like <code>List&lt;int&gt;<\/code> avoid this entirely because the int is stored inline, not boxed.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\n\/\/ Boxing: each Add allocates a new object on the heap\n\nArrayList boxedNumbers = new ArrayList();\n\nfor (int i = 0; i &lt; 1_000_000; i++)\n\n{\n\n    boxedNumbers.Add(i); \/\/ int gets boxed into an object here\n\n}\n\/\/ No boxing: List&lt;int&gt; stores the ints inline, no per-item allocation\n\nList&lt;int&gt; plainNumbers = new List&lt;int&gt;();\n\nfor (int i = 0; i &lt; 1_000_000; i++)\n\n{\n\n    plainNumbers.Add(i);\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Are value types always stored on the stack?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Memory Model<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>No, and this trips up a lot of candidates who memorized &#8220;value types live on the stack&#8221; without the caveat. A value type declared as a local variable inside a method does live on the stack. But a value type that&#8217;s a field of a class instance lives on the heap, as part of that object&#8217;s memory block, because the whole object got heap-allocated. Boxed value types also end up on the heap.<\/p>\n<p>Interviewers sometimes phrase this as &#8220;where does this struct live&#8221; while pointing at a struct property on a class. If your answer is &#8220;the stack, always&#8221;, that&#8217;s the wrong answer, and it&#8217;s a common enough mistake that a few interviewers ask this specifically to filter for it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Reverse a string without using a built-in reverse method, and explain why &#039;in place&#039; doesn&#039;t quite apply here.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding Exercise<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Because <code>System.String<\/code> is immutable in C#, you can&#8217;t mutate its characters directly, there&#8217;s no true in-place reversal of a string instance. Convert it to a <code>char[]<\/code>, swap characters from both ends toward the middle with two pointers, then build a new string from the array.<\/p>\n<p>Working code alone doesn&#8217;t close this one out. Interviewers want to hear you say the immutability part out loud, and a candidate who writes the two-pointer swap but can&#8217;t explain why you needed a char array in the first place hasn&#8217;t really shown they understand the string model.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\npublic static string ReverseString(string input)\n\n{\n\n    char[] chars = input.ToCharArray();\n\n    int left = 0;\n\n    int right = chars.Length \u2013 1;\n    while (left &lt; right)\n\n    {\n\n        char temp = chars[left];\n\n        chars[left] = chars[right];\n\n        chars[right] = temp;\n\n        left++;\n\n        right\u2013;\n\n    }\n    return new string(chars);\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Walk through the .NET garbage collector&#039;s generational model. Why generations at all?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">GC<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The GC splits the heap into Gen0, Gen1, and Gen2. New objects start in Gen0, which gets collected most often and fastest. Anything that survives a Gen0 collection gets promoted to Gen1, and anything that survives long enough gets promoted to Gen2. The idea, confirmed in <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/standard\/garbage-collection\/fundamentals\" target=\"_blank\" rel=\"noopener noreferrer\">Microsoft&#8217;s own documentation<\/a>, is the generational hypothesis: in a well-behaved app, most objects die young, so it&#8217;s cheaper to sweep the young generation constantly than to scan the whole heap every time.<\/p>\n<p>The follow-up that separates candidates: objects 85,000 bytes or larger skip straight to the Large Object Heap, which is logically part of Gen2 and collected far less often. That means a stream of large temporary arrays can fragment memory badly even though nothing is technically &#8220;leaking&#8221;.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s IDisposable for, and when do you actually need a finalizer next to it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Memory Management<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>IDisposable.Dispose()<\/code> lets you release unmanaged resources deterministically, file handles, database connections, native memory, instead of waiting for the GC to eventually get around to it. You only need a finalizer if your class directly owns an unmanaged resource. If you only hold other <code>IDisposable<\/code> objects (a <code>SqlConnection<\/code>, a <code>FileStream<\/code>), you don&#8217;t need one, just call their <code>Dispose()<\/code> from yours.<\/p>\n<p>The standard dispose pattern below is a fair thing to ask a candidate to write from memory. Watch for whether they remember <code>GC.SuppressFinalize(this)<\/code>, plenty of candidates get the <code>if (disposing)<\/code> branch right and forget that line entirely.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\npublic class FileWrapper : IDisposable\n\n{\n\n    private FileStream _stream;\n\n    private bool _disposed;\n    public FileWrapper(string path)\n\n    {\n\n        _stream = new FileStream(path, FileMode.OpenOrCreate);\n\n    }\n    protected virtual void Dispose(bool disposing)\n\n    {\n\n        if (_disposed) return;\n        if (disposing)\n\n        {\n\n            _stream?.Dispose(); \/\/ release managed resources\n\n        }\n        _disposed = true;\n\n    }\n    public void Dispose()\n\n    {\n\n        Dispose(true);\n\n        GC.SuppressFinalize(this);\n\n    }\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Implement a thread-safe singleton in C#. What&#039;s wrong with the classic double-checked locking version?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding Exercise<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The modern, idiomatic approach is <code>Lazy&lt;T&gt;<\/code>, which the CLR guarantees is thread-safe by default (<code>LazyThreadSafetyMode.ExecutionAndPublication<\/code>). Nobody should be hand-rolling locking code for a simple singleton in 2026.<\/p>\n<p>The classic double-checked locking pattern still gets asked because it tests memory visibility across threads, a deeper thing than locking syntax on its own. Without the <code>volatile<\/code> keyword on the backing field, a thread could observe a partially constructed object due to instruction reordering, a subtle bug that only shows up under real concurrency, never in a single-threaded test run.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\npublic sealed class ConfigManager\n\n{\n\n    private static readonly Lazy&lt;ConfigManager&gt; _instance =\n\n        new Lazy&lt;ConfigManager&gt;(() =&gt; new ConfigManager());\n    public static ConfigManager Instance =&gt; _instance.Value;\n    private ConfigManager()\n\n    {\n\n        \/\/ load configuration once, on first access\n\n    }\n\n}\n\/\/ Shown for context only, this is legacy pre-Lazy&lt;T&gt; knowledge:\n\npublic sealed class LegacySingleton\n\n{\n\n    private static volatile LegacySingleton _instance;\n\n    private static readonly object _lock = new object();\n    public static LegacySingleton Instance\n\n    {\n\n        get\n\n        {\n\n            if (_instance == null)\n\n            {\n\n                lock (_lock)\n\n                {\n\n                    if (_instance == null)\n\n                    {\n\n                        _instance = new LegacySingleton();\n\n                    }\n\n                }\n\n            }\n\n            return _instance;\n\n        }\n\n    }\n    private LegacySingleton() { }\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between IEnumerable&lt;T&gt; and IQueryable&lt;T&gt;, and where does it bite people in EF Core?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">LINQ<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>IQueryable&lt;T&gt;<\/code> builds an expression tree that gets translated into a query, SQL in EF Core&#8217;s case, and executed at the data source, only the matching rows come back. <code>IEnumerable&lt;T&gt;<\/code> executes in memory, LINQ to Objects, against whatever&#8217;s already been loaded.<\/p>\n<p>The bug candidates walk into constantly: calling <code>.AsEnumerable()<\/code> or casting to <code>IEnumerable<\/code> before filtering forces every row in the table into memory first, then filters client-side. On a table with a few hundred thousand rows, that&#8217;s the difference between a query that returns in 40 milliseconds and one that takes 12 seconds and pins a chunk of RAM doing it.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\n\/\/ IQueryable: filter is translated to SQL and runs in the database\n\nIQueryable&lt;Order&gt; query = dbContext.Orders\n\n    .Where(o =&gt; o.Total &gt; 500);\n\nList&lt;Order&gt; bigOrders = query.ToList(); \/\/ only matching rows cross the wire\n\/\/ The bug: forcing IEnumerable first pulls every row into memory,\n\n\/\/ then filters in .NET\n\nIEnumerable&lt;Order&gt; allOrders = dbContext.Orders.AsEnumerable();\n\nList&lt;Order&gt; bigOrdersSlow = allOrders.Where(o =&gt; o.Total &gt; 500).ToList();\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain deferred execution in LINQ. What&#039;s the classic bug it causes?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">LINQ<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A LINQ query isn&#8217;t actually executed when you write it, only when you enumerate it: a <code>foreach<\/code>, a <code>.ToList()<\/code>, a <code>.Count()<\/code>. The query variable just holds a description of the work.<\/p>\n<p>Two bugs follow directly from this. First, modifying the underlying collection between defining the query and enumerating it throws <code>InvalidOperationException<\/code> (&#8220;collection was modified&#8221;). Second, enumerating the same query twice re-runs the whole pipeline both times, which means two database round trips for an <code>IQueryable<\/code>, not one cached result.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What actually happens under the hood when you await a Task?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Async<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The compiler rewrites your async method into a state machine. When execution hits an <code>await<\/code> on a task that isn&#8217;t finished yet, the method returns control to its caller right away, it doesn&#8217;t block the calling thread. The rest of the method becomes a continuation that resumes once the awaited task completes, either back on the original synchronization context or on a thread pool thread, depending on the environment.<\/p>\n<p>Interviewers who ask this want to see that you know <code>async<\/code>\/<code>await<\/code> isn&#8217;t magic multithreading, it&#8217;s a compiler transform for non-blocking continuation, and the actual thread that resumes the method may not be the thread that started it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">async void vs. async Task, why is async void considered dangerous?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Async<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>async Task<\/code> gives the caller something to await, which means exceptions thrown inside the method propagate normally and can be caught. <code>async void<\/code> is fire-and-forget: the caller has no way to await it, no way to know when it finishes, and an exception thrown inside gets rethrown directly on the synchronization context, often crashing the process instead of surfacing where you&#8217;d expect.<\/p>\n<p>The one legitimate use of <code>async void<\/code> is a top-level UI event handler, where the framework&#8217;s delegate signature is fixed and you have no choice. Anywhere else, it&#8217;s a smell worth asking about.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Should you catch the general Exception type, or always something specific?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Exceptions<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Specific, almost always. Catch <code>SqlException<\/code>, <code>HttpRequestException<\/code>, whatever the actual failure mode is, and let anything you didn&#8217;t anticipate propagate up. Catching the base <code>Exception<\/code> class swallows bugs you never intended to catch, a <code>NullReferenceException<\/code> from a real defect looks identical to an expected failure once it&#8217;s inside a broad catch block.<\/p>\n<p>My honest take: I&#8217;d rather see an unhandled exception crash a dev environment loudly than have a silent <code>catch (Exception)<\/code> hide a real bug in production for six months. Interviewers who ask this are usually checking whether you treat broad catches as a liability instead of a safety net.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Walk through the ASP.NET Core middleware pipeline. Why does order matter?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">ASP.NET Core<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Middleware is a chain of components registered with <code>app.Use(...)<\/code>, each one acting on the request going in, calling the next delegate, then acting on the response coming back out. Order matters because each middleware only sees what runs before it: authentication has to run before authorization, and exception handling usually goes first so it can catch failures from everything downstream.<\/p>\n<p>A reasonable follow-up: &#8220;what happens if you register UseAuthorization before UseAuthentication?&#8221; Requests get rejected for lacking authorization even when they&#8217;d have authenticated fine, because authorization ran against a request that hadn&#8217;t been authenticated yet.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\nvar app = builder.Build();\napp.UseExceptionHandler(\u201c\/error\u201d); \/\/ catch unhandled exceptions first\n\napp.UseHttpsRedirection();\n\napp.UseRouting();\n\napp.UseAuthentication(); \/\/ must run before UseAuthorization\n\napp.UseAuthorization();\n\napp.MapControllers();\napp.Run();\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">MVC, Web API, or Minimal APIs, what&#039;s actually different in 2026?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">ASP.NET Core<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>ASP.NET Core merged the old separate MVC and Web API frameworks into one controller base years ago, so today&#8217;s real distinction is Controller-based (attribute routing, model binding, filters, either serving views or JSON) versus Minimal APIs, introduced in .NET 6, which map endpoints directly without a controller class at all.<\/p>\n<p>I&#8217;ll take a stance here: for anything that&#8217;s a pure JSON service with maybe a dozen endpoints or fewer, I default to Minimal APIs now, less ceremony, faster to read. Past that size, controllers with filters and model binding start earning their keep again, and I&#8217;d rather not manage forty loose endpoint delegates in one file.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is change tracking in EF Core, and how does SaveChanges know what SQL to generate?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">EF Core<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The <code>DbContext<\/code> tracks the state of every entity it&#8217;s aware of, <code>Added<\/code>, <code>Modified<\/code>, <code>Deleted<\/code>, or <code>Unchanged<\/code>. When <code>SaveChanges()<\/code> runs, it walks the tracked entities, compares current values to the original snapshot, and generates the corresponding INSERT, UPDATE, or DELETE statements.<\/p>\n<p>Interviewers often ask about <code>.AsNoTracking()<\/code> next: for read-only queries where you&#8217;ll never call <code>SaveChanges()<\/code> on the results, turning off tracking skips the snapshot bookkeeping entirely and measurably reduces overhead, sometimes 20 to 30 percent on large result sets in real workloads I&#8217;ve seen reported.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Should I prepare LINQ and raw SQL both, or does EF Core mean I don&#039;t need SQL knowledge anymore?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">FAQ<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Prepare both. EF Core generates SQL for you, but debugging a slow query, reading an execution plan, or explaining why a LINQ query produced a bad JOIN all require actual SQL knowledge underneath. Interviewers at anything beyond junior level will ask you to reason about the generated SQL itself, and the LINQ syntax on top of it won&#8217;t cover for you there.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are C# records, and how are they actually different from a regular class?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">C# records<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>record was added in C# 9 to make writing immutable data-carrying types far less painful. A class needs you to hand-write Equals, GetHashCode, and a constructor if you want value-based comparison; a record gives you all of that for free just by declaring the shape of the data, so two instances with the same property values compare equal and hash consistently without any extra code.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\npublic record Person(string Name, int Age);\nvar p1 = new Person(\u201cAda\u201d, 30);\n\nvar p2 = new Person(\u201cAda\u201d, 30);\n\nConsole.WriteLine(p1 == p2); \/\/ true, value-based equality\nvar older = p1 with { Age = p1.Age + 1 };\n<\/code><\/pre><\/div><\/p>\n<p>Records default to a reference type (record class) unless you write record struct, in which case they behave as a value type. Properties on a positional record are init-only by default, settable during construction or through a with expression like the one above, but not mutable afterward, which is exactly the shape you want for DTOs and message payloads that shouldn&#8217;t change once they&#8217;re created.<\/p>\n<p>One thing that surprises people: the default ToString() on a record prints every property value, which is genuinely useful for logging but also means you need to be careful not to log a record that happens to carry a password or token field, since that convenience will dump it straight into your logs.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">ref, out, and in parameters, what&#039;s actually different about each one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Parameter modifiers<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>All three pass a variable by reference instead of by value, meaning the method operates on the caller&#8217;s actual storage location instead of a copy, but they differ in what the compiler requires and allows.<\/p>\n<p>ref requires the variable to already be assigned before the call, and the method can read it and optionally reassign it. out doesn&#8217;t require the variable to be assigned beforehand, it can be an uninitialized local, but the method is required to assign it before returning, that&#8217;s how TryParse-style methods work. in is the newer one, added for performance on large structs, it passes by reference but the compiler blocks the method from modifying it at all, so you avoid copying a big struct on every call while still guaranteeing the caller&#8217;s value can&#8217;t be mutated by surprise.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\nbool ok = int.TryParse(\u201c42\u201d, out int result);\nvoid Scale(in Matrix4x4 m, ref float factor)\n\n{\n\n    factor *= 2; \/\/ allowed\n\n    \/\/ m.M11 = 1;  \/\/ compiler error, m is read-only here\n\n}\n<\/code><\/pre><\/div><\/p>\n<p>The main everyday use for ref and out today is the TryParse and TryGetValue pattern, and returning a value from a method without allocating a tuple just for that. in mostly earns its keep in hot paths dealing with large readonly structs, a custom vector or matrix type, where copying 64 or more bytes on every single call actually shows up in a profiler.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a delegate, and how do events actually build on top of it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Delegates and events<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A delegate is a type-safe function pointer, it holds a reference to a method, or a list of methods since delegates are multicast, with a matching signature, and you invoke it like a method call. Action, Func, and Predicate are just pre-built generic delegate types the framework gives you so you rarely need to declare a custom delegate type anymore.<\/p>\n<p>An event is a delegate with restricted access. Under the hood it&#8217;s still backed by a delegate field, but exposing it through the event keyword means outside code can only add or remove a handler with += and -=, they can&#8217;t call it directly or reassign the whole thing with a plain =, which would wipe out every other subscriber at once. That restriction is the entire reason events exist as a separate concept from a public delegate field.<\/p>\n<p>The classic bug with events is forgetting to unsubscribe. If a long-lived publisher, a static event or a singleton service, holds a += reference to a short-lived subscriber&#8217;s handler, the subscriber can never be garbage collected because the publisher&#8217;s invocation list still points at it. That&#8217;s a very common source of memory leaks in long-running services and desktop apps, and it&#8217;s easy to miss because nothing in the code you&#8217;re looking at seems to be holding the reference directly.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain extension methods. How do they actually work, and what can&#039;t they do?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Extension methods<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An extension method lets you add a method to a type you don&#8217;t own or can&#8217;t modify, adding something like a title-case helper to string, by writing a static method in a static class where the first parameter carries the this modifier. The compiler resolves a call like value.IsNullOrBlank() into a plain static method call at compile time, it&#8217;s syntactic sugar over a static method, nothing more.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\npublic static class StringExtensions\n\n{\n\n    public static bool IsNullOrBlank(this string s)\n\n        =&gt; string.IsNullOrWhiteSpace(s);\n\n}\n<\/code><\/pre><\/div><\/p>\n<p>Because it&#8217;s compile-time sugar, an extension method can&#8217;t reach the private members of the type it extends, it only sees what&#8217;s already public or internal to the same assembly. It also can never win against a real instance method, if the type already has a method matching that exact signature, the actual instance method is called every time, the extension method is silently ignored for that call.<\/p>\n<p>LINQ is built entirely on this feature. Where, Select, and OrderBy are all extension methods on IEnumerable<T>, which is why you can call .Where() on practically any collection without the interface itself needing to know LINQ exists.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Task vs ValueTask&lt;T&gt;, when do you actually reach for ValueTask?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Task vs ValueTask<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Task is a reference type, every completed async method that returns a value, or every Task.FromResult call, allocates an object on the heap. In a hot path that runs constantly, a caching layer that usually returns a value synchronously from an in-memory dictionary and only occasionally awaits something real, those allocations add up fast.<\/p>\n<p>ValueTask<T> is a struct that can wrap either an already-computed result or an underlying Task, avoiding the heap allocation in the common synchronous-completion case. The tradeoff is real: you shouldn&#8217;t await the same ValueTask twice, you shouldn&#8217;t call its result-fetching method more than once, and you generally shouldn&#8217;t store one to await later, it&#8217;s meant to be consumed exactly once, immediately. Task has none of those restrictions, you can await it repeatedly or hand it to several callers safely.<\/p>\n<p>Most application code should keep returning Task, it&#8217;s simpler and safer to reason about. ValueTask earns its complexity in library and infrastructure code, a custom cache-aside method or a low-level stream reader, where profiling has actually shown the allocation matters.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does ConfigureAwait(false) actually do, and when should you use it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">ConfigureAwait behavior<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>When you await a Task, the continuation after the await tries by default to resume on the original synchronization context it started on. In classic ASP.NET and in WPF or WinForms, that context is a single dedicated thread, and if that thread happens to be blocked waiting on something synchronously, the continuation can never resume there, that&#8217;s the classic async deadlock people run into.<\/p>\n<p>ConfigureAwait(false) tells the awaiter not to bother capturing that context, the continuation can run on any available thread pool thread instead. In library code that has no business knowing about the caller&#8217;s UI thread or request context, using it everywhere you await avoids that whole deadlock class, and it&#8217;s marginally cheaper too since there&#8217;s no context-capture bookkeeping involved.<\/p>\n<p>In ASP.NET Core there&#8217;s no synchronization context by default, so ConfigureAwait(false) isn&#8217;t fixing a deadlock that can even happen there anymore. Plenty of teams still apply it consistently in shared library code anyway, because that code might get consumed by something that does have a context, and being defensive costs nothing.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain yield return. What&#039;s actually happening when you write an iterator method?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Yield return<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Writing yield return inside a method that returns an enumerable tells the compiler to generate a whole state machine class behind the scenes. You aren&#8217;t actually building a list and returning it, the compiler rewrites the method body into a class implementing the enumerator interface, where each call to move to the next item runs your code up to the next yield return and pauses exactly there, remembering local variables and the current position.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\npublic IEnumerable&lt;int&gt; GetEvens(int[] data)\n\n{\n\n    if (data == null) throw new ArgumentNullException(nameof(data));\n    foreach (var n in data)\n\n        if (n % 2 == 0)\n\n            yield return n;\n\n}\n\n\/\/ the ArgumentNullException doesn\u2019t fire when GetEvens(\u2026) is called,\n\n\/\/ it fires on the first MoveNext(), i.e. the first foreach iteration.\n<\/code><\/pre><\/div><\/p>\n<p>That&#8217;s what makes it lazy, nothing inside the method actually runs until something starts pulling values from it, typically a foreach loop or a LINQ operator further up the chain. That&#8217;s also why deferred execution in LINQ works the way it does, methods like Where and Select are themselves implemented with yield return internally.<\/p>\n<p>The gotcha in the code above: if the first line does argument validation, that exception doesn&#8217;t throw when you call the method, it throws on the first iteration, far away from where the method was actually invoked. The usual fix is splitting the method in two, an outer non-iterator wrapper that validates eagerly and throws immediately, calling a private iterator method underneath that does the actual yielding.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Task.WhenAll or Parallel.ForEach, how do you actually choose between them?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Concurrency patterns<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Task.WhenAll is for I\/O-bound work, a batch of independent asynchronous operations, calling several different HTTP APIs or running several database queries, kicked off together and then awaited as a group. It doesn&#8217;t need extra threads for the waiting itself, each task is already non-blocking while it&#8217;s in flight, WhenAll just gives you back one task that completes once all of them do.<\/p>\n<p>Parallel.ForEach is for CPU-bound work, splitting a synchronous, computationally heavy loop across multiple thread pool threads so it actually uses more cores at once, think image resizing or a large in-memory calculation over a big collection. Using Parallel.ForEach on network calls wastes threads sitting there blocked on I\/O, and using Task.WhenAll on pure CPU-bound synchronous work doesn&#8217;t help at all, the work still runs one item at a time unless each item gets explicitly pushed onto the thread pool.<\/p>\n<p>The mistake I&#8217;ve seen most often in code review is someone wrapping a database call just to fan it out the way Parallel.ForEach would, that steals threads from the pool unnecessarily when Task.WhenAll on the natively async calls does the same job with far less pressure on the thread pool.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a concurrency token in EF Core, and what problem does it actually solve?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">EF Core concurrency<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Optimistic concurrency means you don&#8217;t lock a row while you&#8217;re editing it, you check when you save whether it changed since you read it. EF Core does this with a concurrency token, commonly a rowversion column in SQL Server, mapped onto an entity property. EF Core includes that column&#8217;s original value in the WHERE clause of the update statement it generates.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\npublic class Order\n\n{\n\n    public int Id { get; set; }\n\n    public string Status { get; set; }\n    [Timestamp]\n\n    public byte[] RowVersion { get; set; }\n\n}\n\n\/\/ EF Core generates something like:\n\n\/\/ UPDATE Orders SET Status=@p0\n\n\/\/ WHERE Id=@id AND RowVersion=@originalRowVersion\n<\/code><\/pre><\/div><\/p>\n<p>If another process updated that row in between your read and your save, the RowVersion column in the database no longer matches what you loaded, zero rows match the WHERE clause, and EF Core throws a concurrency exception instead of silently overwriting someone else&#8217;s change. You catch it, reload the current values, decide whether to merge or ask the user to redo their edit, and retry.<\/p>\n<p>It&#8217;s the right tool for low-contention scenarios where conflicts are rare and detecting them beats paying for row locks on every read. For a genuinely high-contention row, a shared counter getting hit many times a second, optimistic concurrency there just means a lot of failed retries, you&#8217;re usually better off with an atomic database operation instead.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--hard\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"hard\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Hard questions<\/h2><span class=\"iq-dsec__n\">12<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why does calling .Result or .Wait() on an async method deadlock certain .NET apps?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Async<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Classic ASP.NET (pre-Core), WPF, and WinForms run under a <code>SynchronizationContext<\/code> that allows only one thread back onto it at a time. Blocking on <code>.Result<\/code> occupies that thread while it waits. The awaited task&#8217;s continuation then tries to resume on that same context, but can&#8217;t, since the one allowed thread is the one stuck waiting. Circular wait, deadlock.<\/p>\n<p>ASP.NET Core has no such context by default, so this exact deadlock rarely happens there. Blocking async code is still bad practice, it just starves the thread pool instead. Fix in both cases: await all the way up the call stack, and use <code>ConfigureAwait(false)<\/code> in library code that doesn&#8217;t need a specific context back.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain the three DI lifetimes, Transient, Scoped, and Singleton. What breaks when you mix them wrong?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Dependency Injection<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>Transient<\/code> creates a new instance every time it&#8217;s requested. <code>Scoped<\/code> creates one instance per HTTP request (or per scope). <code>Singleton<\/code> creates exactly one instance for the app&#8217;s entire lifetime.<\/p>\n<p>The scenario interviewers reach for now: inject a <code>Scoped<\/code> service, say a <code>DbContext<\/code>, into a <code>Singleton<\/code>. It resolves once at startup and gets held onto forever. In dev, with one request at a time, that can look fine. Under real concurrent traffic, every request shares that single non-thread-safe instance, corrupted reads or cross-request data bleed follow. This is a captive dependency, and it&#8217;s exactly what a definition-only answer won&#8217;t catch.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the N+1 query problem, and how do you actually fix it in EF Core?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">EF Core<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>It happens when you lazy-load a related collection inside a loop over a parent list: one query loads the parent rows, then one more fires per parent for its related data, N+1 round trips instead of one. A page listing 200 orders with line items turns into 201 database calls where one would do.<\/p>\n<p>The fix is eager loading with <code>.Include()<\/code> (and <code>.ThenInclude()<\/code> for nested relations), which folds the related data into the original query as a join. For read-heavy scenarios where you don&#8217;t need the full entity graph, projecting directly with <code>.Select()<\/code> into a lightweight DTO avoids loading unnecessary columns entirely.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\n\/\/ N+1: one query for orders, then one more per order for its items\n\nvar orders = dbContext.Orders.ToList();\n\nforeach (var order in orders)\n\n{\n\n    var items = order.Items; \/\/ triggers a separate query, lazily\n\n}\n\/\/ Fixed: one query, items are joined in up front\n\nvar ordersWithItems = dbContext.Orders\n\n    .Include(o =&gt; o.Items)\n\n    .ToList();\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain covariance and contravariance in C# generics, and where the analogy breaks down with arrays.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Generic variance<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Covariance and contravariance describe whether a generic interface can be substituted with a more specific or more general type argument. IEnumerable<T> is covariant, which means a sequence of a more specific type can be used wherever a sequence of a more general type is expected, because the interface only ever produces T, methods return it, nothing accepts it as a parameter, so treating a sequence of strings as a sequence of objects for reading purposes is completely safe.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\nIEnumerable&lt;string&gt; strings = new List&lt;string&gt; { \u201ca\u201d, \u201cb\u201d };\n\nIEnumerable&lt;object&gt; objects = strings; \/\/ covariant, fine\nAction&lt;object&gt; logAny = o =&gt; Console.WriteLine(o);\n\nAction&lt;string&gt; logString = logAny; \/\/ contravariant, fine\nobject[] items = new string[3];\n\nitems[0] = 5; \/\/ compiles, throws ArrayTypeMismatchException at runtime\n<\/code><\/pre><\/div><\/p>\n<p>Action<T> is contravariant, an action built for a more general type can stand in for one expecting a more specific type, because the delegate only ever consumes T, an action that knows how to handle any object can certainly handle a string. The compiler only allows this kind of substitution on positions where T is purely output or purely input, which is why a type like IList<T> can&#8217;t be variant at all, it both reads and writes T through its indexer, so neither direction is safe to relax.<\/p>\n<p>Arrays are the trap people bring up in interviews. Array covariance predates generics and has no such compiler safety net, which is why the code above compiles cleanly but throws at runtime the moment you actually write a mismatched value into it. The CLR has to catch at runtime something the type system let through unchecked at compile time, generic interface variance never allows that hole in the first place.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is thread pool starvation, and how do you actually diagnose it in production?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Thread pool starvation<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Thread pool starvation happens when every thread in the pool is busy or blocked, and new work items queue up waiting for a free one, response times climb steadily even though CPU usage looks completely normal, sometimes close to idle. The most common cause in ASP.NET Core is a blocking call inside async code, something waiting synchronously on a task inside a request handler, which ties up a thread pool thread doing nothing but waiting instead of returning it to the pool.<\/p>\n<p>The pool does grow under sustained demand, but slowly, by design, roughly one new thread every couple hundred milliseconds once demand exceeds the current size, so a sudden burst of blocking calls can starve the pool for several seconds before it catches up. That&#8217;s the window where the app looks like it&#8217;s hung even though nothing has actually crashed.<\/p>\n<p>Diagnosing it usually means grabbing a memory dump under load and comparing pending work item count against current thread count, or watching the runtime&#8217;s thread pool event counters, if the queue is deep and the thread count is maxed out or barely growing, that&#8217;s the signal. The fix is almost always removing the blocking call and awaiting properly all the way down, raising the minimum thread count can mask the symptom for a while but it doesn&#8217;t fix the root cause.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Server GC or Workstation GC, what&#039;s actually different and when does it matter?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">GC modes<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Workstation GC is tuned for low latency on a machine that&#8217;s mostly doing other things at the same time, a desktop app sharing the CPU with a UI thread, it uses a single heap and tends toward shorter, more frequent collections. Server GC is tuned for throughput on a dedicated multi-core machine, it creates one heap and one dedicated collector thread per logical core, and collections run in parallel across all of them, clearing far more garbage per pause at the cost of higher overall memory use, since each heap carries its own allocation budget.<\/p>\n<p>ASP.NET Core apps almost always want Server GC turned on, it&#8217;s actually the runtime default for web apps published from a recent SDK, controlled by a setting in the project file, because a web server has many cores and cares about total request throughput far more than any single collection&#8217;s pause time. Getting this backwards, running Workstation GC on a sixteen-core production box, leaves most of those cores sitting idle during every collection instead of using them to clear garbage in parallel.<\/p>\n<p>There&#8217;s a second dimension too, concurrent (background) GC, which lets most of a full generation-two collection run on a background thread while the app keeps allocating, versus non-concurrent mode which pauses everything. Server GC running in concurrent mode is the typical production default for an API, and it&#8217;s rare you&#8217;d want to turn concurrent mode off unless memory is genuinely tight, since background collection needs some extra headroom to work.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Walk through a real memory leak caused by event subscriptions, and how you&#039;d actually fix it.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Event handler leaks<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The classic leak: a long-lived object, a static service or a parent window, subscribes to an event on a shorter-lived object with += but never unsubscribes with -= when that shorter-lived object should be discarded. An event subscription is just the publisher holding a delegate reference to the subscriber&#8217;s method, and a delegate pointing at an instance method keeps that instance alive, so as long as the publisher&#8217;s invocation list still references it, garbage collection can never reclaim it, even though nothing else in your code appears to be holding onto it.<\/p>\n<p>This shows up constantly in WPF and WinForms apps, where a view model subscribes to a static or singleton service&#8217;s event and the window gets closed but never actually goes away, memory climbs slowly across the app&#8217;s lifetime as each closed window quietly accumulates. It&#8217;s just as real server-side, a singleton-scoped service subscribing to events on scoped or transient dependencies is a fast way to keep every request&#8217;s objects alive long after the request finished.<\/p>\n<p>The fix is either disciplined unsubscription, implementing IDisposable on the subscriber and unsubscribing inside Dispose, or the weak event pattern, where the publisher holds a weak reference to the subscriber instead of a strong delegate reference, so the subscriber can still be collected even if nobody ever called -=. WPF ships a weak event manager for exactly this reason, because forgetting to unsubscribe is such a common and hard-to-spot mistake.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What problem do Span&lt;T&gt; and Memory&lt;T&gt; actually solve, and why can&#039;t you use Span&lt;T&gt; everywhere?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Span and Memory<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Span<T> is a ref struct representing a contiguous, type-safe view over memory, whether that memory is a slice of an array, a stack-allocated block, or unmanaged memory, without copying it. Slicing an existing array gives you a window into that array&#8217;s own memory, no new array gets allocated, which is the entire performance case for it, parsing code that used to allocate a new substring or subarray for every slice can instead work directly against the source buffer.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\nReadOnlySpan&lt;char&gt; line = fullText.AsSpan();\n\nint comma = line.IndexOf(\u2018,\u2019);\n\nReadOnlySpan&lt;char&gt; firstField = line.Slice(0, comma);\n\n\/\/ no new string allocated for firstField\n<\/code><\/pre><\/div><\/p>\n<p>Because it&#8217;s a ref struct, Span<T> can only live on the stack, you can&#8217;t put one in a class field, box it, use it across an await inside an async method since the compiler can&#8217;t guarantee the underlying memory stays valid across a suspension point, or capture it in a lambda closure. Memory<T> exists specifically to cover those cases, a regular struct that can sit on the heap, get passed to async methods, and be stored as a field, you call into its span property when you actually need to do the fast synchronous work.<\/p>\n<p>The example that comes up constantly in interviews is parsing a large delimited line or a fixed-format packet without allocating a new string per field, slicing pieces off a span with IndexOf and Slice instead of calling something like Split on the whole string, which allocates a brand-new string object for every single field on every single row.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Native AOT vs JIT and ReadyToRun, what&#039;s actually different in .NET 8 and later?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Native AOT<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Normal .NET execution compiles IL to native machine code with the JIT, at runtime, method by method, the first time each one actually runs, which is why a freshly started app is slower for the first requests until the hot paths have warmed up, tiered compilation actually produces a quick, less-optimized version first, then recompiles hot methods with full optimizations in the background. ReadyToRun improves startup by pre-compiling most methods to native code at publish time and embedding that in the assembly, but it still ships the full runtime and can still fall back to the JIT for anything not covered, generic instantiations especially.<\/p>\n<p>Native AOT compiles the entire application, including the runtime itself, straight into a single native executable ahead of time, there&#8217;s no JIT present at runtime at all. Startup time and memory footprint drop substantially since there&#8217;s no warmup and no need to ship the full runtime alongside the app, which matters a lot for CLI tools, serverless functions cold-starting on every invocation, and containers billed per second of startup.<\/p>\n<p>The tradeoff is real. No runtime code generation means no dynamic reflection emit and no runtime assembly loading, and reflection-heavy code, a lot of traditional MVC model binding and older serializers, needs to be rewritten around source generators instead, since Native AOT can&#8217;t discover types and members dynamically the way reflection normally does. That&#8217;s why the Native AOT templates steer you toward minimal APIs paired with a source-generated JSON serializer rather than full MVC with runtime model binding.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does the lock statement actually compile down to, and why is locking on this a bad idea?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Lock statement internals<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The lock statement is syntactic sugar, the compiler expands it into entering a monitor before the block and exiting it in a finally block after, using an overload that takes a ref bool tracking whether the lock was actually acquired, specifically to guarantee the exit only runs if entry actually succeeded, avoiding a subtle bug in the older two-step expansion where an exception between entering and the try block could leave a lock held forever.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">csharp<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-csharp\">\n\nprivate readonly object _lock = new object();\npublic void Increment()\n\n{\n\n    lock (_lock)\n\n    {\n\n        _count++;\n\n    }\n\n}\n\n\/\/ expands roughly to:\n\n\/\/ bool lockTaken = false;\n\n\/\/ try {\n\n\/\/     Monitor.Enter(_lock, ref lockTaken);\n\n\/\/     _count++;\n\n\/\/ } finally {\n\n\/\/     if (lockTaken) Monitor.Exit(_lock);\n\n\/\/ }\n<\/code><\/pre><\/div><\/p>\n<p>The monitor associates a sync block with the locked object&#8217;s header in memory, whichever thread enters first gets the lock, every other thread calling enter just blocks until the owner exits. That&#8217;s exactly why the object you lock on matters: locking on this is dangerous because any external code holding a reference to your object can also lock on it, from completely unrelated code, creating a deadlock or serialization you never intended and can&#8217;t see from inside your own class. Locking on a type object or a public static field has the same problem at a wider scope, any other code with access to it can jam your lock.<\/p>\n<p>The standard fix is a dedicated, private, readonly object that only your class ever sees, as in the example above. Nothing outside the class can ever get a reference to it, so nothing outside the class can accidentally, or maliciously, contend for or hold your lock.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does lazy loading actually work in EF Core, and why does it bite people using DbContext across request boundaries?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Lazy loading proxies<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Lazy loading in EF Core works by having the context generate a runtime proxy subclass of your entity, where every navigation property getter is overridden to check, on first access, whether it&#8217;s already loaded, and if not, issue a query against the still-open context to fetch it right then. That&#8217;s convenient to write, reading a related property just works without an explicit include, but it means the actual number and timing of queries executing is invisible at the call site. Someone iterating a list of five hundred orders and touching a related customer property on each one triggers five hundred separate round trips without a single line of code looking suspicious.<\/p>\n<p>The sharper edge is context lifetime. Those proxies hold a reference back to the context that created them, and if an entity gets returned out of a scoped service after that context has already been disposed, easy to do if a controller returns an entity straight out of a repository whose using block already closed, touching an unloaded navigation property later throws a disposed-object exception at a spot in the code far away from where the context was actually disposed. That&#8217;s a genuinely confusing bug to track down the first time you hit it.<\/p>\n<p>Most teams either turn lazy loading off entirely and require explicit include calls, which also fixes the serialization trap where a JSON serializer walks navigation properties and quietly lazy-loads half the database into a response, or keep it but enforce that entities never leave the boundary of the request&#8217;s context, mapping to a separate response model before returning anything.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Are exception filters just an if statement inside catch, or is something actually different happening?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Exception filters<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>catch with a when clause attaches a boolean condition to a catch block, the block only actually handles the exception if that condition evaluates true, otherwise the exception keeps propagating up as if that catch clause wasn&#8217;t there at all. It looks like it could just be an if statement inside a plain catch block, checking the condition and rethrowing if it fails, but something meaningfully different happens under the hood.<\/p>\n<p>An exception filter runs before the stack unwinds. The runtime evaluates the when condition while the exception is still sitting at its original throw point, walking up looking for a matching handler, and only unwinds the stack once it finds a catch whose type and filter both match. That means if you log the stack trace or attach a debugger from inside the filter itself, you&#8217;re looking at the original crash site with all its state intact. The rethrow-based approach unwinds the stack into the catch block first, destructively, before your condition ever runs, so by the time you inspect anything you&#8217;ve already lost some of that original context.<\/p>\n<p>The other practical use is retry logic without catching prematurely, a filter checking whether the current attempt count is below a maximum lets you leave the exception completely alone once retries are exhausted, so it surfaces normally to whatever outer handler or logging middleware expects to see it, instead of getting caught, inspected, and manually rethrown, which is exactly the kind of code that accidentally mangles a stack trace if someone rethrows the caught variable instead of using a bare throw.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-callout iq-callout--insight not-prose\"><div class=\"iq-callout__title\">What we notice in .NET mock interview sessions on LastRoundAI<\/div><div class=\"iq-callout__body\"><\/p>\n<p>Across the .NET-track mock interviews run through LastRoundAI, the recurring stumble isn&#8217;t syntax. It&#8217;s async. Candidates define async\/await correctly on the first pass, then lose the thread the moment a follow-up asks them to trace what the compiler-generated state machine actually does, or why a specific <code>.Result<\/code> call would deadlock in one hosting model and not another. Confident definition, shaky mechanism, that gap shows up in the transcripts more than any other single pattern.<\/p>\n<p>If you&#8217;re prepping for a 2026 loop, put disproportionate time into GC generations, the DI captive-dependency scenario, and reproducing an async deadlock yourself in a throwaway console app. Reading an explanation isn&#8217;t the same as saying it out loud correctly while someone watches over a video call, so practice each answer on this page in under 90 seconds before a real loop. That&#8217;s usually the actual test.<\/p>\n<p><\/div><\/div>\n<p><script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\n      \"@type\": \"Question\",\n      \"name\": \"What are the most common .NET interview questions for a fresher role?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Expect OOP basics (encapsulation, inheritance, polymorphism), value vs. reference types, a couple of collection questions like List vs. Dictionary, basic exception handling, and simple LINQ queries. Deep CLR internals and system design mostly show up at mid-level and above.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Do 2026 .NET interviews still test the old .NET Framework, or is it all .NET 8 and later now?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Almost entirely modern .NET now, .NET 6 through .NET 9 and beyond, especially for greenfield roles. Legacy .NET Framework knowledge still matters for teams maintaining older enterprise systems, so it's worth asking the recruiter which stack the team runs.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Is knowing C# enough, or do I need hands-on ASP.NET Core and EF Core experience too?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"C# alone covers the language-fundamentals portion, not the whole loop. Most roles expect working familiarity with ASP.NET Core (controllers or minimal APIs, middleware, DI) and EF Core (migrations, LINQ, the N+1 problem).\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"How many rounds does a typical mid-level .NET developer loop have?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Usually 3 to 4 rounds: a recruiter screen, one or two technical rounds, and a final hiring manager round. Smaller companies sometimes compress this to 2 rounds; larger enterprises sometimes stretch it to 5.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Should I prepare LINQ and raw SQL both, or does EF Core mean I don't need SQL knowledge anymore?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Prepare both. EF Core generates SQL for you, but debugging a slow query or explaining a bad JOIN requires actual SQL knowledge underneath, which interviewers test beyond junior level.\"\n      }\n    }\n  ]\n}\n<\/script><\/p>\n<div class=\"iq-related not-prose\"><div class=\"iq-related__title\">Related interview guides<\/div><div class=\"iq-related__grid\"><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/microsoft-sde\"><span class=\"iq-rel__t\">Microsoft SDE-2 Interview Questions (2026): What They Actually Ask<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/java-developer\"><span class=\"iq-rel__t\">Java Developer Interview Questions (2026): Core Java to Spring Boot<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/backend-developer\"><span class=\"iq-rel__t\">Backend Developer Interview Questions (2026): APIs, Databases &#038; System Design<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/sql\"><span class=\"iq-rel__t\">SQL Interview Questions (2026): From Joins to Window Functions<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><a class=\"iq-rel__card\" href=\"https:\/\/lastroundai.com\/interview-questions\/system-design\"><span class=\"iq-rel__t\">System Design Interview Questions (2026): Must-Know Q&#038;A<\/span><span class=\"iq-rel__arrow\" aria-hidden=\"true\">&rarr;<\/span><\/a><\/div><\/div>\n<div class=\"iq-sources not-prose\"><div class=\"iq-sources__title\">Sources &amp; further reading<\/div><ul class=\"iq-sources__list\"><li><a href=\"https:\/\/survey.stackoverflow.co\/2025\/technology\" target=\"_blank\" rel=\"nofollow noopener\">Stack Overflow Developer Survey 2025<\/a><\/li><li><a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/standard\/garbage-collection\/fundamentals\" target=\"_blank\" rel=\"nofollow noopener\">Microsoft Learn: GC Fundamentals<\/a><\/li><li><a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/standard\/garbage-collection\/large-object-heap\" target=\"_blank\" rel=\"nofollow noopener\">Microsoft Learn: Large Object Heap<\/a><\/li><li><a href=\"https:\/\/codewithmukesh.com\/blog\/dotnet-interview-questions\/\" target=\"_blank\" rel=\"nofollow noopener\">codewithmukesh: .NET Interview Questions<\/a><\/li><\/ul><\/div>\n","protected":false},"excerpt":{"rendered":"<p>A .NET developer interviewing for a mid-level role at a Chicago insurance company last winter got a live-coding prompt most candidates don&#8217;t expect anymore. Fix a deadlocking ASP.NET Core endpoint, on a shared screen, with the interviewer watching every keystroke. She&#8217;d read a dozen blog posts on async in C#. She&#8217;d never actually reproduced a&#8230;<\/p>\n","protected":false},"author":6,"featured_media":1636,"comment_status":"open","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"tags":[],"class_list":["post-1154","iq","type-iq","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>.NET Developer Interview Questions (2026): Must-Know C# &amp; ASP.NET Q&amp;A | LastRoundAI<\/title>\n<meta name=\"description\" content=\"Practice real .NET developer interview questions on C#, async\/await, EF Core, and ASP.NET Core, with answers built to show what interviewers probe for.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\".NET Developer Interview Questions (2026): Must-Know C# &amp; ASP.NET Q&amp;A | LastRoundAI\" \/>\n<meta property=\"og:description\" content=\"Practice real .NET developer interview questions on C#, async\/await, EF Core, and ASP.NET Core, with answers built to show what interviewers probe for.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer\" \/>\n<meta property=\"og:site_name\" content=\"LastRound AI\" \/>\n<meta property=\"og:image\" content=\"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-dotnet-developer-og.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"49 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/dotnet-developer\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/dotnet-developer\",\"name\":\".NET Developer Interview Questions (2026): Must-Know C# & ASP.NET Q&A | LastRoundAI\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/dotnet-developer#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/dotnet-developer#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-dotnet-developer-og.png\",\"datePublished\":\"2026-07-23T16:00:00+00:00\",\"description\":\"Practice real .NET developer interview questions on C#, async\\\/await, EF Core, and ASP.NET Core, with answers built to show what interviewers probe for.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/dotnet-developer#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/dotnet-developer\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/dotnet-developer#primaryimage\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-dotnet-developer-og.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-dotnet-developer-og.png\",\"width\":1200,\"height\":630,\"caption\":\".NET Developer interview questions \u2014 LastRoundAI\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/dotnet-developer#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Interview Questions\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\".NET Developer Interview Questions (2026): Must-Know C# &#038; ASP.NET Q&#038;A\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"name\":\"LastRound AI\",\"description\":\"Interview Assistant prep, tech careers and AI tools\",\"publisher\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\",\"name\":\"LastRound AI\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"width\":400,\"height\":400,\"caption\":\"LastRound AI\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":".NET Developer Interview Questions (2026): Must-Know C# & ASP.NET Q&A | LastRoundAI","description":"Practice real .NET developer interview questions on C#, async\/await, EF Core, and ASP.NET Core, with answers built to show what interviewers probe for.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer","og_locale":"en_US","og_type":"article","og_title":".NET Developer Interview Questions (2026): Must-Know C# & ASP.NET Q&A | LastRoundAI","og_description":"Practice real .NET developer interview questions on C#, async\/await, EF Core, and ASP.NET Core, with answers built to show what interviewers probe for.","og_url":"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer","og_site_name":"LastRound AI","og_image":[{"width":1200,"height":630,"url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-dotnet-developer-og.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"49 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer","url":"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer","name":".NET Developer Interview Questions (2026): Must-Know C# & ASP.NET Q&A | LastRoundAI","isPartOf":{"@id":"https:\/\/lastroundai.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer#primaryimage"},"image":{"@id":"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer#primaryimage"},"thumbnailUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-dotnet-developer-og.png","datePublished":"2026-07-23T16:00:00+00:00","description":"Practice real .NET developer interview questions on C#, async\/await, EF Core, and ASP.NET Core, with answers built to show what interviewers probe for.","breadcrumb":{"@id":"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/lastroundai.com\/interview-questions\/dotnet-developer"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer#primaryimage","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-dotnet-developer-og.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-dotnet-developer-og.png","width":1200,"height":630,"caption":".NET Developer interview questions \u2014 LastRoundAI"},{"@type":"BreadcrumbList","@id":"https:\/\/lastroundai.com\/interview-questions\/dotnet-developer#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/lastroundai.com\/blog"},{"@type":"ListItem","position":2,"name":"Interview Questions","item":"https:\/\/lastroundai.com\/interview-questions"},{"@type":"ListItem","position":3,"name":".NET Developer Interview Questions (2026): Must-Know C# &#038; ASP.NET Q&#038;A"}]},{"@type":"WebSite","@id":"https:\/\/lastroundai.com\/blog\/#website","url":"https:\/\/lastroundai.com\/blog\/","name":"LastRound AI","description":"Interview Assistant prep, tech careers and AI tools","publisher":{"@id":"https:\/\/lastroundai.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/lastroundai.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/lastroundai.com\/blog\/#organization","name":"LastRound AI","url":"https:\/\/lastroundai.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","width":400,"height":400,"caption":"LastRound AI"},"image":{"@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1154","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq"}],"about":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/types\/iq"}],"author":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/comments?post=1154"}],"version-history":[{"count":3,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1154\/revisions"}],"predecessor-version":[{"id":1747,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1154\/revisions\/1747"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media\/1636"}],"wp:attachment":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media?parent=1154"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/tags?post=1154"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}