What Software Developer Interviews Actually Test in 2026 Interview Questions · 2026

What Software Developer Interviews Actually Test in 2026

The Bureau of Labor Statistics projects about 129,200 software developer job openings a year through 2034, a growth number that has held up better than most white-collar projections made around the same time. What's changed isn't demand. It's the loop. A generalist software developer interview in 2026 usually runs three to five rounds and touches coding, database reasoning, and at least a lightweight system design conversation, even for candidates two or three years past a bootcamp or a CS degree.

This page isn't built around one company's loop (we cover those separately, Google, Amazon, Meta, and 40-plus others). It collects the software developer interview questions that repeat across nearly every generalist loop, regardless of company size or stack. We cross-checked the language mix against the 2025 Stack Overflow Developer Survey, where JavaScript sits at 66% adoption among professional developers and Python climbed to 57.9%, up roughly 7 points in a single year. If your prep time keeps circling back to JavaScript and Python specifics, that's not an accident, it's where the survey says everyone else's prep time is going too.

One opinion here that could be wrong: for a true generalist role, the CS-fundamentals section below matters more than the coding section, because most candidates over-prepare LeetCode and under-prepare the reasoning behind their own code. I don't have a clean data set proving that split holds at every company. Team-to-team variance is real, and a pure algorithms shop will weight things differently. But it's the pattern that keeps showing up in the mock sessions we run.

Forty-six questions across four sections: coding and data structures, CS fundamentals and OOP, databases and SQL, and system design basics. Difficulty is tagged on every card, and code shows up where the code is actually the answer, not as decoration.

3-5Typical rounds
Easy-MediumCoding level
3-6 weeksPrep window
Virtual + onsiteFormat

Easy questions

18

The brute-force nested loop works but costs O(n squared). A single pass using a hash map that stores value to index gets it down to O(n) time and O(n) space: for each number, check whether its complement (target minus the number) has already been seen before adding the current number to the map.

python
def two_sum(nums, target):
  seen = {}
  for i, num in enumerate(nums):
    complement = target - num
    if complement in seen:
      return [seen[complement], i]
    seen[num] = i
  return []

Interviewers want the O(n) version specifically. A common follow-up asks what happens with duplicate values, or whether the function should return the first valid pair or every valid pair.

Sort the intervals by start time first. Walk through them once, and for each interval check whether it overlaps the last merged interval (its start is less than or equal to the last merged end). If it overlaps, extend the end. If it doesn't, start a new merged interval.

Watch the touching-but-not-overlapping edge case, [1,3] and [3,5]. Most interviewers expect these merged into [1,5], since the intervals share a boundary point.

Walk the list once, keeping track of the previous node, the current node, and a saved reference to the next node before you overwrite any pointers. Flip current.next to point backward at prev, then advance all three references forward.

python
def reverse_list(head):
  prev = None
  current = head
  while current:
    next_node = current.next
    current.next = prev
    prev = current
    current = next_node
  return prev

The recursive version is a common follow-up. It's cleaner to read but costs O(n) stack space, which matters if the list is long enough to blow the call stack.

Floyd's cycle detection: a slow pointer moves one node at a time, a fast pointer moves two. If there's a cycle, the fast pointer laps the slow one and they eventually point at the same node. If the fast pointer hits null first, there's no cycle.

The usual follow-up asks you to find where the cycle actually starts, not just whether one exists. That needs a second pass: reset one pointer to the head, keep the other at the meeting point, and advance both one step at a time until they meet again, that meeting point is the cycle's start node.

Push opening brackets onto a stack. When you hit a closing bracket, check whether the top of the stack is its matching opener. If it is, pop. If it isn't, or the stack is already empty, the string isn't balanced. At the end, the stack has to be empty too, a trailing unclosed opener should also fail.

Use a queue. Push the root, then repeatedly pop a node, record it, and push its children. Track the number of nodes at the start of each level so you can group the output level by level instead of getting one flat list.

Plain recursion recomputes the same subproblems over and over, O(2^n). Memoizing the recursive calls fixes that, but a bottom-up loop is usually preferred in interviews since it's easier to reason about under pressure and doesn't add recursion overhead.

