Full-stack developer interview questions
Questions an interviewer can ask a Full-stack developer, grouped by years of experience. Open one to see the strongest answer and why it works.
48 of the 1269 questions our bank holds for this job. The rest are in the app.
By subject
0–1 years
They ask why 0 == '' is true and why you would not write that check. Strongest explanation?
The strongest answer: Say == coerces types; use === (or Object.is) and treat empty string and zero as separate cases
Why it works: Naming coercion and choosing === is the junior bar; then handle 0 and '' explicitly if both are valid.
- Explain coercion, then refuse it in app code.
- == turns values into a common type first.
- === compares type and value.
- If 0 and '' are both legal, check them by name.
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 form’s submit button sits under the home indicator and the keyboard. What is wrong?
The strongest answer: The layout ignores safe area and keyboard insets — I inset the content and keep the primary action visible while typing
Why it works: Exactly. Safe area and keyboard avoidance are part of shipping a phone UI — web or native.
- Respect safe areas.
- Avoid the keyboard; do not cover the action.
- Do not shrink type to cheat the layout.
- This is the same judgement as a responsive web form.
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.
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.
In-app navigation full-reloads the page. What did the markup do wrong?
The strongest answer: Use the router's Link for internal routes — a plain <a href> without the router does a full load
Why it works: Client routing is a Link (or useNavigate) that prevents the browser load. Anchors still matter for new tabs and progressive enhancement when the router handles them.
- Internal moves use the router.
- Link (or the framework equivalent) for in-app paths.
- Real <a> for external URLs.
- Don't assign location to move inside the SPA.
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.
Uploading a report returns 200 and a green toast. The background job that converts it fails ten seconds later and nobody tells the user.
How should this work instead?
The strongest answer: The request accepts the work, returns an id, the job writes its outcome on that record, and the UI shows the real state — pending, failed with a reason, or done — not the acknowledgement
Why it works: Exactly — “accepted” and “succeeded” are different facts, and the UI can only be honest if the job writes its result where the UI reads.
- Accepted is not succeeded.
- Return an id and a status.
- The job writes its outcome.
- The UI shows pending, failed, done.
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.
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.
The app feels sluggish when typing in one field. How do you find the cause?
The strongest answer: The React Profiler: record while typing, find which components re-render per keystroke and why, then fix the cause — state held too high, unstable props, an expensive child — rather than sprinkling memo
Why it works: Right. Measure, find the widest re-render, fix its cause.
- Profiler shows what rendered and why.
- State placement is the usual cause.
- Fix the source, then memo where needed.
- Verify with a second recording.
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.
A teammate throws inside `fetch` so the React error boundary can show the outage.
What is the difference?
The strongest answer: An error boundary catches render throws; a failed fetch is data you must handle in the request path — do not throw in the fetch to 'reuse' the boundary for an API 500
Why it works: Exactly. Boundaries are for unexpected render failures. A 500 is an expected state of a request. Mixing them turns every outage into a white screen.
- Boundary = render/unknown.
- Fetch = loading/error/data.
- Do not throw on HTTP to be 'clever'.
- Keep the rest of the page alive.
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.
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.
Choosing between client rendering, server rendering, static generation and streaming for a new product — how do you decide?
The strongest answer: By page: static for content that rarely changes, server or streaming for personalised first paint and SEO, client rendering for logged-in tools where interactivity matters more than first paint; most products mix them per route
Why it works: Right. Per route, by what the page needs.
- Static for stable content.
- Server or streaming for personalised first paint.
- Client for authenticated tools.
- Mix per route.
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.
Every pull request wants the browser suite, and staging is also where the product team demos.
Where should the end-to-end suite run in CI — against shared staging, or against a stack built for the run?
The strongest answer: An ephemeral stack per pull request — the build under test, its own database, its own seed — and a smaller suite against staging on a schedule to catch environment and data drift
Why it works: Right — per-run isolation makes results trustworthy and parallel, and the scheduled staging run still catches what an isolated stack cannot.
- Isolate the stack per run.
- Build under test, own database.
- Keep a scheduled staging run.
- Never run write suites on production.
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.