<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Martin</title>
    <description>The latest articles on DEV Community by Martin (@moputa).</description>
    <link>https://dev.to/moputa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4065327%2Fe7511207-7757-4c52-af26-9d87a6e544be.png</url>
      <title>DEV Community: Martin</title>
      <link>https://dev.to/moputa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moputa"/>
    <language>en</language>
    <item>
      <title>JavaScript interview questions: can you run the interpreter in your head?</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Mon, 26 Oct 2026 12:17:00 +0000</pubDate>
      <link>https://dev.to/moputa/javascript-interview-questions-can-you-run-the-interpreter-in-your-head-4e0l</link>
      <guid>https://dev.to/moputa/javascript-interview-questions-can-you-run-the-interpreter-in-your-head-4e0l</guid>
      <description>&lt;p&gt;Ask someone to define a closure and you'll get the same three sentences everyone gets from a guide they read once. Ask them what a loop full of var and setTimeout actually prints, and you find out whether they've been burned by it.&lt;/p&gt;

&lt;p&gt;The six questions below aren't really six separate topics. JavaScript has no compiler standing between you and the runtime, so almost everything senior candidates get asked is a version of the same test: can you predict what actually happens when this code runs, not what you meant it to do.&lt;/p&gt;

&lt;p&gt;Candidates who can hold that model in their head answer all six. Candidates who memorized definitions answer the first half and then start guessing. If you'd rather work through the topic than read about it, &lt;a href="https://practicedepth.com/app/learn/javascript" rel="noopener noreferrer"&gt;the JavaScript track&lt;/a&gt; covers the same ground one idea at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 1: what logs first, the timeout or the promise?
&lt;/h2&gt;

&lt;p&gt;This is one of the oldest tricks in a JavaScript round and it still works, because most people learn 'async code runs after sync code' as a slogan rather than as a mechanism with parts you can name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// logs: 1, 4, 3, 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A promise callback is a microtask. setTimeout, even at 0ms, is a macrotask, and the whole microtask queue drains before the next macrotask runs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why does the promise log before the zero-millisecond timeout?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Promises are just faster than setTimeout, so they get to run first even when both are scheduled at roughly the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; There's one call stack, and it runs to completion before anything queued gets a turn. Once it's empty, the microtask queue (promise callbacks, including everything after an await) drains completely before the event loop even looks at the next macrotask (timers, I/O, UI events). Nothing about speed is involved: setTimeout(fn, 0) doesn't mean 'run now', it means 'join the macrotask queue', and that queue is checked only after every microtask has run.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The follow-up is usually what happens if a promise chain keeps scheduling more microtasks off the back of each other: the macrotask queue never gets a turn, the browser can't paint, and the tab looks hung with nothing in the code technically wrong.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Worth saying without being asked: &lt;code&gt;await&lt;/code&gt; doesn't pause the language, it desugars to a &lt;code&gt;.then&lt;/code&gt;. Every &lt;code&gt;await&lt;/code&gt; point is a microtask boundary, which is why a loop full of sequential &lt;code&gt;await&lt;/code&gt; calls runs one after another even though nothing looks blocking, and why wrapping independent calls in &lt;code&gt;Promise.all&lt;/code&gt; instead of awaiting each in turn is the fix people reach for once they've actually seen the ordering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 2: fix the loop that logs the wrong number three times
&lt;/h2&gt;

&lt;p&gt;This one shows up as a live task almost as often as it shows up as a question, because writing the fix proves you understand the bug better than describing it does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// logs 3, 3, 3&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// logs 0, 1, 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same loop, same closure, different binding underneath.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why does switching var to let fix it, mechanically?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; let is block-scoped, so it works properly inside a loop and var doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; var has one function-scoped binding for the whole loop, so all three timeouts close over the same variable, and by the time any of them run the loop has already finished with i at 3. let creates a fresh binding for every iteration, so each closure captures its own i, frozen at the value it had that pass. Block scoping is true but it's not the mechanism, the per-iteration binding is.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If they mention memory here, that's a natural moment to ask when closures cause leaks. A closure keeps its captured scope reachable for as long as the closure itself is, which is the same fact behind this bug and behind a real leak in a long-lived app.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 3: why is this undefined inside the callback?
&lt;/h2&gt;

&lt;p&gt;A method that works fine when you call it directly and breaks the moment it's passed as a reference. This is the bug that teaches people what this actually means, usually the hard way, the first time it happens in real code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// this is undefined when the handler fires&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method left the object it belonged to the moment it was passed as a value.&lt;/p&gt;

&lt;p&gt;this is set by how a function is called, not by where it's written. counter.increment() and counter.increment passed to addEventListener are the same function, but only the first one calls it as counter's method. The second hands the browser a bare function, and when the click fires, nothing about that call mentions counter, so this is undefined inside a class method (or the global object outside strict mode). The fix is binding in the constructor, wrapping the call in an arrow function, or writing increment as an arrow class field in the first place.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why does an arrow method behave differently from a normal method under inheritance?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Arrow functions don't have their own this, so they just use whatever this is in the surrounding scope, which fixes the binding problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; That's true, and it's also why arrow methods don't participate in the prototype chain the way normal methods do. An arrow class field is created fresh per instance, in the constructor, with this captured at that moment, so a subclass can't override it through the prototype the way it overrides a regular method. You've traded a real inheritance bug for a real memory cost: every instance now carries its own copy of the function instead of sharing one off the prototype.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the question where naming the trade-off matters more than picking a side. Arrow fields are the right default for event handlers precisely because they can't be overridden out from under you; that's also exactly why they're the wrong choice for anything meant to be extended.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 4: what does the caller see after this function runs?
&lt;/h2&gt;

&lt;p&gt;A function mutates something it was handed. The question is whether the person calling it knows that, and whether the candidate does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;book&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true, and original.items now has one item too&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Objects are passed by reference, so the function and the caller share one array.&lt;/p&gt;

&lt;p&gt;Primitives are copied on assignment or on a function call. Objects and arrays are not, what gets copied is the reference, so the function and the caller are looking at the same underlying structure the whole time. Anyone who's surprised by that has usually only ever seen it go wrong once it's three functions deep, which is exactly why interviewers ask it directly instead of waiting for it to come up.&lt;/p&gt;

&lt;h4&gt;
  
  
  Does spreading an object make a deep copy?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Yes, spreading creates a new object, so changes to the copy don't touch the original.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; Spread copies one level. Top-level keys get new slots, but if any of those values are themselves objects or arrays, the copy and the original point at the exact same nested structure. &lt;code&gt;const copy = { ...state }; copy.user.name = 'x'&lt;/code&gt; changes state.user.name too, because user was never copied, only referenced again. JSON.parse(JSON.stringify(x)) goes deeper but drops functions and Dates and undefined and breaks on anything circular. structuredClone is the honest built-in answer for a real deep copy, and it still can't clone a function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 5: where does a class method actually live?
&lt;/h2&gt;

&lt;p&gt;class reads as a different language feature from everything above it, and that's the point of the question: it isn't one. It compiles down to the same prototype chain that's been there since the beginning.&lt;/p&gt;

&lt;p&gt;Every object has an internal link to another object it falls back to when a property lookup misses, and that chain keeps going until it hits null. A method defined in a class body lives once, on the prototype, shared by every instance, which is different from an arrow class field: those get copied per instance, as question 3 covered. Looking a method up on an instance walks the chain until it finds increment on Counter.prototype, and every instance sees the same function.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;extends&lt;/strong&gt; wires one prototype's fallback to another, so a subclass instance's lookup chain runs through the child's methods, then the parent's, then Object.prototype.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;super.method()&lt;/strong&gt; calls the parent's version explicitly, which you need the moment you override something but still want the original behavior inside the new one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modifying a built-in prototype&lt;/strong&gt; (adding a method to Array.prototype, say) changes every array in the program, including ones from libraries that never expected it, and collides with anything the spec adds under the same name later. It's a real technique and also a real way to make a codebase unpredictable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Two that show up before the real questions start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;== vs ===.&lt;/strong&gt; === skips coercion entirely. == runs a set of rules first, and a couple of the results surprise people who only use it by habit: null == undefined is true, but null == 0 is false, and NaN never equals anything, including itself, which is why you check for it with Number.isNaN rather than a comparison.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hoisting and the temporal dead zone.&lt;/strong&gt; var, let, const and function declarations are all hoisted, but they don't behave the same once you're inside that hoisted space. var initializes to undefined immediately, so reading it early just gives you undefined. let and const are hoisted but sit in the temporal dead zone until their declaration line actually executes, and reading them before that throws a ReferenceError. That's deliberate: it catches use-before-initialization as an error instead of letting it silently return undefined.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The live task: write a debounce
&lt;/h2&gt;

&lt;p&gt;Most rounds have one small keyboard moment, and for JavaScript it's usually this one, or throttle's sibling version of it. What's being scored isn't whether the syntax lands. It's the order you build it in.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Say the problem before touching the keyboard: a rapid-fire event is triggering expensive work on every call, and you want it to run once, after things go quiet.&lt;/li&gt;
&lt;li&gt;Reach for a closure holding a single timer id. It's the same mechanism as question 2, and saying that out loud is a good sign, not a distraction.&lt;/li&gt;
&lt;li&gt;Clear the pending timer on every call, then schedule a new one. That line is the whole mechanism. Throttle is the same shape with a boolean guard and a fixed interval instead of a reset.&lt;/li&gt;
&lt;li&gt;Decide what happens to this and the arguments the wrapped function was called with. Forwarding them with apply, or a rest parameter and an arrow function, is what makes the debounced version actually interchangeable with the original.&lt;/li&gt;
&lt;li&gt;Say which one fits which case. A search box wants debounce, since it's waiting for a pause. A scroll handler wants throttle, since it wants steady progress rather than silence followed by a burst.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The closure over timer is what lets each call see, and cancel, the one before it.&lt;/p&gt;

&lt;p&gt;The implementation is a few lines. The narration is the part that's hard to rehearse alone, because &lt;a href="https://practicedepth.com/blog/surviving-the-follow-up-question" rel="noopener noreferrer"&gt;the follow-up is the interview&lt;/a&gt;, and the follow-up here is almost always how you'd add a leading-edge option or cancel a pending call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 6: what's keeping this object alive?
&lt;/h2&gt;

&lt;p&gt;A closing question that sounds abstract and isn't. Every long-lived JavaScript app leaks eventually, and the question is whether a candidate can point at where.&lt;/p&gt;

&lt;p&gt;Garbage collection is reachability-based: an object is kept as long as something reachable from a root still points to it, and reclaimed once nothing does. Setting a variable to null doesn't free anything by itself, it just removes one path to the object, and if another reference exists elsewhere, nothing changes. The usual leaks in a real app are forgotten event listeners, timers that outlive what they were scheduled for, closures retaining a large object they only needed briefly, and DOM nodes removed from the page but still referenced from JavaScript, which keeps the whole detached subtree alive.&lt;/p&gt;

&lt;h4&gt;
  
  
  How does a WeakMap avoid a leak a Map would cause?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; A WeakMap is basically a faster Map for when your keys are objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; A WeakMap holds its keys weakly, so an entry doesn't count as a reason to keep the key alive. If you're caching computed data per DOM node in a regular Map, that map keeps every node reachable for as long as the map exists, even after the node's been removed from the page, which is a slow leak with a completely ordinary-looking cause. The same cache in a WeakMap lets the node and its cached value get collected together the moment nothing else references the node. WeakRef solves a narrower version of the same problem, holding a reference to a value without being a reason it survives, and it comes up far less often.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If they ask you to point at a real leak in a heap snapshot&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Trigger the suspected action twice, take a snapshot each time, and compare. Growth in the count of a specific constructor between the two, especially a detached DOM tree still showing up, is the signal. The retainers panel answers the actual question: not that something leaked, but what's still holding onto it, which is usually a listener or a closure named right there in the chain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How much of this overlaps with a React round?
&lt;/h3&gt;

&lt;p&gt;More than it looks like on paper. Stale closures inside useEffect, an event handler losing this, async ordering inside a component: all of it is &lt;a href="https://practicedepth.com/blog/react-interview-questions-seniors-get" rel="noopener noreferrer"&gt;React&lt;/a&gt; sitting directly on top of the mechanisms above, not a separate topic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do generators and Proxy come up?
&lt;/h3&gt;

&lt;p&gt;Occasionally, and almost never as 'implement one'. The realistic version is naming a use case: a generator for a lazy or infinite sequence, or paging through an API without loading it all at once; a Proxy for validation or a reactivity system that needs to notice when a property is read or written. Knowing what each one is for is worth more than the syntax.&lt;/p&gt;

&lt;h3&gt;
  
  
  I mostly write TypeScript at work. Does plain JavaScript still get asked?