python
def fib(n):
  if n <= 1:
    return n
  prev, curr = 0, 1
  for _ in range(2, n + 1):
    prev, curr = curr, prev + curr
  return curr

The bottom-up version above only needs O(1) extra space, since you're only ever tracking the last two values, not a full array of every Fibonacci number up to n.

XOR every number in the array together. A number XORed with itself is zero, and XOR is commutative and associative, so every paired number cancels out and only the lone number survives.

python
from functools import reduce
def single_number(nums):
  return reduce(lambda a, b: a ^ b, nums, 0)

This is O(n) time and O(1) space, which is the whole point of the question, a hash-map count works too but doesn't get you the constant-space follow-up credit.

Encapsulation bundles data with the methods that operate on it and restricts direct access to internal state. Abstraction hides implementation detail behind a simpler interface. Inheritance lets a class reuse and extend behavior from a parent. Polymorphism lets different classes respond to the same method call in their own way.

Interviewers rarely stop at definitions. Have one concrete example ready for each pillar from something you've actually built, not a textbook Animal/Dog example.

Overloading is same method name, different parameter list, resolved at compile time in statically typed languages. Overriding is a subclass providing its own implementation of a method it inherited, resolved at runtime through dynamic dispatch.

Python and JavaScript don't support true overloading the way Java or C++ do, since neither language dispatches on parameter types. Candidates coming from those languages sometimes answer this question as if it applies universally, and a sharp interviewer will push on that.

An immutable object can't change after it's created, any "modification" produces a new object instead. That removes an entire category of bugs where one part of a program mutates shared state out from under another part that didn't expect it, which matters even more once multiple threads are involved.

Java's String, Python's tuples and strings, and most functional-language data structures default to immutable for exactly this reason. It also makes an object safe to use as a hash key or a set member, since its hash can't shift after insertion.

Stack memory holds function call frames, local variables, and return addresses. It's allocated and freed automatically as functions are called and return, and access is fast because it's just a pointer moving up and down. Heap memory holds data with a lifetime that outlives a single function call, allocated explicitly (or via a language runtime) and freed later, either manually or by a garbage collector.

Stack space is limited and fixed per thread, which is exactly why deep unbounded recursion crashes with a stack overflow instead of running out of heap.

Big-O describes how an algorithm's runtime or memory use scales as input size grows, ignoring constant factors and lower-order terms. Two O(n) solutions can still perform very differently in practice, one might do a single pass with simple arithmetic, another might do a single pass with an expensive hash computation on every element.

Interviewers who push on this aren't contradicting Big-O, they're checking whether you understand that asymptotic complexity is a starting point for reasoning about performance, not the entire conversation.

An INNER JOIN only returns rows where the join condition matches on both sides, unmatched rows from either table are dropped entirely. A LEFT JOIN keeps every row from the left table regardless of a match, filling in NULL for any right-side columns that don't have a matching row.

sql
SELECT o.order_id, c.name
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id;

That query returns every order, even ones somehow missing a valid customer record, with c.name as NULL in those rows. Swap in INNER JOIN and those orphaned orders disappear from the result set completely.

PropertyWhat it guarantees
AtomicityA transaction either fully commits or fully rolls back, no partial writes
ConsistencyA transaction moves the database from one valid state to another, respecting constraints
IsolationConcurrent transactions don't see each other's uncommitted changes
DurabilityOnce committed, data survives a crash or power loss

Isolation is the one interviewers dig into, since real databases offer several isolation levels (read committed, repeatable read, serializable) with real tradeoffs between correctness guarantees and how much concurrency you give up to get them.

WHERE filters individual rows before they get grouped. HAVING filters groups after aggregation has already happened, so it's the only place you can filter on an aggregate value like COUNT(*) or SUM(amount).

Trying to write WHERE COUNT(*) > 5 fails, since at the point WHERE runs, no grouping or aggregation has happened yet for the engine to count against.

Vertical scaling means adding more resources, CPU, RAM, disk, to a single machine. It's simple but has a hard ceiling, and it usually means downtime while you resize. Horizontal scaling means adding more machines and distributing load across them, which scales further but introduces real complexity: load balancing, data consistency across nodes, and coordination.

