Skip to content

.NET Full Stack Interview Questions for 10 Years Experience

40 questions with answers for candidates with 10+ years of experience — 15 medium, 25 hard.

What interviewers expect: 10 years experience

At ten years, roles are usually architect, principal engineer or engineering manager. Interviews explore enterprise architecture, distributed systems, Azure platform choices, governance, standards across teams, build-versus-buy decisions and delivery risk. Expect deep discussion of past systems you shaped, how you measured success, handled failures and grew engineers, along with high-level coding and design reviews.

This set is weighted towards Async Programming and Multithreading (7), Entity Framework Core (7), ASP.NET Core Web API (6), Angular and React Front End (5).

Enterprise architecture Technology strategy Cross-team governance People 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.
3 questions free Unlock the rest in seconds
01

Where should you store secrets like connection strings and API keys in an ASP.NET Core app?

Save to ▾
Show answer
Never commit them in appsettings.json. During development use the Secret Manager (dotnet user-secrets), which stores them outside the project folder. In production use environment variables or, better, a vault such as Azure Key Vault or AWS Secrets Manager, loaded through a configuration provider and accessed with managed identity so no credential sits in code. Rotate secrets regularly and restrict who can read them.
02

What are services and dependency injection in Angular?

Save to ▾
Show answer
A service is a class, marked @Injectable, that holds logic or data shared across components, such as API calls, authentication or a cart. Angular's DI creates the service and provides it to components that request it in the constructor or with the inject() function. With providedIn: 'root', one shared instance exists for the whole app. This keeps components focused on UI and makes logic easy to test.
03

Explain the SOLID principles briefly.

OOP in C#Medium
Save to ▾
Show answer
Single Responsibility: a class has one reason to change. Open/Closed: extend behaviour without editing existing code. Liskov Substitution: derived types must work wherever the base type is expected. Interface Segregation: prefer small, focused interfaces over one fat interface. Dependency Inversion: depend on abstractions, not concrete classes. In ASP.NET Core, injecting IEmailSender into a controller instead of creating SmtpClient directly applies dependency inversion.
04

Explain covariance and contravariance in C# generics.

Save to ▾
Show answer
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.
05

An API endpoint calls _db.Orders.ToList().Where(o => o.CustomerId == id) and is slow as the table grows. Explain and fix it.

Save to ▾
Show answer
ToList executes immediately, so the entire Orders table is loaded into memory and then filtered in C#. As data grows, memory and response time grow with it. Move the filter before materialisation: await _db.Orders.Where(o => o.CustomerId == id).AsNoTracking().ToListAsync(). Now EF Core sends a WHERE clause, the database uses an index on CustomerId, and only matching rows travel over the network.
06

Your product API runs on four instances behind a load balancer and needs caching. Which ASP.NET Core caching options would you consider?

Save to ▾
Show answer
IMemoryCache stores data in the server's memory, fast but not shared across instances. IDistributedCache uses Redis or SQL Server so all instances share the cache. HybridCache, added in .NET 9, combines an in-memory layer with a distributed one and prevents cache stampedes. Output caching stores whole HTTP responses on the server, while response caching sets HTTP headers for browsers and proxies. Always set expiry and invalidate when data changes.
07

A .NET API's memory keeps growing in production until the pod restarts. How do you investigate?

Save to ▾
Show answer
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.
08

What is a SynchronizationContext?

Save to ▾
Show answer
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.
09

Your multi-tenant SaaS app keeps all tenants in one database. How do you make sure EF Core queries never return another tenant's data?

Save to ▾
Show answer
Add a TenantId column to every tenant-owned entity and a global query filter such as HasQueryFilter(e => e.TenantId == _tenantId), where the tenant ID comes from a scoped provider that reads the authenticated user's claim. Set TenantId automatically in SaveChanges for new rows. Index TenantId, write integration tests that try cross-tenant access, audit every IgnoreQueryFilters call, and consider SQL Server row-level security as a second layer of defence.
10

A developer runs two EF Core queries in parallel with Task.WhenAll on the same DbContext and gets an InvalidOperationException. Why?

Save to ▾
Show answer
DbContext is not thread-safe and supports only one active operation at a time, so starting a second query before the first finishes throws 'A second operation was started on this context instance'. Either await the queries one after another, or create a separate context for each parallel query using IDbContextFactory. Usually sequential queries are fine; parallelism only helps when the queries are slow and independent.
11

A large Angular dashboard becomes sluggish because change detection runs constantly. How do OnPush and zoneless mode help?

Save to ▾
Show answer
Traditionally Zone.js patched browser events and timers, and after each one Angular checked every component for changes, top to bottom. OnPush makes a component re-check only when its inputs change by reference, an event occurs inside it, an async pipe emits or a signal it reads changes, which saves work in big screens. Zoneless mode removes Zone.js entirely and relies on signals and explicit triggers, reducing bundle size and improving performance.
12