&lt;/h3&gt;

&lt;p&gt;Yes, because types are erased at compile time and none of this is a type-system question. A &lt;a href="https://practicedepth.com/blog/typescript-interview-questions" rel="noopener noreferrer"&gt;TypeScript round&lt;/a&gt; checks a different layer entirely, and it assumes the runtime layer underneath it is already solid.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it fine to say I'd reach for lodash's debounce instead of writing one?
&lt;/h3&gt;

&lt;p&gt;In real code, yes, and saying so is a normal engineering answer. In the interview, they're not asking whether you'd use the library, they're using the exercise to see the closure-and-timer model in your hands, so build it anyway and mention the library as what you'd actually ship.&lt;/p&gt;




&lt;p&gt;Reading the mechanics is the easy half. What actually gets scored is narrating the runtime out loud while someone follows up on whatever you just said. Run a JavaScript interview at your level. &lt;a href="https://practicedepth.com/app/practice/interview?focus=javascript&amp;amp;mode=senior" rel="noopener noreferrer"&gt;Practise a JavaScript interview&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>discuss</category>
      <category>interview</category>
      <category>programming</category>
    </item>
    <item>
      <title>Python interview questions: the ones a backend round actually asks</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Fri, 23 Oct 2026 08:32:00 +0000</pubDate>
      <link>https://dev.to/moputa/python-interview-questions-the-ones-a-backend-round-actually-asks-412g</link>
      <guid>https://dev.to/moputa/python-interview-questions-the-ones-a-backend-round-actually-asks-412g</guid>
      <description>&lt;p&gt;Most Python interview lists are written for people learning the language. A backend round isn't that. It assumes you can write Python and goes looking for the places where Python's convenience quietly costs you something in production.&lt;/p&gt;

&lt;p&gt;The questions below have one thing in common: each has an easy answer that's correct and a second layer that decides the round. The easy answer is the doorway, and interviewers use these particular questions precisely because the doorway is so well known that it tells them nothing on its own.&lt;/p&gt;

&lt;p&gt;If you'd rather work through the topic than read about it, &lt;a href="https://practicedepth.com/app/learn/python" rel="noopener noreferrer"&gt;the Python track&lt;/a&gt; covers the same ground one idea at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 1: what does the GIL actually stop you doing?
&lt;/h2&gt;

&lt;p&gt;Everyone has heard that Python has a global interpreter lock and that threads are therefore useless. Half of that is right, and the half that's wrong is the half that matters when you're deciding how to make a slow endpoint faster.&lt;/p&gt;

&lt;h4&gt;
  
  
  You have a slow endpoint. Would threads help?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Probably not, because of the GIL. Python can only really run one thread at a time, so threading doesn't give you a speedup. You'd need multiprocessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; It depends what it's slow on. The lock is held while interpreting bytecode, so two threads never execute Python code at the same time, and a CPU-bound handler gets nothing from threads. But the lock is released around blocking I/O, so if the endpoint is waiting on a database, an HTTP call or the disk, threads overlap that waiting perfectly well. So: I'd measure where the time goes first. I/O-bound, threads or asyncio. CPU-bound, move the work to processes, or into a library like NumPy that drops the lock in its C code, or out of the request entirely onto a queue.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The natural follow-up is what multiprocessing costs you, and the honest answer is: real memory per process, and everything you send between them has to be picklable, which rules out more objects than people expect.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Worth knowing rather than claiming: recent CPython versions ship a build with the lock removed, and it's a genuinely large change to the runtime. Saying it exists is a good signal. Saying your service would get faster by switching to it is a claim you'd have to defend, and single-threaded code on that build isn't automatically quicker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 2: why does this function remember the last call?
&lt;/h2&gt;

&lt;p&gt;The most-asked Python gotcha, and still worth asking, because the mechanism behind it explains a whole category of surprises.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="n"&gt;basket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;basket&lt;/span&gt;

&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apple&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ['apple']
&lt;/span&gt;&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pear&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ['apple', 'pear'] &amp;lt;- the same list
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default is created once, when the function is defined, not each time it's called.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;def&lt;/code&gt; statement runs once. Its defaults are evaluated at that moment and stored on the function object, so every call that doesn't pass the argument shares one list. The fix is &lt;code&gt;basket=None&lt;/code&gt; and &lt;code&gt;if basket is None: basket = []&lt;/code&gt; inside the body.&lt;/p&gt;

&lt;h4&gt;
  
  
  Where else does that same rule bite?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Mostly with lists and dictionaries as defaults. As long as you use None instead, you're fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; Anywhere an expression is evaluated once at definition time and then reused. Default arguments are the famous one, but class attributes behave the same way: a list assigned in the class body is shared by every instance, which is why dataclasses make you use field(default_factory=list) instead of a bare list. The same thing bites with decorators that build state at import time. The rule worth carrying is that class and def bodies run once, at import, and anything mutable created there is shared for the life of the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 3: async, threads or processes?
&lt;/h2&gt;

&lt;p&gt;The question a backend round almost always reaches, because it's the one where a wrong mental model shows up directly in production behaviour.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://slow-api.example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# blocking
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An async endpoint making a blocking call: every other request on that worker waits, however many of them there are.&lt;/p&gt;

&lt;p&gt;An event loop runs on one thread. A coroutine that blocks doesn't yield, so nothing else on that loop runs until it returns, including requests that have nothing to do with it. It's the same failure as blocking the event loop in &lt;a href="https://practicedepth.com/blog/nodejs-interview-deep-dive" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;, and the fix is the same shape: use an async client so the wait is awaited, or push the blocking call off the loop with &lt;code&gt;asyncio.to_thread&lt;/code&gt; or a run-in-executor call.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;asyncio&lt;/strong&gt; for lots of concurrent I/O in one process: thousands of open connections, where a thread each would be wasteful. It costs you a whole parallel ecosystem of libraries, because one blocking call in the wrong place undoes it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threads&lt;/strong&gt; for I/O when the code is already synchronous. Simpler, and fine at the scale most services actually run at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processes&lt;/strong&gt; for CPU-bound work, with real memory cost and pickling at the boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A task queue&lt;/strong&gt; when the work doesn't need to finish inside the request at all. Frequently the right answer, and the one candidates forget to offer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Question 4: this endpoint loads a million rows into memory
&lt;/h2&gt;

&lt;p&gt;Generators are a first-year topic that turns into a senior one the moment the question is about a real dataset instead of a toy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="c1"&gt;# whole table in memory
&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# one row at a time
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two characters of difference, and an entirely different memory profile.&lt;/p&gt;

&lt;h4&gt;
  
  
  When would you not use a generator here?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Generators are more memory efficient, so I'd use one wherever I'm iterating over something large. There's not much reason not to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; When you need the data more than once, or need its length, or need random access, because a generator is consumed and gone. And when laziness moves work somewhere it can't fail safely: if the generator is still pulling rows while the request is being serialised, the database transaction and connection have to stay open that whole time, and an exception now surfaces halfway through a response you've already started writing. For a streaming export that's the right trade. For something the caller retries, I'd rather page explicitly and know each chunk is finished.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A good follow-up to expect: what happens if you call the generator function and never iterate it. Nothing does, the body hasn't run yet, which is also why a bug inside it can show up nowhere near the line that created it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 5: the ORM query that gets slower as the data grows
&lt;/h2&gt;

&lt;p&gt;This one isn't really a Python question, which is exactly why it separates backend candidates from people who know the language.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="c1"&gt;# 1 query
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 1 more query per order
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The N+1: the loop looks like attribute access and is actually a round trip each time.&lt;/p&gt;

&lt;p&gt;Lazy loading is the ORM's best feature and its sharpest edge. Attribute access issues a query, so a loop over a hundred orders is a hundred and one queries, each fast in isolation, and the endpoint gets slower in production as the table grows while staying fine on your laptop's seeded data. The fix is to tell the ORM what you're going to need: a join for a single related row, a second batched query for a collection. In Django that's &lt;code&gt;select_related&lt;/code&gt; and &lt;code&gt;prefetch_related&lt;/code&gt;; in SQLAlchemy, &lt;code&gt;joinedload&lt;/code&gt; and &lt;code&gt;selectinload&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What makes the answer senior is saying how you'd catch it rather than how you'd fix it, because you'll write this bug again. Log query counts per request in development, assert on the count in a test for the endpoints that matter, and watch the count in production rather than only the latency. &lt;a href="https://practicedepth.com/blog/database-interview-questions-backend" rel="noopener noreferrer"&gt;Database interview questions for backend engineers&lt;/a&gt; goes further into what the database itself is doing with these.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two smaller ones that come first
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;is versus ==.&lt;/strong&gt; &lt;code&gt;==&lt;/code&gt; asks whether two objects are equal, &lt;code&gt;is&lt;/code&gt; asks whether they're the same object. They agree often enough by accident to be dangerous: small integers and short strings are cached by the interpreter, so &lt;code&gt;a is b&lt;/code&gt; can be True for &lt;code&gt;256&lt;/code&gt; and False for &lt;code&gt;257&lt;/code&gt; with nothing else changed. Use &lt;code&gt;is&lt;/code&gt; for None, True and False, and &lt;code&gt;==&lt;/code&gt; for values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type hints.&lt;/strong&gt; They're not enforced at runtime. The interpreter ignores them, a type checker reads them before the code ever runs, and passing a string where the annotation says int fails nowhere until something downstream breaks. Pydantic and FastAPI are the exception people are thinking of when they disagree: those validate at runtime, deliberately, because the data came from outside.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The live task: write a retry decorator
&lt;/h2&gt;

&lt;p&gt;If there's a keyboard moment in a Python backend round, it's usually a decorator, and retry is the most common one. It's short, it uses closures, and it has an opinionated second half that most candidates skip.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Say what you're building before typing: a wrapper that calls the function, and on a failure waits and tries again up to a limit.&lt;/li&gt;
&lt;li&gt;Reach for a closure over the arguments, and &lt;code&gt;functools.wraps&lt;/code&gt; on the inner function, so the wrapped function keeps its name and docstring instead of becoming a nameless wrapper in every traceback.&lt;/li&gt;
&lt;li&gt;Forward &lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt; unchanged, so the decorated function stays interchangeable with the original.&lt;/li&gt;
&lt;li&gt;Back off between attempts rather than retrying immediately, because a service that just failed under load is exactly the one you shouldn't hit three more times in a row.&lt;/li&gt;
&lt;li&gt;Catch the exceptions you can retry, not every exception. Retrying a timeout is sensible, retrying a 400 is a bug that hides a bug.&lt;/li&gt;
&lt;li&gt;Say the limit out loud: retries are only safe if the operation is idempotent. This is the sentence that separates the answer from the implementation.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;,)):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nd"&gt;@functools.wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;raise&lt;/span&gt;
                    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three nested functions, because the decorator takes arguments. Re-raising on the last attempt is what keeps the failure visible.&lt;/p&gt;

&lt;p&gt;Expect to be asked what happens when every caller retries a struggling service at once, and whether you'd add jitter. And expect the version of this question that's really about &lt;a href="https://practicedepth.com/blog/talking-about-trade-offs" rel="noopener noreferrer"&gt;trade-offs&lt;/a&gt;: would you write this, or take the dependency that already does it properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 6: what keeps an object alive?
&lt;/h2&gt;

&lt;p&gt;The closing question in a lot of senior Python rounds, and the one people are least ready for, because Python's memory management is usually invisible until a long-running worker starts growing.&lt;/p&gt;

&lt;p&gt;CPython counts references. When the count of things pointing at an object drops to zero, it's freed immediately, which is why the memory behaviour is mostly predictable. Cycles are the exception: two objects referring to each other keep each other's counts above zero forever, so a separate cycle collector runs periodically to find groups that only reference each other. That's why a parent holding children that hold the parent is a real leak shape, and why a cache that never evicts is a leak with no cycle in it at all.&lt;/p&gt;

&lt;h4&gt;
  
  
  Where would you look if a worker's memory grows all day?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Probably a memory leak somewhere. I'd restart the workers periodically and look for anything obviously holding data, like a big list that keeps growing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; First I'd separate growth from a leak: memory that plateaus is usually fragmentation or a pool, while a straight line up is something accumulating. Then I'd look at the usual suspects in order: module-level caches and anything decorated with lru_cache on a method, which keeps every instance it was ever called on alive; long-lived containers keyed by request data; and objects with references from a closure or a registry nobody remembers registering. tracemalloc gives you allocations by line between two snapshots, which answers where it came from. Restarting workers is a real mitigation, and I'd still want the snapshot before I shipped the restart as the fix.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;weakref is the tidy answer when something needs to observe an object without keeping it alive: caches keyed by object, parent pointers, and registries of live instances.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need to know Django or FastAPI specifics?
