All jobs / Full-stack developer

Backend interview questions

Backend questions a Full-stack developer can be asked, from first job to senior. Open one to see the strongest answer and why it works.

  1. The UI posts `{ email }` and the API logs an empty body.

    What is missing?

    The strongest answer: `Content-Type: application/json` on the request, and `JSON.stringify` on the body — otherwise the API may see an empty object

    Why it works: Exactly. Most APIs parse JSON only when the header says so. fetch does not stringify an object for you.

    • JSON is bytes plus a Content-Type.
    • Stringify on the client; parse on the server.
    • Set `application/json` both ways when that is the contract.
    • Do not expect fetch to guess an object into JSON.
  2. 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.
  3. 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.
  4. When would you choose a tuple over a list?

    The strongest answer: When the collection is fixed and its position means something — a coordinate, a record, a dict key — since tuples are immutable and therefore hashable

    Why it works: Right. Immutability is the point, and hashability is what it buys you.

    • Tuple = fixed, positional, hashable.
    • List = a sequence you intend to change.
    • Only a tuple can key a dict or join a set.
    • Choose on meaning, not on length.
  5. What is the most common cause of a NullReferenceException in a new codebase?

    The strongest answer: A reference that was never assigned or came back null from a lookup and was used without a check — nullable reference types and a null check at the boundary are what prevent it

    Why it works: Right, and enabling nullable reference types moves most of these to compile time.

    • Something was null and was dereferenced.
    • Enable nullable reference types.
    • Check at the boundary, not everywhere.
    • ?. and ?? express intent clearly.
  6. What is the difference between == and is?

    The strongest answer: == asks whether two values are equal; is asks whether they are the very same object — use is only for None, True, False and sentinels

    Why it works: Exactly. Identity versus equality, and is belongs to singletons.

    • == compares values, is compares identity.
    • Use is None, not == None.
    • Equal objects need not be the same object.
    • Caching makes is look right by accident.
  7. When do you use an array rather than a List<T>?

    The strongest answer: When the size is fixed and known, or an API demands one — List<T> grows, exposes Add and Remove, and is what most code should take and return

    Why it works: Right. Fixed size is the reason; otherwise List<T> says more about intent.

    • Array = fixed size.
    • List<T> = grows, richer API.
    • Accept IEnumerable<T>, return a concrete type.
    • Span<T> for slicing without copying.
  8. The screen loads a list of drafts and has a button that creates a new one.

    Which call is right?

    The strongest answer: GET to load the list; POST only when the server must create or change something

    Why it works: Exactly. The verb is part of the contract: GET is safe to prefetch and retry; POST is a write.

    • Verb is contract: read with GET, write with POST/PUT/PATCH/DELETE.
    • GET must stay safe to retry and prefetch.
    • Need a body for a read? Still a GET with query or a dedicated search POST you document.
    • Never hide a create behind a GET because the URL looks clean.
  9. You want a value from a dict, or a default if the key is missing, without raising. What do you use?

    The strongest answer: d.get(key, default) — or d.setdefault / defaultdict when the missing key should also be stored

    Why it works: Right. get for a read; setdefault or defaultdict when the miss should create the entry.

    • get(key, default) for a safe read.
    • setdefault / defaultdict when the miss should stick.
    • in then index looks up twice.
    • or swallows falsy stored values.
  10. You remove items from a List<T> inside a foreach over it and get an exception. Why?

    The strongest answer: The enumerator detects that the collection changed and throws — iterate a copy, use RemoveAll with a predicate, or loop backwards by index

    Why it works: Exactly. RemoveAll is usually the clearest of the three fixes.

    • Modifying during enumeration invalidates it.
    • RemoveAll(predicate) in one call.
    • Or iterate a copy, or go backwards by index.
    • The same rule applies to Dictionary.
  11. The inbox UI needs `status=open` and `page=2` from the API.

    Where do the filters go?

    The strongest answer: On the query string of a GET so the URL is shareable, cacheable and safe to refresh

    Why it works: Exactly. A filterable list is a read. Query params are the contract other clients and a refreshed tab can see.

    • Reads belong on GET with query parameters.
    • The URL is the shareable contract for a list.
    • POST-for-search only when the query cannot fit — and you document it.
    • Cookies and localStorage are not API inputs.
  12. if items: — what exactly is being tested?

    The strongest answer: Whether items is truthy: an empty list, empty string, empty dict, 0 and None are all falsy, so this is not the same as testing for None

    Why it works: Exactly — which is why an empty list and None take the same branch unless you separate them.

    • Empty containers, 0 and None are all falsy.
    • if x is not None when None is the thing you mean.
    • if x when any empty value should take the same branch.
    • Be explicit when the two differ.
  13. When is var a good idea?

    The strongest answer: When the type is obvious from the right-hand side and the name carries the meaning; when it is not obvious, spelling the type out helps the reader more than it costs

    Why it works: Right. It is a readability judgement, and reviewers are the audience.

    • var when the type is evident.
    • Spell it out when it is not.
    • It is static typing either way.
    • Required for anonymous types.
  14. Building a long string in a loop with s += piece is slow on big inputs. Why?

    The strongest answer: Strings are immutable, so each += builds a whole new string and copies what came before; collect the pieces in a list and "".join(parts) once

    Why it works: Right. Copy per iteration versus one allocation at the end.

    • Strings cannot be changed in place.
    • += in a loop copies repeatedly.
    • Collect into a list, then join.
    • The cost grows with the square of the length.
  15. Why expose a property rather than a public field?

    The strongest answer: A property is a method pair, so you can add validation, change the backing store or make it read-only later without breaking callers or their compiled binaries

    Why it works: Exactly. It is about being able to change your mind later.

    • A property is a method pair.
    • You can add validation later.
    • Changing a field to a property is a binary break.
    • Auto-properties cost nothing to write.
  16. The first version of `/notifications` has twelve rows. Next month it will have thousands.

    What do you add to the contract?

    The strongest answer: A limit the client cannot raise past a server max, plus a way to ask for the next page — never 'return everything'

    Why it works: Exactly. Lists without a cap will one day be huge. The server owns the max; the UI asks for a page.

    • Every list has a limit and a next page.
    • The server enforces a max, not the UI.
    • Do not fetch the world to render a window.
    • Put page/cursor in the public contract from day one.
  17. You join the team and need to call an endpoint you have never used before.

    Where do you look, and what do you do when the documentation is wrong?

    The strongest answer: The generated API reference first, built from the code that is deployed; confirm it with one real call, and when it is wrong, fix the annotation or the example in the same pull request

    Why it works: Right — documentation generated from the code stays true, and the moment you notice a gap is the cheapest moment to close it.

    • Prefer docs generated from the code.
    • Verify with one real call.
    • Fix the doc where you found it.
    • Never guess at a contract.
  18. What does xs[1:4] give you, and what does xs[:] do?

    The strongest answer: Items at index 1, 2 and 3 — the stop is exclusive — and xs[:] is a shallow copy of the whole list

    Why it works: Exactly. Half-open ranges, and a bare slice copies the outer list only.

    • Stop is exclusive: [1:4] is 1, 2, 3.
    • xs[:] is a shallow copy.
    • Slices clamp instead of raising.
    • Nested objects are still shared after a shallow copy.
  19. const versus readonly — what is the practical difference?

    The strongest answer: const is baked into the callers at compile time, so changing it in a library needs every consumer rebuilt; readonly is set at run time and can differ per instance or come from configuration

    Why it works: Right, and the cross-assembly baking is the one that bites in practice.

    • const is inlined into callers.
    • readonly is assigned at run time.
    • readonly does not make the object immutable.
    • Prefer readonly across assembly boundaries.
  20. You need both the index and the item while looping. What is the idiomatic way?

    The strongest answer: for i, item in enumerate(items) — and enumerate(items, start=1) when the count should begin at one

    Why it works: Right, and the start argument saves the off-by-one arithmetic.

    • enumerate gives index and item together.
    • start=1 for human-facing numbering.
    • range(len(x)) is a smell in review.
    • Manual counters drift.
  21. Why code against an interface rather than a concrete class?

    The strongest answer: So callers depend on the capability rather than the implementation — which is what lets you substitute a fake in a test or swap the implementation without touching them

    Why it works: Right, and testability is usually the first payoff people feel.

    • Depend on the capability, not the implementation.
    • Substitution is what makes testing possible.
    • Do not add an interface with only one forever-implementation by reflex.
    • Keep interfaces small.
  22. A sign-up form should reject an empty email.

    Where does the email rule live?

    The strongest answer: In the UI so they see it immediately, and again on the API — the server is what attackers hit

    Why it works: Exactly. Client checks are UX. Server checks are the contract. Skip either and you get angry users or dirty data.

    • UI validation is kindness; API validation is security.
    • Show the rule before submit.
    • Enforce the same rule on the server — that is the real door.
    • A ticket comment will not be there when someone hits the endpoint.
  23. When is a list comprehension the right choice over a for loop?

    The strongest answer: When you are building one collection from another with a simple expression and filter; once it needs branching, side effects or two levels of nesting, a loop reads better

    Why it works: Exactly. It is a readability call, and the line where it flips is nesting.

    • Build-from-iterable with a simple expression.
    • Side effects belong in a loop.
    • Two levels of nesting is the readability limit.
    • A generator expression when you do not need the list.
  24. What is wrong with catch (Exception ex) around a whole method?

    The strongest answer: It catches the failures you cannot handle along with the one you can, hiding bugs and leaving the object in an unknown state — catch what you can act on, and let the rest reach a top-level handler that logs

    Why it works: Exactly, and a top-level handler is what makes narrow catches affordable.

    • Catch what you can actually handle.
    • A broad catch hides bugs.
    • Let a top-level handler log the rest.
    • throw; preserves the stack, throw ex; loses it.
  25. A create POST timed out. The following GET of the list also failed once.

    Can the UI retry?

    The strongest answer: Retry the GET freely; do not retry the POST unless the API treats it as idempotent

    Why it works: Exactly. GET is safe. A blindly retried POST is how you get two charges or two rows.

    • Retry what is safe by HTTP semantics.
    • GET: retry. POST: only with an idempotency key.
    • The UI must not double-submit a write on a timeout.
    • A spinner-disable on the button is the first defence.
  26. rows2 = rows[:] then rows2[0].append(9). Why did rows change too?

    The strongest answer: The slice copied the outer list but not the inner lists, so both lists hold the same row objects — copy.deepcopy, or build new inner lists, if you need them separate

    Why it works: Right. One level deep is exactly what a shallow copy means.

    • A slice copies one level.
    • Inner objects stay shared.
    • deepcopy, or rebuild the inner values.
    • The same trap applies to dicts of dicts.
  27. What does a using statement guarantee?

    The strongest answer: That Dispose runs when the block exits, exception or not — which is how files, connections and HTTP responses get released instead of waiting for the garbage collector

    Why it works: Right. Deterministic cleanup is exactly what the collector cannot give you.

    • Dispose runs on every exit path.
    • The GC does not free unmanaged handles promptly.
    • using declarations scope to the enclosing block.
    • await using for IAsyncDisposable.
  28. A logged-out user hits `/invoices/88`. A logged-in member hits it without billing access.

    Which status do you return?

    The strongest answer: 401 if we do not know who they are; 403 if we know and they still may not read it

    Why it works: Exactly. 401 means authenticate (or refresh); 403 means you are in and still denied. The UI branches on that.

    • 401 = who are you; 403 = I know you and the answer is no.
    • The UI sends a 401 to login/refresh, a 403 to a calm deny.
    • 200-with-error is not a contract.
    • Reserve 404-as-hide for resources you truly do not want to acknowledge.
  29. What is wrong with except: pass?

    The strongest answer: It swallows everything including typos, KeyboardInterrupt and bugs you needed to see; catch the specific exception you can actually handle, and log anything you deliberately ignore

    Why it works: Exactly. It hides the failures that would have told you what was wrong.

    • Catch the exception you can handle.
    • A bare except hides typos and interrupts.
    • Log anything you choose to ignore.
    • except Exception is broad; bare except is broader.
  30. You compare user input to a stored value with ==. What could go wrong?

    The strongest answer: Culture and case: == is ordinal and case-sensitive, so compare with an explicit StringComparison — OrdinalIgnoreCase for identifiers, culture-aware only for text a human is sorting or displaying

    Why it works: Exactly. Being explicit about the comparison is the habit worth forming.

    • == on string is ordinal, case-sensitive.
    • OrdinalIgnoreCase for identifiers and keys.
    • Culture-aware only for human-facing text.
    • Culture bugs appear only on some machines.