.NET Backend Developer Interview β Reviewed Study Guide
A comprehensive, prioritized reference for C#, ASP.NET Core, EF Core, SQL, HTTP, security, testing, operations, practical rounds, and system design.
Reviewed for the 2026 .NET landscape. Use it as the main study map, then prove the knowledge through coding, SQL, debugging, and mock interviews.
StartWhat Actually Matters in a .NET Backend Interview
This guide is intentionally broad, but not every topic deserves equal study time. Use these tiers to avoid spending hours on advanced material before the fundamentals are solid.
Importance ratings: every section, subtopic, detailed bullet, comparison row, and interview question carries a 1–10 score for how much it matters in a typical .NET backend interview. asked in almost every interview — master these first · very common — expected at mid-level · comes up regularly — senior or role-specific · niche — skip until everything else is solid.
Must know β most interviews
- C# types, collections, generics, LINQ, exceptions, equality, nullable references
- Async/await, I/O vs CPU work, cancellation, concurrency basics
- ASP.NET Core pipeline, DI lifetimes, configuration, validation, auth, error handling
- EF Core querying, tracking, relationships, migrations, concurrency
- SQL joins, indexes, transactions, isolation, query plans
- HTTP/REST, status codes, security basics, unit and integration testing
- Explaining one real project, debugging approach, and behavioral stories
Strong advantage β mid-level+
- Caching, queues, retries, idempotency, observability, health checks
- Docker, CI/CD, cloud fundamentals, deployment and rollback
- API versioning, pagination, OpenAPI, Problem Details
- Performance diagnosis, N+1, projections, keyset pagination
- Basic system design with explicit trade-offs and failure handling
Role-dependent β learn after core
- Advanced dynamic programming, graph algorithms, bit manipulation
- Kubernetes internals, sharding, consistent hashing
- CQRS, event sourcing, sagas, YARP, deep microservices patterns
- Span<T>, Native AOT, low-level GC tuning, newest syntax trivia
Current platform context: as of 2026, .NET 10 is the current LTS release and C# 14 is current. Know which version the employer uses, but prioritize durable runtime, web, data, and design fundamentals over memorizing release notes.
Company style matters: large product companies may emphasize algorithms and system design; typical enterprise .NET teams often emphasize APIs, EF Core, SQL, async code, testing, Azure, and debugging. Adjust practice to the job description.
1Data Structures & Algorithms Company-dependent
This round is pattern-based. You are tested on whether you can choose a suitable data structure, explain trade-offs, write correct code, and reason about time and space. Arrays, dictionaries, strings, stacks/queues, trees, BFS/DFS, binary search, and common two-pointer/sliding-window problems are the highest-value topics. Deep dynamic programming, advanced graphs, and bit tricks are mainly important for algorithm-heavy companies.
1.1 Big-O β what it really means
Big-O describes how the number of operations (time) or amount of memory (space) grows as the input size n grows toward infinity. It ignores constants and hardware β it's about the shape of growth, not the exact count.
How to derive it in 4 steps:
- Count the loops. One loop over
n= O(n). A loop inside a loop, both overn= O(nΒ²). - Halving the input each step = O(log n) (that's binary search, balanced trees).
- Drop constants and lower-order terms: O(2n + 100) becomes O(n); O(nΒ² + n) becomes O(nΒ²) because nΒ² dominates as n grows.
- Recursion: multiply the number of calls by the work per call. Naive Fibonacci makes 2 calls per level for n levels = O(2βΏ).
| Complexity | Name | n=10 | n=1,000 | Typical source |
|---|---|---|---|---|
O(1) | Constant | 1 | 1 | Hash lookup, array index, arithmetic |
O(log n) | Logarithmic | ~3 | ~10 | Binary search, balanced BST |
O(n) | Linear | 10 | 1,000 | One pass over data |
O(n log n) | Linearithmic | ~33 | ~10,000 | Good sorting, divide & conquer |
O(nΒ²) | Quadratic | 100 | 1,000,000 | Nested loops, all pairs |
O(2βΏ) | Exponential | 1,024 | astronomical | Naive recursion, subsets |
O(n!) | Factorial | 3.6M | β | All permutations |
A common optimization: if a problem repeatedly asks βhave I seen X?β or searches for complements, a HashSet/Dictionary can often reduce O(nΒ²) work to O(n) by trading memory for speed. It is not automatic: ordering, range queries, duplicates, and worst-case memory still matter.
Space complexity counts extra memory you allocate: a second array = O(n); recursion uses the call stack, so depth-d recursion = O(d) space even with no explicit arrays.
1.2 The core data structures β when & why
Array / List O(1) access
Contiguous memory. Reading by index is O(1). Appending to a List<T> is amortized O(1) (it occasionally doubles its backing array). Inserting/removing in the middle is O(n) because everything shifts. Use when you need indexed access or you'll iterate in order.
Dictionary / HashSet O(1) lookup
Backed by a hash table: it hashes the key to a bucket. Average insert/lookup/delete is O(1); worst case O(n) if many keys collide. No ordering. Dictionary<K,V> maps keys to values; HashSet<T> just tracks membership. Use for counting, deduping, "seen before?", grouping, caching, adjacency lists.
Stack β LIFO push/pop O(1)
Last in, first out. Use for undo, expression/bracket matching, backtracking, and iterative DFS. The call stack itself is a stack β that's why recursion and DFS are interchangeable.
Queue β FIFO enqueue/dequeue O(1)
First in, first out. Use for BFS, scheduling, producer/consumer buffers. PriorityQueue<TElement,TPriority> (a heap) pops the smallest priority first β use it for Dijkstra and "top-K".
Linked List O(1) insert at node
Each node points to the next (and maybe previous). O(1) to insert/delete if you already hold the node, but O(n) to find something. Rare in real backend code, common in interviews (reverse a list, detect a cycle, merge two lists).
Tree / Binary Search Tree O(log n) balanced
Hierarchical parentβchildren. A BST keeps left < node < right, so search/insert/delete is O(log n) when balanced (degrades to O(n) if it becomes a straight line). Traversals: in-order gives sorted output, pre-order copies a tree, post-order deletes/evaluates, level-order is BFS.
Heap / Priority Queue peek O(1), push/pop O(log n)
A binary tree kept partially ordered so the min (or max) is always at the root. Use for "K largest/smallest/most frequent", merging sorted streams, Dijkstra, schedulers.
Graph V vertices, E edges
Nodes + edges; store as an adjacency list (Dictionary<int, List<int>>) for sparse graphs. BFS finds shortest paths in an unweighted graph; DFS explores deeply and backtracks; Dijkstra uses a priority queue to find shortest paths when edge weights are non-negative.
1.3 The patterns that solve most problems β with templates
When you read a problem, try to map it to one of these. The trigger phrases in italics tell you which to reach for.
Two Pointers sorted array, pair, palindrome
Two indices move toward each other (or one chases the other). Trigger: "sorted array", "pair that sums to target", "is it a palindrome", "remove duplicates in place".
// Pair that sums to target in a SORTED array β O(n), O(1) space
int lo = 0, hi = arr.Length - 1;
while (lo < hi) {
int sum = arr[lo] + arr[hi];
if (sum == target) return new[]{ lo, hi };
if (sum < target) lo++; // need bigger β move left pointer up
else hi--; // need smaller β move right pointer down
}
Sliding Window longest/shortest subarray or substring
Grow a window from the right, shrink from the left when a condition breaks. Turns many O(nΒ²) substring problems into O(n). Trigger: "longest/shortest contiguous β¦", "substring without repeats", "max sum of k elements".
// Longest substring with no repeating characters β O(n)
var last = new Dictionary<char,int>();
int start = 0, best = 0;
for (int end = 0; end < s.Length; end++) {
if (last.TryGetValue(s[end], out int idx) && idx >= start)
start = idx + 1; // jump past the previous duplicate
last[s[end]] = end;
best = Math.Max(best, end - start + 1);
}
return best;
Fast & Slow Pointers cycle detection
A slow pointer moves 1 step, a fast pointer 2 steps. If there's a cycle they meet. Trigger: "does the linked list have a cycle", "find the middle".
Binary Search sorted, or 'search the answer'
Repeatedly halve the search space β O(log n). Also works on an abstract "answer space" (e.g. minimum capacity that satisfies a condition). Trigger: "sorted", "minimum/maximum value that works".
int lo = 0, hi = arr.Length - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2; // avoids integer overflow
if (arr[mid] == target) return mid;
if (arr[mid] < target) lo = mid + 1;
else hi = mid - 1;
}
return -1;
BFS & DFS trees & graphs
BFS uses a queue and visits level by level β shortest path in unweighted graphs. DFS uses recursion/stack and goes deep β good for exploring all paths, detecting cycles, topological sort.
// BFS shortest path (unweighted) β O(V + E)
var q = new Queue<int>(); var dist = new Dictionary<int,int>{ [start]=0 };
q.Enqueue(start);
while (q.Count > 0) {
int node = q.Dequeue();
foreach (int next in graph[node])
if (!dist.ContainsKey(next)) {
dist[next] = dist[node] + 1;
q.Enqueue(next);
}
}
Backtracking permutations, combinations, subsets
Try a choice, recurse, then undo it and try the next. Explores a decision tree. Trigger: "all combinations/permutations/subsets", "sudoku", "generate all valid β¦".
void Backtrack(List<int> path, bool[] used) {
if (path.Count == nums.Length) { results.Add(new(path)); return; }
for (int i = 0; i < nums.Length; i++) {
if (used[i]) continue;
used[i] = true; path.Add(nums[i]); // choose
Backtrack(path, used); // explore
used[i] = false; path.RemoveAt(path.Count-1); // un-choose
}
}
Other must-know patterns
- Hash Map counting β frequencies, anagrams, "first unique". O(n).
- Prefix Sum β precompute cumulative sums so any range sum is O(1).
- Top-K / Heap β keep a heap of size K β O(n log k).
- Merge Intervals β sort by start, then merge overlaps.
- Topological Sort β order tasks with dependencies (build order, course schedule); DFS or Kahn's algorithm.
- Union-Find β group connected items, detect graph cycles, near-O(1) per op.
- Greedy β take the locally best choice; works when local optima lead to a global optimum (interval scheduling, Huffman).
1.4 Sorting & searching you should be able to explain
| Algorithm | Average | Worst | Space | Stable? | Notes |
|---|---|---|---|---|---|
| Quick Sort | O(n log n) | O(nΒ²) | O(log n) | No | Fast in practice; bad pivot β worst case |
| Merge Sort | O(n log n) | O(n log n) | O(n) | Yes | Predictable; used for linked lists & external sort |
| Heap Sort | O(n log n) | O(n log n) | O(1) | No | In-place, no recursion |
| Insertion Sort | O(nΒ²) | O(nΒ²) | O(1) | Yes | Great for tiny/nearly-sorted data |
In real code you just call list.Sort() (introsort β a quicksort/heapsort hybrid) or OrderBy (stable). But interviewers ask you to explain why merge sort is O(n log n): it splits the array in half (log n levels) and does O(n) work merging at each level.
1.5 Recursion & Dynamic Programming (the scary part, demystified)
Recursion = a function calling itself on a smaller input until a base case stops it. Every recursion needs (1) a base case and (2) progress toward it.
Dynamic Programming = recursion where subproblems repeat, so you cache results to avoid recomputing. Two flavors:
- Memoization (top-down): normal recursion + a cache (Dictionary/array).
- Tabulation (bottom-up): fill a table from the smallest subproblem upward.
Classic example β Fibonacci goes from O(2βΏ) to O(n):
// Naive: O(2^n) β recomputes the same values exponentially
int Fib(int n) => n < 2 ? n : Fib(n-1) + Fib(n-2);
// Memoized: O(n) β each value computed once
var memo = new Dictionary<int,long>();
long Fib(int n) {
if (n < 2) return n;
if (memo.TryGetValue(n, out var v)) return v;
return memo[n] = Fib(n-1) + Fib(n-2);
}
How to spot a DP problem: you're asked for a count, a min/max, or "is it possible", AND the answer for n depends on answers for smaller inputs, AND those overlap. Classics: coin change, longest common subsequence, edit distance, knapsack, climbing stairs, house robber.
1.6 Fully worked problems (read these like flashcards)
Idea: as you scan, remember each value's index. For each number, check if its complement (target β value) was already seen. One pass, O(n) time, O(n) space.
public int[] TwoSum(int[] nums, int target) {
var seen = new Dictionary<int,int>(); // value -> index
for (int i = 0; i < nums.Length; i++) {
int need = target - nums[i];
if (seen.TryGetValue(need, out int j)) return new[]{ j, i };
seen[nums[i]] = i;
}
return Array.Empty<int>();
}
Idea: push opening brackets; on a closing bracket, the top of the stack must be its match. Stack empty at the end = valid. O(n).
public bool IsValid(string s) {
var st = new Stack<char>();
var pairs = new Dictionary<char,char>{ [')']='(', [']']='[', ['}']='{' };
foreach (char c in s) {
if (pairs.ContainsKey(c)) {
if (st.Count == 0 || st.Pop() != pairs[c]) return false;
} else st.Push(c);
}
return st.Count == 0;
}
Idea: scan the grid; each time you hit an unvisited '1', it's a new island β flood-fill (DFS/BFS) all connected land so you don't count it again. O(rows Γ cols).
public int NumIslands(char[][] grid) {
int count = 0;
void Sink(int r, int c) {
if (r < 0 || c < 0 || r >= grid.Length || c >= grid[0].Length || grid[r][c] != '1') return;
grid[r][c] = '0'; // mark visited
Sink(r+1,c); Sink(r-1,c); Sink(r,c+1); Sink(r,c-1);
}
for (int r = 0; r < grid.Length; r++)
for (int c = 0; c < grid[0].Length; c++)
if (grid[r][c] == '1') { count++; Sink(r,c); }
return count;
}
Idea: dp[a] = min coins to make amount a. Build up from 0: for each amount, try every coin. O(amount Γ coins).
public int CoinChange(int[] coins, int amount) {
var dp = new int[amount + 1];
Array.Fill(dp, amount + 1); // "infinity"
dp[0] = 0;
for (int a = 1; a <= amount; a++)
foreach (int coin in coins)
if (coin <= a) dp[a] = Math.Min(dp[a], dp[a - coin] + 1);
return dp[amount] > amount ? -1 : dp[amount];
}
Practice set (do in order, understand each fully):
Easy: Two Sum Β· Valid Parentheses Β· Merge Two Sorted Lists Β· Best Time to Buy/Sell Stock Β· Reverse Linked List Β· Contains Duplicate Β· Valid Anagram Β· Maximum Subarray Β· Climbing Stairs.
Medium: Group Anagrams Β· Longest Substring Without Repeating Characters Β· Product of Array Except Self Β· Binary Tree Level Order Traversal Β· Number of Islands Β· Course Schedule Β· Coin Change Β· Top K Frequent Elements Β· House Robber Β· Longest Common Subsequence.
Target: 80β100 problems, deeply understood, beats 300 rushed. Redo any you couldn't solve after a week.
1.7 Bit manipulation Lower priority
Bits are occasionally tested, especially at bigger companies. The essentials:
| Operation | Meaning | Trick |
|---|---|---|
a & b | AND | x & 1 tests if x is odd |
a | b | OR | set a bit |
a ^ b | XOR | equal values cancel to 0 |
x << n | left shift | multiply by 2βΏ |
x >> n | right shift | divide by 2βΏ (arithmetic on signed ints β careful with negatives) |
x & (x-1) | clears lowest set bit | count set bits / power-of-two check |
// "Single number": every value appears twice except one β XOR cancels the pairs
int Single(int[] nums) { int r = 0; foreach (int n in nums) r ^= n; return r; }
// Is x a power of two? Exactly one bit set.
bool IsPowerOfTwo(int x) => x > 0 && (x & (x - 1)) == 0;
1.8 More worked problems
Idea: sort by start; if the next interval starts before the current one ends, merge by extending the end. O(n log n) from the sort.
public int[][] Merge(int[][] intervals) {
Array.Sort(intervals, (a, b) => a[0].CompareTo(b[0]));
var res = new List<int[]>();
foreach (var cur in intervals) {
if (res.Count == 0 || res[^1][1] < cur[0]) res.Add(cur);
else res[^1][1] = Math.Max(res[^1][1], cur[1]); // overlap β extend
}
return res.ToArray();
}
Idea: count frequencies in a dictionary, then keep a min-heap of size K. O(n log k).
public int[] TopKFrequent(int[] nums, int k) {
var freq = new Dictionary<int,int>();
foreach (int n in nums) freq[n] = freq.GetValueOrDefault(n) + 1;
var heap = new PriorityQueue<int,int>(); // min-heap by frequency
foreach (var kv in freq) {
heap.Enqueue(kv.Key, kv.Value);
if (heap.Count > k) heap.Dequeue(); // drop the least frequent
}
var res = new int[k];
for (int i = k - 1; i >= 0; i--) res[i] = heap.Dequeue();
return res;
}
Idea: walk the list, flipping each next pointer to the previous node. O(n) time, O(1) space.
public ListNode Reverse(ListNode head) {
ListNode prev = null, cur = head;
while (cur != null) {
var next = cur.next; // save
cur.next = prev; // reverse pointer
prev = cur; // advance
cur = next;
}
return prev; // new head
}
Idea: it's a topological-sort feasibility question. Build a graph of prerequisites; if there's a cycle, it's impossible. Use Kahn's algorithm (BFS on in-degrees): repeatedly remove nodes with 0 prerequisites. If you can remove all, no cycle.
public bool CanFinish(int n, int[][] prereqs) {
var graph = new List<int>[n];
var indeg = new int[n];
for (int i = 0; i < n; i++) graph[i] = new();
foreach (var p in prereqs) { graph[p[1]].Add(p[0]); indeg[p[0]]++; }
var q = new Queue<int>();
for (int i = 0; i < n; i++) if (indeg[i] == 0) q.Enqueue(i);
int done = 0;
while (q.Count > 0) {
int c = q.Dequeue(); done++;
foreach (int nxt in graph[c]) if (--indeg[nxt] == 0) q.Enqueue(nxt);
}
return done == n; // all scheduled β no cycle
}
1.9 Rapid-fire algorithm Q&A
Hash table for O(1) average lookups when you don't need ordering. Balanced tree (or sorted structure) when you need ordered traversal, range queries, or predecessor/successor β those are O(log n) but ordered; a hash table can't do them.
BFS finds shortest paths in an unweighted graph because it explores by edge distance. DFS is useful for exhaustive traversal, cycle detection, and topological-sort implementations. Dijkstra uses a priority queue for non-negative weighted edges; Bellman-Ford can handle negative edges and detect reachable negative cycles.
Floyd's fast/slow pointers: slow moves 1, fast moves 2. If they ever meet, there's a cycle; if fast hits null, there isn't. O(n) time, O(1) space.
A consistently bad pivot (e.g. already-sorted input with first-element pivot) makes partitions of size 0 and n-1, giving n levels of O(n) work. Randomized or median-of-three pivots make that pathological case unlikely; heapsort/mergesort guarantee O(n log n).
A stable sort preserves the relative order of equal keys. It matters when sorting by multiple keys in passes (sort by name, then by age, keeping name order within equal ages). Merge sort is stable; heapsort and typical quicksort are not.
Two ways: a min-heap of size K β O(n log k); or Quickselect (partition like quicksort but recurse into one side) β O(n) average, O(nΒ²) worst.
Recursion is cleaner for tree/divide-and-conquer problems but uses call-stack memory and can StackOverflow on deep inputs. Iteration (with an explicit stack/queue) avoids that and is often faster. Tail-recursion helps in some languages, but C# doesn't guarantee tail-call optimization.
2Language & Framework Fundamentals Must know
Interviewers use this round to tell a "can code" candidate from a "understands the platform" one. Below is C# / .NET, but the ideas map to any stack. Focus on why things work, because follow-up questions dig into mechanics.
2.1 OOP β the four pillars, explained properly
Encapsulation
Keep data and the logic that changes it together, and hide the internals behind a public surface. Callers can't put the object into an invalid state. Example: a BankAccount exposes Deposit()/Withdraw() but keeps balance private so nobody sets it to a negative number directly.
Abstraction
Expose what something does, hide how. You depend on an IEmailSender interface, not on the SMTP details. This lets you swap implementations (SMTP β SendGrid) without touching callers.
Inheritance
A subclass reuses and extends a base class ("is-a"). Powerful but easily abused β deep hierarchies become rigid. Modern advice: prefer composition (assemble behavior from small parts you inject) over inheritance.
Polymorphism
One call, many behaviors. Given Shape.Area(), a Circle and a Square each compute differently; code that loops over List<Shape> calls Area() without knowing the concrete type. Achieved via virtual methods / interface implementations.
abstract class vs interface (asked constantly):
- Interface = primarily a contract describing available members. A class can implement many interfaces. Modern C# interfaces can also contain default implementations and static members, but they do not hold per-instance fields. Use an interface to describe a capability (
IDisposable,IComparable). - Abstract class = a partial base with shared state/implementation plus some members subclasses must fill in. A class can inherit only one. Use it when subclasses share real code, not just a shape.
- Rule of thumb: "can do" β interface; "is a kind of" with shared code β abstract class.
Access modifiers, virtual/override/new, sealed, static
- Access modifiers:
public(everyone),private(same type only, the default for members),protected(type + subclasses),internal(same assembly, the default for top-level types),protected internal(subclasses OR same assembly),private protected(subclasses AND same assembly). Default to the most restrictive that works. virtualvsoverridevsnew:virtuallets a base method be overridden;overridereplaces it with runtime (polymorphic) dispatch, so a base-typed reference calls the derived version.newmerely hides the base method β which one runs depends on the compile-time type of the reference. Hiding is a common source of bugs; interviewers love asking which method aBase b = new Derived()call picks.sealed: on a class, prevents inheritance; on anoverride, stops further overriding. Communicates design intent and can enable minor devirtualization optimizations.static: a static member belongs to the type, not an instance; a static class cannot be instantiated and holds only static members (e.g. utility/extension-method classes). A static constructor runs once before first use. Mutable static state is shared process-wide β a thread-safety and testability hazard; prefer DI singletons for shared services.
2.2 SOLID β each principle with a concrete fix
- S β Single Responsibility. One class, one reason to change. A class that parses a file and emails results and writes to the DB should be three classes.
- O β Open/Closed. Open to extension, closed to modification. Instead of a growing
switchon payment type, add a newIPaymentMethodimplementation β existing code doesn't change. - L β Liskov Substitution. A subtype must work anywhere its base type is expected. The classic violation: a
SquaresubclassingRectanglebreaks code that sets width and height independently. - I β Interface Segregation. Prefer several small interfaces over one fat one, so implementers aren't forced to stub methods they don't use.
- D β Dependency Inversion. High-level policy should not depend directly on low-level implementation details; both should depend on a useful abstraction, for example
IEmailSenderrather than an SMTP SDK. A DI container helps compose dependencies, but using a container alone does not guarantee good abstractions.
2.3 C# internals interviewers probe
Value types vs reference types
struct, int, bool, enum, DateTime are value types β stored inline and copied when assigned or passed. class, arrays, string, delegates are reference types β the variable holds a pointer; assigning copies the pointer, not the object. This changes equality and mutation behavior: passing a struct to a method and mutating it won't affect the caller's copy; passing a class will.
Storage location vs type semantics & boxing
Do not equate βvalue typeβ with βstackβ and βreference typeβ with βheap.β A value can be stored inline inside an object or array on the managed heap, and the JIT may keep locals in registers. The important distinction is copy semantics: assigning a value type copies its value; assigning a reference type copies a reference to the same object. Boxing wraps a value type in an object/interface representation and usually allocates; generics such as List<int> avoid that boxing.
Why string is immutable
Every "modification" of a string creates a new one. Benefits: safe to share across threads, usable as dictionary keys, enables interning. Cost: concatenating in a loop creates many throwaway strings β use StringBuilder instead.
decimal vs double
Use decimal for money and base-10 business calculations where decimal precision matters. Use double for scientific measurements and performance-sensitive floating-point math. Neither should be compared casually for exact equality after arbitrary calculations; define rounding rules explicitly.
Date and time
Use UTC for instants in storage and transport, often represented by DateTimeOffset. A local calendar time plus a time-zone identifier is different from an instant. Do not store only a UTC offset for future scheduling because daylight-saving rules can change. Inject TimeProvider so time-dependent code is testable.
IEnumerable vs IQueryable (a real bug)
IEnumerable<T> operators run in .NET; IQueryable<T> builds an expression tree that a provider such as EF Core can translate. Calling AsEnumerable() or otherwise switching to enumerable operators before a filter means that filter is no longer translated; when enumerated, SQL may fetch far more rows and C# filters them afterward. Keep translatable filtering, ordering, projection, and paging in IQueryable until execution.
LINQ & deferred execution
A LINQ query often does not run when definedβit runs when enumerated (foreach, ToList(), Count()). Avoid accidental multiple enumeration, especially when the source is a database or network stream. Use Any() rather than Count() > 0 for existence, and know the semantic difference between First, Single, and their OrDefault variants. Materialize only when you need a snapshot or repeated in-memory access.
Cleanup: IDisposable & using
Managed objects that own scarce resourcesβfile handles, sockets, streams, timers, and database connectionsβoften implement IDisposable or IAsyncDisposable. A using/await using statement guarantees deterministic cleanup even if an exception is thrown. The GC reclaims managed memory; it does not replace timely disposal.
2.4 CLR, IL, JIT, assemblies & managed code
From source code to execution
The C# compiler turns source code into Common Intermediate Language (CIL/IL) plus metadata inside an assembly (.dll or .exe). The Common Language Runtime (CLR) loads the assembly, verifies types, manages memory and exceptions, and the JIT compiler converts IL into native machine code as methods are executed. ReadyToRun and Native AOT can move some or all compilation ahead of time.
Assemblies and metadata
An assembly is a deployment/versioning unit containing IL, type metadata, resources, and a manifest. Reflection reads that metadata at runtime; attributes attach metadata that frameworks such as ASP.NET Core and EF Core inspect.
Generics, constraints and variance
Generics provide compile-time type safety and avoid many casts and boxing allocations. Constraints such as where T : class, struct, new(), or an interface describe what T must support. Covariance (out) allows producing a more-derived type; contravariance (in) allows consuming a less-derived type.
Equality and hashing
If a type is used as a dictionary key or in a hash set, equal objects must return the same hash code. Override Equals and GetHashCode together, or implement IEquatable<T>. Never mutate fields that participate in a key's hash while it is inside a hash-based collection.
Exceptions
Use exceptions for exceptional failures, not normal branching. Catch only when you can add context, translate, recover, or clean up. Use throw; to preserve the original stack trace; throw ex; resets it. At API boundaries, map expected failures to suitable HTTP responses and let an exception handler produce a safe ProblemDetails response.
2.5 Async / await β what actually happens
A web server has a limited ThreadPool. When a genuinely asynchronous I/O operation is incomplete, await records the continuation and returns control without blocking a request thread. When the I/O completes, execution resumes later, possibly on a different ThreadPool thread. This improves throughput; it does not make CPU-heavy work faster.
public async Task<User?> GetUserAsync(int id, CancellationToken cancellationToken) {
// The request thread is not blocked while asynchronous database I/O is pending.
var user = await _db.Users.FindAsync(new object[] { id }, cancellationToken);
return user;
}
Avoid sync-over-async: .Result, .Wait(), and unnecessary Task.Run consume threads and can cause ThreadPool starvation under load. Classic SynchronizationContext deadlocks are common in UI apps and legacy ASP.NET; ASP.NET Core has no request SynchronizationContext by default, so starvation is the usual risk there. Prefer async all the way.
- Task vs Thread: a
Taskrepresents an asynchronous operation and its eventual result; it does not necessarily own or run on a thread. AThreadis an OS execution resource. I/O tasks can be pending with no thread blocked. - I/O-bound vs CPU-bound: await asynchronous APIs for database, file, and network I/O. Use
Task.Runonly when you intentionally move CPU-bound work to the ThreadPool; in a web server, sustained CPU work is usually better moved to a worker service. - Composition: use
Task.WhenAllfor independent operations, but do not create unbounded parallelism. Limit fan-out withSemaphoreSlim, a channel, or a bounded worker queue. - Cancellation: pass
CancellationTokenthrough every cancellable call, including EF Core andHttpClient. In APIs, propagateHttpContext.RequestAborted. Cancellation is cooperative and often throwsOperationCanceledException. - Avoid
async void: except for event handlers, returnTaskso callers can await failures. ConsiderValueTaskonly for measured hot paths where operations frequently complete synchronously. ConfigureAwait: ASP.NET Core has no request SynchronizationContext, soConfigureAwait(false)is usually unnecessary in application code. It can still be appropriate in reusable libraries that should not depend on a caller's context.- Thread safety: shared mutable state may require
lock,Interlocked, concurrent collections, or immutability. You cannotawaitinside a C#lock; useSemaphoreSlim.WaitAsyncfor asynchronous coordination.
2.6 ASP.NET Core β the request lifecycle & architecture
Kestrel accepts HTTP requests, commonly behind a reverse proxy or cloud load balancer. A request then flows through the ordered middleware pipeline. Middleware can inspect or short-circuit the request, call the next component, and inspect the response on the way back. Typical ordering puts exception handling early, then forwarded headers/HTTPS/static files as needed, routing, authentication, authorization, and endpoints. Exact ordering depends on the application.
// Interviewers often ask you to sketch a custom middleware:
app.Use(async (context, next) => {
var sw = Stopwatch.StartNew();
await next(context); // run the rest of the pipeline
app.Logger.LogInformation("{Method} {Path} => {Status} in {Ms} ms",
context.Request.Method, context.Request.Path,
context.Response.StatusCode, sw.ElapsedMilliseconds);
});
Dependency Injection lifetimes (a top interview trap):
- Transient β a fresh instance every time it's requested. Use for lightweight, stateless services.
- Scoped β one instance per HTTP request.
DbContextis scoped. - Singleton β one instance for the whole app lifetime. Use for caches/config; must be thread-safe.
The classic bug: injecting a scoped DbContext into a singleton. The singleton captures one DbContext forever, which is not thread-safe and leaks stale data across requests β a "captive dependency". The DI container will actually throw if it detects this at startup in development.
- Controllers vs Minimal APIs: both are first-class. Minimal APIs are concise and fit small focused services; controllers provide conventions, filters, and organization for larger APIs. Choose by complexity and team conventionsβnot by a blanket performance rule.
- Keyed DI services: when several implementations share one interface, register and resolve keyed services with
AddKeyedScoped/AddKeyedSingletonand[FromKeyedServices]. Prefer ordinary constructor injection when one implementation is enough; excessive runtime key selection can hide dependencies. - Middleware vs filters: middleware is framework-wide and runs before MVC/endpoint-specific behavior. MVC filters have action/controller context. Endpoint filters provide similar interception for Minimal APIs.
- Binding, validation and errors: map route/query/header/body data to request models, validate at the boundary, and return standardized RFC 7807/9457-style
ProblemDetails. Use global exception handling such asIExceptionHandler; do not leak stack traces. - Serialization: ASP.NET Core commonly uses
System.Text.Json. Return DTOs rather than EF entities, define naming/null/enum behavior intentionally, prevent reference cycles, and use streaming/source generation only when profiling justifies it. - Authentication and authorization: authentication builds a
ClaimsPrincipal; authorization evaluates roles, claims, policies, or resource-based handlers.UseAuthentication()must run beforeUseAuthorization(). - OpenAPI: expose an OpenAPI document for API discovery, generated clients, and testing. Know request/response contracts, content negotiation, versioning strategy, and backward compatibility.
IHttpClientFactory: do not create and dispose anHttpClientper request. Use factory-created named/typed clients, or a long-lived client configured withSocketsHttpHandler.PooledConnectionLifetime, so connections are reused without pinning DNS indefinitely. Add explicit timeouts and resilience policies.- Background work: never fire-and-forget request-scoped work with unobserved
Task.Run. Use a bounded queue plusBackgroundService, or an external broker/worker. Create a DI scope inside the worker when scoped services are needed. - Configuration and options: configuration is layered from files, environment variables, secret stores, and command-line values. Bind to
IOptions<T>, validate options at startup, and useIOptionsSnapshot/IOptionsMonitoronly when reload semantics are needed. - Operations: use structured
ILoggerlogs, OpenTelemetry traces/metrics, correlation or trace IDs, and separate liveness/readiness health checks. Never log passwords, tokens, or sensitive personal data.
2.7 Testing
- Unit tests: test behavior of a small unit quickly. Use fakes/mocks at real external boundaries; do not mock every internal class or assert implementation details.
- Integration tests: use
WebApplicationFactory<Program>to run the real ASP.NET Core pipeline. EF Core's InMemory provider is not relational and can hide constraint, transaction, translation, and SQL differences. Use SQLite for lightweight relational tests or a disposable instance of the production engine when fidelity matters. Testcontainers is valuable when Docker is available; it is an option, not a universal requirement. - Contract and end-to-end tests: contract tests protect service/API compatibility; end-to-end tests cover a few critical user journeys. Keep the suite deterministic and isolate test data.
- Structure: Arrange, Act, Assert. Name tests by observable behavior, for example
Withdraw_WhenBalanceTooLow_Throws. Include happy paths, boundaries, failures, authorization, cancellation, and concurrency where relevant. - Tools: xUnit, NUnit, or MSTest; a mocking library when useful; and built-in or third-party assertion helpers. The tool matters less than readable, stable tests.
2.8 Checkpoint interview Q&A
abstract class and interface?An interface is a contract with (traditionally) no implementation or state; a class can implement many. An abstract class can hold fields, constructors, and shared method bodies, but a class can inherit only one. Use an interface for a capability that unrelated types can have; use an abstract class when subclasses genuinely share code and identity ("is-a"). Since C# 8, interfaces can have default methods, but they still can't hold instance state.
IEnumerable vs IQueryable with a bug.IEnumerable runs LINQ in memory; IQueryable translates it to SQL. Bug: IEnumerable<User> users = db.Users; var active = users.Where(u => u.IsActive); β because it's typed as IEnumerable, EF loads the whole Users table then filters in C#. Keep it IQueryable and the Where becomes a SQL WHERE, fetching only matching rows.
.Result and .Wait()?They block ThreadPool threads while asynchronous work is pending, reducing throughput and potentially causing ThreadPool starvation under load. ASP.NET Core has no request SynchronizationContext by default, so the classic UI/legacy-ASP.NET deadlock is not the normal explanation, although deadlocks remain possible with custom synchronization or locks. Await the operation instead.
When two threads access shared mutable state concurrently and the outcome depends on timing β e.g. both read a counter as 5, both write 6, losing an increment. Prevent it with a lock, atomic operations (Interlocked.Increment), concurrent collections, or by making the state immutable so there's nothing to corrupt.
Registering a service as Singleton that depends on a Scoped DbContext. The singleton holds one DbContext for the app's life; it's used across many concurrent requests (not thread-safe) and never refreshes, causing data corruption and stale reads. Fix: make the service Scoped, or resolve a fresh DbContext per operation via a factory.
2.9 More C# features interviewers ask about
Delegates, Func, Action, events
A delegate is a type-safe function pointer. Func<int,int> returns a value; Action<T> returns void; Predicate<T> returns bool. Events are a publisher/subscriber wrapper over delegates so subscribers can only +=/-=, not invoke or overwrite.
Lambdas & closures
A lambda (x => x * 2) is an inline function. A closure captures variables from its surrounding scope β a classic bug is capturing a loop variable and finding all lambdas share the final value (fixed by copying into a local inside the loop; modern C# fixes foreach).
record vs class vs struct
class = reference type with reference equality by default. struct = value type, copied by value, best when small and preferably immutable. record class adds value-based equality and concise nondestructive mutation with with; it is not automatically immutable unless its members are init-only/read-only. record struct is the value-type form.
ref, out, in, params
ref passes by reference (read/write), out must be assigned by the callee (e.g. TryParse), in passes a readonly reference (avoids copying big structs), params accepts a variable number of arguments.
yield return (iterators)
yield return builds a lazy sequence β values are produced on demand as the caller enumerates, without materializing a full list. Great for streaming large data.
Extension methods
Add methods to existing types without modifying them (static method with a this first parameter). All of LINQ is extension methods on IEnumerable<T>.
Pattern matching & switch expressions
Modern C# lets you branch on type/shape: obj switch { Circle c => c.R, Square s => s.Side, _ => 0 } β concise and exhaustive.
Nullable reference types
string? vs string lets the compiler warn about possible nulls at compile time, reducing NullReferenceExceptions β the "billion-dollar mistake".
Reflection and attributes
Reflection inspects types and metadata at runtime; attributes attach metadata used by ASP.NET Core routing, validation, serialization, and EF Core mapping. Reflection is powerful but slower, harder to trim/AOT, and should not replace ordinary polymorphism.
Span<T> / Memory<T>
Allocation-free views over arrays/strings for high-performance parsing β slice without copying. Mentioning these signals depth.
IAsyncEnumerable<T>
Combines IEnumerable with async. Great for streaming data from a database or API one chunk at a time without loading it all into memory. You consume it with await foreach.
Recent language features Lower priority
Primary constructors and collection expressions arrived in C# 12; C# 14 adds features such as extension members and the field-backed-property field keyword. Know the version used by the employer, but expect interviews to value async, generics, LINQ, nullable references, equality, and runtime behavior much more than syntax trivia.
2.10 Garbage collection (be able to explain it)
.NET manages memory automatically with a generational, tracing garbage collector:
- Gen 0 β new, short-lived objects; collected frequently and cheaply.
- Gen 1 β survivors of a Gen 0 collection; a buffer between short- and long-lived.
- Gen 2 β long-lived objects (caches, singletons); collected rarely.
- Large Object Heap (LOH) β large allocations (commonly β₯85 KB) are collected with Gen 2 and are compacted less routinely, so repeated large allocations can cause fragmentation and long pauses.
- Workstation vs Server GC β Server GC (the ASP.NET Core default) uses per-core heaps and parallel collection for throughput at the cost of memory; Workstation GC favors a smaller footprint. Gen 0/1 collections are frequent and cheap; blocking full Gen 2 collections are what cause noticeable pauses.
The idea (the "generational hypothesis"): most objects die young, so collecting Gen 0 often and Gen 2 rarely is efficient. You rarely call GC.Collect() manually β instead you avoid leaks: unsubscribe from events, dispose IDisposable, and don't hold references longer than needed. A memory leak in .NET is usually an object still referenced somewhere (e.g. a static list or an event handler) so the GC can't reclaim it.
Managed vs unmanaged resources: the GC handles managed memory automatically, but types like FileStream, Socket, and DbConnection are managed wrappers around unmanaged OS resources β release them deterministically with Dispose()/using. Finalizers are only a safety net: they run late (if at all) and keep objects alive an extra GC cycle.
Assignment and by-value parameter passing copy the value-type data, but copy the reference for reference types, so both reference variables can point to the same object. Either kind may be stored on the managed heap depending on context; storage location is not the definition.
Boxing wraps a value type in a heap object to treat it as object (or a non-generic interface). It allocates and adds GC pressure; in hot loops it's a real cost. Generics (List<int>) avoid it.
const is compile-time, baked into callers, must be a literal. readonly is set at runtime (in the constructor) and can differ per instance. Prefer readonly for values that might change between builds.
For reference types == is reference equality by default (unless overloaded, as string does), while .Equals() can be overridden for value equality. records give value equality for both automatically.
3Databases Must know
Nearly every backend interview has a database round, and many ask you to write SQL on a whiteboard or screen share. This section gives you the mechanics and the actual queries so you can reproduce them.
3.1 Relational fundamentals & normalization
- Primary key (PK) β uniquely identifies a row (often an auto-increment
idor a GUID). - Foreign key (FK) β a column pointing at another table's PK; the database enforces that the referenced row exists (referential integrity).
- Relationships: one-to-one, one-to-many (a user has many orders β the FK lives on
orders), many-to-many (students β courses β needs a junction tableenrollments).
Normalization removes redundancy so data can't contradict itself:
- 1NF β every column holds one atomic value (no comma-separated lists in a cell).
- 2NF β no column depends on only part of a composite key.
- 3NF β no non-key column depends on another non-key column (store
cityonce in anaddressestable, don't repeat it per order). - Denormalization β deliberately duplicating data to avoid expensive joins on read-heavy paths. It's a performance trade-off you now maintain by hand.
3.2 SQL you must write fluently β with full solutions
The examples are mostly portable SQL. Dialects differ: SQL Server commonly uses TOP or OFFSET β¦ FETCH, while PostgreSQL/MySQL use LIMIT. State which dialect you are using in an interview.
-- Filtering, sorting, limiting
SELECT TOP (10) name, email FROM users
WHERE country = 'NO' AND is_active = 1
ORDER BY created_at DESC;
-- Aggregation: WHERE filters ROWS, HAVING filters GROUPS
SELECT country, COUNT(*) AS user_count, AVG(age) AS avg_age
FROM users
WHERE is_active = 1
GROUP BY country
HAVING COUNT(*) > 100;
Join types (know exactly what each returns)
| Join | Returns |
|---|---|
| INNER JOIN | Only rows that match in both tables. |
| LEFT JOIN | All left rows; NULLs where the right has no match. (Great for "find records with none".) |
| RIGHT JOIN | All right rows; NULLs where the left has no match. |
| FULL OUTER JOIN | All rows from both, matched where possible. |
| CROSS JOIN | Every combination (Cartesian product). |
Two standard answers β know both:
-- Simple, portable
SELECT MAX(salary) FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);
-- General "Nth highest" via a window function
SELECT DISTINCT salary FROM (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
) t
WHERE rnk = 2;
LEFT JOIN and keep rows where the right side is NULL:
SELECT u.id, u.name
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;Use DENSE_RANK if ties should all be returned; use ROW_NUMBER only when exactly one deterministic winner is required:
SELECT department_id, name, salary FROM (
SELECT department_id, name, salary,
DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS salary_rank
FROM employees
) t
WHERE salary_rank = 1;SELECT email, COUNT(*) AS n
FROM users
GROUP BY email
HAVING COUNT(*) > 1;3.3 Indexes β how they actually speed things up
An index is an auxiliary access structureβcommonly a B-treeβthat can let the optimizer seek to matching rows instead of scanning every row. Complexity is often described as roughly O(log n), but real performance also depends on selectivity, pages read, key lookups, caching, and how many rows are returned.
- Trade-off: reads get faster, but every
INSERT/UPDATE/DELETEmust also update the index, and indexes use storage. Don't index everything. - Clustered vs non-clustered: in SQL Server, the clustered index organizes table rows by its key and there can be only one; non-clustered indexes are separate structures. Other engines use different storage models, so do not present this as universal SQL behavior.
- Composite index β spans multiple columns; the order matters (leftmost-prefix rule): an index on
(country, city)helps queries filtering bycountryorcountry + city, but not bycityalone. - Covering and filtered indexes: included columns can let SQL Server satisfy a query from the index alone; filtered indexes target a selective subset such as active rows.
- SARGability: write predicates the engine can seek on. For example,
WHERE CreatedAt >= @startis usually better than wrappingCreatedAtin a function. Use the actual execution plan and measured readsβnot guessesβto verify improvement.
3.4 Transactions & ACID
A transaction groups statements so they all commit or all roll back β critical for things like transferring money (debit one account, credit another; you can't do just half).
- A β Atomicity: all steps succeed or none do.
- C β Consistency: a committed transaction preserves declared constraints and application invariants when the transaction logic is correct.
- I β Isolation: controls how concurrent transactions observe and interfere with one another.
- D β Durability: once committed, data survives a crash (written to disk/log).
Isolation levels trade correctness against concurrency, from weakest to strongest:
| Level | Prevents | Still allows |
|---|---|---|
| Read Uncommitted | nothing | dirty reads (see uncommitted data) |
| Read Committed | dirty reads | non-repeatable reads |
| Repeatable Read | non-repeatable reads | phantom reads |
| Serializable | everything (acts serial) | β (most locking/contention) |
Exact behavior is database-specific. SQL Server also offers snapshot-based isolation; PostgreSQL implements some named levels using multiversion concurrency control. In an interview, explain the anomaly you need to prevent rather than assuming every engine behaves identically.
3.5 The N+1 problem & EF Core pitfalls
The most-asked ORM question. With lazy-loading proxies enabled, you fetch a list (1 query), then access a related entity per item (N more queries) β 1 + N round-trips that can crush performance. EF Core does not enable lazy loading automatically.
// BAD β N+1: one extra query per order to load its user
var orders = db.Orders.ToList();
foreach (var o in orders)
Console.WriteLine(o.User.Name); // lazy loads each time
// GOOD β project only the columns the endpoint needs.
var orders = await db.Orders
.Select(o => new { o.Id, UserName = o.User.Name })
.ToListAsync(cancellationToken);
EF Core Essentials for Interviews
- Tracking vs no-tracking: tracking is useful when loaded entities will be updated. Use
AsNoTracking()for read-only entity queries when identity resolution/change tracking is unnecessary; projection to DTOs is often even better. Measure before making a global rule. IQueryablevsIEnumerable:ToList(),First(), enumeration, and similar terminal operations execute the query.AsEnumerable()does not execute immediately, but later LINQ operators run in .NET rather than being translated, which can cause far more data to be fetched.- Query shape: use projection for API responses.
Includeis appropriate when materializing entity graphs, but multiple collection includes can create a cartesian explosion. ConsiderAsSplitQuery()and understand that several intentional queries can be better than one huge join. - Loading: know eager loading (
Include), explicit loading, and optional lazy loading. Avoid accessing navigation properties in loops without knowing which SQL is generated. - Migrations: create with
dotnet ef migrations addand apply withdotnet ef database update. For controlled production deployments, reviewed idempotent scripts or migration bundles are often safer than every app instance migrating on startup. - Concurrency: Use a
[Timestamp]or[ConcurrencyCheck]property. EF checks this token duringUPDATEand throws aDbUpdateConcurrencyExceptionif another user modified the row first (optimistic locking). - Transactions: one relational
SaveChangescall is transactional by default. Use an explicit transaction only when several operations must share a larger atomic boundary, and keep that boundary short. - Set-based updates: EF Core 7+ adds
ExecuteUpdateAsync/ExecuteDeleteAsyncfor bulk UPDATE/DELETE without loading entities. They execute immediately, bypass change tracking, and do not participate inSaveChangesβ combine them with tracked changes only inside an explicit transaction. DbContextpooling:AddDbContextPoolcan reduce context setup allocations in measured high-throughput services. A pooled context is reset and reused, so do not retain request-specific mutable state on it; pooling is an optimization, not a substitute for efficient SQL.
3.6 SQL vs NoSQL β how to choose
| SQL (relational) | NoSQL | |
|---|---|---|
| Schema | Explicit and database-enforced | Often flexible or application-enforcedβnot literally βno schemaβ |
| Strengths | Relationships, joins, ACID, complex queries | Horizontal scale, flexible shapes, high throughput |
| Examples | PostgreSQL, SQL Server, MySQL | MongoDB (document), Redis (key-value), Cassandra (wide-column), Neo4j (graph) |
| Choose when | Data is relational & consistency matters (most business apps) | Huge scale, flexible/evolving schema, or a specific access pattern (caching, time-series) |
Many real systems use both (polyglot persistence): PostgreSQL for core data, Redis for caching/sessions, a search engine for full-text.
3.7 Full interview Q&A
WHERE filters individual rows before grouping; HAVING filters groups after GROUP BY and can use aggregates like COUNT(*). You can't put an aggregate in WHERE.
An index is an auxiliary access structure, commonly a B-tree, that can reduce pages read for selective filters, joins, and ordering. It consumes storage and write/maintenance cost, and a low-selectivity or badly ordered index may not help. Verify with actual plans and measurements.
When you want rows from the left table even if there's no match on the right β e.g. "list all customers and their order count, including customers with zero orders". An INNER JOIN would silently drop the zero-order customers.
Prefer a projection with Select when returning DTOs, or eager-load the required graph with Include. Inspect generated SQL and query counts. The goal is a deliberate, bounded query shapeβnot always exactly one query, because split queries can be the safer choice.
3.8 Advanced SQL you should recognize
CTEs (Common Table Expressions) & recursion
A CTE is a named query expression scoped to one statement that can improve readability. It is not necessarily materialized; the optimizer may inline or otherwise transform it. Recursive CTEs can walk hierarchies such as org charts and category trees.
-- Readable multi-step query
WITH active_users AS (
SELECT id, name FROM users WHERE is_active = 1
),
big_spenders AS (
SELECT user_id, SUM(total) AS spent FROM orders GROUP BY user_id HAVING SUM(total) > 1000
)
SELECT a.name, b.spent
FROM active_users a JOIN big_spenders b ON b.user_id = a.id;
Window functions (very common in senior interviews)
They compute across a set of rows related to the current row without collapsing them (unlike GROUP BY):
ROW_NUMBER()β unique sequential number per partition.RANK()/DENSE_RANK()β ranking with/without gaps on ties.LAG()/LEAD()β previous/next row's value (great for month-over-month deltas).SUM() OVER (...)β running totals.
-- Running total of order amounts per user, in date order
SELECT user_id, created_at, total,
SUM(total) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total
FROM orders;
Subqueries vs JOINs
Many equivalent queries are transformed into similar plans by modern optimizers; neither syntax is inherently faster. Correlated subqueries can become expensive if repeatedly evaluated. EXISTS clearly expresses an existence check and avoids some NULL surprises, while IN may be equally efficient. Compare actual plans and measured reads.
Views & stored procedures
A view is a stored query definition selected from like a table; it can simplify access and restrict exposed columns. A stored procedure is server-side SQL code with parameters and permissions. Both ad-hoc parameterized SQL and procedures can use cached execution plans, so βstored procedures are precompiled and therefore always fasterβ is not a reliable rule.
NULLs, constraints and set operations
NULLmeans unknown/missing, so useIS NULL, not= NULL. SQL uses three-valued logic; predicates involving NULL may evaluate to unknown.- Use
NOT NULL,UNIQUE,CHECK, foreign keys, and appropriate defaults to protect invariants close to the data. UNIONremoves duplicates;UNION ALLkeeps them and is usually cheaper.- For large ordered result sets, keyset pagination is usually more stable and efficient than deep
OFFSETpaging.
3.9 Locking, deadlocks & concurrency control
- Pessimistic locking β acquire locks so conflicting work waits. Syntax is engine-specific: PostgreSQL/MySQL support forms of
SELECT ... FOR UPDATE; SQL Server uses transaction isolation and locking behavior/hints. Safe for some workflows but reduces concurrency. - Optimistic concurrency β don't hold a lock while the user works; include a version/timestamp predicate in the update. If no row is updated, resolve or retry the conflict. EF Core supports this when you configure a concurrency token.
- Deadlock β transaction A locks row 1 then wants row 2; transaction B locks row 2 then wants row 1. Neither proceeds. The DB detects it and kills one ("deadlock victim"). Avoid by always acquiring locks in a consistent order and keeping transactions short.
3.10 Scaling & performance
- Replication β primary handles writes, read replicas serve reads (eventual consistency lag to watch for).
- Partitioning/sharding β split a huge table by a key (range, hash, or geography). Trade-off: cross-shard queries and joins get hard.
- Query tuning β inspect the actual execution plan and logical reads, select only needed columns, use selective/SARGable predicates, avoid accidental cartesian products, and use keyset pagination for deep pages.
- Connection pooling β ADO.NET providers normally pool physical connections. Open late, dispose promptly, and let pooling reuse them; a short-lived
DbContextdoes not imply a new physical connection every time.
GROUP BY collapses rows into one per group (you lose the detail rows). A window function keeps every row but adds an aggregate/rank alongside it. Use window functions when you need "the row and its rank/running total".
Reproduce with representative parameters, inspect the actual plan, duration, waits, and logical reads, then fix the measured cause: poor cardinality estimates, non-SARGable predicates, missing/unused indexes, excess columns/rows, blocking, spills, bad joins, N+1 round-trips, or stale statistics. Cache or precompute only after the query and access pattern are understood.
Optimistic concurrency checks a version when writing and resolves conflicts after they occur, which suits many disconnected web workflows. Pessimistic concurrency holds locks to prevent conflicting access, which may be necessary for short high-contention operations but reduces throughput and can deadlock. Choose by invariant, contention, and transaction duration.
Two transactions acquire the same locks in opposite orders and wait on each other. Prevent by acquiring resources in a consistent order, keeping transactions short, using lower isolation where safe, and adding retry logic for the deadlock victim.
4System Design & Backend Architecture Mid-level+
For junior/mid roles this is usually "design a REST API" or "how would you structure this feature". For senior roles it's a full "design Twitter/Uber" round. Either way, learn the vocabulary below and the repeatable method β then rehearse the full walkthrough at the end.
4.1 REST API design
- Resources are nouns; HTTP methods express semantics:
GET /users(list) Β·GET /users/42(one) Β·POST /users(create/process) Β·PUT /users/42(create or replace at a known URI) Β·PATCH /users/42(partial change) Β·DELETE /users/42. - Status codes: 200 OK Β· 201 Created Β· 204 No Content Β· 400 Bad Request Β· 401 Unauthorized (not logged in) Β· 403 Forbidden (logged in, not allowed) Β· 404 Not Found Β· 409 Conflict (e.g. duplicate) Β· 422 Unprocessable Β· 429 Too Many Requests Β· 500 Server Error Β· 503 Unavailable.
- Statelessness: each request carries everything needed (e.g. an auth token); the server keeps no per-client session in memory, so any server can handle any request β that's what makes horizontal scaling possible.
- Idempotency: PUT and DELETE are idempotent by HTTP semantics; GET and HEAD are also safe. POST is usually not idempotent, and PATCH depends on the patch operation. Use an idempotency key for retried commands such as payments.
- Contracts: validate at the boundary, return
ProblemDetails, publish OpenAPI, and preserve backward compatibility. Use ETags/If-Matchfor conditional updates where lost updates matter. - Collections: filter and sort explicitly, cap page size, and prefer cursor/keyset pagination for large changing datasets. Version only when a breaking contract change cannot be evolved compatibly.
4.2 Authentication & Authorization (JWT deep dive)
AuthN = "who are you?" Β· AuthZ = "what are you allowed to do?"
JWT (JSON Web Token) is one access-token format, not an authentication protocol by itself. A common flow is:
- User logs in with credentials; server verifies them.
- An authorization server issues a signed access token containing limited claims and an expiry.
- The client sends it to the resource API, commonly in the HTTP Authorization header using the Bearer scheme (
Authorization: Bearer <token>). - The API validates the allowed signing algorithm, signature, issuer, audience, lifetime, and required claims before authorizing the request.
- Short-lived access tokens limit exposure. Refresh tokens require secure storage, rotation, reuse detection, revocation, and server-side records.
A signed JWT has three parts: header.payload.signature, base64url-encoded. The payload is readable by anyone (not encrypted) β never put secrets in it. Security comes from the signature: tampering invalidates it.
- OAuth 2.0 is an authorization framework for obtaining delegated access tokens; OpenID Connect (OIDC) adds authentication and identity claims. For browser/mobile public clients, know Authorization Code + PKCE.
- JWT trade-off: local validation can avoid a database lookup, but immediate revocation is harder. Sensitive systems may use reference tokens/introspection or a revocation strategy.
- Storage: do not automatically recommend localStorage. Browser apps often use secure
HttpOnly,Secure, appropriateSameSitecookies or a backend-for-frontend; the right choice depends on XSS, CSRF, and architecture. - Passwords: prefer ASP.NET Core Identity or an external identity provider. Use adaptive salted password hashing; never design a custom password scheme.
4.3 Caching
Store expensive results somewhere faster so you do not repeatedly compute or fetch them. Layers include HTTP/browser and CDN caches, per-instance IMemoryCache, and shared IDistributedCache/Redis. In-memory cache is fastest but not shared across replicas; distributed cache adds network and operational cost.
Cache-aside is the common pattern:
value = cache.Get(key);
if (value == null) { // cache miss
value = db.Load(key); // go to the source
cache.Set(key, value, ttl); // populate for next time
}
return value;
- ASP.NET Core output caching:
AddOutputCache/UseOutputCachecan cache complete endpoint responses with policies for expiry, vary-by values, tags, and invalidation. Cache only responses that are safe to reuse; authenticated or user-specific output needs explicit partitioning or should not be cached.
Correctness comes first: define acceptable staleness, TTL, invalidation, key design, serialization, and what happens when the cache is unavailable. Prevent a cache stampede with single-flight locking, stale-while-revalidate, or randomized TTLs. Never treat Redis as automatically faster for every object or as the only durable copy unless the design explicitly supports that.
4.4 Scaling
- Vertical scaling β a bigger machine (more CPU/RAM). Simple, but has a ceiling and a single point of failure.
- Horizontal scaling β more machines behind a load balancer that spreads requests. Needs stateless servers (or a shared session/cache store) so any server can serve any request.
- Database scaling:
- Read replicas β copies that serve read traffic; writes go to the primary and replicate out (eventual consistency).
- Sharding/partitioning β split data across DBs by a key (e.g. user id range) when one DB can't hold it all.
- Connection pooling β reuse a pool of DB connections instead of opening one per request.
- CDN β caches static content at edge locations near users to cut latency.
4.5 Async processing & message queues
Slow or unreliable work (sending email, processing images, generating reports, calling a flaky third party) shouldn't block the user's request. Put a message on a queue and let background workers process it.
- Producer β queue β consumer. The API returns immediately; workers handle the job. This decouples services and smooths traffic spikes (the queue absorbs bursts).
- Delivery guarantees: most queues are at-least-once, so a message can be delivered twice β make consumers idempotent (processing the same message twice has no extra effect).
- Dead-letter queue β messages that keep failing get moved aside for inspection instead of blocking the queue.
- Backpressure and ordering: bound queues and consumer concurrency so producers cannot exhaust memory. Ordering is usually guaranteed only within a partition/session, not globally.
- In-process producer/consumer:
System.Threading.Channelsprovides asynchronous bounded queues for work inside one process. A bounded channel gives backpressure and fits aBackgroundService; use a durable external broker when work must survive process failure or cross service boundaries. - Failure handling: use bounded retries with backoff, detect poison messages, include correlation and message IDs, and monitor queue ageβnot just queue length. βExactly onceβ normally requires application-level idempotency and transactional design.
- Tools: RabbitMQ, Azure Service Bus, AWS SQS, Kafka (Kafka is really a distributed log, great for event streaming).
4.6 Architecture styles & .NET ecosystem
- Monolith β one deployable app. Simple to build, test, and deploy; great to start with. Downsides: one small change redeploys everything, and you can't scale parts independently.
- Modular monolith β one deployment with explicit module boundaries and controlled dependencies. It preserves simple operations while making later extraction possible; this is often a better default than immediate microservices.
- Microservices Role-dependent β independently deployable services can support team autonomy and targeted scaling, but introduce network failure, data ownership, versioning, observability, and operational complexity. Start with clear boundaries and a concrete reason.
- Layered (n-tier): Controller/API β Application/Service β Domain β Data-access. Each layer only talks to the one below.
- Clean / Hexagonal: the domain sits at the center with no dependencies; infrastructure (DB, HTTP, queues) plugs in at the edges and dependencies point inward. Very common in modern .NET.
- SignalR: .NET's library for real-time web functionality. Uses WebSockets under the hood with fallbacks. Great for chat apps, live dashboards, or progress notifications.
- YARP Optional β a customizable .NET reverse-proxy toolkit. Know it only if the role or existing architecture uses it; reverse-proxy and gateway concepts matter more than the product name.
4.7 Reliability, observability & CAP
- Observability: logs explain events, metrics expose trends and saturation, and traces follow work across dependencies. Use OpenTelemetry and propagate trace context rather than inventing isolated correlation IDs.
- SLI/SLO/SLA: an SLI is a measured indicator such as latency or error rate; an SLO is the target; an SLA is an external commitment. Error budgets guide how much unreliability is acceptable.
- Recovery: RTO is the target time to restore service; RPO is the maximum acceptable data-loss window. Backups are not enoughβrestore them in tests.
- Resilience: start with deadlines/timeouts. Retry only transient, safe/idempotent operations, with exponential backoff and jitter. Add circuit breakers, concurrency limits, rate limiting, and graceful degradation. Modern .NET can use resilience handlers built on Polly.
CAP theorem, accurately: when a network partition separates nodes, a distributed data system cannot guarantee both linearizable consistency and that every request receives a non-error response. It must favor consistency or availability for the affected operation until the partition heals. Outside a partition, CAP does not force a permanent βpick twoβ choice, and different features in one system can make different trade-offs.
4.8 Full worked walkthrough β "Design a URL shortener"
This is the most common junior/mid system-design prompt. Here's how to talk through it using the 7-step method.
1. Clarify requirements. Functional: shorten a long URL β short code; visiting the short code redirects. Non-functional: very read-heavy (redirects β« creates), low latency, highly available, links don't expire (or optionally do). Ask: expected scale? Say ~100M new URLs/month, ~10:1 read:write.
2. Estimate scale. 100M/month β 40 writes/sec, ~400 reads/sec before peak factors. Storage at 500 bytes per record is roughly 50 GB/month before indexes/replication. These request rates are not automatically βsharding scaleβ; start with a well-indexed primary database and add complexity only when measurements require it.
3. API.
POST /shorten { "url": "https://..." } β { "short": "abc123" }
GET /abc123 β redirect to the original URL
4. Data model & the key trick. A table links(id, short_code, long_url, created_at). The core question is how to generate a short unique code:
- Take an auto-increment
idand base-62 encode it (0-9,a-z,A-Z) β 7 chars covers ~3.5 trillion links. Simple, collision-free, but codes are guessable/sequential. - Or hash the URL and take the first 7 chars, handling collisions by retrying. Non-sequential but needs collision checks.
A relational database with a unique index on short_code is a sensible start. A key-value store becomes attractive only when the access pattern and measured scale justify its operational trade-offs.
5. High-level design. Client β load balancer β stateless app servers β database. Add Redis for demonstrably hot short codes when database load or latency requires it; do not make every design depend on a cache from day one.
6. Deep dive β the read path. Redirects dominate, so cache hot codes only when useful. Choose redirect semantics intentionally: a 301/308 may be cached permanently and can reduce analytics/control; a 302/307 preserves flexibility. Read replicas are a later scaling option and introduce replication lag.
7. Bottlenecks & trade-offs. At the stated scale, an identity/sequence is unlikely to be the bottleneck. At much larger multi-region write scale, discuss distributed ID generation or allocated ranges. Protect immediate read-after-write behavior, handle malicious URLs/abuse, and publish click analytics asynchronously only if analytics volume would slow redirects.
Other prompts to rehearse the same way: pastebin, rate limiter, news feed, chat app, a "like" service, a file storage service.
System design Q&A:
- Vertical vs horizontal scaling? Vertical = bigger box (simple, capped, single point of failure); horizontal = more boxes + load balancer (scales far, needs stateless servers).
- Why a message queue? To do slow work off the request path, decouple services, and absorb spikes β e.g. send confirmation emails asynchronously after checkout.
- When NOT to use microservices? Small team, early product, unclear domain boundaries β the operational overhead outweighs the benefits; start with a well-structured monolith.
4.9 REST vs gRPC vs GraphQL
| REST | gRPC | GraphQL | |
|---|---|---|---|
| Format | JSON over HTTP | Protobuf over HTTP/2 (binary) | JSON, single endpoint |
| Best for | Public APIs, simplicity | Fast internal service-to-service | Clients needing flexible/precise data |
| Strengths | Universal, HTTP-cache friendly, simple | Compact typed contracts, streaming, efficient internal calls | Client-selected fields and graph-shaped queries |
| Weaknesses | Over/under-fetching | Not browser-native, binary to debug | Caching & rate-limiting are harder |
Over-fetching means an endpoint returns more than needed; under-fetching means several calls are needed. GraphQL can reduce both, but resolvers can create N+1 queries, authorization and cost controls are harder, and one client query may still trigger many backend calls.
4.10 Load balancing & consistent hashing
- Load-balancing algorithms: round-robin (rotate), least-connections (send to the least busy), IP-hash / sticky sessions (same client β same server), weighted (bigger servers get more).
- L4 vs L7: Layer-4 balances on TCP/IP (fast, dumb); Layer-7 understands HTTP so it can route by path/host/header.
- Consistent hashing β when nodes change, naive
hash(key) % Nremaps most keys. Ring- or rendezvous-based hashing reduces movement and is associated with Dynamo-style systems and Cassandra. Redis Cluster instead maps keys to a fixed set of hash slots; understand the goal rather than claiming every product uses the same algorithm.
4.11 Rate limiting algorithms
- Fixed window β N requests per clock minute. Simple but allows bursts at boundaries.
- Sliding window β smooths the boundary problem.
- Token bucket β tokens refill at a steady rate; each request spends one; allows short bursts up to the bucket size. Most common in practice.
- Leaky bucket β processes at a constant rate, queueing/dropping overflow.
- ASP.NET Core implementation: register policies with
AddRateLimiterand addUseRateLimiterin the pipeline. Built-in policies support fixed/sliding windows, token buckets, and concurrency limits; partition by a trustworthy user, tenant, API key, or address and return429with useful retry metadata.
4.12 Distributed system patterns Senior / role-dependent
Idempotency
An operation that can be applied multiple times with the same intended effect. Critical with retries and at-least-once queues. Implement idempotency keys with an atomic insert/unique constraint, request fingerprint, status, and stored result; simply βcheck then insertβ has a race condition.
CQRS (Command Query Responsibility Segregation)
Separate command and query models so each can express different concerns; they may still use the same database. Separate stores and eventual consistency are optional extensions. Use CQRS when domain/read-model complexity justifies it, not merely because a mediator library is available.
Event Sourcing
Store the authoritative sequence of domain events instead of only current state, then build projections from those events. It offers auditability and temporal reconstruction but requires event versioning, projection rebuilds, idempotency, and significant operational discipline.
Saga pattern
Coordinates a sequence of local transactions across services, using compensating actions when later steps fail. Compensation is business remediationβnot a perfect rollbackβand both choreography and orchestration approaches have failure and observability trade-offs.
API Gateway
A single entry point in front of microservices that handles routing, auth, rate limiting, and aggregation β so clients don't call dozens of services directly.
Service discovery
In dynamic environments service instances come and go; a registry (Consul, Eureka, Kubernetes DNS) lets services find each other without hard-coded addresses.
Circuit breaker & bulkhead
Circuit breaker stops calling a failing dependency for a cooldown (fail fast, recover gracefully). Bulkhead isolates resources (thread pools) so one failing dependency can't sink the whole app. Polly implements both in .NET.
Outbox pattern
Write business changes and an outbox record in the same local database transaction, then publish the record asynchronously. This closes the βdatabase committed but message was lostβ gap, but publishing remains at-least-once, so consumers still need idempotency.
4.13 Cloud / Azure concepts for .NET When the job uses Azure
Many .NET roles use Azure, but cloud-product trivia is less important than understanding the underlying compute, storage, messaging, identity, networking, and observability concepts:
- Compute: App Service (PaaS for web apps), Azure Functions (serverless/event-driven), AKS (Kubernetes).
- Data: Azure SQL Database (managed relational), Cosmos DB (global NoSQL), Blob Storage (files/assets).
- Messaging: Service Bus (enterprise queues/topics), Event Grid (pub/sub), Event Hubs.
- Security: Key Vault (store secrets, connection strings without keeping them in code), Managed Identities (resources authenticate to each other without passwords).
4.14 Second walkthrough β "Design a rate limiter"
1. Requirements: limit each user/API-key to e.g. 100 requests/minute; must work across many app servers; low latency; fail open or closed (decide).
2. Approach: because servers are stateless and horizontally scaled, the counter must live in a shared store β Redis is ideal (fast, atomic operations).
3. Algorithm: token bucket or sliding-window counter. For a fixed window, increment a key such as rate:{userId}:{window} and set expiry. Use an atomic Lua script/transaction so increment and first-time expiry cannot separate; INCR alone is atomic, but a separate failed EXPIRE can leak keys.
4. Where it runs: at the API gateway or as middleware, before business logic, so rejected requests are cheap.
5. Trade-offs: fixed windows allow double bursts at boundaries β use sliding window or token bucket for smoothness. Decide fail-open (allow if Redis is down β availability) vs fail-closed (reject β safety). Return 429 Too Many Requests with a Retry-After header.
4.15 System design Q&A
The client sends an idempotency key (e.g. a GUID) with the request; the server records processed keys and, on a repeat, returns the stored result instead of doing the work again. GET/PUT/DELETE are naturally idempotent; POST needs the key.
Timeouts + retries with exponential backoff and jitter for transient errors, a circuit breaker to stop hammering it while it's down, a fallback/graceful degradation, and bulkheads so it can't exhaust shared resources.
Strong: every read sees the latest write (needed for balances/inventory) β costs latency/availability. Eventual: replicas converge over time; reads may be briefly stale (fine for likes, feeds, view counts) β scales and stays available. Choose per feature.
No single point of failure: multiple stateless app servers behind a load balancer, DB replication with failover, multi-AZ deployment, health checks, retries/circuit breakers, and graceful degradation so a partial outage doesn't take everything down.
Scale horizontally (autoscaling stateless servers), add caching (Redis/CDN) to cut DB load, use read replicas, move slow work to queues, and add rate limiting to protect the core. Identify the bottleneck first (usually the database).
5HTTP & Networking Fundamentals Must know
Backend engineers are expected to know what happens between the browser and the server. These come up constantly.
5.1 HTTP essentials
- Request/response model β client sends a method + path + headers + optional body; server replies with a status code + headers + body.
- Methods: GET/HEAD are safe and idempotent; POST processes or creates and is not inherently idempotent; PUT creates/replaces a resource at a known URI and is idempotent; PATCH applies a partial change whose idempotency depends on the patch; DELETE is idempotent by intended effect.
- Status families: 1xx info, 2xx success, 3xx redirect, 4xx client error, 5xx server error.
- Headers you should know:
Content-Type,Accept,Authorization,Cache-Control,ETag/If-None-Match/If-Match,Location,Retry-After,Set-Cookie, and tracing headers. - Idempotency & safety β "safe" = no side effects (GET); "idempotent" = same effect if repeated (GET/PUT/DELETE).
5.2 HTTP versions
- HTTP/1.1 β persistent connections are reusable, but there is no practical multiplexing; clients often use several connections and requests can block behind earlier work.
- HTTP/2 β multiplexes many requests over one connection, header compression, binary framing.
- HTTP/3 β runs over QUIC (UDP), removes TCP head-of-line blocking, faster connection setup.
5.3 What happens when you type a URL and press Enter
A favorite "does he understand the stack" question:
- DNS lookup β resolve the domain to an IP (cache β resolver β root β TLD β authoritative).
- TCP handshake β SYN, SYN-ACK, ACK establishes a connection (3-way handshake).
- TLS handshake β for HTTPS, verify the certificate and establish shared session keys; asymmetric cryptography authenticates/key-agrees, then efficient symmetric encryption protects application traffic.
- HTTP request sent; load balancer routes it to an app server.
- Server processes (auth, DB, cache) and returns HTML/JSON.
- Browser renders; fetches more assets (often via CDN).
5.4 TCP vs UDP, TLS, cookies vs tokens
- TCP β reliable, ordered, connection-based (web, DB). UDP β fast, connectionless, may drop packets (video, gaming, DNS).
- HTTPS/TLS β provides confidentiality, integrity, and server authentication. Certificates bind a public key to a host identity; clients must validate the chain and hostname.
- Cookies vs tokens: these are not opposites. A cookie is a browser transport/storage mechanism and can contain an opaque session ID or token. A bearer token is an authorization credential and can be sent in a header or cookie. Automatic cookie sending creates CSRF considerations; script-readable token storage creates XSS-exfiltration risk.
- CORS β a browser-enforced policy controlling which origins may read cross-origin responses. It is not authentication, does not protect server-to-server endpoints, and must not replace authorization.
- Real-time choices: WebSockets provide full-duplex communication, Server-Sent Events stream server-to-client updates over HTTP, and long polling is the fallback. SignalR abstracts transport selection and connection management.
- Reverse proxies: TLS termination, compression, request-size limits, forwarded headers, timeouts, and client IP handling often happen before Kestrel. Trust forwarded headers only from known proxies.
401 Unauthorized = not authenticated (no/invalid credentials β "log in"). 403 Forbidden = authenticated but not allowed to access this resource ("you can't do that").
POST submits a command or creates under a collection URI and is not inherently idempotent. PUT creates or replaces the representation at a known URI and is idempotent. PATCH applies a partial change; whether it is idempotent depends on the patch document, for example βset valueβ can be idempotent while βincrement valueβ is not.
6Design Patterns Know selectively
Reusable vocabulary for common design problems. Strategy, Factory, Adapter, Decorator, Observer, and Dependency Injection are the highest-value choices for typical .NET interviews. Do not memorize all 23 GoF patterns; explain the problem, trade-off, and why a simpler design is or is not enough.
6.1 Creational
- Singleton β one shared instance (a cache, config). In .NET, prefer registering it as a DI singleton over a static class.
- Factory / Factory Method β a method decides which concrete type to create, hiding construction from callers.
- Builder β construct a complex object step by step (fluent APIs like
StringBuilder, query builders).
6.2 Structural
- Adapter β wrap an incompatible interface to make it fit (e.g. wrap a third-party SDK behind your own interface).
- Decorator β add behavior by wrapping (logging/caching layer around a repository). ASP.NET middleware is decorator-like.
- Facade β a simple front over a complex subsystem.
- Proxy β stand-in that controls access (lazy loading, caching, access control).
6.3 Behavioral
- Strategy β swap an algorithm at runtime via an interface (different payment/shipping calculators). The Open/Closed principle in action.
- Observer β publish/subscribe; subscribers react to events (C#
events). - Template Method β a base class defines the skeleton, subclasses fill in steps.
- Mediator β objects communicate through a coordinating abstraction. Libraries such as MediatR can implement request/handler dispatch, but using a mediator does not automatically mean the architecture is CQRS.
6.4 Architectural / enterprise
- Repository β can provide a domain-focused persistence boundary when it expresses meaningful aggregate operations. EF Core's
DbSetandDbContextalready provide repository/unit-of-work-like behavior, so a generic CRUD repository often adds indirection and blocks useful query composition. - Unit of Work β tracks a set of changes and commits them together; EF Core's
DbContextalready serves this role. - Dependency Injection β supply dependencies from outside (the whole basis of ASP.NET Core).
- DTO β plain data objects to shape API input/output, decoupled from domain entities.
Interview tip: don't just name a pattern β say the problem it solves. "I'd use Strategy so I can add a new discount rule without modifying existing code (Open/Closed)." And beware over-engineering: interviewers respect knowing when not to add a pattern.
7Security β OWASP & Hardening Must know
Security questions separate juniors from mid-levels. Know the top risks and their fixes.
7.1 The classic web vulnerabilities
SQL Injection
Attacker-controlled text changes the meaning of a SQL command. Fix: parameterize values, restrict database permissions, and never concatenate input into SQL. ORMs help only when you use their parameterized APIs; raw SQL remains dangerous.
// VULNERABLE
var sql = "SELECT * FROM users WHERE name = '" + input + "'";
// SAFE β parameterized
cmd.CommandText = "SELECT * FROM users WHERE name = @name";
cmd.Parameters.Add("@name", SqlDbType.NVarChar, 100).Value = input;
XSS (Cross-Site Scripting)
Attacker injects <script> that runs in other users' browsers. Fix: encode/escape output, use a Content-Security-Policy, and sanitize HTML. Frameworks like Razor auto-encode by default.
CSRF (Cross-Site Request Forgery)
A malicious site tricks a browser into sending an authenticated state-changing request because cookies are attached automatically. Fix: anti-forgery tokens, suitable SameSite settings, and Origin/Referer validation. Header-based bearer tokens are not automatically CSRF-safe if application code also exposes them to an attacker through XSS.
Broken authentication / access control
Weak passwords, no lockout, or missing authorization checks (a user editing another user's data by changing the id β IDOR). Fix: enforce authorization on every request server-side, never trust the client.
Sensitive data exposure
Fix: HTTPS everywhere, a maintained adaptive password hasher, encryption for sensitive data where appropriate, and a managed secret store rather than source control or ordinary configuration files. Never log secrets or tokens.
SSRF (Server-Side Request Forgery)
An attacker makes the server request internal or cloud-metadata URLs. Fix: allow-list destinations, resolve and validate addresses carefully, block private/link-local ranges where appropriate, restrict egress, and do not blindly fetch user-provided URLs.
Overposting / mass assignment
Binding an API request directly to an entity can let users set fields such as IsAdmin or OwnerId. Fix: use dedicated request DTOs, map only allowed fields, and authorize ownership/resource access independently.
Path traversal, command injection and unsafe deserialization
Never combine user input directly into filesystem paths, shell commands, type names, or executable serializers. Canonicalize and constrain paths, avoid shells when APIs exist, use allow-lists, and apply strict request-size/depth limits.
7.2 General hardening checklist
- Validate input at every trust boundary with allow-lists, length/range limits, and request-size limits. Encode output for its destination context; βsanitize everythingβ is not a substitute for parameterization and output encoding.
- Principle of least privilege for DB users, services, and tokens.
- Keep dependencies patched (supply-chain / known-CVE risks).
- Rate limit and add lockouts to resist brute force.
- Don't leak stack traces or internal details in error responses.
- For cookies use
HttpOnly,Secure, and an intentionalSameSitevalue; rotate session identifiers after privilege changes and logout. - Use ASP.NET Core Data Protection for protected application data and a managed secret store/managed identity for service credentials. Plan key rotation and multi-instance key sharing.
- Log security-relevant events without logging credentials, access tokens, reset links, or sensitive personal data.
- Hashing vs encryption: hashing is one-way (passwords); encryption is reversible with a key (data in transit/at rest). Never "encrypt" passwords β hash them.
Use ASP.NET Core Identity or another maintained password hasher configured with an adaptive salted algorithm such as PBKDF2, bcrypt, or Argon2. Never store plaintext or use a fast general-purpose hash alone. Store the algorithm/work-factor metadata so hashes can be upgraded after login.
Parameterized queries or an ORM so user input is treated as data, not code; validate input; and give the DB account least privilege. Never concatenate input into SQL strings.
8DevOps β Git, CI/CD, Docker, Kubernetes Common in backend roles
8.1 Git (you will be asked)
- merge vs rebase: merge preserves history with a merge commit; rebase rewrites your commits on top of the target for a linear history. Don't rebase shared/public branches.
- Common flow: branch β commit β push β pull request β review β merge. Keep commits small and messages meaningful.
- Useful commands:
git stash(shelve changes),git cherry-pick(grab one commit),git revert(undo a commit safely),git reset(move the branch pointer β careful). - Resolving conflicts β Git marks both versions; you pick/merge the correct result and commit.
8.2 CI/CD
- CI (Continuous Integration) β every push builds and runs tests automatically, catching breakage early.
- CD (Continuous Delivery/Deployment) β automatically ship passing builds to staging/production.
- Deployment strategies: blue-green (two environments, switch traffic), canary (roll out to a small % first), rolling (replace instances gradually). All aim for zero-downtime and easy rollback.
- A useful pipeline: restore with a locked dependency graph β compile with warnings treated intentionally β unit/integration tests β security/license scans β build an immutable artifact/image β deploy progressively β smoke/health checks β observe β roll back if needed.
- Database changes: use backward-compatible expand/migrate/contract steps so old and new app versions can overlap during a rolling deployment.
8.3 Docker & Kubernetes
- Container β packages the app and runtime dependencies into an isolated process image. It improves repeatability but does not by itself solve configuration, state, networking, or observability.
- Image vs container β an image is the immutable template; a container is a running instance of it.
- Docker practices: use multi-stage builds, small supported runtime images, a non-root user, immutable tags/digests, environment/secret injection, and a correct
ENTRYPOINT. Write logs to stdout/stderr and handle SIGTERM for graceful shutdown. - Kubernetes Role-dependent β orchestrates containers: scheduling, scaling, replacement, service discovery, and rolling updates. Know Pod, Deployment, Service, Ingress/Gateway, ConfigMap/Secret, resource requests/limits, and liveness/readiness/startup probes if the job uses it.
- 12-Factor App β config in the environment, stateless processes, logs as streams, dev/prod parity β principles for cloud-native services.
- Infrastructure as Code: tools such as Bicep, Terraform, or Pulumi make environments reviewable and repeatable. Keep application configuration separate from secrets.
A VM virtualizes hardware and runs a full guest OS (heavy, slow to start). A container virtualizes the OS and shares the host kernel (lightweight, starts in ms). Containers give faster, denser deployments; VMs give stronger isolation.
Merge to integrate a finished branch while keeping true history (safe for shared branches). Rebase to tidy your local feature branch into a clean linear history before opening a PR. Never rebase commits others have already pulled.
9Practical Interview Rounds Must prepare
Many .NET employers care more about production judgment than puzzle difficulty. Prepare for these formats explicitly.
9.1 Project deep-dive
Choose one project you know well and prepare a 5β10 minute explanation:
- The user/business problem and your exact responsibility.
- The request flow: client β API β domain/service β database/external dependency.
- Why you chose the architecture and data model; what alternatives you rejected.
- Authentication, authorization, validation, error handling, logging, and tests.
- One production bug or performance issue, how you diagnosed it, and the measured result.
- What you would change with more time. Honest trade-offs are stronger than claiming perfection.
9.2 Debugging and troubleshooting
Use a disciplined sequence
- Define impact: who is affected, since when, which endpoints/regions/versions, and whether data integrity is at risk.
- Stabilize: roll back, disable a feature flag, shed load, or fail over if the incident is active.
- Gather evidence: deployment history, structured logs, traces, metrics, exception details, dependency health, database waits and query plans. On a live .NET process,
dotnet-counters,dotnet-trace, anddotnet-dump/dotnet-gcdumpcapture metrics, traces, and heap dumps. - Narrow the fault: application vs database vs network vs dependency; compare healthy and unhealthy requests; reproduce safely.
- Fix and prove: add a focused regression test, deploy gradually, observe the relevant SLI, and document prevention.
Common scenarios
- High latency: inspect p95/p99, traces, ThreadPool/CPU, connection pools, slow SQL, N+1 queries, locks, downstream timeouts, payload size, and GC pressure.
- High CPU: profile before optimizing; look for tight loops, expensive serialization/regex, excessive parallelism, retry storms, and hot allocations.
- Memory growth: distinguish managed heap, native memory, cache growth, retained event handlers/statics, unbounded queues, and large-object allocations; compare heap snapshots.
- Intermittent 500s: correlate by trace ID, preserve exception context, inspect recent changes and dependency failures, and avoid βcatch Exception and return 200β fallbacks.
9.3 Code-review exercise
Review in this order: correctness and edge cases β security/authorization β data consistency and transactions β async/cancellation β SQL/EF query shape β error handling/observability β tests β readability. Comment on high-impact issues, explain the consequence, and propose a concrete fix; do not drown the author in style preferences.
9.4 Take-home or pair-programming API
- Clarify requirements and make the smallest complete design; avoid unnecessary microservices, generic repositories, or patterns.
- Use request/response DTOs, validation, correct status codes,
ProblemDetails, cancellation tokens, and async I/O. - Use migrations, constraints, deliberate EF queries, and tests that cover behavior and one realistic integration path.
- Add a short README explaining how to run it, assumptions, trade-offs, and what you would improveβnot a novel.
- Before finishing, run tests, inspect generated SQL/logs, try invalid/unauthorized/concurrent requests, and remove secrets and debug artifacts.
9.5 Questions to ask the interviewer
- What would success look like in the first 90 days?
- How are architecture decisions made and reviewed?
- What are the largest reliability or delivery challenges today?
- How do you test, deploy, observe, and handle on-call incidents?
- Which .NET versions and cloud/data technologies are actually used?
π―100+ Rapid-Fire Question Bank
Cover the answer, say it out loud, then check. This is a revision toolβnot a substitute for coding, SQL practice, project explanations, and mock interviews.
Algorithms & data structures
- Big-O of hash lookup? O(1) average, O(n) worst.
- Big-O of binary search? O(log n).
- When is quicksort O(nΒ²)? When pivot choices are consistently poorβfor example, always choosing the first item on already sorted input. Randomized/introspective implementations avoid this simple case.
- Which sorts are stable? Merge sort, insertion sort.
- Array vs linked list trade-off? Array: O(1) index; list: O(1) insert at node.
- Stack vs queue? LIFO vs FIFO.
- BFS data structure? Queue. DFS? Stack/recursion.
- Shortest path unweighted? BFS. Weighted? Dijkstra.
- Detect a cycle in a list? Fast/slow pointers.
- What is memoization? Caching subproblem results in recursion.
- DP vs greedy? DP explores optimal substructure; greedy takes local best.
- In-order traversal of a BST yields? Sorted order.
- Heap gives you? Min/max in O(1), insert/remove O(log n).
- Two-pointer use case? Sorted-array pair sums, palindromes.
- Sliding window use case? Longest/shortest contiguous subarray.
- Hash set solves? Dedup / "seen before" in O(1).
- Topological sort needs? A DAG (no cycles).
- Space complexity of recursion depth d? O(d) stack.
C# / .NET
- Value vs reference type? Assignment copies the value data vs copies a reference to the same object; storage location is not the definition.
- Why is string immutable? Its value cannot change after creation, enabling safe sharing, stable hashing, and interning of eligible strings such as literals.
- StringBuilder when? Many concatenations in a loop.
- IEnumerable vs IQueryable?
IEnumerable<T>enumerates values using .NET operators;IQueryable<T>builds expression trees a provider may translate, commonly to SQL. - Deferred execution? LINQ runs when enumerated.
- Task vs Thread? A Task represents an operation/result; it may have no dedicated thread. A Thread is an OS execution resource.
- Why not block on async? It wastes ThreadPool threads and can cause starvation; classic context deadlocks are not the default ASP.NET Core explanation.
- DI lifetimes? Transient, scoped, singleton.
- DbContext lifetime? Scoped; not thread-safe.
- abstract class vs interface? Shared base identity/state vs a multi-implementable contract; modern interfaces may include default members.
- override vs new? Override = runtime polymorphic dispatch; new = hides the base method, chosen by the reference's compile-time type.
- protected internal vs private protected? Subclasses OR same assembly vs subclasses AND same assembly.
- What does sealed do? Blocks inheritance (class) or further overriding (member).
- What is boxing? Copying a value type into an
objector interface representation, normally allocating a boxed object. - const vs readonly? Compile-time vs runtime.
- record vs class? Records provide value-based equality by default; they are not automatically immutable.
- yield return? Lazy iterator.
- What is a closure? Lambda capturing outer variables.
- GC generations? 0 (young), 1, 2 (old), LOH.
- Workstation vs Server GC? Server GC (ASP.NET Core default) trades memory for throughput with per-core heaps; Workstation favors footprint.
- GC vs Dispose? GC reclaims managed memory; Dispose releases scarce resources deterministically.
- What does using guarantee? Dispose runs even on exception.
- Middleware order matters? Yesβexception handling early; routing before conventional authentication/authorization; authentication before authorization.
- Func vs Action? Returns a value vs void.
- What do CLR, IL, and JIT do? C# compiles to IL/metadata; CLR loads/manages it; JIT compiles executed methods to native code.
throw;vsthrow ex;?throw;preserves the original stack trace.- Why avoid
async void? Callers cannot await completion or observe exceptions; reserve it for event handlers. - When use
Task.WhenAll? For independent async work, with bounded fan-out. - What is cancellation? Cooperative signaling through
CancellationToken; propagate it to I/O. - Why
IHttpClientFactory? Handler/connection reuse, DNS refresh, typed configuration, and resilience. - What is
ProblemDetails? A standardized machine-readable API error shape. - Authentication vs authorization? Establish identity vs decide permission.
- Controllers vs Minimal APIs? Both are valid; choose based on conventions and complexity.
- How test the real ASP.NET Core pipeline?
WebApplicationFactory<Program>integration tests. - What are keyed DI services? Named/keyed registrations for choosing among multiple implementations of one service contract.
- How do you enable built-in rate limiting? Register policies with
AddRateLimiter, callUseRateLimiter, and partition by a trustworthy client identity. - Output caching vs data caching? Output caching reuses a complete HTTP response; data caching reuses an internal value used to build a response.
- What is
System.Threading.Channelsfor? Bounded asynchronous producer/consumer queues inside one process, including backpressure.
Databases
- WHERE vs HAVING? Rows before grouping vs groups after.
- INNER vs LEFT JOIN? Matches only vs all-left + NULLs.
- What is an index? An access structure, often a B-tree, that can reduce pages scanned; benefit depends on selectivity and query shape.
- Index downside? Slower writes, storage.
- Clustered vs non-clustered in SQL Server? Rows are organized by one clustered key vs separate index structures that locate rows.
- ACID? Atomicity, Consistency, Isolation, Durability.
- N+1 problem & fix? One parent query plus per-row queries; prefer projection or deliberate eager/split loading.
- Normalization goal? Remove redundancy.
- When denormalize? Read-heavy performance.
- SQL vs NoSQL? Choose by data relationships, invariants, query/access patterns, consistency, and operationsβnot a slogan about scale.
- Window function vs GROUP BY? Keeps rows vs collapses them.
- Optimistic vs pessimistic locking? Version-check vs lock upfront.
- What causes a deadlock? Opposite lock ordering.
- Transaction isolation levels? Read Uncommitted β Serializable.
- EXISTS vs IN? Choose semantics first; modern optimizers may produce the same plan, while NULL behavior can differ.
- Read replica purpose? Scale reads; eventual consistency.
- Sharding? Split data across DBs by key.
- Fix a slow query? Index, plan, select fewer cols, avoid N+1.
- When use
AsNoTracking? Read-only entity queries that do not need change tracking; projection may be better. - When consider
DbContextpooling? After measuring context setup overhead in a high-throughput service; pooled contexts must not retain request-specific mutable state. - When does an EF query execute? On enumeration or terminal operations such as
ToListAsync/FirstAsync. - Bulk update without loading entities?
ExecuteUpdateAsync/ExecuteDeleteAsync(EF Core 7+); they bypass change tracking and run immediately. - Why use projection? Fetch only required columns and avoid unnecessary entities/tracking.
- What is cartesian explosion? Large duplicated result sets caused by joining multiple collection navigations.
- What is SARGability? Writing predicates an index can seek on.
NULL = NULL? Unknown, not true; useIS NULL.UNIONvsUNION ALL? Deduplicates vs keeps all rows and is usually cheaper.
System design, HTTP & security
- Stateless server benefit? Any server handles any request β scale.
- Vertical vs horizontal scaling? Bigger box vs more boxes.
- Cache-aside pattern? Check cache β miss β DB β populate.
- Hard part of caching? Invalidation / staleness.
- Why a message queue? Async work, decoupling, spike absorption.
- At-least-once delivery needs? Idempotent consumers.
- What must an API validate in a JWT? Allowed algorithm, signature, issuer, audience, lifetime, and required claims.
- 401 vs 403? Not authenticated vs not allowed.
- 200 vs 201 vs 204? OK vs Created vs No Content.
- Which HTTP methods are idempotent by specification? GET, HEAD, PUT, DELETE, OPTIONS, and TRACE; POST and PATCH are not guaranteed to be idempotent.
- CAP theorem? During a network partition, an operation cannot guarantee both linearizable consistency and a non-error response from every node.
- Strong vs eventual consistency? Latest read vs converges over time.
- TCP vs UDP? Reliable/ordered vs fast/lossy.
- What is CORS? Browser cross-origin permission via headers.
- Consistent hashing solves? Minimal remap when nodes change.
- Circuit breaker does? Stops calling a failing dependency.
- Load balancer algorithms? Round-robin, least-conn, IP-hash.
- Monolith vs microservices? Simple vs independently scalable+complex.
- Prevent SQL injection? Parameterized queries.
- Prevent XSS? Encode output, CSP.
- Store passwords how? Use a maintained adaptive salted password hasher such as ASP.NET Core Identityβnot custom crypto.
- Hashing vs encryption? One-way vs reversible with key.
- Blue-green deployment? Two envs, switch traffic, easy rollback.
- Container vs VM? Shares kernel (light) vs full OS (heavy).
- Rate limiter algorithm? Token bucket (allows bursts).
- OAuth 2.0 vs OIDC? Delegated authorization vs authentication/identity on top of OAuth.
- Why PKCE? Protect authorization-code flow for public clients from intercepted codes.
- Cookie security flags?
HttpOnly,Secure, and intentionalSameSite. - When is CSRF relevant? When credentials such as cookies are attached automatically to cross-site requests.
- What is SSRF? Attacker causes the server to request restricted/internal destinations.
- What is a cache stampede? Many misses rebuild the same value simultaneously.
- SLI vs SLO vs SLA? Measurement vs target vs external commitment.
- RTO vs RPO? Recovery-time target vs acceptable data-loss window.
- Why the outbox pattern? Atomically save business data and a message-to-publish in one database transaction.
- What is backpressure? Limiting producers/concurrency so slow consumers do not exhaust resources.
Practical engineering
- First step in an incident? Define impact and stabilize the system before deep diagnosis.
- How investigate latency? Start with p95/p99 and traces, then check ThreadPool/CPU, SQL, pools, dependencies, payloads, and GC.
- How investigate memory growth? Measure managed/native memory and compare heap snapshots for retained roots and unbounded structures.
- Code-review priority? Correctness/security/data consistency before style.
- What makes a strong take-home? Small complete design, correct contracts, tests, migrations, errors, security, and documented trade-offs.
- What should a project story contain? Problem, your role, architecture, trade-offs, failure/debugging story, result, and improvements.
β Behavioral Interviews (with sample answers) Must prepare
Technical skill gets you shortlisted; behavioral rounds often decide the offer and your level/salary. Use the STAR method: Situation (context), Task (your goal), Action (what you did), Result (measurable outcome). Keep it 60β90 seconds, first-person, and end with the result.
Prepare 5β6 reusable stories: a hard bug you fixed Β· a conflict with a teammate Β· a mistake/missed deadline and what you learned Β· something you're proud of building Β· learning a new technology fast Β· improving performance or reliability.
(STAR) "On our payments service (S), users occasionally saw duplicate charges (T). I added correlation-ID logging and traced it to retries hitting a non-idempotent endpoint (A β investigation). I introduced an idempotency key stored in Redis so repeated requests were deduplicated, and added a regression test. Duplicate charges dropped to zero and we shipped the fix within two days (R)."
Focus on listening, data, and outcome, never on blame: "We disagreed on SQL vs NoSQL for a new service. Rather than argue, I proposed we each write down our requirements; once we listed them, it was clear the data was highly relational, so we chose Postgres β and my teammate agreed because the decision was based on our actual needs, not opinion."
Common questions to rehearse: "Tell me about yourself" (a 2-minute pitch: present role β relevant past β why this role), "Why this company?", "Biggest weakness?" (a real one + how you're improving it), "Why leaving?". Always prepare questions for them β about the team, tech stack, on-call, and how success is measured. It signals genuine interest.
β How to behave during a live coding interview Must practice
The process is graded as much as the final code. Follow this loop out loud:
- Clarify β restate the problem; ask about input size, types, edge cases, and constraints before writing anything.
- Think out loud β narrate your reasoning. Silence is the #1 reason strong coders fail.
- Brute force first β propose a naive solution and state its complexity, so you have something working.
- Optimize β "can a hash map, two pointers, or sorting reduce this?" Explain the improvement.
- Code cleanly β meaningful names, small steps; handle empty/null/duplicate edge cases.
- Test β trace through a normal example and an edge case, out loud.
- State final complexity β time and space.
- If stuck β reason toward it out loud; asking for a hint is normal and better than freezing.
Red flags to avoid: going silent, coding before you understand the problem, ignoring edge cases, giving up with "I don't know", and arguing with the interviewer instead of engaging with their hint.
π 4-Week Study Plan
Assume roughly 1β2 focused hours per day. If the job description emphasizes algorithms, move extra time into DSA; otherwise keep .NET, SQL, API design, and practical engineering at the center.
Week 1 β C#, CLR, async, HTTP
- Types, collections, generics, equality, LINQ, exceptions, nullable references, CLR/IL/JIT, GC basics.
- Async I/O vs CPU work, cancellation,
Task.WhenAll, thread safety, and common async mistakes. - HTTP methods/status codes, TLS/DNS basics, cookies/tokens/CORS.
- One or two targeted coding problems per day: arrays, dictionaries, strings, stacks/queues, binary search.
Week 2 β ASP.NET Core, EF Core, SQL
- Build or revise one small API: DI, middleware, validation, auth policies,
ProblemDetails, OpenAPI, logging, cancellation. - EF Core tracking, projection, loading, migrations, concurrency, transactions, N+1, split queries.
- Write SQL daily: joins, aggregation, window functions, constraints, indexes, isolation, execution plans, keyset pagination.
Week 3 β Testing, security, operations, design
- Unit tests plus one
WebApplicationFactoryintegration test against a realistic database. - SQL injection, XSS, CSRF, SSRF, overposting, OAuth/OIDC, secure cookies, secret handling.
- Docker/CI/CD, health checks, OpenTelemetry, retries/timeouts, caching, queues, idempotency.
- Practice one API design and one system-design walkthrough with explicit trade-offs.
Week 4 β Interview simulation and weak spots
- Rehearse a project deep-dive, a debugging scenario, a code review, and 5β6 STAR stories.
- Do 2β3 timed mocks: C#/API questions, SQL, coding, and role-appropriate system design.
- Review the target company's stack and job description; deepen Azure, Kubernetes, algorithms, or messaging only where relevant.
Daily loop: learn one concept β explain it aloud without notes β implement or query something small β answer one follow-up/trade-off question β record mistakes for spaced review. Passive reading alone does not prepare you for an interview.
β‘Quick Cheat-Sheet
- C#/.NET: copy semantics Β· generics/equality Β· LINQ execution Β· exceptions Β· CLR/JIT/GC Β· async I/O, cancellation, bounded concurrency.
- ASP.NET Core: Kestrel/pipeline Β· DI lifetimes Β· DTO validation Β· auth policies Β·
ProblemDetailsΒ· OpenAPI Β·HttpClientreuse Β· observability. - EF Core/SQL: projection and deliberate tracking/loading Β· generated SQL Β· joins/indexes/plans Β· ACID/isolation Β· optimistic concurrency Β· keyset pagination.
- Security: parameterization Β· output encoding Β· CSRF/SSRF/overposting Β· OIDC/JWT validation Β· secure cookies/secrets Β· least privilege.
- System design: requirements β scale β API/data model β simple baseline β failures/consistency β cache/queue only with a reason β observability/recovery.
- Interview behavior: clarify Β· think aloud Β· test edge cases Β· discuss trade-offs Β· use evidence when debugging Β· prepare project and STAR stories.
Good luck β consistency beats intensity. A little every day compounds fast. You've got this. π
No items match the current filter
Try switching the filter back to All or clearing your search query.