&lt;/h3&gt;

&lt;p&gt;Know the one on the job description well enough to explain what it does for you: how a request becomes a handler, where validation happens, and how it talks to the database. Framework trivia is rarely the question. Framework behaviour under load usually is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is asyncio expected for a backend Python role?
&lt;/h3&gt;

&lt;p&gt;You're expected to know what it's for and when it's the wrong tool, which is most of the value of the question. Deep asyncio internals only come up when the job is already built on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much algorithm practice do I need for a Python interview?
&lt;/h3&gt;

&lt;p&gt;It depends entirely on the company, and it's worth asking the recruiter. Product companies with an algorithm stage expect it. For most backend roles the round looks like this article: the language's behaviour, the data layer, and what happens in production. The &lt;a href="https://practicedepth.com/blog/how-to-pass-a-technical-interview" rel="noopener noreferrer"&gt;technical interview prep guide&lt;/a&gt; covers the shape the rest of the round takes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will I be asked about packaging and dependencies?
&lt;/h3&gt;

&lt;p&gt;Often, as a short practical question rather than a deep one: how you pin dependencies, what a lock file is for, and how the environment in CI matches production. Naming the tool you actually use and why is enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this overlap with a general backend round?
&lt;/h3&gt;

&lt;p&gt;Heavily. Questions 3, 5 and 6 are backend questions that happen to be asked in Python, which is why those answers travel with you if you switch languages. The Python-specific half is questions 1, 2 and 4, plus the decorator.&lt;/p&gt;




&lt;p&gt;Reading these is the easy half. The hard half is saying one out loud and holding up when the next question goes after the weakest part of your answer. Run a Python interview at your level. &lt;a href="https://practicedepth.com/app/practice/interview?focus=python&amp;amp;mode=senior" rel="noopener noreferrer"&gt;Practise a Python interview&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>interview</category>
      <category>programming</category>
      <category>backend</category>
    </item>
    <item>
      <title>How to pass a software engineering technical interview</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Tue, 20 Oct 2026 10:07:00 +0000</pubDate>
      <link>https://dev.to/moputa/how-to-pass-a-software-engineering-technical-interview-2p69</link>
      <guid>https://dev.to/moputa/how-to-pass-a-software-engineering-technical-interview-2p69</guid>
      <description>&lt;p&gt;Most engineers who fail a technical interview could have done the job. That gap, between being able to do the work and being able to demonstrate it in forty minutes under time pressure, is what this guide is about.&lt;/p&gt;

&lt;p&gt;There is a specific kind of frustration in getting rejected from a role you know you could have done. You wrote the code they asked about last quarter. You have shipped the thing they are hiring for. And still the email arrives, three days later, saying they have decided to move forward with other candidates.&lt;/p&gt;

&lt;p&gt;It usually isn't a knowledge problem. It's a demonstration problem. Interviews are a lossy channel, and the engineers who do well are the ones who have learned to push signal through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, work out which round you are actually in
&lt;/h2&gt;

&lt;p&gt;Candidates lose a surprising amount of ground by preparing for the wrong thing. A take-home rewards polish and tests. A live coding round rewards narration and recovery. A system design round barely cares whether your code compiles. And an AI screen, now the first filter at a growing number of companies, rewards something different again.&lt;/p&gt;

&lt;p&gt;Before you prepare, get specific about the format. Ask the recruiter directly: how long, live or asynchronous, human or automated, coding or discussion, and what the interviewer will be looking at afterwards. Recruiters answer this question honestly almost every time, and almost nobody asks it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recruiter screen.&lt;/strong&gt; Not technical, but it sets your level. Say the level you want out loud, early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated or AI screen.&lt;/strong&gt; A recorded or transcribed technical conversation, scored after the fact. See &lt;a href="https://practicedepth.com/blog/ai-technical-screens-explained" rel="noopener noreferrer"&gt;how AI technical screens actually work&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live coding.&lt;/strong&gt; Someone watches you write code. Narration and recovery matter as much as the solution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical deep dive.&lt;/strong&gt; A conversation about what you know and what you have built. This is where seniority gets decided.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System design.&lt;/strong&gt; Usually senior and above. Judged on how you reason about scale, failure and trade-offs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioural or hiring manager.&lt;/strong&gt; Ownership, conflict, judgement. Rarely the thing that sinks a strong technical candidate, but it can.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The second question decides the interview
&lt;/h2&gt;

&lt;p&gt;Here is the pattern that shows up in almost every technical conversation. You are asked something reasonable. You answer it correctly. Then the interviewer asks a follow-up that goes one layer deeper into the exact thing you just said.&lt;/p&gt;

&lt;p&gt;That follow-up is the interview. The first question is a doorway. The second one is the measurement, because it is generated from your own words, which means you cannot have rehearsed it and you cannot dodge it with a memorised paragraph.&lt;/p&gt;

&lt;p&gt;So the useful preparation question is not "what will they ask me". It's "when I answer, what will they ask next, and can I go there". Read your own answers back and hunt for the words you used without being able to unpack them. Those are the words the follow-up will land on.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A test you can run on yourself tonight&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pick any topic you would claim on your CV. Explain it out loud for sixty seconds. Then ask yourself "why" about the last sentence you said, and answer that. Then do it again. Most people hit a wall on the third layer. That wall is exactly where an interviewer will find you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What a strong answer is actually made of
&lt;/h2&gt;

&lt;p&gt;Strong answers are not longer. Very often they are shorter. What they contain that weaker answers don't is a second layer: the mechanism underneath the definition, the condition where it breaks, and the trade-off you accepted.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why do you use an index on that column?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Because it makes the query faster. Without an index the database has to scan the whole table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; That query filters on user_id and sorts by created_at, so it was doing a sequential scan plus a sort on about two million rows. A composite index on the two columns lets it seek and read in order, which took p95 from around 400ms to under 20ms. The cost is a slower write path and a few hundred megabytes, which was worth it here because the table is read far more than it is written.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Both answers are correct. Only one tells the interviewer that you have actually watched a query plan change.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The senior version does four things: it names the specific situation, it says what was happening underneath, it quantifies the effect, and it volunteers the cost. You can build that shape into any answer you give.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lead with the answer in one sentence. Do not narrate your way toward it.&lt;/li&gt;
&lt;li&gt;Say what is happening one level down, in the runtime, the database, the network, the browser.&lt;/li&gt;
&lt;li&gt;Name the condition where your answer stops being right.&lt;/li&gt;
&lt;li&gt;Say what you would measure to know.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Four sentences. It fits inside a normal answer, it works for almost any technical question, and it moves you out of the group that recites definitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Talk about trade-offs without hedging
&lt;/h2&gt;

&lt;p&gt;"It depends" is the correct answer to a lot of interview questions and the worst possible way to say it. Hedging reads as evasion, and it is the most common way experienced candidates get scored down.&lt;/p&gt;

&lt;p&gt;The fix is to commit first and qualify second. Pick the option you would actually ship, say why, then name the condition that would change your mind. "I'd start with the boring relational option, because the access patterns are simple and I get transactions for free. If write volume went past what one primary can take, I'd revisit it, and the thing I'd watch is replication lag." That is the same nuance, delivered as a decision instead of a shrug.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you genuinely don't know
&lt;/h2&gt;

&lt;p&gt;Everyone gets asked something they cannot answer. The difference between candidates is almost entirely in what happens in the next thirty seconds.&lt;/p&gt;

&lt;p&gt;Do not go quiet, and do not bluff. Bluffing is the fastest way to lose an interviewer, because the follow-up will find you and now they are also questioning everything you said before. Instead, say what you do know, reason out loud from it, and be explicit about where your certainty stops.&lt;/p&gt;

&lt;p&gt;Something like: "I have not used that directly. From how the surrounding thing works, I would expect it to behave roughly like this, and I would check that by trying this. Where I'd be least confident is here." You are showing them how you handle unfamiliar territory, which is a large part of what they are trying to find out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The interviewer is not deciding whether you know everything. They are deciding whether they would be comfortable handing you something unfamiliar on a Tuesday and walking away.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A preparation plan that fits around a job
&lt;/h2&gt;

&lt;p&gt;Most published prep advice assumes you have unlimited evenings. Here is a version that assumes you have about four hours a week and an actual life.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you have four weeks
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Week one: inventory. Write down every technology on your CV and rate your honest confidence one to five. Anything you rated three that you would be asked about is your target list, not the fives and not the ones.&lt;/li&gt;
&lt;li&gt;Week two: depth on the target list. For each item, work down three layers of "why" until you hit the wall, then read until the wall moves. Two items an evening is plenty.&lt;/li&gt;
&lt;li&gt;Week three: out loud, under follow-ups. Reading does not transfer to speaking. Practise answering with someone or something that pushes back on what you actually said.&lt;/li&gt;
&lt;li&gt;Week four: format rehearsal. Run the specific round you are facing, end to end, at full length, with no pausing to look things up.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  If you have one week
&lt;/h3&gt;

&lt;p&gt;Cut weeks one and two to a single evening of honest inventory, and spend everything else on speaking. The bottleneck for experienced engineers is almost never knowing, it's retrieving under pressure and shaping the answer, and that only improves by doing it out loud.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The one habit worth more than the rest&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Answer out loud, to something that asks you a real follow-up, and then look at what you actually said. Silent reading feels like progress and is the least transferable form of practice there is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Practice that transfers, and practice that doesn't
&lt;/h2&gt;

&lt;p&gt;Grinding algorithm puzzles transfers to interviews that are algorithm puzzles. If you are facing a senior deep dive about the systems you have built, five hundred puzzle problems will not help you, and a lot of engineers discover this the expensive way.&lt;/p&gt;

&lt;p&gt;Match the practice to the round. If the round is a conversation, practise conversation. If it is a whiteboard design, draw and defend designs. If it is a live coding session, write code while narrating, because the narration is genuinely half the assessment and it is awkward the first three times you try it.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the day
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Test your microphone on a recording, not in your head. Bad audio costs you points you will never see deducted.&lt;/li&gt;
&lt;li&gt;Have water, and use the pause it buys you.&lt;/li&gt;
&lt;li&gt;Take two seconds before answering. Silence feels much longer to you than to them.&lt;/li&gt;
&lt;li&gt;If you realise mid-answer that you were wrong, say so immediately and correct it. Self-correction reads as strength, right up until it arrives too late to look voluntary.&lt;/li&gt;
&lt;li&gt;Ask what they are worried about with your profile, near the end. The answer is useful, and the willingness to ask reads well.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest summary
&lt;/h2&gt;

&lt;p&gt;You pass technical interviews by being able to go one layer deeper than the answer you just gave, on the topics you actually claim. Not by memorising questions, and not by knowing more things. Find your walls before an interviewer does, and move them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How long should I prepare for a technical interview?
&lt;/h3&gt;

&lt;p&gt;For most working engineers, two to four weeks at a few hours a week is enough, provided the time goes on speaking rather than reading. If you are switching stacks or levels, give it longer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to grind algorithm problems?
&lt;/h3&gt;

&lt;p&gt;Only if you are facing algorithm rounds, which is mostly large product companies and new-grad pipelines. For senior screens built around your own experience, that time is better spent on depth in the technologies you claim.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if I go blank in the interview?
&lt;/h3&gt;

&lt;p&gt;Say so, out loud, and start from something adjacent that you do know. Interviewers are far more tolerant of a visible recovery than of a long silence or a confident guess that unravels.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it bad to say I don't know?
&lt;/h3&gt;

&lt;p&gt;No, as long as it is followed by reasoning rather than a full stop. The failure mode is not admitting a gap, it is having nothing to say about how you would close it.&lt;/p&gt;




&lt;p&gt;The fastest way to find your walls is to be asked follow-ups on your own answers. Run a practice interview at your level and see where the second question lands. &lt;a href="https://practicedepth.com/app/practice/interview" rel="noopener noreferrer"&gt;Try a practice interview&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>programming</category>
      <category>career</category>
      <category>interview</category>
    </item>
    <item>
      <title>The same question, answered by a junior and a senior: eight examples</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Wed, 09 Sep 2026 09:50:00 +0000</pubDate>
      <link>https://dev.to/moputa/the-same-question-answered-by-a-junior-and-a-senior-eight-examples-27cl</link>
      <guid>https://dev.to/moputa/the-same-question-answered-by-a-junior-and-a-senior-eight-examples-27cl</guid>
      <description>&lt;p&gt;Seniority in an interview is not measured by how much you say. Every answer below is correct. Only one of each pair gets you the offer, and the difference is smaller and more learnable than most people expect.&lt;/p&gt;

&lt;p&gt;When engineers ask what a senior answer sounds like, they usually get told to be more confident, or to talk about impact. That advice is not wrong but it is unusably vague.&lt;/p&gt;