What are Span<T> and Memory<T> and why do they matter for performance?

Save to ▾
Show answer
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.
13

What techniques improve EF Core query performance?

Save to ▾
Show answer
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.
14

What are System.Threading.Channels and when would you use them?

Save to ▾
Show answer
Channels provide fast, thread-safe, async producer-consumer queues. A producer writes with await writer.WriteAsync and a consumer reads with await foreach over reader.ReadAllAsync. Bounded channels apply back-pressure by making producers wait when full. They are useful inside one process, for example an API that accepts uploads quickly and hands them to a background worker. For durable or cross-service queues use a broker like RabbitMQ or Azure Service Bus.
15

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?

Save to ▾
Show answer
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.
16

How are exceptions handled in async code, and what is AggregateException?

Save to ▾
Show answer
When you await a faulted task, the original exception is rethrown, so normal try-catch works. If you block with .Wait() or .Result, the exception is wrapped in AggregateException. With Task.WhenAll, await throws only the first exception, but the returned task's Exception property holds all of them in an AggregateException. Unobserved exceptions from fire-and-forget tasks are silently lost, so always await or log them.
17

How do you explain good object-oriented design to a junior developer on your team?

OOP in C#Medium
Save to ▾
Show answer
I avoid theory first. I take a real piece of our code, like a controller that talks to the database directly, and pair with them to move logic into a service behind an interface, then write a unit test with a mock. Seeing how much easier testing becomes explains SOLID better than slides. I then review their next pull request with specific comments and point them to one or two good examples in our codebase.
18

How do you manage application state in Angular and React apps?

Save to ▾
Show answer
Start simple: local component state, then share through a parent or a service. In Angular, a service holding signals covers most needs, and NgRx or NgRx SignalStore suits large apps with complex flows. In React, Context works for low-frequency data like theme or user, Redux Toolkit or Zustand for complex client state, and TanStack Query for server data with caching. Keep server data and UI state separate.
19

Explain eager, lazy and explicit loading in EF Core.

Save to ▾
Show answer
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.
20

Demonstrate a race condition on a counter and fix it.

Save to ▾
Show answer
int count = 0; Parallel.For(0, 100_000, _ => count++); Console.WriteLine(count); // often less than 100000 int safe = 0; Parallel.For(0, 100_000, _ => Interlocked.Increment(ref safe)); Console.WriteLine(safe); // always 100000 count++ is a read, add and write, so two threads can read the same value and lose an update. Interlocked performs the increment atomically without a full lock.
21

What is the difference between controllers and Minimal APIs?

Save to ▾
Show answer
Controllers group actions into classes with attributes for routing, filters and model binding, which suits large APIs with many conventions. Minimal APIs map endpoints directly with app.MapGet and lambdas, with less ceremony and slightly better performance, ideal for microservices and small APIs. Since .NET 7 they support filters, route groups and OpenAPI, and .NET 10 adds built-in validation, so both are production-ready choices.
22

Your product API must handle a festive sale with 10 times normal traffic. How do you prepare it?

Save to ▾
Show answer
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.
23

Access tokens in your app expire in 15 minutes and users complain about being logged out. How do you implement refresh tokens securely?

Save to ▾
Show answer
Issue a short-lived JWT access token plus a long-lived, random refresh token stored hashed in the database with expiry and device info. When the access token expires, the client calls /refresh with the refresh token; the server validates it, revokes it and issues a new pair, which is rotation. Reuse of a revoked token signals theft, so revoke the whole chain. In browsers, store the refresh token in an HttpOnly, Secure, SameSite cookie.
24

A page listing 100 orders with customer names runs 101 SQL queries. What is the problem and how do you fix it?

Save to ▾
Show answer
This is the N+1 problem: one query loads the orders, then lazy loading fires a separate query for each order's Customer. Fix it by loading everything in one query, either with Include(o => o.Customer) or better with a projection: Select(o => new OrderListDto(o.Id, o.Total, o.Customer.Name)), which fetches only needed columns via a join. Turn on EF Core logging to spot such patterns early.
25

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?

Save to ▾
Show answer
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.
26

What is the difference between JIT compilation and Native AOT in .NET?

Save to ▾
Show answer
With JIT, the app ships as IL and the runtime compiles methods to machine code when first called; tiered compilation later recompiles hot methods with better optimisation. Native AOT compiles everything ahead of time into a single native executable, giving very fast startup and lower memory, which suits containers and serverless. The trade-off is limited reflection and dynamic code generation, so some libraries need source generators to work.
27

What are ACID properties and the transaction isolation levels in SQL Server?

Save to ▾
Show answer
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.
28

Give an example of a Liskov Substitution Principle violation and how to fix it.

