{"id":1151,"date":"2026-07-23T09:17:00","date_gmt":"2026-07-23T03:47:00","guid":{"rendered":"https:\/\/lastroundai.com\/blog\/?post_type=iq&#038;p=1151"},"modified":"2026-07-21T22:32:44","modified_gmt":"2026-07-21T17:02:44","slug":"backend-developer","status":"publish","type":"iq","link":"https:\/\/lastroundai.com\/interview-questions\/backend-developer","title":{"rendered":"Backend Developer Interview Questions (2026): APIs, Databases &#038; System Design"},"content":{"rendered":"<p>A backend candidate at a 40-person fintech startup spent 25 minutes in a March 2026 onsite without writing a single line of code. The interviewer wanted to know how she&#8217;d handle a Postgres connection pool running out of slots during a marketing-driven traffic spike, not whether she could reverse a linked list. She got the offer. The candidate before her, who spent the same 25 minutes reciting Big-O notation for five sorting algorithms, did not.<\/p>\n<p>That&#8217;s roughly the shape of backend developer interview questions in 2026. The loop still tests data structures and algorithms, most companies haven&#8217;t dropped that filter, but the weight has shifted toward invisible failure modes: what happens when two requests hit the same row at once, what happens when a queue backs up, what happens when a cache goes stale and nobody notices for six hours. This page covers the questions that come up across REST API design, databases, system design, caching, concurrency, auth, message queues, and testing, eight areas that show up in almost every backend loop regardless of whether the stack is Java, Go, Node, or Python. The stack itself keeps shifting anyway, TypeScript overtook JavaScript as GitHub&#8217;s most-used language for the first time in August 2025, and Python grew nearly as fast on AI workloads, per <a href=\"https:\/\/github.blog\/news-insights\/octoverse\/octoverse-a-new-developer-joins-github-every-second-as-ai-leads-typescript-to-1\/\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub&#8217;s 2025 Octoverse report<\/a>. None of that changes what gets tested. Indexing, isolation levels, and cache invalidation look almost identical whether you write Go, Java, Node, or Python at the day job.<\/p>\n<p>One opinion, and it might be wrong: most mid-level backend loops still overweight LeetCode-style puzzles relative to how often that skill predicts on-the-job performance. A candidate who can explain why a query got slow after the users table crossed 10 million rows is more useful to most teams than one who can invert a binary tree from memory in 90 seconds. Plenty of senior backend and infra roles genuinely need the algorithmic depth, and not every hiring manager will agree. But for the bulk of application-layer backend hiring, the balance feels off, and it&#8217;s slowly correcting.<\/p>\n<div class=\"iq-stats not-prose\"><div class=\"iq-stat\"><span class=\"iq-stat__value\">3-5<\/span><span class=\"iq-stat__label\">Rounds<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">LC Medium<\/span><span class=\"iq-stat__label\">Coding<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">APIs &amp; SQL<\/span><span class=\"iq-stat__label\">Core Focus<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">2-4 weeks<\/span><span class=\"iq-stat__label\">Prep Time<\/span><\/div><\/div>\n<h2>REST and API design: where most backend developer interview questions start<\/h2>\n<p>API design questions test whether you think in contracts, not just endpoints. Interviewers care less about whether you remember every HTTP status code and more about whether you understand what breaks when two clients hit the same endpoint at the same time, or when a mobile app on an old version calls a field that no longer exists.<\/p>\n<div class=\"iq-dsec iq-dsec--easy\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"easy\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Easy questions<\/h2><span class=\"iq-dsec__n\">15<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What makes an API RESTful?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Design<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A RESTful API treats state as resources addressed by URLs, uses standard HTTP verbs (GET, POST, PUT, PATCH, DELETE) to act on those resources, and keeps each request stateless, meaning the server holds no session context between calls.<\/p>\n<p>HATEOAS, the idea that responses include links to the next valid actions, is the textbook fourth pillar almost nobody actually implements. Interviewers rarely push on it. What they do push on: do you actually understand statelessness in practice (no server-side session tied to one request), and can you explain why PUT should be idempotent while POST usually isn&#8217;t.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain the difference between PUT and PATCH.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Design<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>PUT replaces the entire resource with the payload you send. PATCH applies a partial update, changing only the fields included in the request body.<\/p>\n<p>A common trip-up: candidates say PUT is idempotent and PATCH isn&#8217;t, without explaining why. PUT is idempotent because sending the same full replacement twice leaves the resource in the same state both times. A PATCH that says &#8220;increment counter by 1&#8221; is not idempotent, since applying it twice changes the result. A PATCH that says &#8220;set status to shipped&#8221; is idempotent. The verb doesn&#8217;t guarantee idempotency by itself, the semantics of the operation do.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between a clustered and non-clustered index?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">SQL<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A clustered index determines the physical order rows are stored on disk, so a table can have only one. A non-clustered index is a separate structure that points back to the row&#8217;s location, and a table can have several.<\/p>\n<p>Interviewers usually follow up with &#8220;so what&#8217;s the cost of adding a non-clustered index?&#8221; Every insert or update now has to update that index too, which is a real write-cost trade-off, not a free performance win. Candidates who only talk about read speed and skip the write cost usually get pushed harder on this.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When would you reach for Redis instead of just querying the database directly?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Caching<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Redis fits read-heavy paths where the same data gets requested repeatedly and can tolerate a small window of staleness, session data, computed leaderboard rankings, rate-limit counters, or hot catalog rows that get hit far more than average.<\/p>\n<p>The reasoning interviewers want: Redis serves reads from memory in under a millisecond compared to a database round trip, and it takes read pressure off the primary database so it doesn&#8217;t become the bottleneck for everything else the app needs to do. Candidates who reach for Redis everywhere, including data that changes every request and has no reuse, usually get pushed on why that&#8217;s actually worth the added complexity.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a race condition? Give a real example.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Concurrency<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A race condition happens when a program&#8217;s correctness depends on the relative timing of operations that can interleave unpredictably.<\/p>\n<p>A classic example: two requests both read a product&#8217;s inventory count as 1, both decide there&#8217;s stock available, and both decrement it, overselling the last unit. The fix requires either a database constraint (preventing inventory from going negative, so one write fails) or a lock around the read-check-write sequence so it happens atomically.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between authentication and authorization?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Auth<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Authentication answers &#8220;who are you,&#8221; verifying identity, usually via a password, token, or biometric. Authorization answers &#8220;what are you allowed to do,&#8221; checking permissions after identity is already established.<\/p>\n<p>A quick way interviewers catch confusion here: ask what error code a request should return when authentication fails (401) versus when a correctly authenticated user tries to access something they don&#8217;t have permission for (403). Mixing those two up is a small tell that the distinction isn&#8217;t fully internalized.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between a unit test and an integration test for a backend service?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Testing<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A unit test isolates a single function or class, mocking out its dependencies, and runs fast with no real database or network call involved. An integration test exercises real collaborators, an actual test database, an actual downstream service or a close stand-in, and runs slower but catches the class of bug unit tests structurally can&#8217;t see, wrong SQL, serialization mismatches, timeouts against a real connection.<\/p>\n<p>Interviewers usually want to hear that you don&#8217;t treat these as competing, a healthy backend test suite has far more unit tests than integration tests, but the integration tests are the ones that catch the bugs that actually make it to production.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is a stateless service, and why does it matter for backend scalability?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Statelessness scaling<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A stateless service doesn&#8217;t keep any client-specific data in memory between requests. Everything it needs, user identity, session data, cart contents, is passed in with the request or pulled from a shared store like a database or Redis. Each request is self-contained, so you can drop any incoming request onto any server instance and get the same result.<\/p>\n<p>This matters for scaling because you can add or remove instances behind a load balancer without worrying about sticky sessions or losing in-flight data. If a server crashes mid-request, the client can just retry against a different instance with nothing to recover. The tradeoff is that state has to live somewhere, so you push it into your database or cache instead of your application servers, which is usually the right place for it since those systems are built to handle replication and failover.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the real difference between SQL and NoSQL databases, and when would you pick each?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">SQL vs NoSQL<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A SQL database like Postgres or MySQL enforces a fixed schema, relationships between tables, and strong consistency through transactions. NoSQL is really an umbrella term for several different models: document stores like MongoDB, key-value stores like DynamoDB, wide-column stores like Cassandra, and graph databases like Neo4j. They usually trade some consistency or query flexibility for horizontal scalability and a looser schema.<\/p>\n<p>In practice I reach for SQL by default because most applications need relational integrity, orders belong to users, payments belong to orders, and the tooling for migrations, joins, and reporting is much better. I reach for NoSQL when I have a specific access pattern that doesn&#8217;t fit rows and joins, like a chat app storing millions of messages keyed by conversation ID, or a catalog with wildly different attributes per category where a rigid schema would mean a lot of null columns.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is a database transaction, and why do we wrap multiple statements in one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">DB transactions<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A transaction groups multiple SQL statements so they either all succeed or all fail together. The classic example is transferring money between two accounts: you debit one row and credit another, and if the process crashes after the debit but before the credit, you&#8217;ve lost money unless both statements were inside a transaction that rolls back cleanly.<\/p>\n<p>Wrapping it in BEGIN, doing both updates, then COMMIT (or ROLLBACK on error) means the database guarantees you never see a half-finished state. Most ORMs expose this as something like db.transaction(async (tx) => { &#8230; }). A common bug I see in review is doing two separate writes without a transaction and assuming it&#8217;ll probably be fine, which works until a deploy or a network blip happens at exactly the wrong moment in production.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Walk through the HTTP status code categories and give an example of when you&#039;d use each.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">HTTP status codes<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>2xx means success. 200 for a normal response with a body, 201 when you&#8217;ve created a resource like a new user or order, 204 when the request succeeded but there&#8217;s nothing to return, like a DELETE. 3xx is redirection: 301 for a permanent move, 302 or 307 for a temporary one, 304 for not modified when a client&#8217;s cached copy is still valid.<\/p>\n<p>4xx means the client did something wrong. 400 for a malformed request body, 401 when they&#8217;re not authenticated at all, 403 when they&#8217;re authenticated but not allowed to do this specific thing, 404 for a resource that doesn&#8217;t exist, 429 for rate limiting. 5xx means the server messed up: 500 for an unhandled exception, 502 when a reverse proxy couldn&#8217;t get a valid response from an upstream service, 503 when the service is temporarily unavailable, often during a deploy or while shedding load on purpose.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a webhook, and how is it different from polling an API for updates?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Webhooks basics<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Polling means your service repeatedly asks another system whether anything new happened, say every 30 seconds. It&#8217;s simple to build but wastes resources when nothing has changed, and there&#8217;s always a delay between when something happens and when you notice it.<\/p>\n<p>A webhook flips that around: the other system calls your endpoint the moment something happens, so you find out immediately instead of waiting for the next poll. The tradeoff is you now need a publicly reachable HTTP endpoint, retries handled if your endpoint is briefly down, verification that the request actually came from who it claims (usually a signature header), and handling for the same event arriving more than once. Stripe and GitHub both sign their webhook payloads for exactly this reason.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why shouldn&#039;t you hardcode secrets like API keys or database passwords in your code?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Secrets management<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>If a secret is hardcoded, it ends up in your git history forever, even if you delete it in a later commit, anyone with repo access or a leaked backup can read it. It also means every environment, local, staging, prod, needs its own code branch or file just to use different credentials, which defeats the point of having one codebase.<\/p>\n<p>The standard fix is to read secrets from environment variables at runtime, injected by whatever runs the process: Docker Compose, Kubernetes secrets, a gitignored .env file locally, or a secrets manager like AWS Secrets Manager or Vault in production. The code just reads process.env.DATABASE_URL and doesn&#8217;t care where the value came from. I&#8217;ve seen real incidents where a hardcoded AWS key sat in a public repo for months before a billing alert caught a crypto-mining bot using it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between handling a request synchronously and processing it asynchronously?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Sync vs async<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Synchronous means the client&#8217;s HTTP request stays open while your server does the work and only responds once it&#8217;s done. That&#8217;s fine for fast operations like fetching a user profile, but a bad idea for anything slow, like generating a PDF report or sending a batch of emails, because the client is stuck waiting and your server has a thread or connection tied up the whole time.<\/p>\n<p>Asynchronous handling means the server accepts the request, hands the work off, usually by pushing a job onto a queue, and immediately responds with something like a 202 Accepted and a job ID. The client then polls a status endpoint or gets notified later, email, websocket, webhook, once it&#8217;s done. This keeps your API responsive under load and lets you scale the workers doing the heavy lifting independently from the servers handling incoming requests.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a foreign key constraint, and what actually breaks if you skip it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Foreign keys<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A foreign key ties a column in one table to the primary key of another, and the database enforces that you can&#8217;t insert a row referencing a parent that doesn&#8217;t exist, and by default won&#8217;t let you delete a parent row while children still reference it, or it cascades the delete depending on how it&#8217;s configured.<\/p>\n<p>Skip foreign keys, thinking you&#8217;ll enforce the relationship in application code instead, and you&#8217;ll eventually end up with orphaned rows: orders pointing at a user_id that was deleted, or line items pointing at a product that no longer exists. This usually surfaces months later as a mysterious null pointer or a join that silently drops rows, and by then it&#8217;s a data cleanup project instead of a five-minute schema fix.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--medium\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"medium\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Medium questions<\/h2><span class=\"iq-dsec__n\">25<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you design idempotency for a POST endpoint like payment creation?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The client generates a unique idempotency key and sends it in a header. The server checks whether it has already processed that key. If yes, it returns the cached result instead of creating a second charge. If no, it processes the request and stores the result against that key, usually with a short TTL.<\/p>\n<p>The real test is the follow-up: what happens if two requests with the same key arrive within milliseconds of each other? A naive &#8220;check then insert&#8221; has a race condition. The fix is a database-level unique constraint on the idempotency key column, so the second concurrent insert fails fast and the handler can look up and return the first result instead of trusting an application-level check alone.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\ndef create_payment(idempotency_key, amount, customer_id):\n\n    existing = db.query(\n\n        \u201cSELECT result FROM payments WHERE idempotency_key = %s\u201d,\n\n        (idempotency_key,)\n\n    )\n\n    if existing:\n\n        return existing.result\n    try:\n\n        result = charge_provider(amount, customer_id)\n\n        db.execute(\n\n            \u201cINSERT INTO payments (idempotency_key, result) VALUES (%s, %s)\u201d,\n\n            (idempotency_key, result)\n\n        )\n\n        return result\n\n    except UniqueViolation:\n\n        # another request won the race, fetch its result\n\n        return db.query(\n\n            \u201cSELECT result FROM payments WHERE idempotency_key = %s\u201d,\n\n            (idempotency_key,)\n\n        ).result\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you version an API without breaking existing clients?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Two common approaches: put the version in the URL (\/v1\/orders, \/v2\/orders) or in a request header (Accept: application\/vnd.company.v2+json). URL versioning is easier to discover, easier to test in a browser, and easier to explain to a partner team. Header versioning is closer to the REST purist&#8217;s idea of a single stable resource with multiple representations.<\/p>\n<p>Most interviewers accept either answer. What they actually probe is whether you have a deprecation plan, a sunset date, monitoring on old-version traffic, and a communication path to whoever still calls v1, rather than just quietly deleting the old route one day.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain the N+1 query problem and how you&#039;d fix it.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">SQL<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>N+1 happens when you fetch a list of N records, then loop over them and issue a separate query for each one&#8217;s related data, one query plus N more instead of one combined query.<\/p>\n<p>The fix is eager loading, joining or batching the related data into the original query (or one follow-up IN query) instead of one query per row. Most ORMs support this but don&#8217;t do it by default, which is why the bug usually shows up in production long after code review missed it. Interviewers watch for whether you&#8217;d catch this in review by spotting a query call inside a loop, or only after a slow-query alert fires.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n# N+1: one query per order (bad, 1 + N round trips)\n\norders = Order.query.all()\n\nfor order in orders:\n\n    print(order.customer.name)   # triggers a query every iteration\n# fixed: eager load customers in the same round trip\n\norders = Order.query.options(joinedload(Order.customer)).all()\n\nfor order in orders:\n\n    print(order.customer.name)   # no extra query\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are the ACID properties, and which one is hardest to get right in practice?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Databases<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Atomicity means a transaction either fully commits or fully rolls back. Consistency means the database moves from one valid state to another according to its constraints. Isolation means concurrent transactions don&#8217;t see each other&#8217;s uncommitted changes, to whatever degree the isolation level guarantees. Durability means a committed transaction survives a crash.<\/p>\n<p>Isolation is the one most engineers get wrong, because default isolation levels differ by engine (Postgres defaults to read committed, MySQL&#8217;s InnoDB to repeatable read) and most developers assume stronger guarantees than their ORM actually provides. A good follow-up question is a phantom-read scenario, to see if you know what your default isolation level actually allows through.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain horizontal vs vertical scaling, and when you&#039;d pick each.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">System Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Vertical scaling means putting your service on a bigger machine, more CPU, more RAM. It&#8217;s the simplest option, requires no application changes, but hits a hardware ceiling and leaves you with a single point of failure. Horizontal scaling means running more instances behind a load balancer, which scales further and survives one instance dying, but it requires the application to be mostly stateless, since session data tied to one server&#8217;s memory won&#8217;t be visible to the others.<\/p>\n<p>A good answer names the actual precondition: you can&#8217;t horizontally scale a service that keeps user session state in local memory without first moving that state somewhere shared, like Redis or a database.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does a load balancer decide which server gets the next request?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">System Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Round robin sends requests to servers in rotating order, simple but ignores current load. Least connections routes to whichever server currently has the fewest active requests, which handles uneven request durations better. Consistent hashing routes based on a hash of something like a user ID or session key, so the same user reliably lands on the same backend, useful for sticky sessions or a per-node local cache.<\/p>\n<p>Interviewers usually add a wrinkle: what happens when a server fails a health check? The answer is that it gets pulled out of rotation automatically, and traffic redistributes among the remaining healthy nodes, ideally without the client noticing anything beyond a slightly higher latency during the transition.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain cache invalidation strategies, and why this is considered one of the genuinely hard problems in computer science.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Caching<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>TTL-based expiry is simplest: set a time limit, accept some staleness within that window, done. Write-through updates the cache and database together on every write, keeping the cache fresh at the cost of extra write latency. Write-behind writes to the cache first and flushes to the database asynchronously, faster but risking data loss if the cache node dies first. Explicit invalidation deletes or updates the specific key the moment underlying data changes, the most accurate option, but easy to miss one code path and leave a stale entry sitting there indefinitely.<\/p>\n<p>Interviewers check whether you understand that TTL-only strategies quietly serve wrong data for the whole window, and whether you&#8217;d keep a TTL as a safety net even on top of explicit invalidation. Most production systems combine both rather than picking one.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between a deadlock and a livelock?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Concurrency<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A deadlock is when two or more threads each wait on a resource the other holds, and neither can proceed, so both sit frozen. A livelock is when threads keep changing state in response to each other, actively running, but never making progress, like two people repeatedly stepping the same direction trying to get out of each other&#8217;s way in a hallway.<\/p>\n<p>Interviewers usually ask how you&#8217;d prevent deadlocks. The standard answer is a consistent lock acquisition order across the whole codebase (always lock A before B, never the reverse), plus a lock timeout so a stuck thread eventually gives up and retries.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Compare session-based auth to JWTs. What are the actual trade-offs?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Auth<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Sessions store state server-side, often in Redis, and are trivially revocable, delete the record and the user is logged out everywhere immediately. JWTs are stateless and self-contained, verified by signature with no round trip to a shared store, which scales cleanly across many app servers. The cost: a JWT can&#8217;t be revoked before it expires without adding a denylist, which brings back the shared-state problem JWTs were supposed to avoid.<\/p>\n<p>A reasonable middle ground worth mentioning: short-lived JWT access tokens paired with a longer-lived, server-revocable refresh token, limiting the blast radius of a compromised token without a lookup on every request.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Walk through the OAuth 2.0 authorization code flow.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Auth<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The client redirects the user to the authorization server. The user authenticates and approves the requested access. The authorization server redirects back with a one-time authorization code. The client exchanges that code, along with its client secret, for an access token and refresh token, through a server-to-server call that never touches the user&#8217;s browser.<\/p>\n<p>For public clients that can&#8217;t safely hold a secret, mobile apps and single-page apps, PKCE adds a client-generated code verifier and challenge, so an attacker who intercepts the authorization code still can&#8217;t complete the token exchange without the original verifier. Interviewers on mobile or frontend-adjacent teams tend to ask about PKCE specifically.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When should an operation go through a message queue instead of a direct API call?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Message Queues<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Route work through a queue when the caller doesn&#8217;t need the result immediately to keep moving, sending a welcome email, generating a receipt PDF, reindexing a search document, and when you want to absorb a traffic spike without every request blocking on a slow downstream step.<\/p>\n<p>A direct call still wins when the caller genuinely needs the answer synchronously, checking whether a coupon code is valid during checkout, for instance. Interviewers are usually testing whether you default to &#8220;everything&#8217;s async now&#8221; without weighing the added complexity: queues introduce ordering questions, retry logic, and a whole new failure mode where a message sits unprocessed and nobody notices.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you test code that depends on the current time or a random number generator?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Testing<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Inject the clock and the random source as dependencies rather than calling system time or a global RNG directly from inside business logic. In tests, supply a fixed, controllable clock and a seeded random generator, so the same test produces the same result every run.<\/p>\n<p>Candidates who skip this and call datetime.now() directly inside logic usually end up with flaky tests that fail once a day at midnight or once a year on a leap day, and interviewers who&#8217;ve been burned by exactly that bug tend to ask this question specifically to see if you&#8217;ve hit it before.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain the CAP theorem, and give a real example of a system that chooses availability over consistency.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">CAP theorem<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>CAP says that when a network partition happens between nodes in a distributed system, you have to choose between consistency, every node sees the same data, and availability, every request gets a response even if it might be stale. You can&#8217;t have both during the partition, though once the network heals you can reconcile. CAP only applies during an actual partition, most of the time you get both.<\/p>\n<p>DynamoDB and Cassandra are classic AP systems: if a node can&#8217;t reach its peers it still accepts writes and reads, and resolves conflicts later with last-write-wins, vector clocks, or application-level merge logic. A traditional single-primary Postgres setup leans CP: if the primary can&#8217;t confirm a write to enough replicas, it&#8217;ll refuse the write rather than risk returning inconsistent data. AP makes sense for a shopping cart, where losing an item briefly is annoying but not dangerous. CP makes sense for an account balance, where showing the wrong number is worse than a slow response.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is database sharding, and what&#039;s the hardest part of actually implementing it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Database sharding<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Sharding splits one logical table across multiple physical databases based on a shard key, usually something like user_id or tenant_id, so no single machine has to hold or serve the entire dataset. Each shard handles a subset of rows and traffic, which is how you scale writes past what one database instance can handle.<\/p>\n<p>The hard part isn&#8217;t picking a shard key, it&#8217;s everything that assumed a single database afterward. Joins across shards don&#8217;t work anymore, so you either denormalize or do the join in application code. A query like &#8220;all users matching X&#8221; now fans out to every shard and merges results in memory. Transactions that used to span two rows on one machine now span two machines and lose easy ACID guarantees. Resharding later, because your key distribution turned out uneven, means moving live data without downtime. I&#8217;ve seen teams shard by user_id and then get stuck when a top-100-users-by-activity report needed data pulled from every shard and aggregated by hand.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s replication lag, and how has it caused a real bug you&#039;ve seen in a production system?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Replication lag<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Read replicas copy data from a primary database asynchronously, so there&#8217;s a small delay, usually milliseconds but sometimes seconds under load, between a write landing on the primary and that same row showing up on a replica. Applications route reads to replicas to spread out load, and writes always go to the primary.<\/p>\n<p>The bug pattern I&#8217;ve seen repeatedly: a user submits a form, the write goes to the primary, the app immediately redirects to a page that reads from a replica to show the result, and the replica hasn&#8217;t caught up yet, so the user sees stale or missing data right after doing the thing that should have created it. Fixes include reading your own writes from the primary for a short window after a write, using a read-your-writes consistency token some databases support, or just polling with a loading state until the data shows up. It&#8217;s a good reminder that eventually consistent really does mean there&#8217;s a real window where the data is wrong.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is connection pooling, and why does a backend service start throwing connection errors under load even though the database itself is healthy?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Connection pooling<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Opening a new database connection is expensive, it involves a TCP handshake, authentication, and on Postgres specifically, forking a whole new backend process. A connection pool keeps a set of already-open connections around and hands them out to requests as needed, so that setup cost gets paid once instead of on every query.<\/p>\n<p>The failure mode under load happens when the pool is smaller than concurrent demand: every connection is checked out to an in-flight request, new requests queue waiting for one to free up, and eventually time out with something like too many clients already or pool exhausted. That&#8217;s not the database being unhealthy, it&#8217;s your application, or a slow query holding a connection too long, starving the pool. The fix is usually some combination of increasing pool size, bounded by what the database can actually handle since Postgres has a hard max_connections limit, adding a proxy like PgBouncer that pools connections above your app instances, and fixing whatever slow query or long transaction is holding connections open longer than it should.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between optimistic and pessimistic locking, and when would you use each?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Locking strategies<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Pessimistic locking grabs a lock on a row before you read it, SELECT &#8230; FOR UPDATE, so any other transaction touching that row has to wait until you commit or roll back. It guarantees no interference, but it means real contention, a slow transaction can block a bunch of others, and a deadlock if two transactions lock rows in different orders.<\/p>\n<p>Optimistic locking assumes conflicts are rare. You read a row along with a version number, do your work, and when you write back you check the version hasn&#8217;t changed, UPDATE &#8230; WHERE id = ? AND version = ?. If zero rows update, someone else beat you to it, and you retry or surface an error. It scales much better under low contention since nothing blocks, but under high contention on the same row you get a lot of wasted retries. I&#8217;d use pessimistic locking for something like a seat reservation where correctness under contention really matters, and optimistic locking for something like a user editing their own profile where two people rarely touch the same row at once.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a circuit breaker pattern, and why doesn&#039;t a simple retry-on-failure loop solve the same problem?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Circuit breaker pattern<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A circuit breaker sits in front of calls to a downstream dependency and tracks failures. Once the failure rate crosses a threshold, it opens and starts failing fast for a cooldown period instead of letting requests through, then periodically lets a small number through to check whether the dependency has recovered before fully closing again.<\/p>\n<p>A plain retry loop actually makes things worse when a downstream service is genuinely down or overloaded: every failed request retries, multiplying traffic hitting an already struggling service, and each retry still burns time, and holds a connection or thread, waiting for a timeout. A circuit breaker stops sending traffic entirely once it&#8217;s clear the dependency is unhealthy, protecting both the caller with a fast failure instead of a hang, and the downstream service from a pile-on that delays recovery. Libraries like resilience4j for Java or opossum for Node implement this, and it pairs well with a fallback, like cached data or a degraded response instead of a hard error.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Compare token bucket and sliding window rate limiting. What are the practical tradeoffs?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Rate limiting algorithms<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Token bucket gives each client a bucket that refills at a fixed rate, say 10 tokens per second, up to some max capacity, and each request consumes a token. It naturally allows short bursts up to the bucket size while still enforcing a long-run average rate, which is why it&#8217;s the default choice for most public APIs.<\/p>\n<p>Fixed window counters, counting requests per client per minute and resetting at the top of the minute, are simple but have an edge case where a client can send double the limit right at the boundary, once just before reset and once right after. Sliding window approaches fix that by weighting the previous window&#8217;s count based on how far into the current window you are, giving a smoother rate at the cost of a bit more bookkeeping, usually a sorted set in Redis for the log variant. In practice I&#8217;ve used token bucket via Redis with a Lua script to make the check-and-decrement atomic, since two separate Redis calls introduce a race under concurrent requests from the same client.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you implement a graceful shutdown so in-flight requests aren&#039;t dropped during a deploy?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Graceful shutdown<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>When an orchestrator like Kubernetes sends SIGTERM to kill an old instance during a rolling deploy, the default behavior in most frameworks is to just die immediately, dropping any request mid-flight. A graceful shutdown catches that signal, stops accepting new connections, waits for in-flight requests to finish up to a timeout, then exits.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">js<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-js\">\n\nprocess.on(\u2018SIGTERM\u2019, () =&gt; {\n\n  server.close(() =&gt; process.exit(0));\n\n  setTimeout(() =&gt; process.exit(1), 10000).unref();\n\n});\n<\/code><\/pre><\/div><\/p>\n<p>You also need the orchestrator side configured correctly. Kubernetes removes a pod from the load balancer&#8217;s endpoints before sending SIGTERM, but there&#8217;s a race where a request can still get routed to a pod that&#8217;s already shutting down, which is why a short preStop sleep hook helps let that routing settle. Skipping this is a common cause of a small spike in 502s or connection resets during every deploy that nobody bothers to investigate.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is backpressure in a queue-based system, and what happens if you don&#039;t handle it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Backpressure handling<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Backpressure is what happens when consumers can&#8217;t keep up with producers, messages pile up faster than workers can process them. If nothing pushes back on the producer side, the queue grows unbounded, which either exhausts memory on the broker or, if it&#8217;s disk-backed, keeps growing until disk fills up, while the age of the oldest unprocessed message keeps climbing.<\/p>\n<p>Handling it means either slowing the producer down when queue depth crosses a threshold, scaling consumers automatically based on queue depth, common with Kubernetes autoscaling a worker pool off a queue-length metric, or shedding load deliberately by dropping or rejecting lower-priority messages rather than trying to process everything. I&#8217;ve seen systems where nobody alerted on queue depth, so a slow downstream API silently backed up a queue for six hours before anyone noticed customers weren&#8217;t getting notification emails until the next morning.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain the difference between read committed, repeatable read, and serializable isolation levels, with a concrete example of what changes between them.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Isolation levels<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Read committed, Postgres&#8217;s default, means each statement in a transaction sees a fresh snapshot of committed data, so running the same SELECT twice in one transaction can return different results if another transaction commits a change in between, a non-repeatable read. Repeatable read fixes that by taking one snapshot at the start of the transaction and using it for every statement in it, so the same query always returns the same rows regardless of what else commits concurrently.<\/p>\n<p>Serializable is the strictest: it behaves as if transactions ran one at a time in some order, even though they&#8217;re actually concurrent, which prevents phantom reads and write skew, where two transactions each read a set of rows, make decisions based on what they saw, and both commit changes that are individually valid but collectively violate an invariant, like two people booking the last seat because both checked availability before either booked. The cost is that serializable transactions can fail with a serialization error the application has to catch and retry, and both repeatable read and serializable hold onto more resources and increase contention. Most applications run read committed and add explicit row locks or unique constraints where correctness actually matters, rather than pay for serializable everywhere.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do feature flags change the way you roll out risky backend changes, and what can go wrong with them?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Feature flags<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A feature flag lets you deploy code with a new path disabled, then turn it on for a small percentage of traffic or a specific set of users without a new deploy, separating deploying code from releasing a feature. If something goes wrong you flip the flag off instantly instead of rolling back a deploy, and you can test in production with real traffic on a small blast radius before going to 100%.<\/p>\n<p>What goes wrong in practice: flags that never get cleaned up pile up until the codebase has dozens of conditional branches nobody&#8217;s sure are safe to delete, flag evaluation itself becomes a dependency, if the flag service is down does the app fail open or fail closed, and did anyone actually decide that on purpose, and flags gating database schema changes are especially dangerous because both old and new code paths need to work against whatever the schema currently looks like during the rollout window. I&#8217;ve seen an incident where a flag got flipped back off after a bad rollout, but the new code path had already written data in a format the old code path couldn&#8217;t read, so turning the flag off didn&#8217;t actually fix anything.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why do you need correlation IDs across a request that touches multiple services, and how do you actually wire that up?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Distributed tracing<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>In a monolith, one request produces one contiguous stack trace and one set of log lines you can read top to bottom. Once a request fans out across services, an API gateway to an auth service to an order service to a payment service, each one logs independently, and if something fails three hops in you need a way to tie all those log lines back to the single request that caused them.<\/p>\n<p>The standard approach is to generate a correlation ID at the edge, the first service the request hits, attach it as a header, commonly X-Request-Id or the W3C traceparent header if you&#8217;re using OpenTelemetry, and every downstream service reads that header, includes it in every log line it writes, and passes it along on any further calls. When something breaks, you query your log aggregator for that one ID and see the full path the request took, including which service it failed in and how long each hop took. Without this, debugging a cross-service failure usually means guessing at timestamps and hoping the clocks are in sync, which they often aren&#8217;t by more than you&#8217;d like.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between a liveness probe and a readiness probe, and what happens if you configure them the same way?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Health checks<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A liveness probe answers whether this process is stuck and should be killed and restarted. A readiness probe answers whether this instance can currently handle traffic. They sound similar but drive different actions: a failed liveness check gets the container restarted, a failed readiness check just gets the pod pulled out of load balancer rotation without killing it.<\/p>\n<p>Configure them identically, usually a naive check that just returns 200 if the process is running, and you lose the value of having two. A service that&#8217;s alive but temporarily can&#8217;t serve traffic, still warming up a cache, or it lost its database connection but will reconnect in a few seconds, should fail readiness and stop getting new traffic without failing liveness, since a restart won&#8217;t fix a database that&#8217;s down and a restart loop just adds noise. I&#8217;ve seen a database blip cause a fleet-wide restart storm because readiness and liveness were wired to the same endpoint that checked the database connection, so every pod got killed and restarted at once instead of quietly waiting out the blip.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--hard\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"hard\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Hard questions<\/h2><span class=\"iq-dsec__n\">7<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Design pagination for an endpoint returning tens of millions of rows.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Design<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Cursor-based (keyset) pagination beats offset-limit pagination at this scale. OFFSET 500000 LIMIT 20 forces the database to scan and discard 500,000 rows before returning anything, and that cost grows linearly as users page deeper. Keyset pagination instead references the last seen sort value plus a tiebreaker column and asks for rows after that point, which an index can satisfy directly.<\/p>\n<p>Interviewers push on two follow-ups almost every time. First, what happens when the sort column has duplicate values, answer: you need a tiebreaker, usually the primary key, or pages will skip or repeat rows. Second, what happens when rows get deleted between page loads, answer: keyset pagination handles this gracefully since it never depends on absolute position, unlike offset pagination which can skip a row entirely if something ahead of it was removed mid-scroll.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">A query that ran in 40ms now takes 4 seconds after the table grew to 10 million rows. Walk through debugging it.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">SQL \/ Performance<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Start with EXPLAIN ANALYZE to see the actual execution plan, not the guessed one. Look for a sequential scan where you&#8217;d expect an index scan.<\/p>\n<p>Common causes, roughly in order of likelihood: a missing index on the filtered or joined column that used to be fast enough on a small table; a function wrapped around the indexed column in the WHERE clause (WHERE LOWER(email) = &#8230;), which blocks a standard btree index unless a matching expression index exists; an implicit type mismatch between the column and the literal you&#8217;re comparing against; and stale table statistics after a bulk load, fixed with a manual ANALYZE. Interviewers listen for whether you reach for &#8220;just add an index&#8221; before confirming the actual plan. Indexes aren&#8217;t free, every one slows down writes and costs disk space.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Two threads each increment the same counter 1 million times, with no lock. What&#039;s the final value, and why?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Concurrency<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Almost certainly less than 2,000,000, and the exact number varies between runs. Increment isn&#8217;t a single hardware instruction in most languages, it&#8217;s read the current value, add one, write it back. If both threads read the same value before either writes back, one increment gets silently lost.<\/p>\n<p>The fix is making the increment atomic, either with a language-level atomic type, a compare-and-swap loop, or a mutex around the read-modify-write sequence. Interviewers push on why the number isn&#8217;t simply wrong in a predictable way, it&#8217;s non-deterministic because it depends on the exact interleaving the scheduler happens to produce that run, which is exactly why race conditions are so miserable to reproduce in a debugger.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n# unsafe: read-modify-write is not atomic\n\ncounter = 0\n\ndef increment():\n\n    global counter\n\n    for _ in range(1_000_000):\n\n        counter += 1   # read, add, write: three separate steps\n# two threads running increment() concurrently will\n\n# lose updates whenever their read\/write steps interleave\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain at-least-once, at-most-once, and exactly-once delivery. Which one do most systems actually implement?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Message Queues<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>At-least-once means a message might be delivered more than once but is never silently dropped, so the consumer has to handle duplicates. At-most-once means a message might be lost and is never retried, simplest to build, riskiest to run. Exactly-once means each message is processed precisely once, no duplicates, no losses.<\/p>\n<p>Exactly-once is mostly a marketing term. Real systems approximate it by combining at-least-once delivery with an idempotent consumer, deduplicating by a message or event ID. True exactly-once across a network that can partition is provably difficult, and most production queues, including ones with &#8220;exactly-once&#8221; in their docs, still lean on idempotent consumers to make that guarantee hold up.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you test a webhook handler that must be idempotent, given that the payment provider might send the same event twice?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Testing \/ System Design<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Send the same webhook payload, with the same event ID, to the handler twice in the test, and assert the resulting side effect, a ledger credit, an order status change, is recorded exactly once, not twice. Also test the event arriving again after a later, different event has already processed, since providers don&#8217;t guarantee ordering on redelivery.<\/p>\n<p>The harder version targets the idempotency check itself: fire the same event from two concurrent requests and confirm only one side effect gets recorded. An application-level &#8220;look it up, then insert if missing&#8221; check has its own race condition, the same one covered in the payment idempotency question earlier on this page. The fix is the same: a database-level unique constraint on the event ID. Application logic alone, promising itself it won&#8217;t race, doesn&#8217;t hold up under real concurrency.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">You need to write to your database and publish an event to a message broker as part of the same operation. Why is this harder than it sounds, and how do you actually solve it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Transactional outbox<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The naive approach, write to the database then publish to the broker, has a gap: if the process crashes or the broker call fails after the database commit, downstream consumers never find out the write happened. Flip the order and publish first, and you can publish an event for a write that then fails to commit, so consumers act on something that never actually happened.<\/p>\n<p>The transactional outbox pattern solves this by writing the event into an outbox table in the same database transaction as the actual business data change, so either both happen or neither does, which you get for free from ordinary ACID guarantees on a single database.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">sql<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-sql\">\n\nCREATE TABLE outbox (\n\n  id BIGSERIAL PRIMARY KEY,\n\n  aggregate_id UUID NOT NULL,\n\n  event_type TEXT NOT NULL,\n\n  payload JSONB NOT NULL,\n\n  created_at TIMESTAMPTZ DEFAULT now(),\n\n  published_at TIMESTAMPTZ\n\n);\n<\/code><\/pre><\/div><\/p>\n<p>A separate process, a polling job, or more commonly a change-data-capture tool like Debezium reading the database&#8217;s write-ahead log, picks up new rows from the outbox table and publishes them to the broker, marking them sent once the publish succeeds. This gives at-least-once delivery of the event with a guarantee it&#8217;s never published without the corresponding data actually being committed, at the cost of some latency between commit and publish, and needing to clean up the outbox table so it doesn&#8217;t grow forever.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you migrate a billion-row table&#039;s schema, adding a column, changing a type, or splitting a table, without taking the application down?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Zero-downtime migrations<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Some changes are cheap: adding a nullable column with no default in modern Postgres is a metadata-only change that doesn&#8217;t rewrite the table, so it&#8217;s instant even on a huge table. But adding a column with a NOT NULL default, changing a column&#8217;s type, or splitting one table into two all require rewriting every row, and running that as one blocking ALTER TABLE on a billion-row table would hold a lock for potentially hours, taking writes down the whole time.<\/p>\n<p>The standard pattern is expand-contract, done in stages that each ship independently. Expand: add the new column as nullable, deploy application code that writes to both the old and new column on every write, then backfill the new column for existing rows in small batches, a script updating a few thousand rows at a time with a short pause between batches, so you never hold a long lock or generate a burst of write-ahead log that overwhelms replication. Once the backfill is verified, deploy code that reads from the new column instead of the old one. Finally, contract: drop the old column once nothing depends on it, as its own later deploy so you can back out of any stage cleanly. The mistake I see most often is trying to add the column, backfill, and switch reads all in one deploy, which removes your ability to roll back cleanly if something goes wrong halfway, and on a table that size, something always takes longer than expected.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--scenario\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"scenario\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Real-time scenario questions<\/h2><span class=\"iq-dsec__n\">5<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Design a URL shortener that needs to handle 50 million redirects a day with sub-50ms p99 latency.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">System Design<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>50 million redirects a day averages out to roughly 580 requests per second, with real traffic spikier than that average. The read path (the redirect) dominates the write path (creating a link) by a wide margin, which changes where you spend engineering effort.<\/p>\n<p>For writes: generate the short code with a dedicated ID-generation service (Snowflake-style, or a counter service handing out ID ranges) and base62-encode it, rather than a single database auto-increment column, which becomes a coordination bottleneck across multiple app servers or regions. For reads: put a cache, Redis or a CDN edge cache, in front of the datastore, since a small set of popular links account for most daily redirects. Only fall back to the primary store on a cache miss. Keep the datastore simple, a key-value store or Postgres with a unique index on the short code, since it&#8217;s a single indexed lookup, not a complex query. Given the sub-50ms p99 target, cache hit rate is the metric to protect, database throughput mostly stops mattering once the hot set is cached.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Design a rate limiter that has to work correctly across many instances of the same service, not just one process.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Distributed rate limiting<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An in-memory rate limiter, a counter in each process, doesn&#8217;t work once you have more than one instance behind a load balancer, because each instance only sees its own slice of traffic and a client could get three times the intended limit just by hitting three different instances. You need shared state, which almost always means Redis or something similarly fast and centralized.<\/p>\n<p>The tricky part isn&#8217;t the storage, it&#8217;s making the check-and-increment atomic under concurrency. A GET followed by a conditional INCR as two separate Redis calls has a race: two requests from the same client can both read under limit before either increments, and both proceed. The fix is a Lua script executed atomically on Redis, doing the read, the limit check, and the increment in one round trip Redis guarantees runs without interleaving from another client&#8217;s script.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">lua<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-lua\">\n\nlocal tokens = tonumber(redis.call(\u2018HGET\u2019, KEYS[1], \u2018tokens\u2019) or capacity)\n\nlocal last = tonumber(redis.call(\u2018HGET\u2019, KEYS[1], \u2018ts\u2019) or 0)\n\nlocal now = tonumber(ARGV[1])\n\nlocal refilled = math.min(capacity, tokens + (now \u2013 last) * rate)\n\nif refilled &gt;= 1 then\n\n  redis.call(\u2018HSET\u2019, KEYS[1], \u2018tokens\u2019, refilled \u2013 1, \u2018ts\u2019, now)\n\n  return 1\n\nend\n\nredis.call(\u2018HSET\u2019, KEYS[1], \u2018tokens\u2019, refilled, \u2018ts\u2019, now)\n\nreturn 0\n<\/code><\/pre><\/div><\/p>\n<p>At high scale Redis itself can become the bottleneck or a single point of failure, which usually means Redis Cluster with rate limit keys sharded by client ID, accepting the limit is enforced per shard rather than globally unless you&#8217;re willing to pay for cross-shard coordination.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">A backend service&#039;s memory usage climbs steadily over hours until it gets OOM-killed and restarts. Walk through how you&#039;d actually track down the leak.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Memory leak debugging<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>First I&#8217;d confirm it&#8217;s a real leak and not just normal growth. A lot of runtimes, the JVM, Node&#8217;s V8, let heap usage climb toward a configured max before garbage collecting aggressively, so a sawtooth pattern that resets after GC is healthy, while a line that only ever goes up regardless of GC activity is the actual problem. I&#8217;d pull up whatever memory metrics are already being scraped, heap used, RSS, GC pause frequency, to see the shape of the curve before touching anything.<\/p>\n<p>Once confirmed, I want a heap snapshot from a live, leaking instance taken at two points in time far apart, then diff them. In Node that&#8217;s the inspector plus Chrome DevTools&#8217; memory profiler, comparing object counts by constructor between snapshots, the type whose count keeps growing and never drops is usually the leak. In Java it&#8217;s a heap dump via jmap fed into Eclipse MAT, looking at the dominator tree for what&#8217;s retaining the most memory and why it&#8217;s still reachable. Common real causes: an event listener registered on every request but never removed, a cache with no eviction policy or TTL that accumulates keys forever, or a closure that unintentionally captures a large object and keeps it alive through a lingering reference chain nobody realized existed. The fix is almost never adding more memory, that just delays the OOM kill and hides the underlying bug longer.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">A service that normally responds in 50ms starts timing out under load. Walk through how you&#039;d figure out whether it&#039;s CPU-bound, IO-bound, or stuck on lock contention.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Performance debugging<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>First I&#8217;d check CPU utilization on the box or container. If CPU is pegged near 100%, it&#8217;s compute-bound, either the request rate genuinely exceeds what this instance can process, scale out, or find and fix an inefficient hot path with a profiler, or something started doing unexpectedly expensive work, like a regex with catastrophic backtracking or serializing a much larger payload than usual. A flame graph from a profiler, pprof for Go, async-profiler for Java, the built-in profiler flag in Node, narrows this to the actual function burning cycles quickly.<\/p>\n<p>If CPU is low but latency is still high, it&#8217;s more likely IO-bound or blocked, which means checking what the request is actually waiting on: database query time via something like pg_stat_activity for long-running or waiting queries, an outbound call to a dependency that&#8217;s slow, checked through your own outbound metrics or traces rather than assumptions, or thread pool exhaustion where requests queue waiting for a worker thread tied up elsewhere. Lock contention specifically shows up as low CPU, low apparent IO wait, but high latency, and in Postgres you&#8217;d look at pg_locks joined against pg_stat_activity to see what&#8217;s blocking what, while for an application-level lock you&#8217;d want thread dumps taken during the slow period to see multiple threads stuck waiting on the same one. The key discipline is grabbing evidence while the problem is actually happening, because a lot of these symptoms disappear once load drops and you can&#8217;t reconstruct them from memory afterward.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Design a backend that serves writes from multiple regions at once for low latency, and explain what you give up to get it.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Multi-region writes<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Active-active means users in the US write to a US region and users in Europe write to a European region, both accepting writes concurrently, instead of every write funneling through one primary region regardless of where the user is, which adds real latency for anyone far from it. The hard problem is what happens when the same piece of data gets modified in two regions before either replication catches up to the other, a genuine conflict to resolve.<\/p>\n<p>Some data doesn&#8217;t need global ordering and is easy: a user&#8217;s own profile settings written from wherever they last logged in can use last-write-wins with a timestamp, and the rare case where it&#8217;s wrong is low stakes. Other data can&#8217;t tolerate that at all, an account balance or an inventory count can&#8217;t just pick whichever write came later, since both writes might have been individually valid decrements that together overdraw the balance. For that kind of data you either accept the latency of routing writes to a single authoritative region regardless of user location, giving up the low-latency win for that data type, or use CRDTs, conflict-free replicated data types mathematically designed to merge concurrent updates, which works well for counters and sets but doesn&#8217;t solve every kind of conflict, like keeping an inventory count from going negative when two regions both accept an order for the last unit. Realistically most systems end up as a mix, most data active-active with simple conflict resolution, a small set of genuinely global invariants routed through one source of truth even at some latency cost.<\/p>\n<p><\/div><\/div><\/div>\n<h2>How to prepare without wasting three weeks on the wrong things<\/h2>\n<p>Start with SQL and system design fundamentals. They show up in nearly every backend loop regardless of company size, more consistently than framework-specific knowledge. Most backend developer interview questions repeat across companies more than candidates expect once you&#8217;ve sat through a few dozen of them, the wording changes, the underlying concept (indexing, isolation, cache staleness, idempotency) doesn&#8217;t.<\/p>\n<p>Across mock backend interviews run through LastRoundAI&#8217;s practice sessions, the most common stumble isn&#8217;t the SQL query itself, it&#8217;s the follow-up. A candidate writes a correct index recommendation, then goes quiet when asked what happens if two transactions update the same indexed row at the same moment. Practicing the follow-up out loud matters more than grinding another 20 algorithm problems that won&#8217;t actually come up.<\/p>\n<p>I don&#8217;t have great data on how this differs for backend roles at seed-stage startups versus companies past Series C, our sessions skew toward candidates targeting Series A through C companies, so take anything here about large enterprise loops with a grain of salt. What does hold up broadly: demand isn&#8217;t slowing down. The U.S. Bureau of Labor Statistics projects employment for software developers, the category backend engineering rolls up into, to grow 15% from 2024 to 2034, with roughly 129,200 openings a year, well ahead of the average for all occupations, per the <a href=\"https:\/\/www.bls.gov\/ooh\/computer-and-information-technology\/software-developers.htm\" target=\"_blank\" rel=\"noopener noreferrer\">BLS Occupational Outlook Handbook<\/a>.<\/p>\n<p>If you want to rehearse these backend developer interview questions out loud before the real thing, with follow-ups that mirror what actual loops ask, LastRoundAI&#8217;s <a href=\"\/products\/mock-interviews\">mock interview practice<\/a> runs through these same categories live. The free plan includes 15 credits a month that reset monthly, and Starter is $19\/mo for more sessions. If interview performance isn&#8217;t the bottleneck and finding enough matching backend openings is, <a href=\"\/products\/auto-apply\">Auto-Apply<\/a> finds and applies to matching backend roles for you, with every application queued for your review before anything sends. Questions about either product: contact@lastroundai.com.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A backend candidate at a 40-person fintech startup spent 25 minutes in a March 2026 onsite without writing a single line of code. The interviewer wanted to know how she&#8217;d handle a Postgres connection pool running out of slots during a marketing-driven traffic spike, not whether she could reverse a linked list. She got the&#8230;<\/p>\n","protected":false},"author":4,"featured_media":1631,"comment_status":"open","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"tags":[],"class_list":["post-1151","iq","type-iq","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Backend Developer Interview Questions (2026) | LastRoundAI<\/title>\n<meta name=\"description\" content=\"Backend developer interview questions for 2026: REST APIs, SQL, system design, Redis caching, concurrency, and auth. Language-agnostic, mixed difficulty.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/lastroundai.com\/interview-questions\/backend-developer\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Backend Developer Interview Questions (2026) | LastRoundAI\" \/>\n<meta property=\"og:description\" content=\"Backend developer interview questions for 2026: REST APIs, SQL, system design, Redis caching, concurrency, and auth. Language-agnostic, mixed difficulty.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/lastroundai.com\/interview-questions\/backend-developer\" \/>\n<meta property=\"og:site_name\" content=\"LastRound AI\" \/>\n<meta property=\"og:image\" content=\"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-backend-developer-og.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"49 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/backend-developer\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/backend-developer\",\"name\":\"Backend Developer Interview Questions (2026) | LastRoundAI\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/backend-developer#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/backend-developer#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-backend-developer-og.png\",\"datePublished\":\"2026-07-23T03:47:00+00:00\",\"description\":\"Backend developer interview questions for 2026: REST APIs, SQL, system design, Redis caching, concurrency, and auth. Language-agnostic, mixed difficulty.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/backend-developer#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/backend-developer\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/backend-developer#primaryimage\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-backend-developer-og.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-backend-developer-og.png\",\"width\":1200,\"height\":630,\"caption\":\"Backend Developer interview questions \u2014 LastRoundAI\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/backend-developer#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Interview Questions\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Backend Developer Interview Questions (2026): APIs, Databases &#038; System Design\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"name\":\"LastRound AI\",\"description\":\"Interview Assistant prep, tech careers and AI tools\",\"publisher\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\",\"name\":\"LastRound AI\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"width\":400,\"height\":400,\"caption\":\"LastRound AI\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Backend Developer Interview Questions (2026) | LastRoundAI","description":"Backend developer interview questions for 2026: REST APIs, SQL, system design, Redis caching, concurrency, and auth. Language-agnostic, mixed difficulty.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/lastroundai.com\/interview-questions\/backend-developer","og_locale":"en_US","og_type":"article","og_title":"Backend Developer Interview Questions (2026) | LastRoundAI","og_description":"Backend developer interview questions for 2026: REST APIs, SQL, system design, Redis caching, concurrency, and auth. Language-agnostic, mixed difficulty.","og_url":"https:\/\/lastroundai.com\/interview-questions\/backend-developer","og_site_name":"LastRound AI","og_image":[{"width":1200,"height":630,"url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-backend-developer-og.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"49 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/lastroundai.com\/interview-questions\/backend-developer","url":"https:\/\/lastroundai.com\/interview-questions\/backend-developer","name":"Backend Developer Interview Questions (2026) | LastRoundAI","isPartOf":{"@id":"https:\/\/lastroundai.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/lastroundai.com\/interview-questions\/backend-developer#primaryimage"},"image":{"@id":"https:\/\/lastroundai.com\/interview-questions\/backend-developer#primaryimage"},"thumbnailUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-backend-developer-og.png","datePublished":"2026-07-23T03:47:00+00:00","description":"Backend developer interview questions for 2026: REST APIs, SQL, system design, Redis caching, concurrency, and auth. Language-agnostic, mixed difficulty.","breadcrumb":{"@id":"https:\/\/lastroundai.com\/interview-questions\/backend-developer#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/lastroundai.com\/interview-questions\/backend-developer"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/interview-questions\/backend-developer#primaryimage","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-backend-developer-og.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-backend-developer-og.png","width":1200,"height":630,"caption":"Backend Developer interview questions \u2014 LastRoundAI"},{"@type":"BreadcrumbList","@id":"https:\/\/lastroundai.com\/interview-questions\/backend-developer#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/lastroundai.com\/blog"},{"@type":"ListItem","position":2,"name":"Interview Questions","item":"https:\/\/lastroundai.com\/interview-questions"},{"@type":"ListItem","position":3,"name":"Backend Developer Interview Questions (2026): APIs, Databases &#038; System Design"}]},{"@type":"WebSite","@id":"https:\/\/lastroundai.com\/blog\/#website","url":"https:\/\/lastroundai.com\/blog\/","name":"LastRound AI","description":"Interview Assistant prep, tech careers and AI tools","publisher":{"@id":"https:\/\/lastroundai.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/lastroundai.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/lastroundai.com\/blog\/#organization","name":"LastRound AI","url":"https:\/\/lastroundai.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","width":400,"height":400,"caption":"LastRound AI"},"image":{"@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1151","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq"}],"about":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/types\/iq"}],"author":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/comments?post=1151"}],"version-history":[{"count":3,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1151\/revisions"}],"predecessor-version":[{"id":1744,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1151\/revisions\/1744"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media\/1631"}],"wp:attachment":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media?parent=1151"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/tags?post=1151"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}