&lt;p&gt;Here is something more concrete. In pair after pair below, the senior answer differs in the same four ways: it names the mechanism underneath, it points at a specific situation rather than the general case, it volunteers the cost, and it says what it would measure. Nothing else. Once you can see it, you can do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. JavaScript closures
&lt;/h2&gt;

&lt;h4&gt;
  
  
  What is a closure?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; A closure is a function that remembers the variables from the scope where it was defined, so it can still use them later even after that function has returned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; It is a function together with a reference to the scope it was created in, so the variables it captured stay alive on the heap instead of dying with the call. That is what makes module patterns and hooks work, and it is also the classic memory leak: hold a closure over something large in a long-lived handler and it is never collected. It also explains the loop bug people hit with var, since one shared binding gets captured instead of one per iteration.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The follow-up here is almost always the loop bug or the leak. If you volunteered both, you have already answered it.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// 3, 3, 3 -- one binding of i, shared by all three closures&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// 0, 1, 2 -- let creates a fresh binding per iteration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The version of this that gets shown in interviews. Knowing that it prints 3, 3, 3 is table stakes; being able to say why in terms of bindings is the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. React re-renders
&lt;/h2&gt;

&lt;h4&gt;
  
  
  How would you fix a slow React page?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; I would wrap the expensive components in memo and use useMemo and useCallback so they do not re-render unnecessarily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; First I would find out whether it is actually a render problem. The profiler tells you whether you are re-rendering too often, rendering too much at once, or blocking on something that is not React at all. On the last page I did this for, it was a context holding a new object every render, so every consumer re-rendered on every keystroke. Splitting the context fixed it. I reach for memo after measuring, because sprinkling it everywhere adds comparison cost and a lot of stale-closure bugs for people who do not read the dependency arrays.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The junior answer invites 'and what does memo compare?'. The senior one has already moved the conversation to diagnosis, which is where they wanted to be.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Node event loop
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Why is Node considered good for I/O heavy work?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Because it is non-blocking and asynchronous, so it can handle many requests at once without waiting for each one to finish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; Because the I/O is handled off the main thread and the callbacks come back to a single loop, so thousands of connections cost you very little while they are just waiting. The flip side is that anything CPU bound on that thread stops everything. I have watched a synchronous JSON parse of a large payload add hundreds of milliseconds to every concurrent request on the process, which does not show up in local testing at all. That work belongs in a worker or a different service.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The senior answer contains its own failure mode, which is exactly what the next question was going to be.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Database indexes
&lt;/h2&gt;

&lt;h4&gt;
  
  
  When would you add an index?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; When a query is slow, or on columns you filter and join on, to avoid scanning the whole table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; When the plan shows a scan or a sort that the query does not need, and the table is big enough for it to matter. The order of the columns is the part people miss: an index on a filter column plus a sort column only helps if the leading column is the one you filter on. And the cost is real, every write maintains every index, so on a write-heavy table I have removed indexes to speed things up. I would look at the plan before and after rather than guessing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Composite column order is the standard follow-up, and it filters out a lot of people who have only read about indexes.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. REST and retries
&lt;/h2&gt;

&lt;h4&gt;
  
  
  What does idempotent mean and why does it matter?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; It means making the same request more than once has the same effect as making it once. GET, PUT and DELETE are idempotent, POST is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; It means the caller can safely retry, which matters because at some point they will. A network timeout does not tell you whether the write happened, so any client with retries will eventually double-submit. For payments we took an idempotency key from the client, stored it with the result on first use, and returned that stored result on a repeat. The interesting part is the race between two concurrent retries, which needs a unique constraint rather than a check-then-insert, because check-then-insert loses that race under load.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Naming the concurrent-retry race is the single strongest signal in this answer. It is what someone who has actually shipped this says.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Caching
&lt;/h2&gt;

&lt;h4&gt;
  
  
  How do you decide what to cache?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Data that is read often and does not change much, with a TTL so it stays reasonably fresh. Redis in front of the database usually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; I start from how wrong the data is allowed to be, because that decides everything else. Something a user just edited needs to be right immediately, so that is invalidation on write, not a TTL. A dashboard aggregate can be a minute stale and nobody notices, so that is a TTL and it is simple. Where I am careful is the stampede, since when a hot key expires every request goes to the database at once, which is a fun outage. Either you serve stale while one request refreshes, or you lock the refresh.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cache stampede is the follow-up. It is also the thing that turns a caching layer into an incident, which is why it gets asked.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Testing
&lt;/h2&gt;

&lt;h4&gt;
  
  
  What is your testing strategy?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Unit tests for functions, integration tests for how the pieces fit, and end to end for the critical user flows. Aim for good coverage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; I want most of my confidence coming from tests that exercise real boundaries, so integration tests over the actual database rather than a mocked one, because most of the bugs I have shipped lived in that boundary. Unit tests for genuine logic, and a very small number of end to end tests over the flows that lose money if they break, because those are slow and flaky and a suite people ignore is worse than no suite. Coverage percentage I mostly do not care about, it is easy to hit eighty percent while testing nothing that matters.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pushing back on coverage as a metric, with a reason, reads as experience. Doing it without a reason reads as contrarianism.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Storing passwords
&lt;/h2&gt;

&lt;h4&gt;
  
  
  How do you store user passwords?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Hashed and salted, never in plain text. Using bcrypt rather than something like MD5 or SHA-256.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; With a deliberately slow, memory-hard hash, so bcrypt or argon2, with the work factor tuned to what the login path can afford and revisited as hardware gets cheaper. The point of slowness is that offline cracking after a dump is the real threat model, not the hash itself. Salts are per user and stored with the hash, that is what stops one rainbow table covering everyone. I would also make the failure path constant time, because a login that is measurably faster for unknown users leaks which accounts exist.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The timing side channel is not obscure trivia. It is the marker of someone who thinks about attackers rather than about checklists.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What all eight have in common
&lt;/h2&gt;

&lt;p&gt;Read the senior column again and the pattern is repetitive, almost formulaic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A mechanism.&lt;/strong&gt; Not what it does, what is happening underneath that makes it do that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A specific instance.&lt;/strong&gt;"On the last page I did this for" beats "generally you would", every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A volunteered cost.&lt;/strong&gt; Every choice loses something. Saying what you gave up is the clearest seniority signal there is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Something measurable.&lt;/strong&gt; What you would look at to know if you were right.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice what is absent. No extra length. No jargon the junior answer lacked. No claim to have worked somewhere impressive. Several senior answers are barely longer than their junior counterpart, and one is shorter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Interviewers are not listening for the definition. They are listening for whether you have been on the other side of the definition when it went wrong.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to convert your own answers
&lt;/h2&gt;

&lt;p&gt;Take an answer you would give today and run it through four questions. What is happening one level down. When did I actually see this. What did it cost. How would I know. If you cannot answer the second one for a topic on your CV, that topic is a risk, and it is better to find that out tonight than in the interview.&lt;/p&gt;

&lt;p&gt;The uncomfortable part is that you cannot do this by reading. You find your walls by being asked, out loud, and then being asked again about whatever you just said. That is the whole reason the follow-up question exists, and it is &lt;a href="https://practicedepth.com/blog/how-to-pass-a-technical-interview" rel="noopener noreferrer"&gt;the thing worth practising&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What if I have not worked on systems big enough to have these stories?
&lt;/h3&gt;

&lt;p&gt;Use the scale you actually had and be honest about it. A specific story about a small system reads far better than a vague story about a large one, and interviewers can tell the difference immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I volunteer the trade-off even if they did not ask?
&lt;/h3&gt;

&lt;p&gt;Yes. It is the highest-value sentence in most answers, and it usually steers the follow-up onto ground you have already thought about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this apply to junior interviews too?
&lt;/h3&gt;

&lt;p&gt;The mechanism and the measurement parts do. Nobody expects a junior to have production war stories, but explaining what is happening underneath is available to anyone at any level.&lt;/p&gt;




&lt;p&gt;Reading the difference is easy. Producing it under a follow-up is the hard part, and it is the only part that counts. Run a practice interview and see which column your answers land in. &lt;a href="https://practicedepth.com/app/practice/interview" rel="noopener noreferrer"&gt;Test your answers&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>webdev</category>
      <category>node</category>
      <category>career</category>
    </item>
    <item>
      <title>How senior engineers pass hard technical interviews and land big offers</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Fri, 04 Sep 2026 11:44:00 +0000</pubDate>
      <link>https://dev.to/moputa/how-senior-engineers-pass-hard-technical-interviews-and-land-big-offers-23nj</link>
      <guid>https://dev.to/moputa/how-senior-engineers-pass-hard-technical-interviews-and-land-big-offers-23nj</guid>
      <description>&lt;p&gt;Two engineers with the same years and the same stack sit the same loop. One gets a mid-level offer, one gets senior with a materially larger package. The gap is rarely knowledge, and it is almost never luck.&lt;/p&gt;

&lt;p&gt;The level decision is usually made from a handful of moments across the loop, and the same moments come up every time. Once you know which ones they are, they are practisable.&lt;/p&gt;

&lt;p&gt;None of what follows is about sounding impressive. Most of it is about being more specific than the person interviewing you expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  They answer the question that was asked
&lt;/h2&gt;

&lt;p&gt;It sounds trivial. It is the most common gap between a good engineer and a good interview.&lt;/p&gt;

&lt;p&gt;Asked how something works, a mid-level candidate frequently describes a project where they used it. Asked how they would approach a problem, they describe how their previous team did it. Both are relevant and neither is an answer. Senior candidates answer the question directly in one sentence, then use the project as evidence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The one-sentence rule&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whatever the question, your first sentence should be a direct answer to it. Context, history and caveats go after. Interviewers form an impression in the first fifteen seconds of an answer, and a preamble spends all of them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  They scope before they solve
&lt;/h2&gt;

&lt;p&gt;Given an open-ended problem, less experienced candidates start solving. Senior candidates spend the first minute deciding what problem they are solving: who uses it, what the volumes are, what is explicitly out of scope.&lt;/p&gt;

&lt;p&gt;This reads as senior because it is the thing senior engineers actually do at work. Half the value of an experienced engineer is refusing to build the wrong thing efficiently. An interviewer who watches you narrow a vague problem into a specific one has seen the skill demonstrated rather than claimed.&lt;/p&gt;

&lt;h2&gt;
  
  
  They volunteer the cost
&lt;/h2&gt;

&lt;p&gt;This is the highest-value habit on the list and the easiest to adopt today.&lt;/p&gt;

&lt;p&gt;Every technical decision loses something. Candidates who present only the upside sound like they are selling. Candidates who name the downside before being asked sound like they have lived with the decision. It also steers the follow-up onto ground you have already thought about, which is a real tactical advantage on top of the signal.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why did you choose that message queue?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; It is reliable and scales well, it integrates with everything we were already using, and the team had experience with it so we could move quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; Mostly because we already ran it and the operational knowledge was there, which for a team of four mattered more than any feature comparison. The cost is that it is heavier than the problem needed, we were using a fraction of it, and the ordering guarantees we relied on only hold per partition, which caught us out once. If I were starting again at that size I would probably have used something simpler and revisited when the volume justified it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The second answer includes a mistake and is stronger for it. Interviewers read self-criticism as calibration, not weakness.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  They have numbers
&lt;/h2&gt;

&lt;p&gt;"It was quite slow, so we optimised it and it got a lot better" and "p95 was about 800ms, we found it was N+1 queries in the serialiser, and it came down to roughly 90ms" describe the same work. Only one of them is evidence.&lt;/p&gt;

&lt;p&gt;You do not need exact figures and nobody is checking. Approximate numbers, offered confidently, are the difference between a story and a claim. Before an interview, go through your two or three main projects and attach rough numbers: traffic, data size, team size, latency, how long it took.&lt;/p&gt;

&lt;h2&gt;
  
  
  They handle pushback without folding or digging in
&lt;/h2&gt;

&lt;p&gt;At some point an interviewer will disagree with you, sometimes about something you got right. This is deliberate, and both obvious responses are wrong.&lt;/p&gt;

&lt;p&gt;Folding immediately suggests the position was never reasoned. Refusing to move suggests you would be difficult to work with, which is a much more expensive conclusion. The senior move is to restate why you chose it, engage with the specific counter-argument, and then either hold with a reason or concede with a reason. "That is a better point than mine, because your version handles the failure case and mine does not" is a strong sentence to be able to say out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  They are precise about what they did not do
&lt;/h2&gt;

&lt;p&gt;Experienced interviewers listen for the boundary between what you did and what happened around you. Vague ownership is the fastest way to get downlevelled, because it invites the assumption that you were adjacent to the work rather than doing it.&lt;/p&gt;

