Backend Languages interview questions
Backend Languages questions a Backend developer can be asked, from first job to senior. Open one to see the strongest answer and why it works.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The interviewer asks: “In ASP.NET, what belongs in an API controller?” Strongest answer?
The strongest answer: HTTP in and out: bind the route and verb, validate the request, call an application service, return a status and a DTO. Business rules stay out of the action.
Why it works: Exactly. Controllers are the HTTP adapter. Domain rules live in services (or the domain model) so you can test them without Kestrel.
- Controller = HTTP adapter.
- Validate, call a service, return a DTO.
- Keep business rules out of the action.
- Never exec client SQL.
You add a database starter to a Spring Boot project and a connection pool appears without you configuring anything. What is doing that, and how do you take control of it?
The strongest answer: Auto-configuration: Spring Boot creates beans conditionally on what is on the classpath and which properties are set, and backs off when you declare your own — so you override by defining the bean or setting the property
Why it works: Right — conditional beans plus back-off is the whole mechanism, and the condition report printed in debug mode tells you which ones fired.
- Conditional beans that back off.
- Classpath and properties drive the conditions.
- Define your own bean to win.
- Read the auto-configuration report.
When does a finally block run?
The strongest answer: On the way out of the try either way — after a successful run, after a handled or unhandled exception, and on return or break — which is why it is where cleanup goes
Why it works: Right. That guarantee is the whole point of the construct.
- finally always runs on the way out.
- except handles, else runs on success.
- Cleanup belongs in finally, or in a with block.
- It runs on return and break too.
What does int? give you that int does not?
The strongest answer: The ability to represent absent — a value type that may have no value, with HasValue and Value, so a missing database column or unanswered field is not forced to be zero
Why it works: Right, and using 0 or -1 as a sentinel is exactly what this replaces.
- int? represents a missing value.
- Sentinels like 0 or -1 lie.
- GetValueOrDefault and ?? to unwrap.
- Nullable reference types do the same for classes.
The interviewer asks: “What does a Spring @RestController actually do?” Strongest answer?
The strongest answer: It maps HTTP to methods. Keep it thin: validate, call a service, return a representation — don't open JDBC in the method.
Why it works: Exactly. The controller is the web adapter. Transactions and persistence belong one layer down, with a clear boundary.
- RestController maps HTTP to a thin method.
- Call a service; don't open JDBC there.
- Transactions belong below the web layer.
- Don't serialise managed entities by accident.
The interviewer asks: “What does a good Express (or Fastify) route handler look like?” Strongest answer?
The strongest answer: Read the request, validate, await the service, send a status and JSON. Don't swallow errors — pass them to the error middleware so failures become a safe 5xx.
Why it works: Exactly. Async I/O, validation, and a single error path. Unhandled promise rejections are 500s you never logged.
- Validate, await, respond, don't swallow.
- Use error middleware for failures.
- Don't block the event loop.
- Never eval request input.
The interviewer asks: “How should a FastAPI path operation be structured?” Strongest answer?
The strongest answer: Declare the route, verb and response model. Let Pydantic validate input; the function calls a service and returns a model — I don't grow SQLAlchemy queries inside the route.
Why it works: Exactly. The path operation is the HTTP edge. Types and models catch bad input; services own the use case.
- Path ops are the HTTP edge.
- Pydantic validates; a service does the work.
- Keep the response model explicit.
- Never execute client SQL.
A Node service that normally answers in 20 ms starts timing out on every endpoint whenever one particular report is requested.
What do you suspect first?
The strongest answer: Something on that path runs synchronously — a long loop, a large JSON parse, a sync file or crypto call — and it blocks the single thread that runs your JavaScript, so every other request waits behind it
Why it works: Exactly — one thread runs your code, so a synchronous stretch stalls everyone, not only the caller.
- One thread runs your code.
- Find the synchronous stretch.
- Move CPU work off the loop.
- One slow path stalls every path.