Fundamentals: DSA, System Design & LLD — Interview Questions
In short: The stack holds method call frames — parameters, local value-type variables and references — per thread;
- allocation and cleanup are automatic and very fast (push/pop) but it is small (≈1 MB per thread).
- The heap holds objects created with
new, shared by all threads, larger, and in .NET cleaned by the garbage collector. - Stack overflow comes from too-deep recursion; heap problems are leaks and fragmentation.
In short: A process is a running program with its own isolated virtual memory and resources.
- A thread is an execution path inside a process; threads of the same process share its heap and resources but each has its own stack and registers.
- Processes are isolated and heavier; threads are lighter but need synchronisation for shared data, and an unhandled exception in one thread kills the whole process.
In short: RAM is volatile main memory accessed electronically in ~100 ns;
- SSDs take tens of microseconds and HDDs milliseconds because of slower technology (and seek mechanics for HDDs).
- A cache is a small, faster copy of frequently used data closer to where it's needed — CPU L1/L2/L3 caches over RAM, SQL Server's buffer pool over disk, Redis over a database.
- Caches work because of locality: recently or nearby used data is likely to be used again.
In short: An OS + CPU mechanism that gives each process its own large, private address space, mapped page by page (usually 4 KB) to physical RAM or to disk (page file/swap).
- It isolates processes, lets programs use more memory than physically available, and loads pages lazily.
- Accessing a page not in RAM causes a page fault; heavy paging (thrashing) makes a system extremely slow.
In short: When the result depends on the timing of threads accessing shared data, e.g. two threads doing count++ (read, add, write) and one update is lost.
- Prevent it with mutual exclusion (
lock,SemaphoreSlim), atomic operations (Interlocked), thread-safe collections (ConcurrentDictionary), immutable data, or by not sharing state (message passing). - In databases, transactions and isolation levels play the same role.
In short: Two or more threads (or transactions) each hold a resource the other needs and wait forever.
- Conditions: mutual exclusion, hold-and-wait, no preemption, circular wait.
- Avoid by acquiring locks in a consistent order, holding locks briefly, using timeouts (
Monitor.TryEnter,SemaphoreSlim.WaitAsync(timeout)), and avoiding sync-over-async (.Result) in .NET. - SQL Server detects deadlocks and kills one victim transaction.
In short: Probably memory pressure and paging: processes (or SQL Server with no max memory limit, or a leaking app) use more than physical RAM, so the OS keeps swapping pages to disk (thrashing).
- Check memory usage per process, page faults/sec and page file usage; set SQL Server max server memory, fix leaks, reduce caching, or add RAM.
- Also check whether the disk activity is actually database reads due to missing indexes.
In short: It describes how an algorithm's time or memory grows with input size n, ignoring constants and smaller terms.
- O(1) dictionary lookup, O(log n) binary search, O(n) one loop, O(n log n) good sorts, O(n²) nested loops over the same data, O(2ⁿ) all subsets.
- It lets you predict whether a solution scales — e.g. O(n²) on a million items is ~10¹² operations, too slow.
In short: Arrays (and List<T>) store elements contiguously: O(1) index access, cache-friendly iteration, but O(n) inserts/removes in the middle.
- Linked lists store nodes with pointers: O(1) insert/remove at a known node, but O(n) access by position and poor cache locality.
- In practice
List<T>wins most of the time; linked lists suit frequent middle insertions with a node reference (LRU caches pair a linked list with a dictionary).
In short: It computes the key's hash code, maps it to a bucket (hash % size), and stores the entry there; lookups recompute the hash, go to the bucket and compare with Equals.
- Collisions are handled by chaining or probing; when the load factor grows, it resizes and rehashes.
- Average O(1) insert/lookup/delete, worst O(n) if many keys collide.
- Keys must have consistent
Equals/GetHashCodeand shouldn't change while stored.
In short: Stack is LIFO: function call stack, undo/redo, browser back button, expression evaluation, DFS.
- Queue is FIFO: print jobs, request/message queues, thread-pool work items, BFS.
- Both have O(1) push/pop or enqueue/dequeue.
In short: The data must be sorted.
- Compare the target with the middle element; if smaller, search the left half, otherwise the right half; repeat until found or the range is empty — O(log n).
- Compute the middle as
lo + (hi - lo) / 2to avoid overflow. - Database index seeks work on the same principle over B-tree pages.
In short: Both average O(n log n).
- Merge sort is always O(n log n), stable, but needs O(n) extra memory — good for linked lists and external sorting of huge data.
- Quicksort sorts in place (O(log n) stack), is usually faster in practice thanks to cache locality, but is O(n²) in the worst case with bad pivots and is not stable. .NET's
Array.Sortuses introsort to avoid the worst case.
In short: A binary tree where every node's left subtree has smaller keys and right subtree larger keys, so search/insert/delete follow one path: O(height).
- Balanced height is log n; inserting sorted data into a plain BST makes it a linked list with O(n) operations.
- Self-balancing trees (AVL, red-black) and B-trees keep the height logarithmic — SQL Server indexes are B+ trees.
In short: BFS explores level by level using a queue — finds the shortest path in unweighted graphs, uses memory proportional to the widest level.
- DFS goes as deep as possible using recursion or a stack — good for connectivity, cycle detection, topological sort and backtracking, uses memory proportional to depth.
- Both are O(V + E).
In short: DP solves problems with overlapping subproblems and optimal substructure by storing subproblem results (memoisation top-down, or a table bottom-up) so each is computed once.
- Plain recursion may recompute the same subproblem exponentially many times (naive Fibonacci O(2ⁿ) vs DP O(n)).
- Typical DP problems: coin change, knapsack, longest common subsequence, edit distance, climbing stairs.
In short: Nested loops are O(n × m) ≈ 4 × 10¹⁰ comparisons.
- Put one list into a
HashSet<int>(O(m)) and check each id of the other list withContains(O(1) average) — O(n + m), milliseconds. - Or sort both and merge with two pointers (O(n log n)), or let SQL do it with an indexed JOIN/EXISTS.
WHERE Name LIKE '%term%' on 5 million products and is slow. How would you improve it?In short: A leading wildcard can't use a B-tree index, so every keystroke scans the table.
- Options: prefix search
LIKE 'term%'on an indexed column (index seek), a trie or sorted in-memory list of names for prefix lookups, full-text search (SQL Server full-text index, Azure AI Search, Elasticsearch) for word matches, debounce keystrokes and limit to top 10, cache popular prefixes.
Convert to a char[], swap from both ends towards the middle with two pointers, then build a new string. O(n) time, O(n) space for the array (strings are immutable, so you can't do it in place).
static string Reverse(string s)
{
var c = s.ToCharArray();
for (int i = 0, j = c.Length - 1; i < j; i++, j--) (c[i], c[j]) = (c[j], c[i]);
return new string(c);
}Mention: for user-visible text with emoji or combining characters, use StringInfo text elements.
Two pointers from both ends, skip non-alphanumerics, compare case-insensitively. O(n) time, O(1) space. Simple version: s == new string(s.Reverse().ToArray()), which is O(n) extra space.
static bool IsPal(string s)
{
for (int i = 0, j = s.Length - 1; i < j; i++, j--)
if (char.ToLower(s[i]) != char.ToLower(s[j])) return false;
return true;
}long a = 0, b = 1;
for (int i = 0; i < n; i++) { Console.Write($"{a} "); (a, b) = (b, a + b); }Iterative is O(n) time and O(1) space. Naive recursion F(n-1)+F(n-2) is O(2ⁿ) — mention memoisation if asked for recursion.
Use a HashSet<int>: Add returns false when the item already exists. O(n) time, O(n) space. LINQ alternative: arr.GroupBy(x => x).Where(g => g.Count() > 1).Select(g => g.Key). If O(1) space is required and the array can be modified: sort first (O(n log n)) and compare neighbours.
var seen = new HashSet<int>();
var dups = arr.Where(x => !seen.Add(x)).Distinct().ToList();var counts = new Dictionary<char, int>();
foreach (var c in s) counts[c] = counts.GetValueOrDefault(c) + 1;
// LINQ: s.GroupBy(c => c).ToDictionary(g => g.Key, g => g.Count());
// .NET 9: s.CountBy(c => c)O(n) time, O(k) space where k = distinct characters.
In short: Single pass tracking first and second (skip values equal to first if distinct values are needed).
- O(n), O(1).
- Sorting is simpler but O(n log n):
arr.Distinct().OrderByDescending(x => x).Skip(1).First(). - Clarify: what if all elements are the same or there's only one?
Brute force: two loops, O(n²). Optimal: one pass with a Dictionary<value, index>. For each number, check whether target - number was seen. O(n) time, O(n) space. If the array is sorted, use two pointers in O(1) space.
var seen = new Dictionary<int, int>();
for (int i = 0; i < nums.Length; i++)
{
if (seen.TryGetValue(target - nums[i], out var j)) return [j, i];
seen[nums[i]] = i;
}In short: Sliding window: keep the last index of each character.
- When a repeat appears inside the window, move
startpast its previous position, and track the max window length. - O(n) time, O(k) space.
- Example: "abcabcbb" → 3 ("abc").
In short: At each element, either extend the current subarray or start fresh: cur = max(a[i], cur + a[i]), and best = max(best, cur).
- O(n) time, O(1) space.
- Works with all-negative arrays if you initialise with
a[0]. - Example: [-2,1,-3,4,-1,2,1,-5,4] → 6 ([4,-1,2,1]).
In short: Push opening brackets on a Stack<char>.
- For each closing bracket, the stack must be non-empty and the top must match.
- At the end the stack must be empty.
- O(n) time and space.
In short: Iterate with three pointers: prev, current, next.
- Save
next, pointcurrent.Nexttoprev, advance both. - Return
prev. - O(n) time, O(1) space.
- Recursive version: O(n) stack space.
In short: Floyd's cycle detection: slow pointer moves one step, fast moves two.
- If they meet, there is a cycle; if fast reaches null, there isn't.
- O(n) time, O(1) space.
- To find the cycle start, reset one pointer to head and move both one step at a time until they meet.
- Alternative: a
HashSetof visited nodes (O(n) space).
In short: On a sorted array, compare with the middle element and discard half each time: O(log n) time, O(1) space iteratively.
- Compute
mid = lo + (hi - lo) / 2to avoid overflow. - Variants: first/last occurrence, search in a rotated array, lower bound.
In short: Combine a Dictionary<key, LinkedListNode> for O(1) lookup with a doubly LinkedList ordered by recency.
- On get: move the node to the front.
- On put: update and move to the front, or insert at the front.
- If over capacity, remove the last node and its dictionary entry.
- Thread safety: wrap in a lock or use per-key strategies.
- In production use
MemoryCachewith a size limit.
In short: If the distinct IPs fit in memory: stream the file line by line (File.ReadLines), count in a Dictionary<string,int>, then keep a min-heap of size 10, O(n log 10).
- If not: partition by hash of the IP into N temp files so each file's distinct IPs fit in memory, count each file, and merge the per-file top-10s.
- For a stream with bounded error, use a Count-Min Sketch or the Space-Saving algorithm.
- In practice you'd load the data into SQL/Kusto and `GROUP BY ...
- ORDER BY COUNT(*) DESC`.
In short: Find the repeated work in the inner loop.
- Usual upgrades: (1) a hash map/set to replace an inner search, O(n²) → O(n);
- (2) sort + two pointers, O(n log n);
- (3) a sliding window for contiguous subarray/substring problems;
- (4) prefix sums for range sums;
- (5) a heap for top-K;
- (6) binary search on a sorted structure or on the answer;
- (7) memoisation/DP for overlapping subproblems.
- Say the trade-off (more memory for less time).
In short: URL parsed → browser/OS caches checked → DNS resolves the host to an IP (resolver → root → TLD → authoritative) → TCP three-way handshake to port 443 (or QUIC for HTTP/3) → TLS handshake validates the certificate and sets up encryption → HTTP request sent with headers/cookies → CDN/load balancer/reverse proxy route it → web server and application handle it (auth, routing, controller, database) → HTTP response with status, headers, body → browser parses HTML into the DOM, loads CSS/JS/images, builds the render tree, lays out and paints the page;
- further requests reuse the connection.
In short: DNS translates domain names to IP addresses through a hierarchy of servers with caching (TTL).
- An A record maps a name to an IPv4 address (AAAA for IPv6);
- a CNAME makes a name an alias of another name (e.g.
www→shop.azurewebsites.net); - an MX record lists the mail servers for the domain.
- TXT records are used for verification and email security (SPF, DKIM, DMARC).
In short: HTTPS is HTTP over TLS: the connection is encrypted (confidentiality), protected from tampering (integrity), and the server proves its identity with a certificate signed by a trusted certificate authority (authentication).
- Default ports 80 vs 443.
- HTTPS is required for modern browser features, SEO and any login or personal data.
In short: TCP is connection-oriented and reliable: handshake, acknowledgements, retransmission, ordering, flow and congestion control — used for the web, databases, email.
- UDP is connectionless with no delivery guarantees but lower latency and overhead — used for DNS, voice/video, games and QUIC (HTTP/3), where the application handles reliability.
In short: GET (read), POST (create/action), PUT (replace, idempotent), PATCH (partial update), DELETE (remove, idempotent), OPTIONS/HEAD.
- Status: 200 OK, 201 Created, 204 No Content, 301/302 redirects, 304 Not Modified, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 429 Too Many Requests, 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout.
In short: A component that distributes incoming traffic across several servers for scalability and availability, using health probes to skip unhealthy instances.
- Algorithms: round robin, weighted round robin, least connections, IP hash/sticky sessions, least response time.
- Layer 4 balancers route by IP/port (fast); layer 7 balancers understand HTTP and can route by path, host or header and terminate TLS.