&lt;p&gt;Say which part was yours plainly. "I owned the ingestion side, another engineer did the query layer, and the migration plan was mine" is far stronger than a we-shaped answer, and it makes everything else you claim more credible.&lt;/p&gt;

&lt;h2&gt;
  
  
  They know their own edges
&lt;/h2&gt;

&lt;p&gt;Nobody expects a senior engineer to know everything, and pretending to is disqualifying in a way that not knowing is not.&lt;/p&gt;

&lt;p&gt;The strong pattern is: state what you do know, reason from it, and mark exactly where your certainty stops. That last part is what makes the rest of your answers trustworthy. An engineer who never signals uncertainty gives the interviewer no way to calibrate anything they said.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Interviewers are not looking for someone with no gaps. They are looking for someone whose confidence tracks their actual knowledge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The part that decides the number
&lt;/h2&gt;

&lt;p&gt;The offer is set by the level, and the level is usually argued for in a debrief conversation you are not in. Someone has to make the case, and they can only use what you gave them.&lt;/p&gt;

&lt;p&gt;That is why specificity matters so much more than polish. "They were solid" loses the argument. "They caught the duplicate-write problem before I asked, and they had a real reason for the storage choice" wins it. Your job in the interview is to hand your advocate concrete sentences.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;State the level you are targeting to the recruiter early, before the loop is built.&lt;/li&gt;
&lt;li&gt;Give at least one story where you owned a decision, not just an implementation.&lt;/li&gt;
&lt;li&gt;Give at least one where something went wrong and you say what you learned.&lt;/li&gt;
&lt;li&gt;Make sure every interviewer hears at least one number.&lt;/li&gt;
&lt;li&gt;Ask, near the end, what would make them hesitate. It is useful, and the willingness to ask reads well.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What this does not require
&lt;/h2&gt;

&lt;p&gt;It does not require a FAANG background, a large team, or a system with millions of users. A specific story about a small system consistently beats a vague story about a large one, and interviewers can tell the difference instantly.&lt;/p&gt;

&lt;p&gt;What it does require is having gone one layer deeper on the things you claim, which you can find out about yourself in an evening. &lt;a href="https://practicedepth.com/blog/junior-vs-senior-answers" rel="noopener noreferrer"&gt;The junior and senior answers to eight common questions&lt;/a&gt; are a concrete version of the same gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I ask for a higher level than the one I was screened for?
&lt;/h3&gt;

&lt;p&gt;Yes, and it is much easier before the loop is scheduled than after. Say the target level to the recruiter and ask what evidence the panel will want for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does years of experience decide the level?
&lt;/h3&gt;

&lt;p&gt;It sets expectations and rarely decides. Ownership, judgement and the ability to defend decisions move the level far more than the date on your CV.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it risky to admit something went wrong?
&lt;/h3&gt;

&lt;p&gt;The opposite, provided you say what changed as a result. A candidate with no failures either has not owned much or is not being straight.&lt;/p&gt;




&lt;p&gt;Every habit here shows up under follow-up pressure, which is exactly what a practice interview reproduces. Run one at senior level and see which of them you actually do. &lt;a href="https://practicedepth.com/app/practice/interview" rel="noopener noreferrer"&gt;Practise at senior level&lt;/a&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>senior</category>
      <category>hiring</category>
      <category>technicalinterviews</category>
    </item>
    <item>
      <title>The follow-up question is the interview</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Tue, 01 Sep 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/moputa/the-follow-up-question-is-the-interview-2na9</link>
      <guid>https://dev.to/moputa/the-follow-up-question-is-the-interview-2na9</guid>
      <description>&lt;p&gt;You can prepare an answer. You cannot prepare the question that comes after it, because it is assembled from the exact words you just used. That is the whole design, and it is why memorising answers stops working above a certain level.&lt;/p&gt;

&lt;p&gt;Watch a good technical interviewer and the pattern is always the same. They ask something broad. You answer. Then they pick one phrase out of your answer and ask about that. Then they do it again.&lt;/p&gt;

&lt;p&gt;Three questions in, you are somewhere no preparation could have anticipated, discussing a specific thing you personally raised. That is not a hostile technique. It is the only reliable way to tell the difference between someone who has read about a thing and someone who has used it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why interviewers do this
&lt;/h2&gt;

&lt;p&gt;Because first answers are cheap. Anyone can learn what a database index is, what a closure is, what eventual consistency means. Definitions are freely available and memorising them takes an evening.&lt;/p&gt;

&lt;p&gt;What is not freely available is the second layer: why it works that way, what it costs, when it stops being true. That knowledge mostly comes from having been burned, and it is exactly what the follow-up is fishing for. An interviewer who only asked first questions would learn almost nothing about you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The first question tests what you have read. The second tests what you have done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How the drilling actually works
&lt;/h2&gt;

&lt;p&gt;Follow-ups come in a small number of recognisable shapes. Once you can name them, they stop being a surprise.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The definition probe.&lt;/strong&gt; You used a term. Now define it precisely. Triggered by any word you said without unpacking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The mechanism probe.&lt;/strong&gt; You said what happens. Now say why it happens, one level down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The boundary probe.&lt;/strong&gt; When does this stop being true? What would break it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost probe.&lt;/strong&gt; What did that choice lose you? Everything trades something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The alternative probe.&lt;/strong&gt; Why not the other approach? Testing whether you chose or defaulted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The scale probe.&lt;/strong&gt; What changes at a hundred times the volume?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can rehearse against all six without knowing the topic. Take any answer you would give and ask yourself each of them in turn. Wherever you run out of road, that is a gap, and it is much cheaper to find it now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the drill usually lands
&lt;/h2&gt;

&lt;p&gt;There is a reliable pattern to which phrase gets picked. Interviewers drill on the words that carry the most meaning per syllable, because those are where a candidate is most likely to be borrowing language rather than describing understanding.&lt;/p&gt;

&lt;p&gt;If you say a system is eventually consistent, expect to be asked what a user sees during the window. If you say you added a cache, expect invalidation. If you say you used a queue, expect what happens when consumers fall behind. If you say something is thread-safe, expect to be asked what makes it so. These are not obscure follow-ups. They are the obvious next question, which is precisely why they get asked.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The audit worth doing before any interview&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Read your own prepared answers and underline every noun phrase you would not want to be asked to unpack. That underlined list is your interview. Nothing else on the page matters nearly as much.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Four things to do when the drill lands
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Slow down rather than speed up
&lt;/h3&gt;

&lt;p&gt;The instinct under pressure is to talk faster and cover more ground, hoping something lands. It reliably makes things worse, because more surface means more things to drill into. Take two seconds and answer the specific thing asked.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Answer the narrow question, not the broad one
&lt;/h3&gt;

&lt;p&gt;A follow-up is deliberately narrow. Retreating to the general topic is a visible dodge, and interviewers read it as one. If you are asked what happens to in-flight requests during a deploy, talk about in-flight requests, not about your deployment philosophy.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Say where your certainty ends
&lt;/h3&gt;

&lt;p&gt;"I know it holds the lock for the duration of the transaction. What I am less sure about is whether that is per row or escalates, and I would check that before relying on it." That sentence costs nothing and buys a lot, because it tells the interviewer your confidence is calibrated. Once they believe that, everything else you said gets more weight, not less.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Never invent a mechanism
&lt;/h3&gt;

&lt;p&gt;This is the one genuinely unrecoverable move. If you make up how something works and the interviewer knows, the round is effectively over, and worse, everything you said earlier is now suspect. Reasoning aloud from what you do know sounds completely different from bluffing, and interviewers can tell the two apart easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a good recovery sounds like
&lt;/h2&gt;

&lt;h4&gt;
  
  
  You mentioned the index made it faster. Why is a B-tree index faster for a range query than a hash index?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Because B-trees are the standard index type and they are optimised for lookups, so the database can find the rows much more efficiently than scanning. Hash indexes are older and generally slower for most queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; Because a B-tree keeps keys in sorted order, so a range query seeks to the start and then walks sequentially. A hash index scatters keys by hash, so there is no ordering to walk and a range means checking everything. Where I am less certain is the exact page-level behaviour, but the ordering property is the part that matters for the range case.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The second answer contains an admission of uncertainty and is much stronger. The first is confidently wrong about hash indexes, and the interviewer now doubts the original claim too.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to practise for an unpredictable question
&lt;/h2&gt;

&lt;p&gt;You cannot rehearse the specific follow-up. You can absolutely build the depth it lands on, and you can practise the shape of a good response.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;List every technology you would claim in an interview.&lt;/li&gt;
&lt;li&gt;For each, write the answer you would give to the obvious first question.&lt;/li&gt;
&lt;li&gt;Run the six probes above against that answer. Definition, mechanism, boundary, cost, alternative, scale.&lt;/li&gt;
&lt;li&gt;Note where you run out. That is the real preparation list, and it is usually short.&lt;/li&gt;
&lt;li&gt;Close those gaps by reading until you could answer two layers past them, then say it out loud once.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The out-loud part is not optional. Knowing something and being able to produce it under pressure, in order, in a sentence, are different skills, and only one of them gets tested. This is also why &lt;a href="https://practicedepth.com/blog/ai-technical-screens-explained" rel="noopener noreferrer"&gt;AI screens&lt;/a&gt; feel so much harder than they look: the follow-ups arrive without social brakes, so the drill goes one layer further than a polite human would push.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How deep do interviewers actually go?
&lt;/h3&gt;

&lt;p&gt;Usually until you stop being able to add anything, which is the point. Reaching the end of your knowledge is expected. How you handle arriving there is what gets scored.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it a bad sign if they keep drilling?
&lt;/h3&gt;

&lt;p&gt;Normally the opposite. Interviewers drill hardest where they think there is something to find. A round with no follow-ups often means they stopped being curious.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if I contradict something I said earlier?
&lt;/h3&gt;

&lt;p&gt;Correct it immediately and out loud. A visible correction reads as rigour. Hoping nobody noticed does not work, and being caught later is much worse.&lt;/p&gt;




&lt;p&gt;There is only one way to find out where your follow-ups run out, and it is to be asked. Run a practice interview and watch where the second question lands. &lt;a href="https://practicedepth.com/app/practice/interview" rel="noopener noreferrer"&gt;Find your gaps&lt;/a&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>interview</category>
      <category>softwareengineering</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to talk about trade-offs without sounding like you are hedging</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Fri, 28 Aug 2026 12:50:00 +0000</pubDate>
      <link>https://dev.to/moputa/how-to-talk-about-trade-offs-without-sounding-like-you-are-hedging-3eia</link>
      <guid>https://dev.to/moputa/how-to-talk-about-trade-offs-without-sounding-like-you-are-hedging-3eia</guid>
      <description>&lt;p&gt;Nuance is the thing that gets you levelled up, and hedging is the thing that gets you levelled down. They sound almost identical from the outside, and the difference is entirely structural.&lt;/p&gt;

&lt;p&gt;Ask a junior engineer whether to use SQL or NoSQL and you get an answer. Ask a senior engineer and you often get "well, it depends", which is correct, and delivered badly it costs them the round.&lt;/p&gt;

&lt;p&gt;The problem is not the nuance. It is the order. Hedging leads with the uncertainty and never arrives at a decision. Judgement leads with the decision and then shows the uncertainty around it. Same knowledge, opposite impression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why hedging reads badly
&lt;/h2&gt;

&lt;p&gt;An interviewer is trying to answer one question: would I trust this person to make a call without me in the room. A candidate who lists options without choosing has actively failed to demonstrate the thing being assessed, no matter how well they understand the options.&lt;/p&gt;

&lt;p&gt;There is a second, less obvious cost. Refusing to commit removes the interviewer's ability to go deeper. They cannot probe a decision you did not make, so the conversation stays shallow, and shallow conversations produce mid-level scores by default.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A candidate who says it depends and stops has told the interviewer nothing except that they know it is complicated. Everyone at this level knows it is complicated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The four-part structure
&lt;/h2&gt;

&lt;p&gt;This works for almost any technical choice you will be asked about, and it takes about twenty seconds to deliver.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Commit.&lt;/strong&gt; Name what you would actually ship. One sentence, no preamble.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Justify.&lt;/strong&gt; Give the specific reason, tied to the constraints in the question rather than to general virtue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; Say what you are giving up. Every choice loses something and naming it is the seniority signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger.&lt;/strong&gt; State the condition that would change your mind, and ideally what you would watch for it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice that all the nuance from "it depends" is present. It is simply arranged behind a decision instead of in place of one.&lt;/p&gt;