Most systems start vertical because it's simpler, and move horizontal once a single machine genuinely can't keep up, or once availability requirements mean a single point of failure is no longer acceptable.

Use a queue when the caller shouldn't have to wait for the work to finish, sending a confirmation email, resizing an uploaded image, generating a report, or when the receiving service might be temporarily down or overloaded and you don't want to lose the request.

A direct API call is simpler and fine when the caller genuinely needs the result back before it can proceed. Queues add real operational complexity, monitoring, dead-letter handling, ordering guarantees, so reaching for one by default rather than by need is its own kind of mistake.

Medium questions

24

Keep a sliding window with two pointers and a set (or a map of character to last-seen index). Expand the right pointer one character at a time. When you hit a character already inside the window, move the left pointer past its previous occurrence instead of resetting to zero, that's the part candidates skip when they first try this.

Track the window length at every step rather than only at the end, since the answer can shrink and grow multiple times before you reach the actual longest window.

Use one stack for incoming pushes and a second for outgoing pops. When the "outgoing" stack is empty and a pop or peek is requested, dump the entire "incoming" stack into it, reversing the order once. Otherwise, pop straight from the outgoing stack.

Each element only gets moved between stacks once over its lifetime, so the amortized cost per operation is still O(1), even though a single dump-and-reverse step is O(n).

Recurse from the root. If the current node matches either target, return it. Otherwise recurse into both children. If both sides return a non-null result, the current node is the LCA, since the two targets sit in different subtrees. If only one side returns something, pass that result upward.

This assumes a plain binary tree, not a binary search tree. If the interviewer specifies a BST, there's a faster approach: compare both target values against the current node's value and walk down a single path instead of exploring both subtrees.

Scan the grid for a land cell you haven't visited. When you find one, run BFS or DFS outward, marking every connected land cell as visited, and count that as one island. Repeat until the whole grid is scanned.

DFS is usually simpler to write under time pressure, but watch stack depth on a very large grid, an iterative DFS with an explicit stack avoids a recursion-depth crash that a recursive version can hit.

Compute the sum of the first window of size k. Then slide the window one position at a time: subtract the element leaving the window, add the element entering it. That keeps the whole thing at O(n) instead of recomputing the sum from scratch at every position, which would be O(n·k).

python
def max_sum_subarray(nums, k):
  window_sum = sum(nums[:k])
  best = window_sum
  for i in range(k, len(nums)):
    window_sum += nums[i] - nums[i - k]
    best = max(best, window_sum)
  return best

Build a table where dp[i][w] is the best achievable value using the first i items with weight capacity w. For each item, you either skip it (carry forward dp[i-1][w]) or take it (its value plus dp[i-1][w - weight]), whichever is larger, as long as it fits.

Say the space out loud as you code it, most interviewers care more about whether you can define the subproblem correctly in words before writing a single line than about the final table filling in.

Reach for an abstract class when related classes share actual implementation, not just a method signature, common state, a partially implemented method, a constructor that sets up shared fields. Reach for an interface (or a protocol, depending on the language) when you only need to guarantee a contract, no shared code, and you want a class to be able to satisfy multiple contracts at once.

Most languages only allow single inheritance from one abstract class but let a class implement several interfaces, which is usually the deciding factor in practice more than any philosophical preference.

Instead of a class inheriting behavior from a parent, it holds a reference to another object and delegates to it. A Car class that has an Engine field it calls methods on is composition. A Car class that extends a Vehicle base class is inheritance.

The practical reason teams prefer composition: deep inheritance chains get brittle fast, change a base class and every subclass three levels down might break in a way that's hard to trace. Composition keeps that coupling looser and easier to test in isolation.

Most garbage collectors work from reachability, not reference counting alone. Starting from a set of roots (global variables, active stack frames), the collector traces every object still reachable through that graph. Anything unreachable gets reclaimed. Generational collectors additionally bet that most objects die young, so they scan recently created objects far more often than long-lived ones.

The exact algorithm varies by runtime, and I won't pretend the JVM's generational collector works identically to V8's or CPython's reference-counting-plus-cycle-collector hybrid, but the reachability idea holds across nearly all of them.

