Top 30 Backend Dev Interview Questions and Answers
30 multiple-choice questions on Backend Dev, of the kind that come up in a technical interview, drawn from 30 bites in the Backend Dev 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
When you annotate a FastAPI route parameter with a Pydantic model, what does the framework do with that type hint?
Show the answer
Answer: a · It leverages the hint for automatic request validation and OpenAPI schema generation
FastAPI reads type hints at startup to construct Pydantic models that validate incoming requests and generate OpenAPI schemas automatically. The distractor about runtime enforcement is wrong because Python itself ignores type hints during execution unless an external tool checks them.
Read the full bite: Explain Python type hints and their importance in FastAPI
Question 2 of 30
What is the main reason to place a unique constraint on a non-primary column such as email?
Show the answer
Answer: c · To enforce business-level uniqueness while keeping a stable primary key for joins
The card gives email as an alternate key that stops duplicate signups while the auto-incrementing primary key stays the stable join target. The distractor that it serves as the main row identifier conflates a unique constraint with a primary key, a red flag the card explicitly warns against.
Read the full bite: What is the difference between primary, foreign, and unique keys?
Question 3 of 30
When evaluating a new cloud infrastructure project, which statement accurately reflects the trade-off between using Rust versus Go?
Show the answer
Answer: a · Rust provides deterministic latency and compile-time data-race safety at the cost of a steeper learning curve, whereas Go trades runtime GC overhead for simplicity and faster onboarding.
B is correct because it accurately pairs Rust's deterministic, zero-cost memory safety and compile-time data-race prevention with its steeper learning curve, while recognizing Go's GC introduces runtime overhead in exchange for simplicity and faster onboarding. C is a tempting distractor because Go does automate memory reclamation, but its GC does not prevent data races—memory safety and concurrency safety are distinct, and Go leaves more thread-safety responsibility to the developer.
Question 4 of 30
Which statement best describes how Node.js handles I/O for thousands of concurrent connections?
Show the answer
Answer: a · Network sockets are watched by the OS, while blocking tasks like DNS and file reads use a small internal thread pool.
Node.js relies on the OS kernel to monitor network sockets and notify the event loop when data arrives, while delegating blocking operations like DNS and some file system work to a limited internal thread pool. Distractor D is tempting because Node is famous for non-blocking I/O, but it wrongly assumes every I/O operation can run without thread assistance.
Read the full bite: How does Node.js handle thousands of connections on one thread?
Question 5 of 30
You write an async def FastAPI endpoint that calls requests.get. What is the main risk?
Show the answer
Answer: c · The event loop is blocked, freezing concurrent request handling until the call finishes.
The correct answer is C because calling a blocking library like requests inside async def stalls the event loop, stopping all other requests. The most tempting distractor is A because beginners often assume FastAPI magically threadpools any blocking code, but only def endpoints are run in a threadpool.
Read the full bite: What is the difference between def and async def in Python and FastAPI?
Question 6 of 30
During a bank transfer, if the debit succeeds but the credit fails, which ACID property ensures the money does not vanish?
Show the answer
Answer: b · Atomicity, because the entire transaction must complete or roll back entirely
Atomicity guarantees that a transaction either fully completes or fully rolls back, preventing partial debits without matching credits. Consistency is a tempting distractor because it concerns valid state invariants, not the undo of incomplete operations.
Read the full bite: Explain ACID properties and why they matter for banking or e-commerce
Question 7 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 8 of 30
You add a new variant to an error type during a large refactor. Why does Rust typically surface every place needing an update more reliably than Go?
Show the answer
Answer: d · Rust's exhaustive match makes non-updated handling sites fail to compile
Adding an enum variant makes existing exhaustive matches incomplete, so Rust's compiler flags each site. Go's error values do not force callers to branch, so it does not flag missed sites; Rust does not auto-rewrite code.
Read the full bite: Refactoring under Go simplicity versus Rust correctness
Question 9 of 30
When adding a new column to a table and then filling it with values, how do the two types of SQL commands involved differ?
Show the answer
Answer: d · The first command is DDL that modifies the schema and may lock the table, while the second is DML that changes row data inside a transaction
Adding a column is DDL because it changes the schema and often locks the table, while updating rows is DML that operates row by row within a transaction. Option B is tempting because it labels the command types correctly, but it is wrong because DDL often auto-commits and cannot be rolled back in many engines, and DML—not DDL—runs inside explicit transactions.
Read the full bite: What is the difference between DDL and DML in SQL?
Question 10 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 11 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 12 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 13 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 14 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 15 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 16 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 17 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 18 of 30
If you write let x = 5 in Rust and x := 5 in Go, which statement about mutability is correct?
Show the answer
Answer: c · The Rust declaration is immutable, while the Go declaration allows reassignment without extra keywords
Rust makes bindings immutable by default, so let x = 5 cannot be reassigned without let mut, while Go variables declared with := or var are mutable by default. Option D is a common misconception because both var and := create mutable bindings in Go, and true immutability requires const.
Read the full bite: How does Go's variable declaration and mutability differ from Rust?
Question 19 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 20 of 30
Why is it a problem to list a library your source code imports at runtime under devDependencies?
Show the answer
Answer: d · A production install that skips devDependencies leaves it missing, causing runtime errors
Production installs omit devDependencies, so a runtime import placed there will be absent and throw module-not-found. Runtime libraries must live under dependencies.
Read the full bite: dependencies vs devDependencies in package.json
Question 21 of 30
What does Node do first when it encounters require('fs')?
Show the answer
Answer: a · Matches it against built-in core modules, which take precedence
Bare specifiers are checked against built-in core modules first; fs is compiled into the binary and resolves without touching node_modules. The package search only runs for non-core bare names.
Read the full bite: Resolving core vs relative module specifiers
Question 22 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 23 of 30
What is the main mechanical difference between Go's for i := 1; i <= 5; i++ and Rust's for i in 1..=5?
Show the answer
Answer: a · Go uses a C-style control statement that manually manages the index, while Rust consumes an iterator hiding index management.
Go's for is a control statement with explicit initialization, condition, and increment, whereas Rust's for consumes an iterator produced by the inclusive range. Option D is a tempting distractor because both loops look similar, but claiming they are semantically identical misses the statement-driven versus iterator-based gap.
Question 24 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 25 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 26 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 27 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 28 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 29 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 30 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
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.