&lt;h4&gt;
  
  
  Would you use a relational database or a document store for this?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; It really depends on the use case. Relational is good for structured data with relationships and transactions, document stores are better for flexible schemas and horizontal scaling. There are trade-offs both ways, so it depends what matters most for the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; I would start relational. The data here is clearly relational, orders belong to users and contain items, and I want transactions when money is involved rather than reinventing them. What I give up is schema flexibility, so every shape change is a migration, and that is a real cost on a fast-moving product. I would revisit if we hit write volume one primary cannot take, and the thing I would watch is write latency at peak rather than total row count.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The first answer is a textbook. The second is a decision, and the interviewer can now push on it, which is exactly what you want.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the justification right
&lt;/h2&gt;

&lt;p&gt;Most weak justifications are weak because they are general. "It scales better" is not a reason, it is a property. The strong version ties the choice to something in the question.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Weak:&lt;/strong&gt; it is more performant. &lt;strong&gt;Strong:&lt;/strong&gt; the read path is a hundred to one against writes here, so I optimise for reads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak:&lt;/strong&gt; it is more maintainable. &lt;strong&gt;Strong:&lt;/strong&gt; this team is four people with no dedicated operations, so I want the option that is boring to run at 3am.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak:&lt;/strong&gt; it is industry standard. &lt;strong&gt;Strong:&lt;/strong&gt; we already run it, and the operational knowledge is worth more here than any feature difference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak:&lt;/strong&gt; it is more flexible. &lt;strong&gt;Strong:&lt;/strong&gt; the requirements changed twice in the last quarter, so I want the shape that survives a third change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Team size, existing stack, deadline pressure and operational maturity are all legitimate reasons and they are the ones real engineers actually use. Interviewers recognise them immediately as the voice of someone who has shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming the cost is the whole move
&lt;/h2&gt;

&lt;p&gt;If you take one habit from this piece, take this one. Candidates who present only the upside sound like they are selling something. Candidates who name the downside unprompted sound like they have lived with the consequence.&lt;/p&gt;

&lt;p&gt;It is also tactically useful. The cost you name is usually the thing the interviewer was about to probe, so volunteering it moves the conversation onto ground you have already thought through, and it makes the rest of the answer more credible by demonstrating you are not overselling.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The sentence to practise&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"What that costs me is X." Attach it to every technical decision you describe for a week and it becomes automatic. It is the single highest-return phrase in a senior interview.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When you genuinely need more information
&lt;/h2&gt;

&lt;p&gt;Sometimes the honest answer really is that you cannot choose yet. That is fine, and there is a way to say it that still demonstrates judgement.&lt;/p&gt;

&lt;p&gt;Name the specific fact that would decide it, say what you would pick under each branch, and then commit to a default. "The deciding factor is whether these writes need to be transactional across entities. If they do, relational, no question. If they genuinely do not, the document store buys us real flexibility. Without knowing, I default to relational, because that mistake is cheaper to unwind." You have shown the analysis and still made a call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling the pushback
&lt;/h2&gt;

&lt;p&gt;Commit to something and the interviewer will very likely argue with it. That is a good sign. It means there is now something to push on.&lt;/p&gt;

&lt;p&gt;Two failure modes, both common. Folding instantly suggests the position was never reasoned. Refusing to move suggests you would be exhausting to work with, which is the more expensive conclusion of the two. What works is engaging with the specific counter-argument, and then holding or conceding with a stated reason.&lt;/p&gt;

&lt;p&gt;"You are right that this breaks if the write volume is much higher than I assumed. If that is the constraint, I would go the other way" is a strong sentence. It shows the original position had conditions attached, which is what having judgement means. The same instinct is what &lt;a href="https://practicedepth.com/blog/system-design-interview-what-interviewers-score" rel="noopener noreferrer"&gt;system design rounds&lt;/a&gt; spend most of their time measuring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What if I commit to something and it is wrong?
&lt;/h3&gt;

&lt;p&gt;You will find out, and adjusting cleanly in front of the interviewer scores better than never having committed. A wrong-but-reasoned position with a stated trigger is far more informative than a survey of options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this apply when I genuinely have no experience with either option?
&lt;/h3&gt;

&lt;p&gt;Yes, with the uncertainty stated. Reason from properties you do understand, pick the one you could operate, and say plainly which parts you would need to verify.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it arrogant to be this decisive?
&lt;/h3&gt;

&lt;p&gt;Not when the cost and the trigger are attached. Arrogance is an unconditional claim. This structure is explicitly conditional, it just leads with the conclusion.&lt;/p&gt;




&lt;p&gt;Trade-off reasoning only shows up when something pushes back on your answer. Run an architecture-flavoured session and see whether your decisions survive the second question. &lt;a href="https://practicedepth.com/app/practice/playbook" rel="noopener noreferrer"&gt;Practise defending decisions&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>career</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>React interview questions seniors actually get asked</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Tue, 25 Aug 2026 14:51:00 +0000</pubDate>
      <link>https://dev.to/moputa/react-interview-questions-seniors-actually-get-asked-4b9b</link>
      <guid>https://dev.to/moputa/react-interview-questions-seniors-actually-get-asked-4b9b</guid>
      <description>&lt;p&gt;Senior React interviews are rarely about API surface. They circle a handful of areas where a candidate either understands the rendering model or is pattern-matching, and the follow-ups find out which within about two minutes.&lt;/p&gt;

&lt;p&gt;Nobody senior gets asked to list the lifecycle methods. What they get asked is why a page re-renders more than it should, why an effect fired twice, and why the app feels slow when every request is fast.&lt;/p&gt;

&lt;p&gt;Here are the areas those questions come from, and what a strong answer contains.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually causes a re-render
&lt;/h2&gt;

&lt;p&gt;The most common senior React question in any form, and the one that most reliably separates candidates.&lt;/p&gt;

&lt;p&gt;A component re-renders when its state changes, when its parent re-renders, or when a context it consumes changes. That is the whole list. The part people get wrong is the second one: a child re-renders because its parent did, regardless of whether its props changed at all. Props are not an input to the decision, they are only an input to what the render produces.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why is this component re-rendering when its props have not changed?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Because something in its props is changing identity between renders, so React sees a new value and re-renders it. Wrapping it in memo and memoising the props usually fixes it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; By default it re-renders because its parent did, and props do not come into it. Identity only matters once you wrap it in memo, at which point a new object or function prop defeats the comparison, which is why memo so often changes nothing. Before reaching for memo I would find out why the parent is rendering that often, since that is usually the actual bug. On the last one of these I looked at, a context was holding a new object every render, so every consumer rendered on every keystroke.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The follow-up is always what memo actually compares. Answer one gets stuck there.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Effects, and why they are the biggest source of bugs
&lt;/h2&gt;

&lt;p&gt;Expect at least one effect question. The strong framing is that an effect is for synchronising with something outside React: a subscription, a timer, an imperative browser API. Most of the time, a bug that arrives with an effect is a sign the work did not belong in one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Derived state in an effect.&lt;/strong&gt; Computing state from props inside an effect causes an extra render and a frame of stale UI. Compute it during render instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The stale closure.&lt;/strong&gt; An effect captures the values from the render it ran in. Miss a dependency and it quietly reads an old value forever, which is the single most common React bug in production code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing cleanup.&lt;/strong&gt; Subscriptions, intervals and in-flight requests need a cleanup function, or you get leaks and out-of-order responses overwriting newer ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetching in an effect without cancellation.&lt;/strong&gt; Two quick navigations and the slower response wins. Interviewers ask about this specifically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can explain why an effect that fetches needs to handle its own obsolescence, you are well past the median candidate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keys
&lt;/h2&gt;

&lt;p&gt;Looks like a beginner topic, is not. Everyone knows keys should be stable and not the array index. Far fewer can say what actually goes wrong.&lt;/p&gt;

&lt;p&gt;Keys tell React which element in the new list corresponds to which element in the old one. With index keys, deleting the first item means every element shifts, so React reuses the wrong instances: state stays attached to the wrong row, inputs keep the wrong value, animations play on the wrong element. The list still looks right until the components hold state, at which point the bug is bizarre and hard to trace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EditableRow&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// index key: identity shifts on delete&lt;/span&gt;
&lt;span class="p"&gt;))}&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EditableRow&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// stable identity survives reorder&lt;/span&gt;
&lt;span class="p"&gt;))}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bug that makes this concrete: remove the first row and the text input's value follows the wrong item, because index keys made React reuse the wrong instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where state should live
&lt;/h2&gt;

&lt;p&gt;Architecture questions in a React interview are usually really state-ownership questions. Expect some version of how you would structure state in a large application.&lt;/p&gt;

&lt;p&gt;The answer that reads as senior starts by separating server state from client state. Data that lives in a database and is cached in the browser has completely different needs from whether a dropdown is open: it needs revalidation, deduplication and staleness handling, none of which a general state container gives you. Once you say that out loud, the question of which global state library to use mostly evaporates, which is exactly the point being tested.&lt;/p&gt;

&lt;p&gt;Then colocate the rest. State should live at the lowest common ancestor of the components that use it, and lifting everything to the top is a performance and maintenance problem disguised as tidiness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance, properly
&lt;/h2&gt;

&lt;p&gt;The weak answer to any performance question is a list of memoisation hooks. The strong answer starts with diagnosis, because the three causes have different fixes and applying the wrong one wastes a sprint.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rendering too often: a context or a parent updating more than it should. Fix the source, then memoise if it still matters.&lt;/li&gt;
&lt;li&gt;Rendering too much at once: a large list. Virtualise it. No amount of memoisation helps here.&lt;/li&gt;
&lt;li&gt;Not a React problem at all: a slow request, a large bundle, an expensive synchronous computation blocking the main thread.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Mention that memoisation is not free, since every memo adds a comparison and a dependency array that can be wrong, and you have said something most candidates do not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The question behind the question&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When an interviewer asks about performance, they usually want to know whether you measure. Saying which tool you would open and what you would look for first is worth more than any list of optimisations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Rendering on the server
&lt;/h2&gt;

&lt;p&gt;Increasingly standard in senior frontend rounds, and a place where a lot of otherwise strong candidates are vague.&lt;/p&gt;

&lt;p&gt;Be able to say plainly what runs where, what hydration actually is and why a mismatch happens, and why moving data fetching to the server changes the waterfall problem. The interesting follow-up is usually about anything time or locale dependent, since a date formatted with the server's timezone and then re-rendered in the browser's is the classic mismatch, and explaining that specific case demonstrates real experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare
&lt;/h2&gt;

&lt;p&gt;Take a component you have written and answer three questions about it. When does it re-render. What would happen if this effect's dependency array were wrong. Where would this fall over with ten thousand rows.&lt;/p&gt;

&lt;p&gt;That exercise finds gaps faster than any question list, because it produces the layer underneath the answer, which is &lt;a href="https://practicedepth.com/blog/surviving-the-follow-up-question" rel="noopener noreferrer"&gt;where the follow-up will land&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need to know class components?
&lt;/h3&gt;

&lt;p&gt;Enough to work in a codebase that has them, and to talk about migration. Nobody senior is being tested on lifecycle trivia.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much state library knowledge is expected?
&lt;/h3&gt;

&lt;p&gt;Less than people think. What is expected is the reasoning about which state is server state, which is local, and why that distinction decides the tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are React internals fair game?
&lt;/h3&gt;

&lt;p&gt;Reconciliation and the rendering model, yes, because they explain behaviour you hit in practice. Fiber implementation details, rarely, and an interviewer who leads with them is testing something other than your ability to do the job.&lt;/p&gt;




&lt;p&gt;The fastest way to find out whether your React answers hold up is to be followed up on them. Run a React-focused interview at your level. &lt;a href="https://practicedepth.com/app/practice/interview?focus=react&amp;amp;mode=senior" rel="noopener noreferrer"&gt;Practise a React interview&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>frontend</category>
      <category>programming</category>
      <category>seniordev</category>
    </item>
    <item>
      <title>Node.js interview deep dive: what they are really testing</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Mon, 24 Aug 2026 06:19:00 +0000</pubDate>
      <link>https://dev.to/moputa/nodejs-interview-deep-dive-what-they-are-really-testing-1m6o</link>
      <guid>https://dev.to/moputa/nodejs-interview-deep-dive-what-they-are-really-testing-1m6o</guid>
      <description>&lt;p&gt;Almost every senior Node question is the same question wearing a different hat: do you understand that there is one thread doing your work, and do you know what happens when you block it.&lt;/p&gt;

&lt;p&gt;Node interviews look varied and are not. Streams, timers, clustering, memory, async error handling: pull on any of them and you end up at the event loop, which is why interviewers use them as entry points.&lt;/p&gt;

&lt;h2&gt;
  
  
  The event loop, said properly
&lt;/h2&gt;

&lt;p&gt;The definition everyone gives is that Node is non-blocking and asynchronous. True, and it does not demonstrate anything. The useful version explains the division of labour.&lt;/p&gt;