A process has its own memory space and its own set of OS resources. A thread shares memory with every other thread in the same process, which makes communication cheaper but introduces the risk of two threads mutating the same data at once.

Pick multiprocessing for CPU-bound work where you need genuine parallelism across cores and don't mind the higher memory overhead and inter-process communication cost. Pick threading for I/O-bound work, network calls, file reads, where threads spend most of their time waiting rather than computing, and the shared-memory model is actually convenient rather than dangerous.

A mutex allows exactly one thread to hold a lock at a time, and typically only the thread that acquired it can release it. A semaphore maintains a count and allows up to N threads to proceed simultaneously, and in many implementations any thread can release it, not just the one that acquired it.

MutexSemaphore
Binary: locked or unlockedCounting: 0 to N permits available
Owner-aware, same thread releases itNot owner-aware in most implementations
Protects one shared resourceLimits concurrent access to a pool of resources

Use a mutex to protect a single critical section. Use a semaphore when up to N callers should be allowed in at once, a connection pool capped at 10 connections is the classic example.

JavaScript runs on a single thread with a call stack, a microtask queue, and a macrotask (callback) queue. Synchronous code runs first and empties the call stack. Once it's empty, the event loop drains the entire microtask queue (Promise callbacks, queueMicrotask) before touching a single macrotask.

That's why Promise.resolve().then(...) logs before a setTimeout(fn, 0) callback, even though both look like they should run "immediately." The macrotask queue only gets a turn once the microtask queue is completely empty, and that includes any new microtasks a microtask itself schedules along the way.

The Global Interpreter Lock allows only one thread to execute Python bytecode at a time inside a single process, even on a machine with a dozen cores. Threading still helps for I/O-bound work, since the GIL releases while a thread waits on a network call or disk read, letting another thread run in the meantime.

For CPU-bound work, threading in pure Python doesn't buy real parallelism, the threads take turns rather than running simultaneously. Multiprocessing sidesteps the GIL entirely by using separate OS processes, each with its own interpreter, at the cost of higher memory use and slower inter-process communication.

Normalization organizes data to reduce redundancy, typically by splitting data into related tables so each fact is stored in exactly one place. Third normal form (3NF) is the common target: every non-key column depends on the whole primary key and nothing but the key.

Denormalizing on purpose, duplicating some data across tables, trades storage and write complexity for faster reads, since you skip a join at query time. Read-heavy analytics tables and reporting dashboards are the usual candidates. It's a real tradeoff, not a mistake, as long as you're explicit about which reads you're optimizing for.

A subquery that excludes the maximum handles the common case cleanly, and it doesn't blow up if there are ties for first place the way a naive OFFSET 1 approach can.

sql
SELECT MAX(salary) AS second_highest
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);

The follow-up worth preparing for: what if there are duplicate top salaries, and you actually want the second-highest distinct value, or the second-highest row regardless of ties? Those are different questions with different queries, and interviewers ask which one you're solving before you start typing.

Reach for a relational database when your data has clear structure, relationships matter, and you need strong transactional guarantees, an e-commerce order system with inventory, payments, and customer records is a textbook fit. Reach for a document or key-value store when your data is naturally nested and access patterns are simple lookups by key, and when horizontal scalability matters more than complex joins.

Most real systems end up using both, a relational store for the transactional core and a document or cache store for something like session data or a product catalog. "It depends" is a fair answer here as long as you can say specifically what it depends on.

An index speeds up reads that filter or sort on the indexed column, often turning an O(n) table scan into an O(log n) lookup. It's not free: every insert, update, or delete also has to update every index on that table, so write-heavy tables with too many indexes can get noticeably slower.

Indexes also take disk space, sometimes a meaningful amount on a large table with several composite indexes. The real skill interviewers probe for is knowing which columns are worth indexing, ones that show up often in WHERE clauses, JOIN conditions, or ORDER BY, not indexing every column defensively.

It shows up when code fetches a list of N parent records, then loops through them and fires a separate query for each one's related data, one query to get the list plus N more, hence N+1. A page listing 50 blog posts that each triggers its own query to fetch the author ends up running 51 queries for something that could be a single join.

