.NET Full Stack Interview Questions for 7 Years Experience
40 questions with answers for candidates with 7 years of experience — 21 medium, 19 hard.
What interviewers expect: 7 years experience
Seven-year candidates are evaluated as technical leads. Interviews include system design for real business scenarios, security practices, multi-tenant data design, observability with logs, metrics and tracing, cloud cost and reliability trade-offs, and leading migrations from .NET Framework to modern .NET. You must show how you guided teams, managed technical debt and communicated with stakeholders.
This set is weighted towards Entity Framework Core (10), C# Fundamentals (7), ASP.NET Core Web API (6), SQL Server and T-SQL (6).
Architecture trade-offs Security and observability Legacy migration Team leadership
Solved at this levelStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
Constraints restrict which types can be used for a type parameter so the generic code can rely on certain members. Examples: where T : class (reference type), where T : struct, where T : new() (has a parameterless constructor), where T : BaseEntity (inherits a class), where T : IComparable<T>, and where T : notnull. A repository might use where T : class, IEntity so it can read the Id property safely.
02
Users trigger a large Excel report that takes 3 minutes, and the HTTP request times out. How would you redesign it?
Make it asynchronous at the workflow level. The API validates the request, saves a job record and returns 202 Accepted with a job ID. A background worker, using a queue such as a Channel, Hangfire or a message broker, generates the file and stores it in blob storage. The UI polls a status endpoint or receives a SignalR notification, then downloads the file. This avoids timeouts and survives retries.
03
You must change an API response format, but an older mobile app version still calls it. How do you avoid breaking those users?
Versioning lets you change contracts without breaking existing clients such as mobile apps that users have not updated. Common approaches are URL segments (/api/v1/orders), query strings (?api-version=2.0) and headers. The Asp.Versioning library supports all of them and integrates with OpenAPI docs. URL versioning is the most visible and cache-friendly. Keep old versions running for a published period, then deprecate them with headers.
04
Show bulk update and delete with ExecuteUpdate and ExecuteDelete.
// mark all unpaid invoices older than 30 days as overdue
int updated = await db.Invoices
.Where(i => !i.IsPaid && i.DueDate < DateTime.UtcNow.AddDays(-30))
.ExecuteUpdateAsync(s => s
.SetProperty(i => i.Status, "Overdue")
.SetProperty(i => i.UpdatedAt, DateTime.UtcNow));
// delete old logs
int deleted = await db.AuditLogs
.Where(l => l.CreatedAt < cutoff)
.ExecuteDeleteAsync();
These run a single SQL UPDATE or DELETE without loading entities, available since EF Core 7. They bypass the change tracker.
05
Your team debates wrapping EF Core in a generic repository. What is your view?
DbContext already implements Repository and Unit of Work, so a thin generic repository with GetAll and Add often adds code while hiding useful features like Include, projections and AsNoTracking. I prefer either using DbContext directly in services, or specific repositories with meaningful methods like GetOverdueInvoicesAsync that encapsulate queries. For tests, use the SQLite in-memory provider or Testcontainers rather than mocking DbSet.
ref passes a variable by reference; it must be initialised before the call and the method can read and change it. out also passes by reference but need not be initialised, and the method must assign it before returning, as in int.TryParse. in passes by reference as read-only, which avoids copying large structs while preventing changes. Overusing ref and out makes code harder to read, so tuples are often cleaner.
07
Add rate limiting to a login endpoint in ASP.NET Core.
public async IAsyncEnumerable<Order> GetOrdersAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
await foreach (var o in _db.Orders.AsNoTracking().AsAsyncEnumerable().WithCancellation(ct))
yield return o;
}
// consumer
await foreach (var order in service.GetOrdersAsync())
Console.WriteLine(order.Id);
Items are produced and consumed one at a time asynchronously. ASP.NET Core can stream an IAsyncEnumerable result as JSON without buffering the whole list.
09
A complex reporting query written in EF Core is slow and hard to tune. When would you move it to Dapper?
Dapper is a micro-ORM that maps raw SQL results to objects with very low overhead. It suits read-heavy reports, complex hand-tuned queries and stored procedures where you want full SQL control. EF Core offers change tracking, migrations, LINQ and relationships, which speed up CRUD development. Many projects use both: EF Core for writes and normal features, Dapper for performance-critical queries, sharing the same connection string.
An async void method cannot be awaited, so the caller does not know when it finishes. Exceptions thrown inside it cannot be caught by the caller; they go to the synchronization context and can crash the process. It is also hard to unit test. Use async Task instead. The only accepted use of async void is for event handlers, such as button click handlers in desktop apps, where the signature is fixed.
11
A developer wraps every database call in Task.Run inside ASP.NET Core controllers to make them async. Is this a good idea?
No. Task.Run moves work to another thread pool thread, but the request still waits for it, so you use two threads instead of one and gain nothing. The database call itself is still blocking. The correct approach is to use true async APIs like ToListAsync and SaveChangesAsync, which release the thread during I/O. Task.Run makes sense mainly in desktop apps to keep the UI thread free.
12
What are record types and when would you use them?
Records are reference types (or record struct for value types) designed for immutable data. The compiler generates value-based equality, a readable ToString, a deconstructor and support for with-expressions that create a modified copy. A positional record is one line: public record Order(int Id, decimal Total). They suit DTOs, API responses, events and value objects, but not EF Core entities that need change tracking by identity.
13
Implement global exception handling that returns ProblemDetails.
public class Customer { public int Id { get; set; } public List<Order> Orders { get; set; } = []; }
public class Order { public int Id { get; set; } public int CustomerId { get; set; } public Customer Customer { get; set; } = null!; }
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<Order>()
.HasOne(o => o.Customer)
.WithMany(c => c.Orders)
.HasForeignKey(o => o.CustomerId)
.OnDelete(DeleteBehavior.Restrict);
mb.Entity<Order>().Property(o => o.Total).HasPrecision(18, 2);
}
Restrict prevents deleting a customer who still has orders.
15
What techniques improve EF Core query performance?
Project only needed columns with Select instead of loading full entities. Use AsNoTracking for reads. Avoid N+1 with Include or projections. Page with Skip and Take or keyset paging. Add proper indexes via HasIndex. Use ExecuteUpdate and ExecuteDelete for bulk changes. For hot queries, compiled queries with EF.CompileAsyncQuery and DbContext pooling with AddDbContextPool reduce overhead. Always check generated SQL with logging or ToQueryString.
16
A singleton CacheService injects AppDbContext, and users start seeing stale data and occasional 'a second operation was started on this context' errors. What is wrong?
This is a captive dependency: a scoped DbContext is captured by a singleton, so one context lives forever and is shared by all requests across threads. DbContext is not thread-safe and its change tracker grows stale. Fix it by injecting IServiceScopeFactory or IDbContextFactory<AppDbContext> into the singleton and creating a short-lived context per operation. Enabling ValidateScopes in development catches this at startup.
17
What are ACID properties and the transaction isolation levels in SQL Server?
ACID means Atomicity (all or nothing), Consistency (rules hold), Isolation (transactions do not interfere) and Durability (committed data survives crashes). Isolation levels are READ UNCOMMITTED (dirty reads allowed), READ COMMITTED (the default), REPEATABLE READ, SERIALIZABLE (strictest) and SNAPSHOT, which uses row versions so readers do not block writers. Read Committed Snapshot Isolation is often enabled to reduce blocking in busy OLTP apps.
18
What are Span<T> and Memory<T> and why do they matter for performance?
Span<T> is a ref struct that represents a contiguous slice of memory, such as part of an array, a string or stack memory, without copying it. Slicing a span is free, which makes parsing much faster with zero allocations. Because it is stack-only, it cannot be stored in fields or used across await. Memory<T> is the heap-storable version for async code. ASP.NET Core and System.Text.Json use them heavily.
19
Write a switch expression using pattern matching to calculate shipping cost.
First check indexes and query plans, since good indexing often solves it. Then archive older data: move rows older than a set period to an archive table or database in small batches during off-peak hours, keeping reports on history available separately. For ongoing growth, use table partitioning by month on OrderDate, which enables fast partition switching and lets queries scan only relevant partitions. Agree retention rules with the business first.
21
Users report intermittent errors saying their transaction was chosen as a deadlock victim. How do you investigate and fix it?
A deadlock happens when two sessions each hold a lock the other needs. Get the deadlock graph from the system_health Extended Events session to see the statements and objects involved. Common fixes: access tables in the same order in all code paths, keep transactions short, add indexes so queries lock fewer rows, and consider RCSI. Also add retry logic in the app, since EF Core's SQL Server execution strategy can retry deadlocks.
22
Give an example of a Liskov Substitution Principle violation and how to fix it.
A classic case is Square inheriting Rectangle. Setting Width on a square also changes Height, so code that sets width 5 and height 4 and expects area 20 breaks. Another real case is a ReadOnlyRepository that inherits Repository and throws NotSupportedException on Save. The fix is to model shared behaviour with interfaces such as IShape or IReadRepository and IWriteRepository, so subclasses never weaken the contract.
23
Show a class using a primary constructor with dependency injection.
public class OrderService(IOrderRepository repo, ILogger<OrderService> logger)
{
public async Task<Order?> GetAsync(int id)
{
logger.LogInformation("Fetching order {Id}", id);
return await repo.FindAsync(id);
}
}
Primary constructors on classes arrived in C# 12. Parameters are captured and usable in all members, removing boilerplate fields. They are not readonly, so assign to a readonly field if immutability matters.
A SynchronizationContext represents where continuations should run. WPF and WinForms have one that marshals work back to the UI thread, so after await you can update controls safely. Classic ASP.NET had one tied to the request. ASP.NET Core and console apps have none, so continuations run on any thread pool thread. Understanding it explains why .Result deadlocks in UI apps but often not in ASP.NET Core.
25
A .NET API's memory keeps growing in production until the pod restarts. How do you investigate?
First confirm with metrics using dotnet-counters: watch GC heap size, Gen 2 count and LOH size. Then capture a heap dump with dotnet-gcdump or dotnet-dump and compare two dumps over time to find which object types keep growing and what roots hold them. Common causes are static caches without expiry, event handlers never unsubscribed, undisposed HttpClient or DbContext, and singletons capturing scoped services. Fix the root, then load test.
26
Explain covariance and contravariance in C# generics.
Covariance, marked out T, lets you use a more derived type where a base type is expected: IEnumerable<string> can be assigned to IEnumerable<object> because the interface only returns T. Contravariance, marked in T, allows the reverse for inputs: an Action<object> can be used as Action<string>. They apply only to interfaces and delegates, and only with reference types. List<T> is invariant because it both reads and writes T.
27
Your product API must handle a festive sale with 10 times normal traffic. How do you prepare it?
Make the API stateless so you can scale it horizontally behind a load balancer or Kubernetes autoscaler. Cache read-heavy data such as product details in Redis or HybridCache. Ensure all I/O is async and database queries are indexed, projected and paged. Move slow work like emails to queues. Add rate limiting, timeouts and circuit breakers to dependencies, then load test with k6 or JMeter and watch metrics.
28
What is the difference between const, readonly and static readonly?
const is a compile-time constant, implicitly static, and its value is copied into calling assemblies, so changing it requires recompiling them. readonly fields are set at declaration or in the constructor and can differ per instance. static readonly is set once per type at runtime, so it can hold values like DateTime.Now or a new object. Use const only for values that will never change, such as Math.PI.
Authorization filters run first and can block a request. Resource filters run before model binding, useful for caching. Action filters run before and after an action, for logging or validation. Exception filters handle errors thrown by actions. Result filters run around the result execution, for example to add headers. Filters apply globally, per controller or per action. Unlike middleware, they can access MVC details like action arguments.
30
A read-only product listing endpoint in EF Core uses a lot of memory and CPU. What simple change helps, and why?
By default, EF Core tracks every entity it loads so it can detect changes on SaveChanges, which costs memory and CPU. AsNoTracking tells EF Core not to track the results, making read-only queries faster and lighter. Use it for lists, reports and GET endpoints where you will not update the data. AsNoTrackingWithIdentityResolution avoids duplicate instances of the same entity in results while still skipping tracking.
31
A stored procedure is fast for most customers but very slow for one large customer, and sometimes slow for everyone after a restart. What could cause this?
This is parameter sniffing. SQL Server compiles a plan using the first parameter values it sees and reuses it. A plan built for a small customer, using index seeks, can be terrible for a huge one, and vice versa. Fixes include OPTION (RECOMPILE) for that statement, OPTIMIZE FOR, splitting logic for skewed values, or Query Store plan forcing. SQL Server 2022 adds Parameter Sensitive Plan optimisation to keep multiple plans.
32
Tell me about a time you made a mistake that affected data, and what you did.
Early in my job (Situation) I ran an UPDATE on a test copy that I believed was staging, but it was a shared QA database, and 2,000 test orders lost their status. My task was to fix it fast. I told my lead immediately, restored the rows from the nightly backup with the DBA, and verified counts. Result: QA lost only an hour. Since then I always wrap manual updates in a transaction and run a SELECT with the same WHERE first.
33
Tell me about a time you improved the performance of slow code.
In my previous role (Situation) a daily reconciliation job took 40 minutes and sometimes missed its window. My task was to bring it under 10 minutes. I profiled it and found nested loops over two large lists and one database call per row. I replaced the inner loop with a Dictionary lookup and batched the database reads. Result: the job ran in about 4 minutes, and I documented the approach for the team.
34
Explain a technical trade-off you had to justify to a manager or client.
On a reporting module (Situation) the manager wanted everything in EF Core for consistency, but one report was too slow. My task was to recommend a fix without derailing the plan. I measured the EF query against a Dapper version with a hand-tuned SQL query, showed the 8x difference and explained that we would use Dapper only for that read path. Result: the manager approved, the report loaded in under a second, and the rest stayed in EF Core.
35
Explain eager, lazy and explicit loading in EF Core.
Eager loading fetches related data in the same query using Include and ThenInclude, for example orders with their items. Lazy loading loads a navigation property automatically the first time you access it, requiring proxies and virtual properties; it is convenient but easily causes N+1 queries. Explicit loading loads a navigation on demand with Entry(order).Collection(o => o.Items).LoadAsync(). Projection with Select is often better than all three.
36
Your security team objects to storing the JWT in localStorage in your Angular app. What are the options?
localStorage is readable by any JavaScript on the page, so an XSS bug can steal the token. A safer pattern is to keep the short-lived access token in memory and store the refresh token in an HttpOnly, Secure, SameSite cookie that scripts cannot read. Even better is the backend-for-frontend pattern, where ASP.NET Core holds tokens server-side and the browser uses a session cookie. Add CSRF protection when using cookies.
A covering index contains every column a query needs, so SQL Server answers it from the index alone without key lookups to the clustered index. INCLUDE adds non-key columns to the leaf level only, keeping the index key small. For SELECT Name, Total FROM Orders WHERE CustomerId = 5, an index on CustomerId INCLUDE (Name, Total) covers it. Do not include too many columns, since every index slows writes.
39
An EF Core query that includes Items and Payments for each order returns a huge, duplicated result set. What is happening and how do split queries help?
When you Include two or more collection navigations, such as Orders with Items and Payments, EF Core generates one query with joins. Rows multiply: 10 items times 5 payments return 50 rows per order, duplicating data heavily. AsSplitQuery() tells EF Core to run separate queries per collection and stitch results together, avoiding duplication. The trade-off is more round trips and possible inconsistency without a transaction.
40
Tell me about a time you refactored messy code that others depended on.
In my last project (Situation) an invoice module had one 900-line class with duplicated GST logic. My task was to add a new tax rule without breaking billing. I first wrote unit tests around current behaviour, then extracted tax rules into strategy classes and moved data access into a repository, merging in small pull requests. Result: the new rule took one day, and bugs in that module dropped noticeably the next quarter.
Frequently asked questions
What is asked in .NET Full Stack interviews for 7 years of experience?
Seven-year candidates are evaluated as technical leads. Interviews include system design for real business scenarios, security practices, multi-tenant data design, observability with logs, metrics and tracing, cloud cost and reliability trade-offs, and leading migrations from .NET Framework to modern .NET. You must show how you guided teams, managed technical debt and communicated with stakeholders.
How many questions should I practise before a .NET Full Stack interview?
Work through all 40 questions here, then the company-wise sets for the employers you are targeting. Quality matters more than count: be able to explain why each answer is right.
Which topics matter most for 7 years of experience?
Focus first on Architecture trade-offs, Security and observability, Legacy migration, Team leadership.