Top 30 Advanced Backend Dev Interview Questions and Answers
30 advanced multiple-choice Backend Dev interview questions, the deep end: internals, failure modes, and the design calls a senior engineer is expected to defend. They come from 30 bites in the Backend Dev library, the hardest slice of the 533 Backend Dev interview questions in the library. Answer them here or read straight down. Every question carries the correct option, why it is correct, and a link to the bite it came from.
Backend engineering, APIs, and databases
30 questions. Pick an answer, or open “Show the answer” to read it.
Answers are graded in your browser. Nothing is saved, and no XP or streak is earned here. The app keeps score.
Question 1 of 30
Which statement accurately contrasts the safety responsibilities you assume when using unsafe in Rust versus Go?
Show the answer
Answer: b · Rust's borrow checker still enforces rules on safe code and only five superpowers bypass checks, while Go requires guaranteeing GC reachability and valid memory
Rust's borrow checker continues enforcing rules on safe code inside unsafe blocks; only the five superpowers bypass checks, while Go unsafe requires manual cooperation with the GC to ensure reachability and valid memory. Distractor A is wrong because the borrow checker is not fully disabled, and D is wrong because Rust still requires upholding aliasing invariants and Go unsafe enables pointer arithmetic.
Read the full bite: Contrast unsafe in Go versus Rust and the invariants you assume
Question 2 of 30
When a FastAPI endpoint awaiting asyncpg is suspended during a database query, how can Uvicorn process another incoming connection in the same worker process?
Show the answer
Answer: d · The event loop yields the coroutine at the await, registers the socket with epoll or kqueue, and schedules the new connection's coroutine on the same thread.
The correct answer describes cooperative multitasking: the event loop suspends the coroutine at await and interleaves I/O-bound tasks on a single thread. Distractor A is wrong because Uvicorn does not use multiple Python threads to handle requests; concurrency comes from the loop scheduling coroutines, not from threading or GIL behavior.
Read the full bite: How does Uvicorn use asyncio to handle thousands of concurrent connections?
Question 3 of 30
In FastAPI, why should an async database session dependency wrap yield in try and place await session.close() in finally?
Show the answer
Answer: a · It guarantees cleanup runs even if the path operation raises an exception.
A try/finally block guarantees that await session.close() runs even when the path operation raises an exception, preventing database connection leaks. The thread pool issue in distractor C is caused by using def instead of async def, not by omitting exception handling.
Question 4 of 30
A transaction reads a row twice and gets different committed values each time. Which isolation level permits this while still forbidding dirty reads?
Show the answer
Answer: d · Read Committed
Read Committed forbids dirty reads but allows non-repeatable reads, so committed changes can appear between two reads. Read Uncommitted would also allow dirty reads, which is stricter than the scenario describes.
Read the full bite: Read Committed versus Serializable isolation levels
Question 5 of 30
In Pydantic v2, what is the runtime validation behavior of a generic wrapper field typed as T when the model is used without parametrization?
Show the answer
Answer: a · The field is validated as Any, accepting arbitrary data and generating an overly permissive schema
The card explicitly states that unparametrized TypeVars are treated as Any at validation time, yielding an overly permissive OpenAPI schema. Option C is a tempting distractor because developers often assume missing generic parameters cause runtime errors, but Pydantic v2 gracefully falls back to Any instead.
Read the full bite: Create a generic Pydantic BaseModel for API response wrappers
Question 6 of 30
An endpoint fires 1 query for a list and then one extra query per item to load a relation. Which fix most directly reduces the number of round trips?
Show the answer
Answer: c · Eager-load the relation with a JOIN or single batched IN query
Eager loading collapses the per-item queries into one or two statements, attacking the round-trip count itself. Caching only masks the volume and adds invalidation work without removing the structural N+1 pattern.
Read the full bite: Diagnosing and fixing the N+1 query problem
Question 7 of 30
A social app adds a like_count column directly on the posts table instead of counting rows in a likes table on every read. According to the card, what new problem does this denormalization introduce?
Show the answer
Answer: a · The count can drift out of sync if updates aren't handled atomically, requiring periodic reconciliation
The card's example warns that a missed update or race condition can leave the count wrong, requiring periodic reconciliation, that is the core cost of denormalizing. The tempting wrong answer has it backwards: the point of adding the column is that reads become a fast single column fetch, not a join.
Read the full bite: When to intentionally denormalize a schema
Question 8 of 30
When auditing a request-rate counter that may overflow, which statement accurately distinguishes how Rust and Go handle fixed-width integer overflow by default in production release builds?
Show the answer
Answer: d · Rust panics on overflow only in debug builds and wraps in release like Go; Rust offers wrapping_ methods for explicit modular arithmetic
Rust panics on integer overflow in debug builds but wraps silently in release builds, mirroring Go's default behavior, and the wrapping_ methods explicitly opt into modular arithmetic in any mode. Option B is tempting because many candidates mistakenly believe Rust always panics, but it actually wraps in release, and saturating_ clamps to bounds rather than preventing debug panics.
Read the full bite: Default integer overflow behavior in Go versus Rust
Question 9 of 30
When module B requires module A in the middle of A's own loading, what does B receive?
Show the answer
Answer: c · A's exports object as populated so far, possibly incomplete
CommonJS caches the exports object at load start and returns it as-is, so B gets whatever A has assigned up to that point. It is not re-executed, does not throw, and the reference is not retroactively backfilled.
Read the full bite: Circular dependencies in CommonJS modules
Question 10 of 30
Why can two installed major versions of the same library cause instanceof checks to fail across packages?
Show the answer
Answer: b · Each copy is a distinct module instance with its own classes, so cross-copy instanceof returns false
Two physical copies are separate module instances with separate class identities, so an object from one copy is not an instance of the other copy's class. npm does not strip prototypes.
Read the full bite: Diamond dependencies and nested node_modules
Question 11 of 30
When designing a lookup function that may not find a value, what distinguishes Rust's Option<&T> from Go's *T at the type-system level?
Show the answer
Answer: c · Option<&T> explicitly encodes absence while &T remains non-null, costing no extra space via the null pointer optimization.
Option C is correct because Rust references are non-null by construction, and Option<&T> uses the null pointer optimization to represent None without extra memory, forcing compile-time handling of absence. Option A is a common misconception because Option is an algebraic data type with semantic guarantees that Go's implicit nullability lacks, not merely syntactic sugar.
Read the full bite: Go nil pointers vs Rust Option: impact on signatures and safety
Question 12 of 30
What is a distinctive advantage of monorepo workspaces over consuming shared code as published private packages?
Show the answer
Answer: d · A breaking change and all consumer updates can land in one atomic commit without a publish step
Workspaces link internal packages locally, so changes to shared code and its consumers ship in a single atomic commit. Published packages instead require a publish-and-bump cycle and risk version drift.
Read the full bite: Monorepo workspaces vs private npm packages
Question 13 of 30
Which statement correctly contrasts the scope of a Go if initializer's := binding with a Rust let binding inside a block?
Show the answer
Answer: b · In Go, a variable declared with := in an if initializer is visible in both the if and else branches but not outside, while in Rust, a let binding inside a block is strictly confined to that block.
The card states that Go's if initializer creates bindings visible in every branch of that if but not outside, whereas Rust's let inside a block is confined to that block. Option C reverses these scope rules, and option D repeats the common misconception that := always mutates rather than potentially shadowing.
Read the full bite: Shadowing in Go and Rust: idioms, bugs, and if-block scoping
Question 14 of 30
A client sends GET /items?limit=foo to an endpoint with parameter limit: int. What is FastAPI's default response?
Show the answer
Answer: a · HTTP 422 Unprocessable Entity with a JSON body whose detail array contains objects with loc, msg, and type fields
FastAPI relies on Pydantic to automatically validate query parameters and returns a 422 Unprocessable Entity with a JSON detail array of objects containing loc, msg, and type fields. Option B is tempting because the status code is correct, but the body structure is actually a detailed array rather than a single string.
Read the full bite: FastAPI non-integer query param default behavior
Question 15 of 30
Why can't a standard foreign key enforce integrity on the commentable_id column in a polymorphic comments table?
Show the answer
Answer: b · A foreign key can reference only one specific table, not a target chosen at runtime
A foreign key is bound to one referenced table at definition time, so it cannot validate an ID whose parent table varies per row. Foreign keys can reference any unique column and text can be indexed, so those options are wrong.
Read the full bite: Polymorphic associations and referential integrity
Question 16 of 30
A Cassandra feed table partitioned by user_id makes feed reads fast. What is the main cost this design imposes compared to a relational read-time join?
Show the answer
Answer: b · Each new post must be written into every follower's partition
Fan-out on write copies each post into all followers' partitions, creating heavy write amplification, which is the trade-off for cheap single-partition reads. Cassandra avoids cross-partition joins and favors availability over strong consistency, so those options are wrong.
Read the full bite: Relational versus wide-column for a news feed
Question 17 of 30
To accept multiple values for a single query key and allow per-item length constraints in current FastAPI, which parameter declaration should you use?
Show the answer
Answer: c · tag: Annotated[list[str], Query()] = []
Annotated[list[str], Query()] = [] is the modern pattern that separates validation metadata from the default value, enabling per-item constraints. tag: list[str] = Query(default=[]) is the outdated alternative that mixes the default with validation metadata.
Read the full bite: How do you type-hint repeated query params in FastAPI?
Question 18 of 30
Which statement accurately describes the behavior of {file_path:path} compared to {file_path} in a FastAPI route definition?
Show the answer
Answer: c · {file_path:path} uses a Starlette converter to greedily match slashes across segments while {file_path} stops at the next slash
{file_path:path} relies on Starlette's path converter to consume the rest of the URL including slashes, whereas a plain parameter matches only one segment regardless of the str type hint. Option B is wrong because a str annotation does not make routing greedy, and option A incorrectly confuses a router directive with Pydantic validation.
Question 19 of 30
A users table is sharded by user_id. What is the most efficient way to support frequent logins that look users up by email?
Show the answer
Answer: a · Maintain a secondary email-to-user_id index to resolve the shard in one hop
A secondary mapping from email to user_id lets a login resolve the correct shard directly, avoiding a broadcast. A UNIQUE constraint only enforces uniqueness within a single shard, and scatter-gather wastes resources on every login.
Read the full bite: Shard key impact on uniqueness and cross-shard lookups
Question 20 of 30
Why does a worker-pool design usually finish faster than processing fixed chunks of ten sequentially?
Show the answer
Answer: a · It keeps ten requests always in flight instead of waiting for each chunk's slowest item
Fixed chunks must wait for the slowest request in each batch before starting the next, leaving slots idle. A worker pool immediately refills a freed slot, maintaining full concurrency throughout.
Read the full bite: Bounded concurrency for many async requests
Question 21 of 30
In Go, if an outer struct defines a method M and embeds a type that also promotes M, what happens when M is called on the outer struct?
Show the answer
Answer: a · The outer struct's own method shadows the promoted method
Go gives precedence to methods defined directly on the outer type, so the outer struct's method shadows the promoted one. Distractor B describes the compile-time ambiguity that occurs only when two embedded fields at the same depth promote identical method names, not when the outer type defines its own.
Read the full bite: Explain Go struct embedding vs inheritance and method promotion
Question 22 of 30
When is Promise.allSettled the better choice over Promise.all?
Show the answer
Answer: c · When each operation is independent and you need every outcome, including failures, reported
allSettled waits for every input and reports each outcome, ideal when partial success is acceptable and you must see all failures. all aborts on the first rejection, hiding other results.
Question 23 of 30
On a 64-bit architecture, why can reordering struct fields from largest to smallest reduce memory usage in Go and Rust?
Show the answer
Answer: b · Compilers preserve source order and insert padding to satisfy alignment, which manual reordering minimizes.
The correct answer is C because Go and Rust maintain declared field order and insert padding bytes to meet alignment requirements; manually ordering fields by size reduces this internal padding. The most tempting distractor is A because candidates often incorrectly assume compilers automatically optimize struct layout, but the card explicitly states this is a red flag.
Read the full bite: How does struct field ordering affect memory layout in Go and Rust?
Question 24 of 30
Why is for await...of preferable to Promise.all for processing a multi-gigabyte file line by line?
Show the answer
Answer: c · It consumes lines lazily one at a time, keeping memory bounded with backpressure
for await...of awaits each item before requesting the next, so only one line is held at a time and memory stays flat. Promise.all would require buffering every line in memory at once.
Read the full bite: Async iterators and for await...of for streaming
Question 25 of 30
When choosing between an enum and boxed trait objects for a heterogeneous shape collection in Rust, which statement best captures a fundamental architectural difference?
Show the answer
Answer: a · Vec<Box<dyn Draw>> stores fat pointers and scatters shape data across heap allocations, while Vec<Shape> keeps all data contiguous with static dispatch.
Vec<Shape> stores variants contiguously with static dispatch, while Vec<Box<dyn Draw>> uses fat pointers that scatter heap allocations and incur vtable indirection. The distractor claiming dyn Trait inherently requires Box is incorrect because trait objects only require indirection, which can also be provided by references.
Read the full bite: Compare enum vs trait objects for heterogeneous shapes in Rust
Question 26 of 30
Why is simple row-level locking insufficient to prevent write skew under snapshot isolation?
Show the answer
Answer: a · The two transactions write to different rows, so no lock conflict arises
Write skew involves transactions that update disjoint rows based on an overlapping read, so locking individual written rows produces no conflict. The shared dependency is a predicate over a set, which needs serializable isolation or predicate locks, not single-row locks.
Question 27 of 30
Under write-ahead logging, what must be guaranteed durable on disk before a transaction's commit is acknowledged?
Show the answer
Answer: c · The transaction's log records describing its changes
Only the log records must be flushed at commit, which is why commits are cheap yet durable; the data pages can be written later. Forcing all dirty data pages on every commit is exactly what the WAL avoids.
Read the full bite: How the write-ahead log ensures atomicity and durability
Question 28 of 30
In Pydantic v2, which validator configuration correctly enforces that end_date is after start_date while ensuring both fields are already parsed and coerced?
Show the answer
Answer: c · A model_validator with mode='after' that compares self.end_date and self.start_date and raises ValueError on violation
A model_validator with mode='after' receives the fully constructed instance with coerced datetime objects, making cross-field comparison type-safe and reliable. A model_validator with mode='before' is tempting but forces you to handle raw, unparsed input instead of validated types.
Read the full bite: Ensure end_date is after start_date in Pydantic
Question 29 of 30
In a large Rust plugin system, why does a workspace of plugin crates typically give better incremental build times than one crate with module plugins?
Show the answer
Answer: a · The crate is the compilation unit, so separate crates rebuild independently while a module change rebuilds the whole crate
Because the crate is Rust's compilation unit, changing one plugin crate recompiles only it and its dependents, whereas a module lives inside one crate that rebuilds wholesale on any change. Caching applies to crates, not the reverse of the first option.
Read the full bite: Rust workspace versus single crate for plugins
Question 30 of 30
In a cluster setup, what is the primary (master) process responsible for?
Show the answer
Answer: a · Forking and supervising worker processes while workers serve traffic
The primary forks workers and restarts them on exit; the workers handle requests across cores. It does not serve traffic itself, share a heap, or run the heavy compute.
Read the full bite: Scaling across cores with cluster and os
Could you explain these out loud?
That is what an interview actually tests. Tezvyn gives you questions like these with what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.