The fix is eager loading, fetching the related data in a single query up front (a JOIN, or a batched WHERE id IN (...) query), instead of lazily loading it one record at a time. Most ORMs have a specific method for this, and interviewers who ask this question are usually checking whether you've actually hit it in production, not whether you can define it.

Start with the scan type on each table involved, a full table scan on a large table is usually the first thing worth fixing, versus an index scan or index-only scan, which are far cheaper. Then look at estimated row counts at each step versus what you'd actually expect, a huge mismatch usually means outdated statistics or a query the planner can't optimize well.

Join order and join type matter too, a nested loop join over two large tables with no usable index is a common culprit behind a query that looks simple but runs slowly. Most engines also show actual runtime per step when you ask for it, which is more useful than the estimate alone once you're debugging a real slow query rather than a hypothetical one.

A load balancer sits in front of multiple servers and distributes incoming requests across them, so no single server gets overwhelmed and traffic keeps flowing if one server goes down. Round robin is the simplest algorithm, cycling through servers in order. Least connections routes to whichever server currently has the fewest active requests, which handles uneven request durations better than round robin does.

For stateful sessions, consistent hashing or IP-hash-based routing keeps a given client's requests landing on the same server, which matters if session data lives in that server's memory rather than a shared store.

In a distributed system, when a network partition happens (and eventually, one will), you have to choose between consistency (every node sees the same data at the same time) and availability (every request gets a response, even if it might be stale). You can't fully guarantee both during a partition.

Most real systems pick a point on a spectrum rather than a hard binary choice, a banking ledger leans consistency, a social media like-count leans availability, and plenty of systems make different tradeoffs for different pieces of data within the same product.

Cache-aside (lazy loading) checks the cache first on a read. On a miss, it reads from the database and populates the cache for next time. Writes go straight to the database, and the cache entry gets invalidated or updated separately. Write-through writes to the cache and the database at the same time, on every write, so the cache is never stale, at the cost of every write now paying the latency of both operations.

Cache-aside is the more common default because most workloads read far more than they write, and it tolerates a cache outage more gracefully, reads just fall through to the database instead of failing.

Writes go to a primary database, and replicas copy those writes asynchronously to serve read traffic and reduce load on the primary. That copying takes time, usually milliseconds, sometimes longer under heavy write load, and during that window a replica can return data that's already out of date.

A user who updates their profile and immediately refreshes the page can briefly see the old value if that read hits a lagging replica. The common fixes: route a user's own reads to the primary right after they write (read-your-writes consistency), or accept the staleness for data where it genuinely doesn't matter, like a follower count that's a few seconds behind.

Token bucket is the algorithm interviewers reach for most often. Each client gets a bucket that holds up to a fixed number of tokens, refilled at a steady rate. A request consumes one token if available; if the bucket is empty, the request gets rejected or queued.

python
class TokenBucket:
  def __init__(self, capacity, refill_rate):
    self.capacity = capacity
    self.tokens = capacity
    self.refill_rate = refill_rate # tokens per second
    self.last_check = time.time()

  def allow_request(self):
    now = time.time()
    elapsed = now - self.last_check
    self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
    self.last_check = now
    if self.tokens >= 1:
      self.tokens -= 1
      return True
    return False

Token bucket allows short bursts up to the bucket's capacity, which a simpler fixed-window counter doesn't handle as gracefully, fixed windows can let through nearly double the intended rate right at a window boundary.

Hard questions

10

DFS with two sets: a visited set and a recursion-stack set (nodes on the current call path). If DFS reaches a node already in the recursion stack, there's a cycle. Build the topological order by pushing each node onto the front of a result list only after all of its neighbors have finished processing.

Kahn's algorithm is the common alternative: repeatedly remove nodes with in-degree zero, decrementing their neighbors' in-degrees as you go. If you can't remove all nodes this way, a cycle exists. Interviewers sometimes ask for both approaches specifically to see if you know the queue-based version isn't just DFS with extra steps.

Push the head node of each of the k lists into a min-heap keyed on value. Pop the smallest, append it to the result, and push its next node (if one exists) back into the heap. Each pop-and-push pair costs O(log k), and you do this n total times across all the lists, giving O(n log k) overall.

