.NET Full Stack Interview Questions for 3 Years Experience
40 questions with answers for candidates with 3 years of experience — 4 easy, 30 medium, 6 hard.
What interviewers expect: 3 years experience
Three-year candidates are judged on ownership and quality. Interviewers ask about design patterns, SOLID in practice, async pitfalls such as deadlocks, concurrency handling, API versioning, IHttpClientFactory, unit and integration testing, and CI/CD. Expect discussions on how you designed a module, handled requirement changes and reviewed other developers' code. Knowledge of Docker and cloud basics is a plus.
This set is weighted towards OOP in C# (9), Async Programming and Multithreading (5), Angular and React Front End (5), SQL Server and T-SQL (5).
Design patterns and SOLID Testing strategy Async and concurrency Module design
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
What is the difference between Select and SelectMany?
Select projects each element to one result, so a list of customers mapped to their orders gives a list of lists. SelectMany projects each element to a sequence and flattens all sequences into one, so customers.SelectMany(c => c.Orders) gives a single list of all orders. In EF Core, SelectMany over a navigation collection translates to a SQL join, which is handy for reports.
02
Implement global exception handling that returns ProblemDetails.
A Thread is an OS-level execution unit with its own stack, costly to create. A Task is a higher-level promise of work that may or may not use a thread; I/O tasks often use no thread while waiting, and CPU tasks run on pooled threads. Tasks support return values, continuations, cancellation, exceptions and composition with WhenAll. In modern .NET you almost always use Tasks rather than creating threads manually.
04
What is the difference between JIT compilation and Native AOT in .NET?
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.
05
You must rename a column and split a Name field into FirstName and LastName in a production database with live data. How do you deploy safely?
Avoid destructive one-step migrations. Use an expand-and-contract approach: first add new nullable columns, deploy code that writes both old and new, and backfill data with a script in batches. Then switch reads to the new columns, and only in a later release drop the old column. Generate idempotent SQL scripts, review them with the DBA, take a backup and test on a production copy first.
06
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.
lock ensures that only one thread at a time executes a block of code for a given lock object, preventing race conditions on shared data. It compiles to Monitor.Enter and Monitor.Exit inside try-finally. Lock on a private readonly object, never on this or a string. Since .NET 9 there is a dedicated System.Threading.Lock type that is faster. You cannot await inside a lock; use SemaphoreSlim for async code.
08
What is the difference between an RxJS Observable and a Promise?
A Promise represents a single future value, starts immediately and cannot be cancelled. An Observable can emit many values over time, is lazy until subscribed, and can be cancelled by unsubscribing. RxJS operators like map, debounceTime, switchMap and retry make it powerful for streams such as search boxes or WebSocket data. Angular's HttpClient returns Observables; use the async pipe or toSignal to avoid manual unsubscribe.
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.
10
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.
11
A page listing 100 orders with customer names runs 101 SQL queries. What is the problem and how do you fix it?
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.
12
Protect an Angular route so only logged-in users can open it.
Two common options exist. Build the SPA with ng build or npm run build and serve the static files from the API's wwwroot with UseStaticFiles and MapFallbackToFile, giving one deployment and no CORS. Or host the SPA separately on a CDN or static hosting such as Azure Static Web Apps and the API on App Service or containers, which scales independently. Use environment config for API URLs and a CI/CD pipeline for both.
14
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.
15
Show how to bind configuration from appsettings.json using the Options pattern.
// appsettings.json
// "Smtp": { "Host": "smtp.example.com", "Port": 587 }
public class SmtpOptions
{
public string Host { get; set; } = "";
public int Port { get; set; }
}
builder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection("Smtp"));
public class EmailService(IOptions<SmtpOptions> options)
{
private readonly SmtpOptions _smtp = options.Value;
}
appsettings.{Environment}.json and environment variables override base values. Use IOptionsMonitor to pick up changes without restart.
A view is a saved SELECT query that acts like a virtual table; it simplifies complex joins and can restrict which columns users see, but by default it stores no data. An indexed view has a unique clustered index, so SQL Server physically stores and maintains its results, which speeds up heavy aggregations. The cost is slower writes on base tables, and it requires SCHEMABINDING and deterministic expressions.
17
What is the difference between an abstract class and an interface in C#?
An abstract class can have fields, constructors, implemented and abstract members, and a class can inherit only one. An interface defines a contract; a class can implement many. Since C# 8, interfaces can have default method implementations but still no instance fields. Use an abstract class for shared state and base behaviour among closely related types, and interfaces for capabilities like IComparable or for dependency injection.
18
A job matches 2 lakh uploaded records against 2 lakh existing IDs using list.Where(x => existingIds.Contains(x.Id)) and takes minutes. What is wrong?
existingIds is a List, so Contains is O(n) and the whole operation becomes O(n x m), around 40 billion comparisons. Convert the IDs to a HashSet<int> first, making each lookup O(1) and the job run in milliseconds. If you also need associated data, build a Dictionary keyed by ID. Always check data structure complexity before parallelising or adding hardware.
19
Your Angular app's first load is slow on mobile because the main bundle is very large. How does lazy loading of routes help?
Lazy loading splits the app into chunks that download only when the user navigates to that route, so the initial bundle is smaller and the first page loads faster, especially on mobile data. In Angular use loadComponent or loadChildren with a dynamic import in the route config; in React use React.lazy with Suspense or the router's lazy option. Angular's @defer blocks can also delay heavy parts of a page.
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.
21
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?
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.
22
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.
23
Configure JWT bearer authentication in an ASP.NET Core API.
public static class StringExtensions
{
public static bool IsIndianMobile(this string? value) =>
value is not null &&
System.Text.RegularExpressions.Regex.IsMatch(value, @"^[6-9]\d{9}$");
}
// usage
bool ok = "9876543210".IsIndianMobile();
Extension methods are static methods in a static class whose first parameter uses this, so they appear like instance methods.
public sealed class AppConfig
{
private static readonly Lazy<AppConfig> _instance =
new(() => new AppConfig());
public static AppConfig Instance => _instance.Value;
private AppConfig()
{
// load settings once
}
}
Lazy<T> is thread-safe by default and creates the object only on first access. The private constructor and sealed class prevent other instances. In ASP.NET Core, prefer AddSingleton.
26
A dashboard API calls three independent services one after another, each taking about 1 second. How can you make it faster?
Because the calls do not depend on each other, start all three tasks first without awaiting, then await Task.WhenAll. Total time becomes roughly the slowest call, about 1 second instead of 3. Add a timeout and handle failure of one service gracefully, for example showing partial data. If the same data is requested often, also add caching so most requests skip the calls entirely.
Interpolation {{ value }} shows component data in the template. Property binding [src]="imageUrl" sets an element or child property. Event binding (click)="save()" calls a component method on a DOM event. Two-way binding [(ngModel)]="name" combines property and event binding so the input and the field stay in sync. The first two flow from component to view, event binding flows from view to component.
28
A query that used to run in 1 second now takes 30 seconds. How do you troubleshoot it?
Capture the actual execution plan and look for table or index scans on large tables, key lookups, big differences between estimated and actual rows, and warnings like implicit conversions or spills to tempdb. Check missing or fragmented indexes and outdated statistics, and update them. Use Query Store to compare the old and new plans; if a plan regressed, you can force the good plan while fixing the root cause.
29
Explain the access modifiers in C#, including protected internal and private protected.
public is accessible everywhere, private only inside the type, protected inside the type and derived types, and internal anywhere in the same assembly. protected internal means accessible from the same assembly or from derived types in any assembly. private protected is stricter: only derived types inside the same assembly. Class members default to private and top-level types default to internal. Use the narrowest modifier that works.
virtual marks a base method as replaceable. override in a derived class replaces it, so calls through a base reference run the derived version. new hides the base method instead: a call through a base reference still runs the base version, and only a derived-type reference sees the new one. Method hiding is usually a sign of a design problem, and the compiler warns when you hide without writing new.
31
How do you use an explicit transaction in EF Core across multiple SaveChanges calls?
await using var tx = await db.Database.BeginTransactionAsync();
try
{
db.Orders.Add(order);
await db.SaveChangesAsync();
var stock = await db.Stocks.SingleAsync(s => s.ProductId == order.ProductId);
stock.Quantity -= order.Quantity;
await db.SaveChangesAsync();
await tx.CommitAsync();
}
catch
{
await tx.RollbackAsync();
throw;
}
A single SaveChanges is already transactional; explicit transactions are needed when you combine several saves or raw SQL into one unit.
ASP.NET Core has a built-in ILogger<T> abstraction injected into any class. You log with levels Trace, Debug, Information, Warning, Error and Critical, and configure minimum levels per category in appsettings.json. Use structured logging with message templates, like logger.LogInformation("Order {OrderId} placed", id), so values become searchable fields. Providers such as Serilog or OpenTelemetry send logs to files, Seq, Elasticsearch or Application Insights.
33
How do you use CancellationToken in an ASP.NET Core API?
[HttpGet("report")]
public async Task<IActionResult> Report(CancellationToken ct)
{
var rows = await _db.Sales
.Where(s => s.Year == 2026)
.ToListAsync(ct);
await _http.PostAsJsonAsync("/audit", rows.Count, ct);
return Ok(rows);
}
ASP.NET Core binds the token to HttpContext.RequestAborted, so if the user closes the browser the token is cancelled and EF Core and HttpClient stop work, saving database and CPU time.
34
Write a LINQ query that joins orders with customers and returns customer name and order total.
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.
35
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.
36
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.
37
What is the difference between a shallow copy and a deep copy?
A shallow copy duplicates the object but its reference fields still point to the same child objects; MemberwiseClone and record with-expressions do this. A deep copy duplicates the whole object graph, so changes to the copy never affect the original. Deep copies are usually written by hand or by serialising and deserialising. ICloneable is avoided because it does not say which kind of copy it returns.
38
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.
39
You must resize 10,000 images and also call an external API for 10,000 records. Which approach do you use for each task?
Parallel.ForEach is for CPU-bound work, such as resizing thousands of images, where splitting work across cores speeds things up. async/await is for I/O-bound work, where you mostly wait on networks or disks. Using Parallel.ForEach for HTTP calls wastes threads while they wait. For many concurrent I/O operations with a limit, use Parallel.ForEachAsync with MaxDegreeOfParallelism, available since .NET 6.
40
You need to add a new method to a widely implemented interface without breaking existing classes. How can you do it?
Since C# 8, an interface can provide a method body. Classes implementing the interface get that behaviour without writing it, though they can override it. The main use is evolving a published interface: you can add a new member without breaking every existing implementation. The default method is only callable through the interface type, not the class, and overusing it blurs the line with abstract classes.
Frequently asked questions
What is asked in .NET Full Stack interviews for 3 years of experience?
Three-year candidates are judged on ownership and quality. Interviewers ask about design patterns, SOLID in practice, async pitfalls such as deadlocks, concurrency handling, API versioning, IHttpClientFactory, unit and integration testing, and CI/CD. Expect discussions on how you designed a module, handled requirement changes and reviewed other developers' code. Knowledge of Docker and cloud basics is a plus.
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 3 years of experience?
Focus first on Design patterns and SOLID, Testing strategy, Async and concurrency, Module design.