Save to ▾
Show answer
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.
29

A complex reporting query written in EF Core is slow and hard to tune. When would you move it to Dapper?

Save to ▾
Show answer
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.
30

An admin page renders 20,000 rows in a table and the browser freezes. How do you fix it?

Save to ▾
Show answer
Do not send or render everything at once. Add server-side paging, sorting and filtering in the ASP.NET Core API so the page loads perhaps 50 rows at a time. If users truly need to scroll a long list, use virtual scrolling, such as Angular CDK virtual scroll or TanStack Virtual in React, so only visible rows exist in the DOM. Also use trackBy or keys, and debounce search inputs.
31

Your security team objects to storing the JWT in localStorage in your Angular app. What are the options?

Save to ▾
Show answer
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.
32

Profiling shows a hot cache-lookup method returning Task<T> allocates millions of Task objects. Would ValueTask help?

Save to ▾
Show answer
ValueTask<T> is a struct that avoids allocating a Task when a method usually completes synchronously, for example when a value is served from an in-memory cache most of the time. In very hot paths this reduces GC pressure. The rules are stricter: await a ValueTask only once, do not await it concurrently, and do not call .Result before it completes. For normal application code, Task is simpler and recommended.
33

Tell me about a time you made a mistake that affected data, and what you did.

Save to ▾
Show answer
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.
34

Show how Task.WhenAll and Task.WhenAny are used.

Save to ▾
Show answer
// run three independent calls in parallel var userTask = userApi.GetAsync(id); var ordersTask = orderApi.GetAsync(id); var walletTask = walletApi.GetAsync(id); await Task.WhenAll(userTask, ordersTask, walletTask); var dashboard = new Dashboard(userTask.Result, ordersTask.Result, walletTask.Result); // timeout using WhenAny var work = SlowCallAsync(); if (await Task.WhenAny(work, Task.Delay(2000)) != work) throw new TimeoutException(); WhenAll completes when all finish; WhenAny returns the first task to finish.
35

Write a LINQ query that joins orders with customers and returns customer name and order total.

Save to ▾
Show answer
var result = orders .Join(customers, o => o.CustomerId, c => c.Id, (o, c) => new { c.Name, o.Total }) .ToList(); // query syntax var result2 = (from o in orders join c in customers on o.CustomerId equals c.Id select new { c.Name, o.Total }).ToList(); Join is an inner join. For a left join use GroupJoin with DefaultIfEmpty, or the LeftJoin operator added in .NET 10.
36

Show how Func, Action and Predicate are used.

OOP in C#Medium
Save to ▾
Show answer
Func<decimal, decimal> addGst = price => price * 1.18m; Action<string> log = msg => Console.WriteLine($"[LOG] {msg}"); Predicate<int> isAdult = age => age >= 18; decimal total = addGst(1000m); // 1180 log($"Total {total}"); bool ok = isAdult(21); // true Func returns a value (last type argument is the return type), Action returns void, and Predicate<T> returns bool. They save you from declaring custom delegate types.
37

Users report intermittent errors saying their transaction was chosen as a deadlock victim. How do you investigate and fix it?

Save to ▾
Show answer
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.
38

Under load, an API's response time jumps to many seconds while CPU stays low. Thread count keeps rising. What is likely happening?

Save to ▾
Show answer
This pattern points to thread pool starvation. Some code is blocking threads with .Result, .Wait(), Thread.Sleep or synchronous I/O, so pool threads are stuck waiting and the pool injects new threads slowly. Confirm with dotnet-counters, watching thread pool queue length and thread count, and a dump showing many threads blocked in Wait. Fix by making the call chain fully async; raising minimum threads is only a temporary patch.
39

An Orders table has 50 million rows and queries are slowing down, but most reports only need the last 12 months. What would you do?

Save to ▾
Show answer
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.
40

You load 5,000 pincode-to-city mappings at startup and read them millions of times a day from many threads. Which collection would you use?

Save to ▾
Show answer
Immutable collections like ImmutableList and ImmutableDictionary never change; modifications return a new collection that shares structure, which makes them safe to share across threads. FrozenDictionary and FrozenSet, added in .NET 8, are built once and heavily optimised for fast reads but cannot be modified. They suit lookup tables loaded at startup, such as state codes or feature flags, read millions of times per day.

Frequently asked questions

What is asked in .NET Full Stack interviews for 10+ years of experience?

At ten years, roles are usually architect, principal engineer or engineering manager. Interviews explore enterprise architecture, distributed systems, Azure platform choices, governance, standards across teams, build-versus-buy decisions and delivery risk. Expect deep discussion of past systems you shaped, how you measured success, handled failures and grew engineers, along with high-level coding and design reviews.

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 10+ years of experience?

Focus first on Enterprise architecture, Technology strategy, Cross-team governance, People leadership.