Candidates who reach for "merge them two at a time, repeatedly" get a working solution too, but at O(n log k) it's the same complexity through a different path, and interviewers usually ask you to justify which approach you'd actually pick and why.

A deadlock happens when two transactions each hold a lock the other one needs, and neither can proceed. Transaction A locks row 1 and wants row 2. Transaction B locks row 2 and wants row 1. Both wait forever unless something intervenes.

Most databases detect this automatically and kill one transaction (the "victim") to break the cycle, which your application then needs to catch and retry. Avoiding deadlocks up front usually comes down to one discipline: always acquire locks on multiple rows in a consistent order across every code path, so two transactions can't end up waiting on each other in a circle in the first place.

With plain modulo hashing, key -> hash(key) % N, changing N from adding or removing a node remaps almost every key to a different server. For a cache that means a near-total cold start and a stampede of requests hitting your database at once, which is exactly when the cluster is least able to absorb it.

Consistent hashing fixes this by mapping both servers and keys onto points on a fixed hash ring (say, 0 to 2^32-1). A key belongs to the first server found walking clockwise from its position. When you add a node, it only takes over the keys between itself and the previous node on the ring, everything else stays put. Removing a node only reshuffles the keys it owned to its neighbor. Roughly 1/N of keys move on a topology change instead of nearly all of them.

The catch with a naive ring is uneven load if you only place one point per server, since ring gaps aren't uniform and a server next to a big gap gets hammered. The fix is virtual nodes, hashing each physical server to many points on the ring (Cassandra and DynamoDB both do this), which smooths the distribution and lets you weight beefier machines by giving them more virtual nodes.

Two-phase commit gives you an all-or-nothing guarantee across participants: a coordinator asks everyone to prepare, waits for unanimous yes, then tells everyone to commit. It's strongly consistent, but it's also blocking. If the coordinator crashes after the prepare phase, participants sit holding locks indefinitely until it recovers, which kills availability exactly when you need the system most. It works reasonably well inside a single datacenter across a small number of tightly coupled resources, like XA transactions spanning two databases owned by the same team, but it doesn't scale to independent microservices with their own deploy cycles and failure domains.

The saga pattern replaces one big transaction with a sequence of local transactions, each with a compensating action to undo it if a later step fails. Order service reserves inventory, payment service charges the card, shipping service schedules a pickup, if shipping fails you run compensations in reverse: refund the payment, release the inventory. Sagas can be orchestrated by a central coordinator that calls each step and tracks state, or choreographed via events where each service reacts to the previous one's event and emits its own.

The real cost of sagas is that compensations must be idempotent and you have to design for the system being observably inconsistent for a window of time, which means your API and UI need to communicate "processing" states honestly. I pick 2PC only when everything lives in one transactional boundary and losing availability during a partition is acceptable; I pick sagas for anything crossing service or team boundaries, and I budget real time for building the dead-letter queues and reconciliation jobs that catch compensations that themselves fail.

That symptom, disappearing the moment you attach a debugger or add a breakpoint, is the classic signature of a heisenbug caused by a timing-dependent race. The debugger changes the interleaving of threads enough that the specific ordering that triggers the bug never happens. Stepping through code line by line is the wrong tool here because it slows execution down to the point where races can't win.

Instead I add structured logging at the suspected shared-state boundaries, timestamped with thread IDs, so I can reconstruct the actual interleaving after the fact from a run where the bug occurred. I also look hard for non-atomic check-then-act patterns: a null check followed by a field read on another thread, a "if not exists then create" against a map without synchronization, or double-checked locking on a field that isn't declared volatile in Java, which lets a thread observe a partially constructed object.

For languages with race detectors I lean on them directly, Go's `-race` flag or ThreadSanitizer for C and C++, run under the actual load test rather than a toy repro, since detectors catch races that never happen to corrupt state in a given run but are still real bugs. I'll also deliberately widen the race window with a stress test, adding a tiny random sleep at the suspected contention point to increase the odds of hitting the bad interleaving, and add fail-fast invariant assertions in staging so the bug crashes loudly with a stack trace instead of silently corrupting data in production.

