Top 30 Intermediate Backend Dev Interview Questions and Answers
30 intermediate multiple-choice Backend Dev interview questions, past the definitions: how the pieces fit together, what breaks in practice, and the trade-off behind a choice. They come from 30 bites in the Backend Dev library, the middle 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
In which event loop phase do the majority of completed I/O callbacks, such as a finished network read, actually execute?
Show the answer
Answer: d · The poll phase, which retrieves and runs I/O completions
The poll phase retrieves new I/O events and executes most of their callbacks. The timers phase only handles elapsed setTimeout/setInterval, not I/O completion.
Question 2 of 30
What mechanism triggers FastAPI to automatically validate and parse an incoming JSON request body against a schema?
Show the answer
Answer: b · Using a Pydantic model as the type hint for a route parameter
FastAPI inspects function signature type hints at runtime, so using a Pydantic model as a parameter type hint automatically triggers request parsing and validation. Manually calling json.loads inside the route is a red flag that ignores this declarative mechanism, and response_model governs response serialization, not request validation.
Read the full bite: How does FastAPI leverage Pydantic for request validation and serialization?
Question 3 of 30
When a developer wants to abstract over a third-party type, which difference between Go interfaces and Rust traits is most significant?
Show the answer
Answer: d · Go lets a consumer define an interface that the third-party type implicitly satisfies, while Rust requires an explicit impl block to bind the trait to the type.
Go uses structural typing, so a consumer can define an interface that an existing third-party type automatically satisfies without any declaration; Rust uses nominal typing, requiring an explicit impl block. Distractor A reverses these exact mechanics, reflecting the common misconceptions that Go needs explicit declarations and that Rust traits are implicit.
Question 4 of 30
When both are scheduled from inside a completed fs I/O callback, which runs first and why?
Show the answer
Answer: c · setImmediate, because the check phase follows poll in the same iteration
From within an I/O callback the loop is in poll, so the next phase is check, making setImmediate fire before the timers phase comes around again. setTimeout(0) waits for the next iteration.
Read the full bite: nextTick vs setImmediate vs setTimeout(fn, 0)
Question 5 of 30
When building an async execution-time decorator for FastAPI, why is omitting functools.wraps on the inner wrapper considered a red flag?
Show the answer
Answer: b · It strips the original function's metadata, which breaks FastAPI's OpenAPI schema generation and dependency injection.
functools.wraps preserves the original function's name, signature, and metadata, which FastAPI relies on to generate OpenAPI docs and resolve dependencies; omitting it exposes the wrapper's metadata instead. Distractor A is wrong because wraps has no effect on whether code runs synchronously or blocks the event loop.
Read the full bite: Write an async decorator that logs execution time for FastAPI
Question 6 of 30
When is it appropriate to intentionally violate 3NF by duplicating a customer name in an orders table?
Show the answer
Answer: c · When read latency is critical and avoiding joins outweighs update anomaly risks
Intentionally violating 3NF trades update anomaly risk for faster reads by eliminating joins, which is appropriate in read-heavy workloads. The most tempting distractor confuses the goal of normalization—preventing transitive dependencies—with a reason to denormalize.
Read the full bite: Describe 1NF, 2NF, 3NF, normalization's purpose, and its performance trade-off.
Question 7 of 30
Why does wrapping a heavy synchronous computation in an async function fail to keep a Node server responsive?
Show the answer
Answer: a · The computation is still synchronous and never yields the single JS thread
async/await only helps when there is an awaited asynchronous boundary; a synchronous CPU loop still occupies the single thread, blocking the event loop. Worker Threads provide real parallelism.
Read the full bite: Offloading CPU-bound work with Worker Threads
Question 8 of 30
Which implementation correctly minimizes total latency when a FastAPI endpoint must fetch data from two independent external APIs?
Show the answer
Answer: a · Inside an async def endpoint, pass two httpx.AsyncClient coroutines to asyncio.gather and await the result.
asyncio.gather with an async HTTP client schedules both I/O-bound coroutines concurrently on the event loop, reducing total latency to roughly the slower call. Option C is tempting because it uses async/await correctly, but sequential awaiting means the second request cannot start until the first finishes, so latencies add up.
Read the full bite: How do you structure concurrent API calls with asyncio.gather in FastAPI?
Question 9 of 30
Which statement best captures the mechanical cost of maintaining multiple indexes on a write-heavy table?
Show the answer
Answer: c · Every table write typically triggers random I/O to update each index's B-Tree, plus node splits and log overhead
The card explains that every write likely updates every index, causing extra random I/O, node splits, and WAL overhead. Option B reflects the common misconception that binary search trees are the classic disk structure, while D confuses hash indexes with the standard B-Tree approach.
Read the full bite: Explain database indexes, the classic data structure, and write-heavy trade-offs
Question 10 of 30
When the Orders table may contain NULL user_ids, which statement correctly explains the safest way to find users who never placed an order?
Show the answer
Answer: b · NOT EXISTS is preferred because it is immune to NULLs in the subquery and avoids duplicate rows.
NOT EXISTS handles NULL values safely and stops at the first match per user, avoiding duplicate rows. Option D is tempting because LEFT JOIN is a common pattern, but it can inflate results when a user has multiple orders unless you add DISTINCT or GROUP BY.
Read the full bite: Find users who never placed an order and explain JOIN choice
Question 11 of 30
What core problem does committing package-lock.json solve that package.json alone cannot?
Show the answer
Answer: c · It guarantees every install resolves to the exact same dependency tree, including transitive packages
package.json uses version ranges, so installs can drift; the lockfile pins exact versions and tree shape for all transitive deps, making installs deterministic. It does not store tarballs or block additions.
Question 12 of 30
Which accurately describes a key difference between Go's switch and Rust's match?
Show the answer
Answer: d · Rust match is an expression that yields a value and requires exhaustive patterns, while Go switch is a statement with implicit breaks and no exhaustiveness check.
Rust match evaluates to a uniform value and the compiler rejects non-exhaustive patterns, whereas Go switch is statement-oriented, auto-breaks, and never checks exhaustiveness. Option B is tempting but wrong because Go implicitly breaks unless fallthrough is explicit, and Rust match arms never fall through.
Question 13 of 30
When writing a query that groups by department and filters on COUNT(*) > 10, why must the predicate be in HAVING rather than WHERE?
Show the answer
Answer: b · WHERE is evaluated before GROUP BY, so the aggregate count does not yet exist
WHERE filters individual rows before grouping and aggregation occur, so aggregate values like COUNT(*) have not been computed yet and do not exist at that stage. Option D is wrong because repeating the aggregate expression in WHERE does not help; the aggregate still does not exist when WHERE is evaluated.
Read the full bite: What is the difference between WHERE and HAVING in SQL?
Question 14 of 30
Which statement about using ES Modules instead of CommonJS in Node is accurate?
Show the answer
Answer: d · ESM lacks __dirname by default and is enabled via type module or .mjs
ESM omits __dirname and require, and is enabled by type module or the .mjs extension. import is not an alias for require, and only ESM supports top-level await.
Read the full bite: Choosing between CommonJS and ES Modules
Question 15 of 30
When passing a string to a function by value, why is Go's operation cheap while Rust's String may require explicit cloning?
Show the answer
Answer: c · Go strings are small two-word headers copied by value with immutable backing data, whereas Rust String moves ownership and must be cloned to duplicate its heap buffer.
Go strings are small two-word headers (pointer and length) that are cheaply copied by value while remaining immutable, whereas a Rust String owns its heap buffer and moves ownership by default, so duplicating it requires an explicit clone. Option D is tempting because copy-on-write is common in other languages, but Rust String is uniquely owned and Go strings do not use reference counting.
Read the full bite: Compare Go string and Rust &str/String types, mutability, UTF-8, ownership
Question 16 of 30
What happens if a caller ignores the error when parsing a string to an integer in Go versus Rust?
Show the answer
Answer: d · Go compiles silently because error checking is purely conventional, while Rust emits a must-use warning since Result is enforced by the type system
Go relies on programmer discipline to check the second error return value, so ignored errors compile silently. Rust's Result is must-use, so the compiler warns if it is not handled via match or ?; distractor A reverses these roles.
Read the full bite: Parse a string to integer in Go and Rust with errors
Question 17 of 30
Why should the service layer in a layered Express API avoid referencing the request and response objects?
Show the answer
Answer: d · It keeps business logic framework-agnostic and unit-testable without HTTP
Keeping req and res out of services makes the business logic reusable and testable without spinning up HTTP. Express does not forbid it and services are not on a separate thread.
Read the full bite: Layered structure for a scalable Express API
Question 18 of 30
Given a dependency written as ^1.4.2, which upgrade would npm refuse to install on its own?
Show the answer
Answer: b · 2.0.0, a major release
The caret allows updates below the next major, so anything under 2.0.0 is permitted, but 2.0.0 itself is excluded. The tilde would be the operator that also blocks 1.7.0.
Read the full bite: SemVer and the caret vs tilde range operators
Question 19 of 30
When you create b[1:4] from a Go slice b, how does the resulting value compare to borrowing &v[1..4] from a Rust Vec?
Show the answer
Answer: d · The Go result is a new three-word header sharing the original array, while the Rust result is a two-word borrowed slice with no capacity field.
Go slicing creates a new header (pointer, length, capacity) that points into the same underlying array, whereas Rust's &[T] is a two-word fat pointer without capacity because it is only a borrowed view. Distractor B is tempting because one might assume slicing copies data, but Go only copies the descriptor and shares the backing array.
Read the full bite: Describe Go slice internals and compare to Rust slice and Vec
Question 20 of 30
A FastAPI endpoint declares a path parameter as item_id: int. A request arrives for /items/abc, which cannot be coerced to an integer. What happens?
Show the answer
Answer: a · FastAPI returns an automatic 422 error describing the invalid value, and the endpoint function body never executes
FastAPI validates the coerced type before your function runs, so an uncoercible value short circuits into an automatic 422 with no handler code executing. Python itself does nothing with the int annotation at runtime, which is why expecting a TypeError or manual parsing misses the point of the hint.
Read the full bite: How FastAPI uses type hints for validation
Question 21 of 30
Which FastAPI endpoint declaration identifies a user in the URL path and accepts an optional search term after the question mark?
Show the answer
Answer: a · Route /users/{user_id}/items with user_id in path and q optional
Option A correctly places the user identifier in the path template and gives q a default of None so it becomes an optional query parameter. Option B is tempting because it also makes q optional, but it omits user_id from the path so FastAPI treats it as a query parameter, violating RESTful hierarchy.
Read the full bite: What is the difference between a path parameter and a query parameter?
Question 22 of 30
An endpoint declares a query parameter as q: Optional[str] with no default value. What happens when a client omits it?
Show the answer
Answer: c · FastAPI returns a 422 Unprocessable Entity error
FastAPI derives requirement from the presence or absence of a Python signature default, not from type hints, so Optional[str] without = None is still required and omitting it triggers a 422 error. Option D is a common misconception because Optional alone does not make a parameter optional in FastAPI.
Read the full bite: How does FastAPI distinguish required optional and default query parameters
Question 23 of 30
A category tree is read constantly to render menus but almost never restructured. Which model best fits, and why?
Show the answer
Answer: a · Nested set, because subtree reads are a single range query
Nested set encodes descendants as a left/right range, so a whole subtree is one indexed query, ideal for read-heavy trees. Its weakness is expensive writes, which barely matters here since the tree is almost never restructured.
Read the full bite: Adjacency List versus Nested Set for hierarchies
Question 24 of 30
A table is in 3NF but not BCNF because of a dependency Teacher to Subject where Teacher is not a superkey. What makes this still acceptable for 3NF?
Show the answer
Answer: d · Subject is a prime attribute, part of a candidate key
3NF permits a non-superkey determinant when the dependent attribute is prime, so Subject being part of a candidate key keeps it in 3NF. BCNF has no such exception, which is exactly why the table violates BCNF.
Read the full bite: 3NF versus BCNF and the overlapping-key gap
Question 25 of 30
What combination of standard and code sources enables FastAPI's automatic interactive documentation?
Show the answer
Answer: d · It dynamically builds an OpenAPI schema from type hints, Pydantic models, decorators, and docstrings
FastAPI dynamically generates an OpenAPI schema by extracting metadata from type hints, Pydantic models, decorators, and docstrings, so no manual schema file is required. Option B is wrong because maintaining a separate openapi.yaml by hand is unnecessary and contradicts FastAPI's design.
Read the full bite: What standard and code elements power FastAPI's auto-generated API docs?
Question 26 of 30
Why does the Promise callback print before the setTimeout(0) callback despite both being scheduled in the same tick?
Show the answer
Answer: d · The microtask queue is fully drained before the next macrotask runs
After the synchronous stack clears, all microtasks (Promise reactions) drain before any macrotask (setTimeout) runs. The delay value is not the deciding factor here; queue priority is.
Read the full bite: Output order of sync, microtask, and macrotask
Question 27 of 30
What happens to the remaining requests when one input to Promise.all rejects?
Show the answer
Answer: b · The combined Promise rejects immediately, but the other requests still run to completion
Promise.all rejects as soon as the first input rejects, but JavaScript Promises are not cancellable, so the other in-flight requests continue running. They simply have no remaining handler.
Read the full bite: Running independent requests with Promise.all and race
Question 28 of 30
Which solution best supports many concurrent readers on a typed Go map while keeping compile-time type safety?
Show the answer
Answer: d · Wrap the map in a struct with sync.RWMutex, using RLock for reads and Lock for writes
An RWMutex-wrapped map allows multiple simultaneous readers and retains compile-time type safety. sync.Map is tempting because it handles concurrency internally, but it stores interface{} values and therefore sacrifices compile-time type checking.
Read the full bite: How do you safely share a Go map across goroutines?
Question 29 of 30
In classic Express 4, how should an error from an awaited database call inside async middleware reach the error handler?
Show the answer
Answer: c · Catch it and pass it to next(err) so the error-handling middleware runs
Express 4 does not auto-catch async throws, so you must catch and forward with next(err) to trigger the four-argument error handler. Throwing alone leaves the request hanging.
Read the full bite: Handling async errors in Express middleware
Question 30 of 30
Why might a try/catch fail to catch an error from an async call inside it?
Show the answer
Answer: b · If the returned Promise is not awaited, control leaves the block before it rejects
Without await, the async call returns a pending Promise and execution exits the try block immediately, so a later rejection is not caught there. Awaiting the call keeps it within the try/catch scope.
Read the full bite: Comparing the three async error-handling styles
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.