Backend developer interview questions
Questions an interviewer can ask a Backend developer, grouped by years of experience. Open one to see the strongest answer and why it works.
48 of the 961 questions our bank holds for this job. The rest are in the app.
By subject
- Programming Fundamentals
- Backend Languages
- API Development
- Authentication & Authorization
- Databases
- ORMs
- Architecture
- Distributed Systems
- Performance
- Testing
- Security
- Deployment Basics
0–1 years
What is the difference between hashing, encryption and encoding, and when do you use each?
The strongest answer: Hashing is one-way and used to verify, such as a password check; encryption is reversible with a key and used to keep data confidential; encoding such as base64 is only a format change and protects nothing
Why it works: Exactly — and calling out encoding matters, because that is the confusion the question is testing for.
- One-way, reversible, or neither.
- Hash to verify; encrypt to protect.
- Encryption needs a managed key.
- Encoding is a format, not a control.
What is a primary key actually for, and what makes a good one?
The strongest answer: It identifies exactly one row, never changes and is never null — so a stable surrogate or a truly immutable natural key, not an email address people edit
Why it works: Exactly — uniqueness, immutability and not-null are the contract; a key that can change breaks every reference to it.
- One row, one key, forever.
- Never null, never edited.
- Natural keys must be truly immutable.
- Every table needs one.
A teammate commits an API key “just for staging.” What do you do?
The strongest answer: Treat it as a leak: rotate the key, pull it from a vault or CI secrets, and assume the commit was copied — staging is not “fake enough”
Why it works: Exactly. A committed secret is public to anyone with the repo history. Rotate, do not decorate.
- Rotate the leaked key.
- Store secrets outside git.
- Staging is still a system.
- Do not encode and call it safe.
The interviewer asks: “When do you use GET versus POST?” Strongest answer?
The strongest answer: GET for reads that must not change server state — it can be cached, logged and retried. POST when the call creates something or has a side effect.
Why it works: Exactly. The verb is a contract: GET is safe and idempotent; POST is allowed to create or change. Browsers, caches and CDNs rely on that.
- GET = no side effects; POST = create or change.
- Treat GET as cacheable and retry-safe.
- Use POST (or PUT/PATCH/DELETE) when the server will mutate.
- Never put secrets in a query string.
A teammate calls their test a unit test, but it starts a database container and an HTTP server. Does the label matter?
The strongest answer: Yes — a unit test exercises one behaviour with no database, network, clock or filesystem, so it is fast and deterministic; what they have is an integration test, which is fine but belongs in a slower stage
Why it works: Exactly — the label sets the expectation: runs on every save, deterministic, and never red because something else was down.
- Unit: one behaviour, no I/O.
- Fast and deterministic by definition.
- Integration tests are fine — elsewhere.
- Name the layer honestly.
A teammate wants to “save CI minutes” by running tests only before release. What do you say?
The strongest answer: Run the fast tests on every push — a broken main is more expensive than the minutes — and keep a longer suite on a schedule or a label
Why it works: Exactly. Fast signal on every change. Long suites are a different knob.
- Fast tests on every push.
- Longer suite on a slower train.
- Do not wait for release day.
- Do not make the gate unbearable.
A new product with a team of four: monolith or microservices to start?
The strongest answer: A well-structured monolith with clear module boundaries — services add network, deployment and data-consistency costs the team does not need yet
Why it works: Exactly. Modules now, services when a boundary earns the network hop. The structure is what keeps the option open.
- Start modular, not distributed.
- Services cost network, deploys and consistency.
- Boundaries follow the domain.
- Keep the split as a later option.
def add(item, bag=[]): bag.append(item); return bag — you call add(1) then add(2). What comes back the second time?
The strongest answer: [1, 2] — the default list is created once when the function is defined, so every call without a bag shares it; use bag=None and build a new list inside
Why it works: Exactly. One default object, shared for the life of the function. None plus a fresh list inside is the fix.
- Default arguments are evaluated once, at definition.
- A mutable default is shared by every call.
- Use None and create the value inside the function.
- The bug is silent, not an error.
You pass a class instance and a struct to a method and both are modified inside. What does the caller see?
The strongest answer: The class change is visible because the reference points at the same object; the struct was copied, so the caller's copy is untouched unless it was passed with ref
Why it works: Exactly. Reference types share the object, value types are copied.
- class = reference type, struct = value type.
- The reference is copied; the object is not.
- ref / out change the parameter binding itself.
- Mutable structs are a known source of surprises.
What is the difference between a region and an availability zone?
The strongest answer: A region is a geographic area with several availability zones; each zone is one or more separate data centres with its own power and network, so spreading across zones survives a data-centre failure
Why it works: Right. The zone is the fault boundary you design around; the region is where your data lives.
- Region: a geography with multiple zones.
- Zone: isolated power, cooling and network.
- Spread across zones for availability.
- Spread across regions for disaster recovery.
What is the difference between an INNER JOIN and a LEFT JOIN?
The strongest answer: INNER returns only rows that match on both sides; LEFT returns every row from the left table and fills the right side with NULLs where there is no match — so LEFT is how you keep the customers with no orders
Why it works: Right. The question to ask is: do I want rows with no match to survive?
- INNER: matches on both sides only.
- LEFT: all left rows, NULLs for missing right.
- Use LEFT to keep the ones without a match.
- A WHERE on the right table can silently turn LEFT into INNER.
An interviewer says: “Design a URL shortener.” What do you do first?
The strongest answer: Ask what it must do and at what scale — reads vs writes, expiry, custom aliases, analytics — and state the assumptions you will design against before drawing anything
Why it works: Right. Requirements and numbers first; the boxes follow from them.
- Clarify functional and non-functional requirements first.
- State assumed scale in numbers.
- Only then draw.
- Check assumptions with the interviewer.
1–3 years
Orders currently succeed or fail based on whether the payment provider answers within thirty seconds.
The payments team wants the order service to call them synchronously for every order. What do you weigh?
The strongest answer: Whether the order truly needs the payment answer before it can proceed; if yes, keep it synchronous with timeouts and a fallback state, if not, make it an event and reconcile
Why it works: Exactly. The business question decides the integration style, and either way the failure behaviour is designed.
- Does this step need the answer now?
- Sync: timeouts and a fallback state.
- Async: events and reconciliation.
- Own your availability either way.
How do you change the shape of an event that three other teams already consume?
The strongest answer: Add optional fields and never repurpose existing ones; for a breaking change publish a new version alongside the old until consumers have moved, and record the contract in a schema registry or shared repo
Why it works: Exactly. Additive by default, versioned when breaking, contract written down.
- Additive changes only, by default.
- Breaking change: new version side by side.
- Write the contract down.
- Consumers move on their timeline.
A client retries a “create payment” request after a timeout. How does the design stop a double charge?
The strongest answer: The client sends an idempotency key; the server stores the result under that key and returns the same response on a repeat, so retries are safe by design
Why it works: Exactly. Key on the request, result stored, replay returns the original. Retries become harmless.
- Client supplies an idempotency key.
- Server stores the result by key.
- Replay returns the same answer.
- Retries are a given; design for them.
How do you version a public API so you can change it without breaking customers?
The strongest answer: Make additive changes without a version bump, put breaking changes behind a new major version, run old and new in parallel with a published sunset date, and measure who still calls the old one
Why it works: Exactly. Additive quietly, breaking under a version, parallel run, measured deprecation.
- Additive changes need no version.
- Breaking changes get a new major.
- Run both; publish the sunset date.
- Measure remaining callers before removal.
What does CAP actually mean for a design decision, in plain terms?
The strongest answer: When the network partitions, each part of the system either refuses some requests to stay consistent or answers with possibly stale data to stay available — you choose per operation, not per system
Why it works: Exactly. Partition is the given; the choice is what each operation does when it happens.
- Partitions happen; that is the given.
- Refuse (consistent) or answer stale (available).
- Choose per operation.
- No product “solves” it.
The checkout page has been timing out for five minutes and the on-call is asking for help.
Dozens of sessions are waiting and the app has slowed to a crawl. How do you find the culprit?
The strongest answer: Query the lock and session views to find the head of the blocking chain, see what that session is doing and who owns it, then decide whether to wait, contact them or kill it
Why it works: Exactly — find the root blocker, understand it, then act with a decision rather than a reflex.
- Find the head of the chain.
- Understand before you kill.
- Waiters are victims.
- Restarts are not lock management.
The interviewer asks: “What is an N+1 query, and how do you stop it?” Strongest answer?
The strongest answer: One query for the parent list, then one query per row for children. I fix it with a join, an INCLUDE/fetch-join, or a batched WHERE id IN (…).
Why it works: Exactly. You diagnose it with a query count or a trace, then load the graph in one (or two batched) round-trips.
- N+1 = 1 + one query per row.
- See it in traces or query counts.
- Join, fetch-join, or batch IN.
- Don't “fix” it with more connections.
A service fails to start with “address already in use” on port 8080. How do you find out what holds it?
The strongest answer: `ss -ltnp` (or `lsof -i :8080`) shows the listening socket and the owning PID; then I decide whether it is a stale copy of the same service or something that belongs there
Why it works: Right — the socket table tells you exactly who listens; guessing from process names does not.
- Ask the socket table, not the process list.
- ss -ltnp or lsof -i :port.
- Identify before you kill.
- Do not reboot to avoid looking.
Two services both need customer data. Who owns it, and how does the other get it?
The strongest answer: One service owns the write model and is the source of truth; the other reads through its API or keeps a local copy fed by events, accepting that the copy lags
Why it works: Exactly. One writer, one truth; others read or subscribe and accept the lag.
- One owner writes.
- Others read via API or events.
- Copies lag by design.
- No shared tables between services.
Both code paths update the `accounts` and `ledger` tables; the errors cluster around payroll runs.
The application logs show deadlocks a few times a day. What do you do?
The strongest answer: Capture the deadlock graph, find the two code paths that lock the same rows in opposite orders, and fix the ordering or shorten the transactions; the app should also retry the victim
Why it works: Exactly — deadlocks are a design symptom; the graph names the culprits.
- Read the deadlock graph.
- Fix lock ordering.
- Shorten transactions.
- Retry is hygiene, not the cure.
You read a 40 GB log file to count matching lines. What shape does the code take?
The strongest answer: Iterate the file object line by line, or write a generator that yields matches — one line is in memory at a time, so the file size stops mattering
Why it works: Exactly. Streaming turns memory cost into a constant.
- Iterating a file yields lines lazily.
- Generators keep memory constant.
- readlines and read materialise everything.
- yield lets you compose stages without buffering.
What does await actually do?
The strongest answer: It returns control to the caller while the operation is in flight and resumes the rest of the method when it completes — the thread is released rather than blocked, which is why a server scales
Why it works: Exactly. Freeing the thread, not adding one, is the point.
- await frees the thread while waiting.
- No new thread for I/O.
- The continuation resumes afterwards.
- Scalability comes from not blocking.
3–5 years
How do you rename a heavily used column without downtime?
The strongest answer: Expand and contract: add the new column, dual-write from the application, backfill in batches, switch reads, then drop the old column in a later release once nothing references it
Why it works: Exactly — several small, reversible steps instead of one coordinated leap.
- Add, dual-write, backfill, switch, drop.
- Each step is reversible.
- Old and new code coexist.
- Drop only when nothing references it.
The interviewer asks: “The replica is behind. Do you serve stale reads or error?” Strongest answer?
The strongest answer: It depends on the use case: a feed can be stale; a balance after a debit cannot. I name the consistency the product needs (read-your-writes, bounded staleness) and pick primary vs replica per query, not as a global religion.
Why it works: Exactly. This is a per-operation choice. Stronger consistency costs latency and availability during partitions; weaker consistency needs a UX that can live with it.
- Staleness is a product choice per read.
- Money and “did my write stick?” → primary / RYW.
- Feeds can use replicas.
- Don't busy-wait on lag.
The e-commerce team is expanding from one country to four continents.
Product wants globally consistent inventory counts and sub-second responses from every continent. What do you say?
The strongest answer: Those pull against each other; propose strong consistency on the reserve-and-purchase path and a slightly lagging count on display, and show the cost of each alternative
Why it works: Exactly. Split the requirement by operation and make the cost of each visible.
- Name the tension out loud.
- Strong consistency where money moves.
- Lag allowed on display.
- Show the cost of each option.
A backfill must touch every row of a large live table. How do you run it?
The strongest answer: In small keyed batches with short transactions, pausing between them, throttled on replication lag and lock waits, resumable from where it stopped, and run outside the peak
Why it works: Exactly — small, resumable, throttled on the signals that matter.
- Small keyed batches.
- Throttle on lag and locks.
- Resumable from a checkpoint.
- Off-peak, from a server.
A CPU-bound function is too slow. You add threads and nothing improves. Why?
The strongest answer: The GIL lets only one thread execute Python bytecode at a time, so threads help blocking I/O, not computation — use processes, a native library that releases the GIL, or move the work out of Python
Why it works: Exactly. Threads for waiting, processes for computing.
- The GIL serialises bytecode execution.
- Threads still win on I/O waits.
- multiprocessing for CPU-bound work.
- NumPy and friends release the GIL in C.
Under load the app stops responding, CPU is low and requests queue. What do you suspect?
The strongest answer: Thread-pool starvation — blocking on async work with .Result or .Wait() ties up pool threads, and the pool only grows slowly, so everything queues behind it
Why it works: Right. Async all the way down is the fix; sync-over-async is the cause.
- Sync-over-async starves the pool.
- Low CPU with queuing is the signature.
- Async all the way down.
- Never .Result or .Wait() on a hot path.
The business asks for a disaster recovery plan. What two numbers do you ask for first, and why?
The strongest answer: RPO — how much data loss is acceptable — and RTO — how long the service may be down; together they decide whether backups, a warm standby or an active second region is the right answer, and what it costs
Why it works: Right. Every DR design is a point on the RPO/RTO/cost triangle; get the two numbers before the architecture.
- RPO: acceptable data loss.
- RTO: acceptable downtime.
- They pick backup, warm standby or active-active.
- Cost rises steeply as both approach zero.
Two requests read a balance of 100, each subtracts 30, and the balance ends at 70. What happened and what fixes it?
The strongest answer: A lost update: both read then wrote; fix with a single UPDATE that does the arithmetic in the database (SET balance = balance - 30 WHERE balance >= 30), optimistic locking with a version column, or SELECT ... FOR UPDATE
Why it works: Right. Let the database do the arithmetic, or lock or version the row.
- Read-modify-write races.
- Atomic UPDATE with the arithmetic.
- Version column for optimistic locking.
- SELECT FOR UPDATE for pessimistic.
Your queue delivers at least once. What does that force on every consumer?
The strongest answer: Idempotency: each message carries a key and the consumer records what it has processed, so a redelivery after a crash or a slow ack is a no-op; you design the side effects to tolerate repeats rather than hoping for exactly-once
Why it works: Right. Repeats are certain; make them harmless.
- At-least-once means duplicates.
- Consumer keeps processed keys.
- Side effects tolerate repeats.
- Do not rely on exactly-once.
The team wants true continuous deploy to production. What do you still insist on?
The strongest answer: An automated gate that matches the risk (tests, a canary, a halt) and an owner who can stop the train — CD is not “no judgement”
Why it works: Exactly. Automate the judgement you already trust. CAB-on-every-commit is the opposite failure.
- Automate the gate.
- Keep a halt.
- Do not CAB every commit.
- Do not drop signing.
A Spring service bean keeps the current user in a field so its helper methods can read it. Under load, users start seeing each other’s data.
What is wrong, and what is the fix?
The strongest answer: Beans are singletons by default — one instance serves every concurrent request — so a mutable field is shared state; keep the bean stateless and pass the request data in as parameters
Why it works: Exactly — one instance, many threads. Statelessness is the fix; scope tricks only work around a design mistake.
- Singleton bean, many threads.
- Keep request state out of fields.
- Pass it in as a parameter.
- Scope tricks hide the design bug.
A burst of autosaves returns 429 from the API gateway.
What does the UI do with 429?
The strongest answer: Back off using `Retry-After` (or a jittered default), tell the user we are slowing down, and do not tight-loop the same write
Why it works: Exactly. 429 is a contract: wait. The header is the join between gateway and spinner. A tight loop is how you DDoS yourself.
- Honour `Retry-After`.
- Jitter if the header is missing.
- Explain the wait; do not login-bounce.
- Never tight-loop a 429.
5+ years
Product wants full click tapes plus form field values in the frontend logger. How do you respond?
The strongest answer: Minimize: no secrets or PII in RUM, scrub fields, purpose-limit the log, and run it past privacy
Why it works: Frontend logs are production data. Minimization and purpose are the staff answer, not a bigger tape.
- Collect less than product asked.
- Scrub inputs and secrets.
- State the purpose.
- Never log passwords, even temporarily.
The interviewer asks: “Billing keeps importing Order's entity. What's the design problem?” Strongest answer?
The strongest answer: We've crossed a bounded context. Billing should depend on a published language — an API or event — not on Order's tables and types. I split the model and the ownership, not just the repo folders.
Why it works: Exactly. DDD here is practical: independent evolution, independent deploys, a contract at the edge. Shared entities are a distributed monolith.
- Contexts meet at contracts, not tables.
- Don't share entities across domains.
- A shared kernel is still coupling.
- No drive-by SQL into another team's DB.
You are designing a Python service that fans out to many slow downstreams. What shapes the design?
The strongest answer: Async end to end so waiting costs a task rather than a thread, with bounded concurrency per downstream, timeouts on every call and a budget for the whole request
Why it works: Right, and the bound per downstream is what stops you from becoming their outage.
- Async suits many concurrent waits.
- Bound concurrency per dependency.
- Timeout every call, budget the request.
- Measure with real downstream latency.
Your service calls three internal APIs and one is intermittently slow. What do you build in?
The strongest answer: A timeout shorter than your own deadline, limited retries only for idempotent calls with backoff and jitter, a circuit breaker so you stop hammering a dead dependency, and a degraded response when it is unavailable
Why it works: Right, and the degraded path is what turns their outage into a smaller one for you.
- Timeout every outbound call.
- Retry only what is idempotent, with jitter.
- Break the circuit on sustained failure.
- Design what degraded looks like.
You are asked to make a payments service active in two regions. What is the first design question?
The strongest answer: Which data must be strongly consistent and which can be eventually consistent — balances need one writer or a consensus store, receipts can replicate later; that split decides the architecture and most of the cost
Why it works: Right. Sort the data by consistency need first; everything follows from that.
- Classify data by consistency requirement.
- One writer or consensus for the strongly consistent part.
- Eventual replication for the rest.
- Cost follows the split.
Product wants dashboards over the transactional database. When do you say no and build a separate analytical store?
The strongest answer: When the analytical queries scan large fractions of tables, need historical snapshots or joins across services, and start competing with the product for the working set; then a warehouse fed by replication or CDC with its own model is the honest answer
Why it works: Right. Row stores serve transactions; scans and history want a columnar, separately modelled store.
- OLTP: many small transactions.
- OLAP: scans, history, cross-source joins.
- Replica for light reporting.
- Warehouse when they compete.
Design multi-tenancy for a product where some tenants are regulated.
The strongest answer: Tiered isolation: shared schema with a tenant key and row-level enforcement for most, and a separate database or cluster for tenants whose contract or regulation demands it — one code path, an isolation level chosen per tenant, with noisy-neighbour limits and per-tenant keys for encryption
Why it works: Right. One product, several isolation tiers.
- Isolation as a per-tenant tier.
- Shared with row-level enforcement.
- Dedicated where required.
- Same code path.
One endpoint renders PDFs and pegs a core for about two seconds. It is stalling an otherwise healthy Node service.
What do you do?
The strongest answer: Get the CPU work off the request path — a worker thread pool, or better, a queue and a separate worker service — so the API stays responsive and the two scale, retry and deploy independently
Why it works: Right — and the judgement is in the second half: a separate worker lets the slow path fail, retry and scale without touching the API.
- Move CPU work off the request.
- Worker threads free the event loop.
- A queue and worker scale separately.
- Do not reach for a rewrite.
Your worker cannot keep up with the producer and memory climbs. What is the right answer?
The strongest answer: Backpressure — a bounded queue that makes the producer wait or shed load, so the system degrades predictably instead of consuming memory until it dies
Why it works: Exactly. A bounded queue turns an outage into a slowdown you can see.
- Bounded queues create backpressure.
- Unbounded queues move the failure to memory.
- Decide explicitly: wait, shed or drop.
- Make the decision observable.
A payment endpoint can be retried by clients. How do you stop double charges?
The strongest answer: Accept a client-supplied idempotency key, store the outcome against it, and return the original result on a repeat — so a retry after a timeout is safe by construction rather than by luck
Why it works: Right. A timeout is exactly when the client cannot know, so the server must.
- Idempotency key from the client.
- Store the outcome, replay it on a repeat.
- Timeouts are ambiguous by nature.
- Design for at-least-once delivery.
What is a cell-based architecture and when is it worth the complexity?
The strongest answer: Running many small, independent copies of the whole stack, each serving a slice of customers, so a failure or a bad deploy hurts one cell rather than everyone; worth it once a single shared stack's blast radius is the biggest risk you have
Why it works: Right. Blast radius is the product; complexity is the price.
- Many independent full-stack copies.
- Customers routed to a cell.
- Failure contained to one cell.
- Adopt when blast radius is the top risk.
How do you model a warehouse so analysts can answer new questions without you?
The strongest answer: Facts and dimensions — a star schema: a fact table of measurable events keyed to conformed dimensions like date, customer and product — so any question is a join of the fact to dimensions the analysts already know
Why it works: Right. Conformed dimensions are what make questions composable.
- Fact table: events with measures.
- Dimensions: date, customer, product.
- Conformed dimensions across facts.
- Grain declared per fact table.