TCP starts each connection in slow start, doubling the congestion window (cwnd) every round trip until it hits a threshold or detects loss, then switches to additive-increase, multiplicative-decrease in congestion avoidance: cwnd grows by roughly one segment per round trip and gets cut in half on a detected loss. Fast retransmit kicks in on three duplicate ACKs so the sender doesn't have to wait for a full timeout, and fast recovery keeps the connection from dropping all the way back to slow start after an isolated loss.

The cascading failure risk shows up under real load. If a downstream service starts getting slow, its accept queue backs up, packets get dropped or delayed, and clients see retransmission timeouts. Those clients, especially with naive retry logic, resend the request immediately, which adds more load onto a service that was already struggling, and if many clients time out around the same moment you get a synchronized retry storm that looks identical to a DDoS from the receiving service's point of view.

The fix isn't in TCP tuning, it's in your retry policy: exponential backoff with jitter so retries spread out instead of syncing up, circuit breakers that stop sending requests entirely once error rates cross a threshold instead of retrying into a dead service, and sane connection pool limits so one slow dependency can't exhaust your thread pool waiting on it. Worth knowing about TCP incast too, where many-to-one traffic patterns in a datacenter (all your worker nodes replying to one aggregator at once) can cause synchronized packet loss even with well-behaved congestion control on each individual connection.

The simplest approach is last-write-wins, tagging each write with a timestamp and keeping whichever one is newer. It's cheap and requires no application logic, but it silently drops data: if clocks are skewed across nodes, a genuinely later write from a node with a slow clock can lose to an earlier write from a node with a fast clock, and there's no record that a conflict even happened. NTP drift of a few hundred milliseconds is enough to cause real data loss in a high-write-rate system.

Vector clocks fix the "did I even know about the conflict" problem without relying on wall clocks. Each write carries a per-replica counter, and when two versions have counters that are neither strictly greater nor lesser than each other, you know they're concurrent (a true conflict) rather than one being a causal descendant of the other. Riak popularized this: it stores both sibling values and pushes the merge decision up to the application, which knows the domain-specific right answer, like a shopping cart where the correct merge is the union of items from both siblings, not a coin flip.

For cases where you want automatic, correct-by-construction merging without application code, CRDTs (conflict-free replicated data types) are the right tool: G-counters for increment-only counters, OR-sets for add/remove sets that handle concurrent add and remove deterministically, LWW-registers when last-write-wins semantics are actually fine for that specific field. I pick LWW for low-stakes fields like a "last seen" timestamp, vector clocks plus app-level merge for anything like a shopping cart or collaborative document where losing a concurrent write is a real bug, and CRDTs when I want the merge guarantee baked into the data structure itself rather than trusted to application code someone will eventually get wrong.

The core insight most candidates miss at first: this system is read-heavy, often by a wide margin, since every shortened link gets clicked far more times than it gets created. Design around that ratio rather than around the write path.

Generate a short code either by hashing the long URL (base62-encoding a portion of the hash) or with an auto-incrementing counter converted to base62, handling collisions by appending a suffix and retrying. Store the mapping in a key-value store for fast lookups, put a CDN or cache in front of the redirect endpoint since the same popular links get hit constantly, and keep click analytics on a separate write path so it never blocks the redirect itself.

A senior-level follow-up asks about custom aliases and expiration, both of which add real constraints, custom aliases mean you can't just auto-generate a code, you have to check availability, and expiration means you need a cleanup process instead of storing links forever.

First I'd confirm it's an actual leak and not just a workload change, by pulling GC logs and checking whether full GCs are reclaiming memory. If old-gen keeps growing after full GC runs, something is being retained that shouldn't be. I'd enable GC logging with timestamps if it isn't already on, and grab a heap dump with jmap or a JFR recording once usage crosses a threshold, ideally two dumps spaced an hour apart so I can diff the dominator tree instead of eyeballing one snapshot.

In Eclipse MAT or VisualVM I sort by retained size, not shallow size, since the leak is usually a collection holding thousands of small objects rather than one giant object. Common culprits in production-only leaks: a static map or cache used as a session store with no eviction policy, listeners or callbacks registered but never deregistered when a connection closes, ThreadLocal values not cleared in a pooled-thread environment so they outlive the request that set them, or a connection/stream that isn't closed in a finally block and only shows up under real traffic volume that dev never generates.