&lt;p&gt;Your JavaScript runs on one thread. Input and output does not: it is handed to the operating system or to a thread pool, and when it completes, the callback is queued back onto that single thread. That is why thousands of idle connections cost almost nothing, and it is also why one expensive synchronous function stops every other request on that process, not just the one it belongs to.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if a request handler does something CPU-heavy?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; It will be slower and could block other requests, so you should avoid heavy computation in Node and move it to a background job or a different service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; It stalls the whole process for its duration, because there is one thread running JavaScript, so every other in-flight request waits behind it even though they are doing nothing. It also never shows up in local testing where you are the only user. I have watched a synchronous parse of a large payload add a few hundred milliseconds to every concurrent request. The fix is a worker thread if it is genuinely CPU work, or streaming it if the size is the problem.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Naming the concurrency effect, and the fact that it hides locally, is what makes this an operator's answer.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the ordering questions come from
&lt;/h2&gt;

&lt;p&gt;Expect at least one question about what runs before what. The point is not trivia, it is whether you know microtasks and macrotasks are different queues.&lt;/p&gt;

&lt;p&gt;A resolved promise callback runs before a timer that was already due, because the microtask queue is drained completely between each phase. The practically important consequence is that a recursive chain of promise callbacks can starve the loop entirely, and the process will look alive while serving nothing. That consequence is the answer worth having, more than the ordering itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streams and backpressure
&lt;/h2&gt;

&lt;p&gt;The most reliable senior discriminator in a Node interview, because it is the thing you only learn by having run out of memory in production.&lt;/p&gt;

&lt;p&gt;Reading a large file into memory and sending it works fine until the file is large or ten users do it at once. Streaming processes it in chunks. But the question interviewers actually care about is what happens when the consumer is slower than the producer, and the answer is that without backpressure the chunks queue in memory until the process dies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// No backpressure: 'data' keeps firing regardless of whether res can keep up&lt;/span&gt;
&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Backpressure honoured: pipeline pauses the source when the sink is full&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference is not style. The first version buffers the entire response in memory when the client is slow; the second stops reading until the socket drains.&lt;/p&gt;

&lt;p&gt;Say the words 'the writable returns false and you wait for drain', or simply use the pipeline helper and explain what it does for you, and you have answered a question most candidates talk around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Async error handling
&lt;/h2&gt;

&lt;p&gt;A favourite because there are several ways to lose an error silently, and each one is a real production incident someone has had.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An async function called without await.&lt;/strong&gt; The promise rejects with nobody listening. The request has already returned 200.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throwing inside a callback.&lt;/strong&gt; Nothing up the stack catches it, because the stack it was called from is long gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A try/catch around a promise you did not await.&lt;/strong&gt; It catches nothing and looks like it does, which is worse than no handler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An error event on a stream with no listener.&lt;/strong&gt; In Node this is not silent, it is fatal, which surprises people.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The strong closing point is what you do at the boundary: an unhandled rejection handler that logs and exits, with a supervisor restarting the process, because a process in an unknown state serving traffic is worse than a process that died cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory leaks
&lt;/h2&gt;

&lt;p&gt;Usually asked as a debugging scenario rather than a definition: memory climbs over hours, what do you do.&lt;/p&gt;

&lt;p&gt;The reasoning matters more than the tooling. Long-lived references are the cause: a module-level cache with no eviction, listeners added per request and never removed, closures held by a timer that outlives what it captured. Take two heap snapshots under load, compare, and look at what grew. Saying that you would compare snapshots rather than guess is most of the signal here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling a Node process
&lt;/h2&gt;

&lt;p&gt;One process uses one core, so scaling means multiple processes, whether through the cluster module, a process manager or separate containers behind a load balancer.&lt;/p&gt;

&lt;p&gt;The follow-up is what breaks when you go from one process to several, and it is always state. In-memory sessions, in-memory caches, in-memory rate limit counters and scheduled jobs all quietly assume there is exactly one of you. Naming that class of bug before being asked is a strong senior signal, and it is the same instinct &lt;a href="https://practicedepth.com/blog/system-design-interview-what-interviewers-score" rel="noopener noreferrer"&gt;system design rounds&lt;/a&gt; are testing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The sentence that carries most Node answers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"There is one thread running my JavaScript, so the question is always what I am doing to it and for how long." Almost every Node follow-up can be reasoned from there.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need to know libuv internals?
&lt;/h3&gt;

&lt;p&gt;No. You need the model: one JS thread, I/O offloaded, callbacks queued back. Internals only matter when they explain observable behaviour, like why file I/O uses a thread pool and network I/O generally does not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the event loop question not a bit basic?
&lt;/h3&gt;

&lt;p&gt;The question is basic. The follow-ups are not, and they are where the round is decided. Most candidates give a definition and cannot describe the consequences.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much of this changes with worker threads?
&lt;/h3&gt;

&lt;p&gt;It gives you a way to move CPU work off the main thread, so the model still holds, you just have more than one thread and a message boundary to reason about.&lt;/p&gt;




&lt;p&gt;These questions all have a second layer, and that is where the round is decided. Run a Node-focused interview and find out where yours stops. &lt;a href="https://practicedepth.com/app/practice/interview?focus=node&amp;amp;mode=senior" rel="noopener noreferrer"&gt;Practise a Node.js interview&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>backend</category>
      <category>interview</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Database interview questions for backend engineers</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Wed, 19 Aug 2026 11:21:00 +0000</pubDate>
      <link>https://dev.to/moputa/database-interview-questions-for-backend-engineers-4nob</link>
      <guid>https://dev.to/moputa/database-interview-questions-for-backend-engineers-4nob</guid>
      <description>&lt;p&gt;The database section is where senior backend interviews get decided, because it is the hardest area to fake. You either have read a query plan or you have not, and the follow-ups find out quickly.&lt;/p&gt;

&lt;p&gt;Almost everyone can write a join. Far fewer can say why the query is slow, what the index is doing, or what happens when two of these run at the same time. That gap is what the round is measuring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Indexes, past the definition
&lt;/h2&gt;

&lt;p&gt;The opening question is usually some form of when would you add an index. The definitional answer, that it avoids scanning the table, is table stakes. Three things separate a senior answer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Column order in a composite index.&lt;/strong&gt; An index on the filter column plus the sort column only helps if the leading column is the one you filter on. Reverse them and it is close to useless for that query. This is the single most common follow-up in the whole area.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The write cost.&lt;/strong&gt; Every insert and update maintains every index on the table. On write-heavy tables, removing an index can be the optimisation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selectivity.&lt;/strong&gt; An index on a boolean that is true for most rows will often be ignored, because scanning is cheaper than jumping around. Interviewers like this one because it shows you know the planner makes a decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The strongest habit is to talk about the plan rather than about intentions. "I would look at the plan, see whether it is a scan and a sort, add the index, and confirm the plan changed" is a different level of answer from "I would add an index on the where clause".&lt;/p&gt;

&lt;h2&gt;
  
  
  The N+1 query problem
&lt;/h2&gt;

&lt;p&gt;Universal, because every ORM makes it easy and it is invisible in development where the list has four rows.&lt;/p&gt;

&lt;p&gt;Be able to describe both the mechanism and the detection. One query fetches the list, then accessing a related field on each row issues another query, so a hundred rows becomes a hundred and one round trips. Each is fast, the total is not, and it degrades linearly with data you do not control. The tell in production is a request whose latency scales with page size while every individual query looks healthy.&lt;/p&gt;

&lt;h4&gt;
  
  
  An endpoint got slower as data grew. How would you find out why?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; I would check the slow query log and add indexes to the columns being filtered on, and probably add caching if it is read heavy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; First I would look at whether one query is slow or many queries are being issued, because those have completely different fixes and the second one hides from a slow query log. If the count scales with the number of rows returned, it is N+1 and the fix is eager loading, not an index. If it is one slow query, then the plan tells me whether it is a scan, a sort or a bad join order. Caching I would consider last, since caching a query I have not understood usually just moves the problem and adds an invalidation bug.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Distinguishing one-slow-query from many-fast-queries is the whole diagnosis, and it is where most candidates skip straight to a fix.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Transactions and isolation
&lt;/h2&gt;

&lt;p&gt;Expect at least one. The trap is reciting the four ACID letters, which demonstrates reading rather than experience.&lt;/p&gt;

&lt;p&gt;What earns credit is being able to describe a concrete anomaly. Two requests read the same balance, both subtract from it, and one update is lost. Or a report reads a row twice inside one transaction and gets different values because another transaction committed in between. Then say which isolation level prevents which, and why nobody runs everything at the strictest level: it costs throughput and increases the chance of deadlocks and aborts.&lt;/p&gt;

&lt;p&gt;The practical follow-up is usually about the read-then-write pattern. Reading a value in application code and writing a computed result back is a race unless the database is enforcing it, which is why a unique constraint, a conditional update or a select for update is the actual fix rather than checking first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Locking and deadlocks
&lt;/h2&gt;

&lt;p&gt;Asked as a scenario more often than a definition: your writes intermittently fail with a deadlock error, what is happening.&lt;/p&gt;

&lt;p&gt;Two transactions taking the same locks in different orders is the classic cause, and the practical fix is to make the order consistent, keep transactions short, and not do slow work such as an external API call while holding a lock. Mentioning that last one tends to land, because it is the version of this bug that people actually ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema and migrations
&lt;/h2&gt;

&lt;p&gt;Increasingly common in senior rounds, because it is where database knowledge meets operational judgement.&lt;/p&gt;

&lt;p&gt;Normalise until you have a reason not to, and be able to name your reason for denormalising rather than presenting it as a default. Then the migration question: how do you change a schema without downtime.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the new column or table, nullable, deploying nothing that requires it.&lt;/li&gt;
&lt;li&gt;Deploy code that writes both old and new.&lt;/li&gt;
&lt;li&gt;Backfill in batches, not one large statement that locks the table.&lt;/li&gt;
&lt;li&gt;Deploy code that reads the new path.&lt;/li&gt;
&lt;li&gt;Stop writing the old one, then drop it in a later release.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That expand-and-contract shape is the answer, and knowing that a naive index creation can lock writes for the duration on a large table is the follow-up worth having ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connection pooling
&lt;/h2&gt;

&lt;p&gt;Quietly one of the most useful things to understand, and one of the least discussed in preparation material.&lt;/p&gt;

&lt;p&gt;Connections are expensive and databases cap them, so applications hold a pool. The interview-relevant part is what happens when the pool is exhausted: requests queue, latency climbs everywhere at once, and it looks exactly like the database being slow while the database is comfortable. Being able to describe that misdiagnosis is a strong signal, because most people have chased it in the wrong direction at least once.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The habit that carries this whole area&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Talk about evidence, not intentions. Reading a plan, comparing query counts, checking pool saturation. Candidates who say what they would look at outscore candidates who say what they would change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every one of these topics has a second layer, and the second layer is what gets asked. If you can only give the definition, that is worth knowing before the interview rather than during it, which is &lt;a href="https://practicedepth.com/blog/surviving-the-follow-up-question" rel="noopener noreferrer"&gt;the audit worth running on yourself&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How much SQL should I be able to write live?
&lt;/h3&gt;

&lt;p&gt;Joins, aggregation with grouping, and usually a window function or a subquery. Perfect syntax is rarely the point; being able to explain what the query makes the database do is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to know a specific database?
&lt;/h3&gt;

&lt;p&gt;Know one properly and you can reason about the others. Interviewers care much more about depth in one than familiarity with four.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is NoSQL knowledge expected?
&lt;/h3&gt;

&lt;p&gt;Enough to say when you would choose it and what you give up. The strongest answer starts from access patterns, not from the label on the technology.&lt;/p&gt;




&lt;p&gt;Database rounds are decided by the follow-up, not the first answer. Run a database-focused interview and see how far yours goes. &lt;a href="https://practicedepth.com/app/practice/interview?focus=database&amp;amp;mode=senior&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=organic" rel="noopener noreferrer"&gt;Practise a database interview&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
      <category>programming</category>
      <category>career</category>
    </item>
    <item>
      <title>Security questions every senior backend engineer should handle</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Sun, 16 Aug 2026 14:57:00 +0000</pubDate>
      <link>https://dev.to/moputa/security-questions-every-senior-backend-engineer-should-handle-2n3g</link>
      <guid>https://dev.to/moputa/security-questions-every-senior-backend-engineer-should-handle-2n3g</guid>
      <description>&lt;p&gt;You are not being interviewed as a security engineer. You are being checked for one habit: whether, while describing a system, you notice who could abuse it.&lt;/p&gt;

&lt;p&gt;Security questions rarely arrive labelled. They show up as follow-ups inside an ordinary technical conversation, and the ones that matter come back to a single instinct: treating input as hostile and asking who this trusts and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication and authorisation are different questions
&lt;/h2&gt;