Since it reproduces only in prod, I'd also check what's different: prod uses a thread pool where dev runs single-threaded, prod has a longer-lived cache TTL, or a feature flag enables an extra code path. I'd correlate the leak's onset with the deploy timeline first, because half the time it maps directly to a recent change, and only fall back to a full heap dump investigation if that doesn't turn anything up.

Junior and mid-level loops lean hardest on the coding and fundamentals sections above. Senior loops shift weight toward system design and toward how you reason about tradeoffs out loud, not just whether you land on the "right" answer. Don't spend your last week before an onsite grinding more mediums you've half-memorized. An hour spent actually explaining the mutex-versus-semaphore table above, out loud, to another person, usually does more for a real interview than one more LeetCode rep.

A static list of software developer interview questions can't push back the way a live interviewer does, though, and that's the real limitation of prepping by reading alone.

What we see in LastRoundAI mock sessions

Candidates who narrate as they code, saying something like "I'm handling the null case first, then the main logic" out loud before typing it, consistently get better feedback in our mock interview sessions than candidates who code silently and only explain afterward. It's a small habit. It doesn't show up in any single question on this page. But it changes how nearly every answer above actually lands with a real interviewer, since the interviewer is grading your reasoning as much as your final code.

The second pattern worth naming: candidates blank out on fundamentals more often than on coding. A correct hash-map solution to Two Sum followed by a fumbled explanation of why a Python dict is close to O(1) costs more than most people expect, since it signals the first answer might have been memorized rather than understood.

Practicing the follow-up, not just the first answer

If a concept above feels shaky when you try to explain it out loud, mutex versus semaphore is a common one, that's usually a concept gap, not nerves. LastRoundAI's Concept Explainer breaks down any of these topics on demand while you practice, so you can check your own explanation against a clean one before an interviewer does it for you.

During a real live interview, Interview Copilot listens in and surfaces structured, real-time guidance on exactly the kind of follow-up questions covered on this page, responses land in under 200 milliseconds across more than 50 languages, and it's built to stay invisible on a screen share. The free plan includes 15 credits a month that reset every month, not a one-time trial, and Starter runs $19/mo if you need more. Both run on desktop and on a mobile-friendly web app; there's no separate native mobile app yet. Questions go to contact@lastroundai.com, the only inbox we check.

Practice, don't just read
Rehearse a real interview, live

LastRoundAI runs a realistic mock interview and gives you real-time guidance on the exact questions above.

LastRound data

What we see on our side

Across 1,393 interview sessions configured on LastRound between January 2025 and July 2026, 109 were set up for backend development. That is a small sample and we are not going to dress it up as more, but it is first-hand rather than borrowed, and it is the pool these questions were sanity-checked against.

Frequently asked questions

What comes up most in software developer interviews?

Data structures, a practical coding exercise and system reasoning appropriate to the level. The weighting shifts toward design as seniority rises, but coding fluency remains the entry gate almost everywhere.

How much LeetCode preparation is actually needed?

Enough for pattern recognition, not exhaustive coverage. Most candidates get better returns from understanding a smaller set of patterns deeply than from volume, and many companies have shifted toward practical exercises.

Do I need system design at junior level?

Increasingly, in a lighter form. Junior candidates are rarely asked to design a large system, but are often asked how they would structure a feature and where they would put state.

What do interviewers actually score?

How you reason out loud. Silent correct answers score worse than narrated approaches with a small bug, because the panel is assessing collaboration as much as correctness.

How this list was built

Worth being straight about where these questions come from, because plenty of pages in this category are not. The set was compiled from a research pass across official documentation, vendor release notes, published engineering writing and public discussion of hiring processes, then cross-checked against the current version of each technology so nothing here describes behaviour that has since changed.

What that means in practice: these are the questions the material supports as reasonable and current for this role, not a transcript of any one company's loop. We have not sat in on your interview and we are not going to claim we have. Treat the list as well-sourced preparation rather than a leaked question bank, and expect your panel to phrase things their own way.

If you spot something out of date, tell us at contact@lastroundai.com and we will fix it.

Leave a Reply

Your email address will not be published. Required fields are marked *