&lt;p&gt;Everyone can define the difference. The interesting part is that authentication bugs are rare and authorisation bugs are everywhere, because authentication is one well-tested code path and authorisation is a decision at every single endpoint.&lt;/p&gt;

&lt;p&gt;The specific failure worth naming is trusting an identifier from the client. An endpoint that fetches a record by an id in the URL, checks that the caller is logged in, and returns it, is broken: the caller is authenticated and has no right to that record. Being able to describe that in one breath is more valuable than any definition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Password storage
&lt;/h2&gt;

&lt;p&gt;A near-guaranteed question, and one where a good answer takes fifteen seconds.&lt;/p&gt;

&lt;h4&gt;
  
  
  How do you store user passwords?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; Hashed and salted, never plain text, using bcrypt rather than something like MD5 or SHA-256 because those are too fast and not designed for passwords.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; A deliberately slow, memory-hard hash, so bcrypt or argon2, with the work factor tuned to what the login path can afford and raised as hardware gets cheaper. The slowness is the point, because the real threat is offline cracking after a dump rather than anything at login time. Salts are per user and stored alongside the hash, which is what stops one precomputed table covering every account. I would also keep the failure path constant time, since a login that returns measurably faster for an unknown email leaks which accounts exist.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The timing side channel is the follow-up. Volunteering it moves you into a different category of candidate.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sessions and tokens
&lt;/h2&gt;

&lt;p&gt;Expect a discussion of stateless tokens versus server-side sessions, and expect it to be a trade-off question rather than a right-answer question.&lt;/p&gt;

&lt;p&gt;Stateless tokens avoid a lookup on every request, which is why they scale nicely. What they cost you is instant revocation: until it expires, a stolen token is valid, and there is no server-side record to delete. That is the whole trade, and the mature answer is a short access token plus a refresh token, with a deny list only on the paths where the risk justifies the round trip.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never accept the algorithm from the token itself. Pin it server side.&lt;/li&gt;
&lt;li&gt;Verify signature, issuer, audience and expiry, not just that it decodes.&lt;/li&gt;
&lt;li&gt;The payload is readable by anyone holding it. Signed does not mean private.&lt;/li&gt;
&lt;li&gt;Store it somewhere script cannot read it if you can, which usually means an httpOnly cookie rather than local storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Injection, and why parameterisation is the answer
&lt;/h2&gt;

&lt;p&gt;Still asked, still worth answering precisely, because most candidates say escaping when the actual answer is separation.&lt;/p&gt;

&lt;p&gt;A parameterised query does not clean the input, it sends the query and the data separately, so the database never parses user text as SQL. That distinction matters, because escaping is a filter that can be wrong and separation is structural. The same reasoning covers shell commands and any other interpreter: pass arguments, do not build strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// injectable: the value becomes part of the parsed statement&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM users WHERE email = '&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// parameterised: the value is never parsed as SQL&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM users WHERE email = $1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first version concatenates user input into the statement. The second sends the statement and the value on separate channels, so nothing in the value can change the query's structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  XSS and CSRF, and why people mix them up
&lt;/h2&gt;

&lt;p&gt;Worth being able to separate cleanly, because candidates routinely give the mitigation for one when asked about the other.&lt;/p&gt;

&lt;p&gt;Cross-site scripting is untrusted content becoming executable in your page, and the defence is contextual output encoding plus a content security policy. Cross-site request forgery is another site causing the browser to make an authenticated request to yours, and the defence is a token the other site cannot read plus same-site cookies. One is about what runs in your page, the other is about who caused a request. If you have XSS, your CSRF defences are irrelevant, since the attacker is already running inside your origin, and saying that shows you understand the relationship rather than the labels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets
&lt;/h2&gt;

&lt;p&gt;Usually asked practically: where do your credentials live. The expected answer is injected at runtime from a secret manager or the platform's environment, never committed, with rotation possible without a code change.&lt;/p&gt;

&lt;p&gt;The follow-up worth preparing is what you do when a secret leaks. Rotate first, then work out the exposure, and assume anything committed to a repository is compromised permanently regardless of whether the commit was later removed. That last point is the one interviewers listen for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limiting and abuse
&lt;/h2&gt;

&lt;p&gt;Increasingly common, particularly for anything with a login form or an expensive endpoint.&lt;/p&gt;

&lt;p&gt;The reasoning to show is what you are limiting on and why. Per IP is easy and weak, since attackers rotate addresses and shared networks hurt real users. Per account protects against credential stuffing against one target. Both, at different thresholds, is usually the honest answer. The good follow-up is where the counter lives once you run several processes, which lands you back on shared state, the same problem &lt;a href="https://practicedepth.com/blog/nodejs-interview-deep-dive" rel="noopener noreferrer"&gt;scaling a Node process&lt;/a&gt; runs into.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is actually being scored&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not a checklist. Interviewers are listening for whether you volunteer the abuse case unprompted, the same way you would volunteer a trade-off. Someone who says which endpoint they would attack first, in their own design, has demonstrated the instinct the round is looking for.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How much security is expected from a normal backend role?
&lt;/h3&gt;

&lt;p&gt;The common classes and a threat-model instinct. Nobody expects exploit development. Everybody expects you to notice that user input reaches a query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I memorise the OWASP Top Ten?
&lt;/h3&gt;

&lt;p&gt;Know the categories and be able to describe two or three properly. Reciting ten labels with no mechanism behind them is transparent and scores badly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if I have never worked on anything security sensitive?
&lt;/h3&gt;

&lt;p&gt;Say so and reason from the systems you have built. Naming where you would be worried about your own past work is a strong, honest answer.&lt;/p&gt;




&lt;p&gt;Security questions are almost entirely follow-up territory, which is where a definition stops helping. Run a security-focused interview and see where yours ends. &lt;a href="https://practicedepth.com/app/practice/interview?focus=security&amp;amp;mode=senior&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=organic" rel="noopener noreferrer"&gt;Practise a security interview&lt;/a&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>ai</category>
      <category>interview</category>
      <category>security</category>
    </item>
    <item>
      <title>AI engineering interview questions: retrieval, evaluation and everything that breaks</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Fri, 14 Aug 2026 08:37:00 +0000</pubDate>
      <link>https://dev.to/moputa/ai-engineering-interview-questions-retrieval-evaluation-and-everything-that-breaks-62n</link>
      <guid>https://dev.to/moputa/ai-engineering-interview-questions-retrieval-evaluation-and-everything-that-breaks-62n</guid>
      <description>&lt;p&gt;The field is young enough that nobody has ten years of experience, so interviewers weight engineering judgement heavily over tool familiarity. The questions are less about what you have used and more about how you would know it was working.&lt;/p&gt;

&lt;p&gt;Almost every candidate has now built something with a language model. That means the demo no longer differentiates anyone, and interviews have moved to the parts that are hard: making retrieval actually relevant, knowing whether a change made things worse, and handling text you did not write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieval, past the diagram
&lt;/h2&gt;

&lt;p&gt;Everyone can draw the pipeline: embed the documents, embed the question, find the nearest, put them in the context. Interviewers have seen it. The questions are all about what makes it work badly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chunking.&lt;/strong&gt; Too small and a chunk loses the context that made it meaningful. Too large and the relevant sentence is diluted by everything around it. There is no universal size, and saying so with a reason is better than naming one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic search missing exact matches.&lt;/strong&gt; Nearest-neighbour search is poor at identifiers, error codes and product names, which is why hybrid retrieval with a keyword component so often beats pure embeddings in practice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieving the wrong thing confidently.&lt;/strong&gt; Similarity is not relevance. Something always comes back, whether or not anything useful exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale content.&lt;/strong&gt; The index is a copy. What happens when the source changes is an operational question people forget to answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Users say the answers are often wrong. How would you debug that?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Junior answer:&lt;/strong&gt; I would improve the instructions to the model, try a larger model, and adjust the temperature to make it more accurate and less prone to making things up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior answer:&lt;/strong&gt; First I would find out whether retrieval or generation is failing, because they need opposite fixes and guessing wastes a week. I would take a sample of the bad answers and look at what was actually retrieved. If the right passage was never retrieved, no amount of instruction tuning will help and the work is in chunking or hybrid search. If the right passage was retrieved and the answer still contradicted it, that is a generation and grounding problem. Before changing anything I would build a small set of question and expected-answer pairs, so I can tell whether the next change helped or just moved the failures somewhere I was not looking.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Splitting retrieval failure from generation failure is the single most useful diagnostic in this area, and most candidates skip straight to the model.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation, which is the real interview
&lt;/h2&gt;

&lt;p&gt;If there is one topic that separates people who have shipped this from people who have prototyped it, this is it. Non-deterministic output means you cannot rely on the usual assertion-based safety net, and the temptation is to test by trying a few things and feeling good about them.&lt;/p&gt;

&lt;p&gt;What a strong answer describes is a fixed evaluation set built from real failures, run on every change, with results compared rather than eyeballed. Then the honest caveats: automated judging is itself unreliable and needs spot-checking, and an evaluation set drifts away from real usage unless you keep feeding production failures back into it.&lt;/p&gt;

&lt;p&gt;Say that you would add every reported bad answer to the set as a regression case and you have described the practice that actually keeps these systems from decaying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting structured output reliably
&lt;/h2&gt;

&lt;p&gt;A practical question because it is what most production integrations need. The model produces text; your code needs a shape it can rely on.&lt;/p&gt;

&lt;p&gt;The expected answer has three parts: constrain the output where the platform supports it, validate against a schema on the way in rather than trusting it, and have a defined path when validation fails. That last part is where the follow-up goes. Retrying forever is a cost and rate-limit incident waiting to happen, so a bounded retry and then a fallback is the answer that shows you have run this in production.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The framing that lands&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Treat model output as untrusted input, exactly like a request body from the internet. Validate it, bound it, and never let it reach anything consequential unchecked. That single sentence answers a surprising number of questions in this area.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Untrusted text and injection
&lt;/h2&gt;

&lt;p&gt;Increasingly asked, and the answers separate people quickly.&lt;/p&gt;

&lt;p&gt;The core problem is that instructions and data arrive through the same channel, so text from a document, a web page or a user message can contain something that reads as an instruction. There is no complete fix through wording alone, and a candidate who claims otherwise has not thought about it hard.&lt;/p&gt;

&lt;p&gt;What actually reduces risk is architectural: keep untrusted content clearly separated from your own instructions, do not grant the system capabilities it does not need, require confirmation before anything consequential or irreversible, and validate any tool call before executing it. The mindset here is the same one a &lt;a href="https://practicedepth.com/blog/security-interview-questions-senior-engineers" rel="noopener noreferrer"&gt;security round&lt;/a&gt; tests, applied to a component that is non-deterministic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost and latency
&lt;/h2&gt;

&lt;p&gt;Frequently the most practical section, and the one where product-minded engineers do well.&lt;/p&gt;

&lt;p&gt;Cost scales with tokens, so long context is a per-request bill, not a one-off design choice. Latency scales with output length in particular, which is why streaming exists and why it changes perceived speed far more than raw model speed. The senior framing is routing: not every request needs the most capable option, and using a cheaper path for the easy majority is usually the largest available win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine-tuning versus retrieval versus better instructions
&lt;/h2&gt;

&lt;p&gt;A judgement question, and the wrong answer is reaching for fine-tuning first.&lt;/p&gt;

&lt;p&gt;The distinction to draw is between knowledge and behaviour. If the system needs facts it does not have, that is retrieval, and fine-tuning is an expensive way to bake in something that will be stale next month. If it needs a consistent format or style, that is a case for tuning, once you have exhausted cheaper options. Being explicit that you would start with the cheapest reversible change is the signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need to know specific frameworks?
&lt;/h3&gt;

&lt;p&gt;Much less than you would think. The field moves fast enough that interviewers weight reasoning about retrieval quality, evaluation and failure handling well above tool familiarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is machine learning theory expected?
&lt;/h3&gt;

&lt;p&gt;For an AI engineering role, usually not. Embeddings and why similarity is not relevance, yes. Gradient descent, rarely, unless the role is actually a modelling role.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I show experience if my project was small?
&lt;/h3&gt;

&lt;p&gt;Talk about what went wrong and what you measured. A small project with a real evaluation set and a documented failure mode is stronger evidence than a large one with neither.&lt;/p&gt;




&lt;p&gt;This area is almost entirely judgement under follow-up, which is exactly what a focused practice round exposes. &lt;a href="https://practicedepth.com/app/practice/interview?focus=ai-engineering&amp;amp;mode=senior&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=organic" rel="noopener noreferrer"&gt;Practise an AI engineering interview&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>career</category>
      <category>interview</category>
    </item>
  </channel>
</rss>
