<?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: codeintuition</title>
    <description>The latest articles on DEV Community by codeintuition (@codeintuition).</description>
    <link>https://dev.to/codeintuition</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.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F13233%2F59681660-c664-447c-b4c3-86a05c4ce87c.png</url>
      <title>DEV Community: codeintuition</title>
      <link>https://dev.to/codeintuition</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codeintuition"/>
    <language>en</language>
    <item>
      <title>Tree Traversal: Why the Order You Pick Is a Data Flow Decision</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Thu, 28 May 2026 11:13:38 +0000</pubDate>
      <link>https://dev.to/codeintuition/tree-traversal-why-the-order-you-pick-is-a-data-flow-decision-21li</link>
      <guid>https://dev.to/codeintuition/tree-traversal-why-the-order-you-pick-is-a-data-flow-decision-21li</guid>
      <description>&lt;p&gt;Tree traversal usually gets taught as three separate algorithms to memorize: preorder, inorder, postorder. They are not three algorithms. They are one recursive function with a single line moved to a different spot, and that one line decides which problems you can solve.&lt;/p&gt;

&lt;p&gt;I watched this trip up people prepping for months. They had all four traces memorized and still froze when a new problem asked them to pick an order. The trace is the easy part. Knowing which order hands you the information you need is the part that actually matters in an interview.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Traversal visits every node once. The four standard orders differ only in &lt;em&gt;when&lt;/em&gt; the current node gets processed relative to its children. Preorder processes the node before its children, postorder after, inorder between, and level order goes breadth first with a queue. Pick the order by asking which direction data has to move between a parent and its children.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The four orders on one tree
&lt;/h2&gt;

&lt;p&gt;Run all four on the same tree and the difference stops being abstract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        1
       / \
      2   3
     / \   \
    4   5   6
   /
  7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Preorder (node, left, right): 1, 2, 4, 7, 5, 3, 6&lt;/li&gt;
&lt;li&gt;Inorder (left, node, right): 7, 4, 2, 5, 1, 3, 6&lt;/li&gt;
&lt;li&gt;Postorder (left, right, node): 7, 4, 5, 2, 6, 3, 1&lt;/li&gt;
&lt;li&gt;Level order (breadth first): 1, 2, 3, 4, 5, 6, 7&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inorder looks unsorted here because this is not a binary search tree. The sorted property only shows up when the values obey the BST invariant of left less than node less than right. On a plain binary tree, inorder still walks left, node, right, but the numbers come out in whatever order the structure gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The only thing that changes is one line
&lt;/h2&gt;

&lt;p&gt;The three depth first orders are the same code. The recursive call structure is identical. The only difference is where the line that processes the node sits relative to the two recursive calls.&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;preorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node&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;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# before the children
&lt;/span&gt;    &lt;span class="nf"&gt;preorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;preorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&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;inorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node&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;inorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# between the children
&lt;/span&gt;    &lt;span class="nf"&gt;inorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&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;postorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node&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;postorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;postorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# after the children
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Move one line, change the order. That is the whole trick for the depth first family.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why postorder is the one that computes heights
&lt;/h2&gt;

&lt;p&gt;This is where order stops being trivia. Take the height of a binary tree. To know a node's height, you need the heights of both children first. You take the larger child height and add one. That dependency, children before parent, is exactly what postorder gives you.&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;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;            &lt;span class="c1"&gt;# an empty subtree has height -1
&lt;/span&gt;    &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&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;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trace it on the tree above and watch every node wait for its children before it can answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Node 7: leaf                      -&amp;gt; 0
Node 4: left(7)=0, no right       -&amp;gt; max(0, -1) + 1 = 1
Node 5: leaf                      -&amp;gt; 0
Node 2: left(4)=1, right(5)=0     -&amp;gt; max(1, 0) + 1 = 2
Node 6: leaf                      -&amp;gt; 0
Node 3: no left, right(6)=0       -&amp;gt; max(-1, 0) + 1 = 1
Node 1: left(2)=2, right(3)=1     -&amp;gt; max(2, 1) + 1 = 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try the same thing with preorder and you hit the parent first, with no information about the subtree below it. You can still make it work by passing depth down as a parameter and tracking a maximum in an outer variable, but it is fifteen awkward lines where postorder is five. Diameter, subtree sums, balanced checks, distributing coins: same shape every time. The parent cannot answer until both children have reported back, so you process the children first.&lt;/p&gt;

&lt;p&gt;The first time I had to write any of these iteratively under a timer, I realized I had memorized the recursion and never the stack sitting under it. That gap is part of why I built Codeintuition, and &lt;a href="https://www.codeintuition.io/courses/binary-tree" rel="noopener noreferrer"&gt;the binary tree course&lt;/a&gt; traces the stack state for the iterative versions one frame at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level order is the odd one out
&lt;/h2&gt;

&lt;p&gt;Level order is the only one that is not depth first. It uses a queue, not the call stack. Enqueue the root, then repeatedly dequeue a node and enqueue its children. Nodes come out one full level at a time.&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;level_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;root&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;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;queue&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;queue&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything that works on nodes at the same depth (level sums, zigzag traversal, connecting nodes across a level) needs this breadth first access. Forcing depth first to do the same job means tracking depth by hand, which is more bookkeeping than the problem deserves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the order: which way does the data flow?
&lt;/h2&gt;

&lt;p&gt;Almost every tree problem opens with the same hidden question. Does the parent need answers from its children, or do the children need data handed down from the parent? Answer that and the order falls out.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Postorder:&lt;/strong&gt; the parent needs results from both children (height, diameter, subtree sums, balanced checks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preorder:&lt;/strong&gt; the children need data passed down from the parent (root to leaf path sums, serialization)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inorder:&lt;/strong&gt; you want values in sorted order out of a BST&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level order:&lt;/strong&gt; the work happens per level (level sums, zigzag, connecting same depth nodes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick aside worth its own paragraph: there is a real argument that some problems read fine in more than one order, and reconstructing a tree from traversals is its own rabbit hole (preorder alone is not enough, you need inorder too, or null markers). That is a separate post. For the day to day of picking an order on sight, the data direction question settles it.&lt;/p&gt;

&lt;p&gt;Listing the four orders is the easy part of this topic. Looking at an unfamiliar tree problem and knowing which order hands you the right information before you write a line is the skill that actually transfers to an interview, and it comes from understanding why each order exists, not from re-reading the traces.&lt;/p&gt;

&lt;p&gt;I wrote a longer version on &lt;a href="https://www.codeintuition.io/blogs/binary-tree-traversal" rel="noopener noreferrer"&gt;my own blog&lt;/a&gt; that walks through the iterative conversions for all three depth first orders.&lt;/p&gt;

&lt;p&gt;Which traversal order was the one that finally made sense to you only after you got burned picking the wrong one on a real problem?&lt;/p&gt;

</description>
      <category>leetcode</category>
      <category>algorithms</category>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>Binary Tree Recursion in Interviews: The Call Stack Diagnostic</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Tue, 26 May 2026 16:07:59 +0000</pubDate>
      <link>https://dev.to/codeintuition/binary-tree-recursion-in-interviews-the-call-stack-diagnostic-4baj</link>
      <guid>https://dev.to/codeintuition/binary-tree-recursion-in-interviews-the-call-stack-diagnostic-4baj</guid>
      <description>&lt;p&gt;I've watched plenty of engineers explain binary tree traversals on a whiteboard and then freeze five minutes into an unfamiliar tree problem. The gap isn't conceptual. They know what preorder, inorder, and postorder mean. They can sketch a BST. What they can't do is predict, in their head, how the call stack will branch when a recursive function meets a tree's shape.&lt;/p&gt;

&lt;p&gt;That's the real skill tree interview problems test. And once you can do it, the patterns stop feeling like 14 separate things to memorise.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tree problems test whether you can mentally simulate the call stack's shape before writing code.&lt;/li&gt;
&lt;li&gt;The six traversal patterns split along one axis: which direction does information flow?&lt;/li&gt;
&lt;li&gt;Down through parameters, up through return values, or sideways across siblings.&lt;/li&gt;
&lt;li&gt;Get the direction right and the solution writes itself.&lt;/li&gt;
&lt;li&gt;Get it wrong and you spend the interview debugging recursion that looks correct at every node.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why arrays don't prepare you for trees
&lt;/h2&gt;

&lt;p&gt;When you scan an array, you hold the whole traversal in your head as a walk from left to right. One element at a time. Your mental model is a line.&lt;/p&gt;

&lt;p&gt;Trees break that completely. At every node, execution splits into two branches, and each of those branches splits again. The call stack grows in a shape you can't flatten. To predict what your code does, you have to think in the shape of the tree itself.&lt;/p&gt;

&lt;p&gt;This is why someone can solve medium array problems comfortably and still stumble on easy tree problems. The mental simulation load is qualitatively different. You're tracking which branch you're in, what state you carried down, and what values are coming back up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The call stack is a concrete thing, not a metaphor
&lt;/h2&gt;

&lt;p&gt;Before you can simulate tree recursion, you have to know what's physically happening in memory when a recursive function runs. The &lt;a href="https://en.wikipedia.org/wiki/Call_stack" rel="noopener noreferrer"&gt;call stack&lt;/a&gt; is a real structure in your program's memory, not a teaching analogy.&lt;/p&gt;

&lt;p&gt;Every function call pushes a stack frame. The frame holds the function's local variables, parameters, and the return address. When the function returns, the frame pops and the frame below resumes exactly where it left off.&lt;/p&gt;

&lt;p&gt;Walk through &lt;code&gt;factorial(4)&lt;/code&gt; and the stack looks like this at maximum depth:&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;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&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;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| factorial(1) |  &amp;lt;- top, currently executing
| factorial(2) |  &amp;lt;- waiting on factorial(1)
| factorial(3) |  &amp;lt;- waiting on factorial(2)
| factorial(4) |  &amp;lt;- original call, waiting on result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each frame keeps its own &lt;code&gt;n&lt;/code&gt;. Nothing shared. When &lt;code&gt;factorial(1)&lt;/code&gt; returns &lt;code&gt;1&lt;/code&gt;, that frame pops. &lt;code&gt;factorial(2)&lt;/code&gt; resumes, computes &lt;code&gt;2 * 1&lt;/code&gt;, returns &lt;code&gt;2&lt;/code&gt;. The chain unwinds until &lt;code&gt;factorial(4)&lt;/code&gt; returns &lt;code&gt;24&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'd recommend stepping through this in a debugger once. Watching frames push and pop in real time teaches more about recursion in ten minutes than an hour of reading explanations. Most engineers I see prep have never done this, and it changes how you predict what recursive code will do.&lt;/p&gt;

&lt;p&gt;Now extend the same idea to a binary tree. Each node makes two recursive calls instead of one. The stack still pushes and pops frame by frame, but the shape it traces is no longer a straight line down and back up. It's the branching shape of the tree itself. Predict that shape correctly and the solution becomes mechanical.&lt;/p&gt;

&lt;h2&gt;
  
  
  The information flow diagnostic
&lt;/h2&gt;

&lt;p&gt;Every tree problem maps to one of six traversal patterns. The patterns split cleanly when you ask a single question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which direction does information have to flow to solve this problem?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Down (preorder).&lt;/strong&gt; State is carried from parent to children through function parameters. The node is processed before its children. Useful when each node's computation depends on what came before it on the path from the root.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Up (postorder).&lt;/strong&gt; Children are processed first, and their results bubble up through return values. The node is processed after its children. Useful when the answer at each node depends on its subtrees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sideways (level order).&lt;/strong&gt; Nodes at the same depth are processed together before moving to the next level. The call stack doesn't help here. You want a queue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That diagnostic alone resolves most pattern-selection questions in under 30 seconds.&lt;/p&gt;

&lt;p&gt;The six patterns are refinements of the three directions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stateless preorder: pure top-down. No accumulator. No return value carrying anything.&lt;/li&gt;
&lt;li&gt;Stateful preorder: top-down with a shared accumulator that mutates on descent.&lt;/li&gt;
&lt;li&gt;Stateless postorder: pure bottom-up. Each subtree's result is a function of its children's results.&lt;/li&gt;
&lt;li&gt;Stateful postorder: bottom-up with a global side effect updated from subtree results.&lt;/li&gt;
&lt;li&gt;Root to leaf path: top-down accumulation, evaluation only at leaves.&lt;/li&gt;
&lt;li&gt;Level order: queue-based BFS, not recursion at all.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stateful postorder is the hardest of the six. The return value at each node is what the parent needs, but the answer to the problem lives in a global variable updated as a side effect. Tree diameter is the textbook example. Each call returns subtree height, but the diameter (the actual answer) accumulates separately as the maximum of &lt;code&gt;left_height + right_height&lt;/code&gt; seen at any node.&lt;/p&gt;

&lt;h2&gt;
  
  
  BST validation, the recursion that looks correct but isn't
&lt;/h2&gt;

&lt;p&gt;A binary search tree adds one invariant to the binary tree: every value in the left subtree is smaller than the node, every value in the right subtree is larger. That single rule lets you do things on a BST that aren't possible on a general binary tree.&lt;/p&gt;

&lt;p&gt;The most natural use of the invariant is inorder traversal, which visits BST nodes in sorted order. Left subtree, then node, then right subtree. The traversal physically walks the sorted sequence.&lt;/p&gt;

&lt;p&gt;BST validation is where most people write the wrong recursion. The instinctive version checks each node against its direct children.&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="c1"&gt;# Wrong: checks only direct parent-child constraints
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_valid_bst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;is_valid_bst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;is_valid_bst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consider the tree &lt;code&gt;[10, 5, 15, null, null, 6, 20]&lt;/code&gt;. Every direct parent-child pair is locally fine. &lt;code&gt;5 &amp;lt; 10&lt;/code&gt;. &lt;code&gt;15 &amp;gt; 10&lt;/code&gt;. &lt;code&gt;6 &amp;lt; 15&lt;/code&gt;. &lt;code&gt;20 &amp;gt; 15&lt;/code&gt;. The naive recursion returns True. But the tree is invalid, because &lt;code&gt;6&lt;/code&gt; sits in the right subtree of &lt;code&gt;10&lt;/code&gt; and &lt;code&gt;6 &amp;lt; 10&lt;/code&gt; violates the BST invariant against its ancestor.&lt;/p&gt;

&lt;p&gt;The correct recursion uses inorder traversal and tracks the previously visited value. If the inorder sequence is strictly increasing, the tree is a valid BST.&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;is_valid_bst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-inf&lt;/span&gt;&lt;span class="sh"&gt;'&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;inorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;nonlocal&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;inorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;inorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&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;inorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the same tree, inorder visits &lt;code&gt;5, 10, 6, 15, 20&lt;/code&gt;. When it reaches &lt;code&gt;6&lt;/code&gt; with &lt;code&gt;prev = 10&lt;/code&gt;, the check &lt;code&gt;6 &amp;lt;= 10&lt;/code&gt; fires and returns False. The mechanism that makes this work is that inorder converts the ordering invariant into a single forward-walking check across the entire tree. The naive version only checks two-node relationships and misses every ancestor violation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four BST-only patterns
&lt;/h2&gt;

&lt;p&gt;The ordering invariant opens four traversal strategies that don't exist on general binary trees:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sorted traversal: use inorder for problems where the BST's ascending order is the answer. Validation, conversion to a sorted array, conversion to a doubly linked list.&lt;/li&gt;
&lt;li&gt;Reverse sorted traversal: visit right, then node, then left, for descending order. Kth largest, ranking, enriched sum trees.&lt;/li&gt;
&lt;li&gt;Range postorder: prune entire subtrees by comparing the node's value against a range. If the node sits below the range, skip its left subtree entirely. This is where BSTs get their logarithmic performance on range queries.&lt;/li&gt;
&lt;li&gt;Two pointer on BST: run a forward iterator (inorder) and a reverse iterator (reverse inorder) simultaneously. Two sum on a BST, finding the median, pair sum problems.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For heaps, two patterns cover most of what interviews ask. Top K elements maintains a heap of size K (a min heap to track the K largest, counterintuitively). The comparator pattern uses custom ordering when "priority" isn't the raw value, which covers K most frequent, K smallest pairs, K closest points, and K way merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistakes I see most often
&lt;/h2&gt;

&lt;p&gt;Across the engineers I help prep, the same handful of mistakes show up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confusing preorder and postorder state. You try to compute tree height by passing depth down through parameters, when height has to aggregate up from children through return values.&lt;/li&gt;
&lt;li&gt;Forgetting to capture the return value. Writing &lt;code&gt;postorder(node.left)&lt;/code&gt; without assigning the result. The recursive call executes, but the computed information evaporates.&lt;/li&gt;
&lt;li&gt;Mixing global and local. The function returns a local value (the subtree's result for the parent) while a global accumulator holds the actual answer. Diameter is the classic case. Confuse the two and the recursion looks right at every node but the answer comes out wrong.&lt;/li&gt;
&lt;li&gt;Using recursion for level order. Level order is breadth first by nature, and recursion is depth first. You can simulate level order with recursion plus a depth parameter, but a queue-based BFS is cleaner and what interviewers expect.&lt;/li&gt;
&lt;li&gt;Skipping the subproblem definition. Before writing any recursive function, answer in one sentence what &lt;code&gt;f(node)&lt;/code&gt; returns. If you can't, the recursion will fail.&lt;/li&gt;
&lt;li&gt;Forgetting the null base case. Null children show up everywhere in binary trees. A leaf has two null children. Forgetting to handle them is the most common runtime error in tree solutions.&lt;/li&gt;
&lt;li&gt;State not undone on backtrack. In root to leaf path problems, you append to a path list on descent. If you don't pop on return, the path accumulates stale entries from sibling branches.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to practice the simulation skill
&lt;/h2&gt;

&lt;p&gt;Mental simulation isn't built by solving more problems. It's built by predicting the call stack's shape before running the code, and then verifying the prediction.&lt;/p&gt;

&lt;p&gt;Take a problem you've solved before. Cover the solution. Sketch the tree. Predict the call sequence on paper. Now run the solution in a debugger and step through frame by frame. Note where your prediction diverged from what actually happened.&lt;/p&gt;

&lt;p&gt;That divergence is the gap interviews test under pressure. Every problem you do this on builds the simulation skill directly. After about ten rounds, the prediction step stops being a separate exercise and starts running automatically as you read the problem statement.&lt;/p&gt;

&lt;p&gt;That's the gap I built Codeintuition to close. The &lt;a href="https://www.codeintuition.io/courses/binary-tree" rel="noopener noreferrer"&gt;binary tree course&lt;/a&gt; walks through the call stack frame by frame on every pattern, so the simulation becomes something you've already done dozens of times rather than something you're constructing from scratch under interview pressure.&lt;/p&gt;

&lt;p&gt;I wrote a longer version on &lt;a href="https://www.codeintuition.io/blogs/binary-tree-recursion-interview" rel="noopener noreferrer"&gt;my own blog&lt;/a&gt; that covers the BST sorted traversal family and the heap patterns I skipped here.&lt;/p&gt;

&lt;p&gt;Which pattern was the one that finally made the call stack visible in your head? Mine was stateful postorder, the first time I traced diameter by hand.&lt;/p&gt;

</description>
      <category>learning</category>
      <category>leetcode</category>
      <category>interview</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Binary Tree Interview Problems: 6 Traversal Patterns, 15 Problems</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Mon, 25 May 2026 12:01:59 +0000</pubDate>
      <link>https://dev.to/codeintuition/binary-tree-interview-problems-6-traversal-patterns-15-problems-36jl</link>
      <guid>https://dev.to/codeintuition/binary-tree-interview-problems-6-traversal-patterns-15-problems-36jl</guid>
      <description>&lt;p&gt;The default tree prep is sorting LeetCode's tree tag by acceptance rate, doing the top 40, and hoping the patterns transfer. They mostly don't. Tree problems feel unpredictable because the practice was organised by popularity, not by mechanism. Every binary tree interview problem is moving information through the tree in one of six ways, and once the six are visible, fifteen problems give more coverage than forty random ones.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Binary tree interview problems cluster around six traversal patterns: preorder, postorder, root to leaf, level order, lowest common ancestor, and simultaneous traversal. Two problems per pattern is enough for interview-grade coverage. The trick is to practice in pattern order, not difficulty order.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The six patterns that cover the binary tree interview space
&lt;/h2&gt;

&lt;p&gt;Every tree problem is a question about how information has to move. Preorder pushes information down from parent to child. Postorder pushes it up from child to parent. Root to leaf accumulates state along a single path. Level order processes nodes layer by layer with a queue. Lowest common ancestor is about which subtrees contain which targets. Simultaneous traversal walks two trees in parallel to compare or merge structure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Preorder.&lt;/strong&gt; Information flows down. Pass current state into the recursive call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postorder.&lt;/strong&gt; Information flows up. Compute the answer from what the children return.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Root to leaf.&lt;/strong&gt; State accumulates along a path. Backtrack after each child.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level order.&lt;/strong&gt; Use a queue. Process all nodes at depth &lt;code&gt;d&lt;/code&gt; before any at depth &lt;code&gt;d+1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lowest common ancestor.&lt;/strong&gt; Recursive search. Return a target on the way back up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simultaneous traversal.&lt;/strong&gt; Two trees, one walk. Compare or merge at every step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two problems per pattern is the right calibration. The first problem teaches the mechanism. The second forces a variation that reveals whether the mechanism transferred or got memorised. Past two, the marginal practice value drops fast, because the third problem in a pattern usually just rehearses what the second one already established.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stateful postorder, where the medium and hard problems live
&lt;/h2&gt;

&lt;p&gt;Most of the medium and hard binary tree problems are stateful postorder. The recursion returns something the parent needs (usually height), but the answer lives in a variable updated at each node. That split between "what the function returns" and "what you care about" is what makes the pattern hard to grasp from one problem alone.&lt;/p&gt;

&lt;p&gt;Diameter is the cleanest place to see it. The diameter of a binary tree is the longest path between any two nodes, measured in edges. The path can pass through the root or be entirely inside a subtree. The recursion has to return the height of each subtree (so the parent can compute its own height), but the answer (longest path) is computed at each node as &lt;code&gt;left_height + right_height&lt;/code&gt; and tracked separately.&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;diameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;longest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;nonlocal&lt;/span&gt; &lt;span class="n"&gt;longest&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;left_h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;right_h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;longest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left_h&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;right_h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left_h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right_h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&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;longest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to notice. First, the function is named &lt;code&gt;height&lt;/code&gt;, not &lt;code&gt;diameter&lt;/code&gt;. That naming is deliberate. The recursion is computing height, and diameter falls out as a side effect at each node. Second, &lt;code&gt;longest&lt;/code&gt; is a &lt;code&gt;nonlocal&lt;/code&gt; variable, which is the Python way to track an answer outside the recursive frame. In other languages you'd use an instance variable or a single-element list. The mechanism is the same.&lt;/p&gt;

&lt;p&gt;Once the diameter mechanism clicks, half a dozen other problems unlock with no extra learning. &lt;a href="https://www.codeintuition.io/courses/binary-tree/EuGerHj4zNCQdgmrr9vyj" rel="noopener noreferrer"&gt;Distribute coins&lt;/a&gt; tracks surplus or deficit at each subtree while a global counter tracks total moves. &lt;a href="https://www.codeintuition.io/courses/binary-tree/4_trmpij9BcM0n8FshxtK" rel="noopener noreferrer"&gt;Longest monotonic path&lt;/a&gt; tracks the longest increasing and decreasing chain at each node while a global tracks the overall longest. Maximum path sum tracks the best downward path from each node while a global tracks the best path that bends through any node. Same skeleton, different state.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codeintuition.io/courses/binary-tree/7D8VO_D2WFnGmlT79LtgS" rel="noopener noreferrer"&gt;diameter lesson&lt;/a&gt; walks the recursion variable by variable, which is the depth that makes the "function returns one thing, answer lives elsewhere" framing stick. The framing is what transfers, not the specific problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 15 problems, grouped by pattern
&lt;/h2&gt;

&lt;p&gt;The list below is what comes out when you group binary tree interview problems by mechanism instead of by popularity or difficulty. Two problems per pattern. The first is the direct application, the second is the variant that proves the mechanism transferred.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preorder (information flows down):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/gzqv3wSNB0fLtGUaHqIGq" rel="noopener noreferrer"&gt;Sum of path&lt;/a&gt;. Stateless preorder. Pass the remaining target sum down, check for zero at a leaf.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/VN5y0nj9hrbaH50Yr7DP3" rel="noopener noreferrer"&gt;Right view&lt;/a&gt;. Stateful preorder. Pass depth down, track the deepest depth seen so far globally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Postorder (information flows up):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/GXFycvfMF94E4A14c4kdA" rel="noopener noreferrer"&gt;Height of binary tree&lt;/a&gt;. Stateless postorder. Return &lt;code&gt;1 + max(left, right)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/7D8VO_D2WFnGmlT79LtgS" rel="noopener noreferrer"&gt;Diameter of tree&lt;/a&gt;. Stateful postorder. Return height, track longest path globally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Root to leaf (state accumulates along a path):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/AeXH6FknJNkFz0Je1Rw5y" rel="noopener noreferrer"&gt;Root to leaf paths&lt;/a&gt;. Enumerate every path. Backtrack after each recursive call.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/ARsmRsSslpsO-t9Cf7yjG" rel="noopener noreferrer"&gt;Equal paths&lt;/a&gt;. Check whether any path sums to a target. Validate only at leaves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Level order (queue, layer by layer):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/9fL3An4CoPeH5uygVkUQh" rel="noopener noreferrer"&gt;Level sum&lt;/a&gt;. Sum the values at each depth.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/Rf5y4h0H5prvgmDUU27SF" rel="noopener noreferrer"&gt;Zigzag traversal&lt;/a&gt;. Alternate left-to-right and right-to-left per level.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/jAFEIFIawRLvc2HI0PbCc" rel="noopener noreferrer"&gt;Vertical traversal&lt;/a&gt;. Group nodes by &lt;code&gt;(column, row)&lt;/code&gt; coordinates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lowest common ancestor (multi node reasoning):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/23LPmm1m-KZkwIIB1PG97" rel="noopener noreferrer"&gt;LCA&lt;/a&gt;. Find the deepest shared ancestor of two target nodes.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/j1X8mlH7RIoEQ4HQBinPy" rel="noopener noreferrer"&gt;Distance between nodes&lt;/a&gt;. Compose LCA with depth-from-LCA.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Simultaneous traversal (two trees, one walk):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/QNPqJOr1KU4-t6EJxOjT4" rel="noopener noreferrer"&gt;Identical trees&lt;/a&gt;. Compare both trees node by node in lockstep.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/binary-tree/73TW763zTrxFxMOk10yDE" rel="noopener noreferrer"&gt;Subtree detection&lt;/a&gt;. Run identical-trees from every node of the larger tree.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three additional postorder problems (distribute coins, longest monotonic path, maximum path sum) sit alongside diameter as variants of the same stateful skeleton. Adding any of them to your list gives diminishing returns once diameter is solid, which is why the 15-problem cut works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why pattern order beats difficulty order
&lt;/h2&gt;

&lt;p&gt;The conventional advice is to do easy problems first, then mediums, then hards. For binary tree interview problems specifically, that ordering is wrong. A medium postorder problem can be easier than a hard preorder problem if you already understand preorder and haven't met postorder yet. The skill that transfers is the pattern mechanism, not the difficulty class.&lt;/p&gt;

&lt;p&gt;Pattern order looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Preorder before postorder, because preorder is what most people already do intuitively when they imagine walking a tree.&lt;/li&gt;
&lt;li&gt;Postorder before root to leaf, because root to leaf borrows the return mechanism from postorder.&lt;/li&gt;
&lt;li&gt;Root to leaf before level order, because level order is a different paradigm (queue based) and benefits from the recursive patterns being solid first.&lt;/li&gt;
&lt;li&gt;Level order before LCA, because LCA is multi node reasoning and the single-walk patterns have to feel automatic first.&lt;/li&gt;
&lt;li&gt;LCA before simultaneous traversal, because simultaneous traversal scales LCA-style reasoning to two trees instead of two nodes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Within each pattern, do the direct application first and the variant second. Height before diameter. LCA before distance between nodes. Identical trees before subtree detection. If you can't trace the solution to the direct application on paper without running the code, the variant won't transfer either.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three recursion mistakes that derail tree rounds
&lt;/h2&gt;

&lt;p&gt;These come up across most tree rounds I see, in roughly this order under interview pressure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Returning the answer when the parent needs supporting information.&lt;/strong&gt; The "what does the recursion return?" question is the one most candidates get wrong on stateful problems. In longest monotonic path, the function needs to return the longest increasing and decreasing chains so the parent can extend them. The actual answer (longest path overall) gets updated at each node by combining left and right chains. If you try to return the answer directly, the parent can't use it to extend its own chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing the backtrack in path enumeration.&lt;/strong&gt; Root to leaf paths shares a path list across recursive calls. If you don't remove the last element after each child returns, earlier paths bleed into later ones. The output looks almost right, which is what makes the bug expensive to diagnose mid-interview.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong base case for the problem.&lt;/strong&gt; &lt;code&gt;if not node: return 0&lt;/code&gt; is correct for height because the height of a null subtree is zero. It is wrong for equal paths, where you need to distinguish between "node is null" and "node is a leaf." Returning at null makes you incorrectly treat the null children of single-child nodes as valid leaves. The fix is to add an explicit leaf check before the null check, or to handle the null case as "no path here, ignore" rather than "valid empty path."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern in all three is the same. The mechanism is right, but the boundary handling is wrong, and the failure mode is "passes most test cases, breaks on the edge case the interviewer planted on purpose."&lt;/p&gt;

&lt;p&gt;The 6-pattern framing is the thing I keep coming back to in how I think about tree prep on Codeintuition, because grouping by mechanism is what turns 15 problems into 15 instances of recognisable techniques instead of 15 stand-alone puzzles. Most tree practice I see does the opposite: 40 problems, none of them grouped, and the candidate ends up freezing on the next unfamiliar one anyway.&lt;/p&gt;

&lt;p&gt;I wrote a longer version on &lt;a href="https://www.codeintuition.io/blogs/binary-tree-interview-problems" rel="noopener noreferrer"&gt;my own blog&lt;/a&gt; that walks each pattern through its two problems and includes the recursion-bug catalogue with worked examples.&lt;/p&gt;

&lt;p&gt;Which tree pattern did the most for you, where one or two problems unlocked a dozen variations you didn't have to memorise?&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>career</category>
      <category>leetcode</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Binary Search Tree Explained: All Operations From One Rule</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Mon, 25 May 2026 09:08:49 +0000</pubDate>
      <link>https://dev.to/codeintuition/binary-search-tree-explained-all-operations-from-one-rule-2jbd</link>
      <guid>https://dev.to/codeintuition/binary-search-tree-explained-all-operations-from-one-rule-2jbd</guid>
      <description>&lt;p&gt;Most BST tutorials teach search, insert, and delete as three procedures. You memorise one, then the next, then the three deletion cases. Then deletion shows up in an interview and the whole stack collapses, because what got memorised was the sequence of steps and not the rule the steps were trying to preserve.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; A binary search tree is defined by a single ordering invariant: every node's left subtree contains smaller values and every right subtree contains larger ones. Search, insert, and delete are all derivable from that rule. Treat the invariant as primary and the procedures stop being three things to memorise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The one rule the whole data structure hangs on
&lt;/h2&gt;

&lt;p&gt;A BST is a binary tree where every node's left subtree contains only smaller values and every right subtree contains only larger values. That is the entire data structure. One ordering invariant, nothing else.&lt;/p&gt;

&lt;p&gt;The invariant gives the tree something arrays and linked lists can't give: a way to eliminate half the remaining work at every step. When you're looking for a value, you compare it to the current node and pick a direction. You never look at the other side. The same halving idea that makes binary search fast on sorted arrays, except the tree structure also makes insertion and deletion cheap because nothing has to shift.&lt;/p&gt;

&lt;p&gt;A useful framing: every BST operation is the same recursive walk. The walk asks one question at each node (target vs current), takes one of two branches, and stops at some terminal condition. What changes between operations is the terminal condition and what happens when you reach it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search and insert: one walk, two endings
&lt;/h2&gt;

&lt;p&gt;Start with a BST holding &lt;code&gt;[8, 3, 10, 1, 6, 14]&lt;/code&gt; and search for &lt;code&gt;6&lt;/code&gt;. From the root, &lt;code&gt;6 &amp;lt; 8&lt;/code&gt;, go left to &lt;code&gt;3&lt;/code&gt;. Then &lt;code&gt;6 &amp;gt; 3&lt;/code&gt;, go right and land on &lt;code&gt;6&lt;/code&gt;. Three comparisons, three levels. The right subtree of &lt;code&gt;8&lt;/code&gt; was never touched.&lt;/p&gt;

&lt;p&gt;Now insert &lt;code&gt;7&lt;/code&gt; into the same tree. From the root, &lt;code&gt;7 &amp;lt; 8&lt;/code&gt;, go left. Then &lt;code&gt;7 &amp;gt; 3&lt;/code&gt;, go right to &lt;code&gt;6&lt;/code&gt;. Then &lt;code&gt;7 &amp;gt; 6&lt;/code&gt;, go right again and land on a &lt;code&gt;null&lt;/code&gt; pointer. Create node &lt;code&gt;7&lt;/code&gt; there. Same walk, different ending. Search stops when the target matches or the pointer is &lt;code&gt;null&lt;/code&gt;. Insert stops when the pointer is &lt;code&gt;null&lt;/code&gt; and writes a new leaf.&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&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;node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;target&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;node&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&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;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&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;node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&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;node&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the two functions side by side. The structure is identical. The terminal action differs by a single line. That is the derivation.&lt;/p&gt;

&lt;p&gt;Insert always creates a leaf. It never restructures the existing tree, which is what makes it cheap. It is also what eventually makes the tree fragile, since nothing pushes back when an input order produces an unbalanced shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deletion: three cases collapsed to one question
&lt;/h2&gt;

&lt;p&gt;Deletion is where most candidates lose the thread, because removing a node can violate the invariant. The standard explanation gives you three cases and tells you which procedure to run for each. The better framing is one question with three answers.&lt;/p&gt;

&lt;p&gt;The question is: how do I fill the gap left by the removed node without breaking &lt;code&gt;left &amp;lt; node &amp;lt; right&lt;/code&gt;?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the node is a leaf, the answer is nothing. Drop the pointer to it. Done.&lt;/li&gt;
&lt;li&gt;If the node has one child, the answer is to promote that child. The child's subtree already satisfies the invariant relative to the deleted node's parent, so promotion preserves the rule.&lt;/li&gt;
&lt;li&gt;If the node has two children, the answer is to find a replacement that is structurally allowed to sit in that slot. That replacement is the inorder successor (the smallest value in the right subtree), or symmetrically the inorder predecessor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two child case is the one that trips people up. You can't promote one child arbitrarily, because the other child's subtree would end up misplaced. The successor works because it is the smallest value larger than the node being deleted, which is what the position requires after the node is gone. Copy its value into the deleted node's slot, then delete the original successor from its own location. The successor has at most one child (it is the leftmost node in a subtree, so its left pointer is null by construction), and removing it falls into case 1 or case 2.&lt;/p&gt;

&lt;p&gt;Trace deleting node &lt;code&gt;3&lt;/code&gt; from &lt;code&gt;[8, 3, 10, 1, 6, 14, 4]&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: Find node 3. It has two children (1 and 6). Two child case.
Step 2: Find inorder successor of 3.
        Go right to 6, then left to 4.
        Node 4 is the smallest value in 3's right subtree.
Step 3: Copy 4's value into node 3's slot.
Step 4: Delete the original node 4 from its previous position.
        Node 4 was a leaf. Drop the pointer.

Result:
        8
       / \
      4   10
     / \    \
    1   6   14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the invariant holds: &lt;code&gt;1 &amp;lt; 4 &amp;lt; 6&lt;/code&gt; in the left subtree, &lt;code&gt;4 &amp;lt; 8 &amp;lt; 10&lt;/code&gt; across the root, &lt;code&gt;10 &amp;lt; 14&lt;/code&gt; on the right. The deletion preserved the rule.&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;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&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;node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;
        &lt;span class="n"&gt;successor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;successor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;successor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;successor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;successor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;successor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&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;node&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recursive call inside the two child branch is the only piece of the procedure that isn't immediately obvious. It is the same &lt;code&gt;delete&lt;/code&gt; walking into the right subtree to remove the successor, which is guaranteed to be a leaf or a single child node and so resolves into one of the first two branches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why deletion is where interviews break
&lt;/h2&gt;

&lt;p&gt;Search and insert can be passed by sketching the walk on paper and following the comparisons. Deletion can't, because the two child case requires you to articulate why the inorder successor specifically is the right replacement. That articulation reveals whether you understand the invariant or just memorised three procedures.&lt;/p&gt;

&lt;p&gt;I keep seeing the same failure mode. The candidate handles the leaf case and the single child case, then stalls on the two child case. They reach for an arbitrary replacement, realise something is off, and try to patch the resulting violation downstream. The patch usually breaks a different invariant somewhere else, the trace gets longer, and the clock runs out.&lt;/p&gt;

&lt;p&gt;The fix isn't to memorise the inorder successor trick harder. The fix is to be able to answer "why this specific replacement?" If you can say "the inorder successor is the smallest value larger than the deleted node, which is what the slot requires after deletion," you'll derive the procedure from the rule in real time. If you can't, you'll keep losing the thread the moment the interviewer adds a constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the invariant holds but the speed doesn't
&lt;/h2&gt;

&lt;p&gt;Every BST operation runs in &lt;code&gt;O(h)&lt;/code&gt; time where &lt;code&gt;h&lt;/code&gt; is the tree's height. The marketing claim is &lt;code&gt;O(log n)&lt;/code&gt;, but that only holds when the tree stays balanced. A plain BST has no mechanism to enforce that.&lt;/p&gt;

&lt;p&gt;Insert &lt;code&gt;[1, 2, 3, 4, 5]&lt;/code&gt; into an empty BST in that order and you get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
 \
  2
   \
    3
     \
      4
       \
        5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The invariant is technically satisfied. Every left subtree is empty, every right subtree contains larger values. But the tree is structurally a linked list. Search degrades to &lt;code&gt;O(n)&lt;/code&gt;. The performance promise comes from balance, and balance comes from input order. Sorted input is the worst possible input for a plain BST.&lt;/p&gt;

&lt;p&gt;The fix is a self balancing variant such as AVL or Red-Black trees, which add rotation logic that restructures the tree after each insertion to guarantee &lt;code&gt;O(log n)&lt;/code&gt; height regardless of input order. In an interview, if you state "BST operations are &lt;code&gt;O(log n)&lt;/code&gt;" without qualifying balance, you'll lose points. The honest answer is "&lt;code&gt;O(log n)&lt;/code&gt; on a balanced tree, &lt;code&gt;O(n)&lt;/code&gt; worst case, and balance is not automatic on a plain BST."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where candidates trip up under pressure
&lt;/h2&gt;

&lt;p&gt;These are the patterns I see derail BST questions in interviews, roughly in the order they show up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checking only immediate children for the invariant.&lt;/strong&gt; The rule is not &lt;code&gt;left child &amp;lt; node &amp;lt; right child&lt;/code&gt;. It is &lt;code&gt;every node in the left subtree is smaller&lt;/code&gt;. A value can pass the immediate-children check and still violate the constraint against a grandparent. The &lt;a href="https://www.codeintuition.io/courses/binary-search-tree/m6rGRHwW1i6agm77i-YYS" rel="noopener noreferrer"&gt;BST validator lesson&lt;/a&gt; covers why you have to pass upper and lower bounds down through the recursion to catch this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confusing BST ordering with heap ordering.&lt;/strong&gt; A BST orders left-to-right (&lt;code&gt;left &amp;lt; node &amp;lt; right&lt;/code&gt;). A heap orders parent-to-child (&lt;code&gt;parent &amp;gt; children&lt;/code&gt; in a max heap). Mixing them up sends you walking the wrong direction during search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stalling on two child deletion.&lt;/strong&gt; Two child deletion is the case interviewers probe deliberately. If you've practiced naming the inorder successor and stating why it works, you've already reduced the case to a previously solved one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asserting &lt;code&gt;O(log n)&lt;/code&gt; without qualifying balance.&lt;/strong&gt; The interviewer doesn't want to hear "BST operations are logarithmic." They want to hear "logarithmic when balanced, linear in the degenerate case, and here's what causes the degenerate case." That sentence reveals whether you've thought about the invariant or just memorised the headline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The BST work was where the "derive from the invariant, don't memorise the procedure" framing first clicked for me when I was building Codeintuition's learning path. Most candidates I see practice the procedures three times and the invariant zero. Flipping that ratio is the part that compounds, because once the invariant is the unit of memory, every operation that respects the invariant becomes derivable on the spot.&lt;/p&gt;

&lt;p&gt;I wrote a longer version on &lt;a href="https://www.codeintuition.io/blogs/binary-search-tree-explained" rel="noopener noreferrer"&gt;my own blog&lt;/a&gt; that walks the inorder traversal proof and the recursive construction of a balanced BST from a sorted array.&lt;/p&gt;

&lt;p&gt;Which BST operation finally clicked for you only after sketching the tree on paper instead of reading another explanation?&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>career</category>
      <category>leetcode</category>
      <category>datastructures</category>
    </item>
    <item>
      <title>Big O Notation Explained: Derive It, Don't Memorize It</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Sat, 23 May 2026 11:26:16 +0000</pubDate>
      <link>https://dev.to/codeintuition/big-o-notation-explained-derive-it-dont-memorize-it-758</link>
      <guid>https://dev.to/codeintuition/big-o-notation-explained-derive-it-dont-memorize-it-758</guid>
      <description>&lt;p&gt;You can recite that binary search is &lt;code&gt;O(log n)&lt;/code&gt; and nested loops are &lt;code&gt;O(n²)&lt;/code&gt;. That recitation stops helping the moment an interviewer writes an unfamiliar function on the whiteboard and asks you to analyse it. The useful version of Big O is the one you can derive, not the one you can look up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Big O describes how an algorithm's work grows as input size grows. The interview skill isn't naming complexities from memory. It's counting how many times the dominant operation runs as a function of input size and watching what dominates as the input gets large.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Big O actually measures
&lt;/h2&gt;

&lt;p&gt;Big O describes the upper bound on how an algorithm's running time or space usage grows as its input grows. That word, &lt;em&gt;grows&lt;/em&gt;, is the one that matters. It doesn't tell you that binary search takes exactly 20 comparisons on an array of a million elements. It tells you that doubling the array adds roughly one more comparison. The relationship between input and work is the thing being measured.&lt;/p&gt;

&lt;p&gt;There are two simplifications Big O does on purpose. It drops constants, so &lt;code&gt;O(3n)&lt;/code&gt; becomes &lt;code&gt;O(n)&lt;/code&gt;. And it drops lower-order terms, so &lt;code&gt;O(n² + n)&lt;/code&gt; becomes &lt;code&gt;O(n²)&lt;/code&gt;. Both simplifications stop mattering at scale, which is the only scale the notation is built for. If &lt;code&gt;n&lt;/code&gt; is 10, the constants matter. If &lt;code&gt;n&lt;/code&gt; is 10 million, the highest-order term swallows everything else.&lt;/p&gt;

&lt;p&gt;The "upper bound" piece is the worst case. An algorithm marked &lt;code&gt;O(n²)&lt;/code&gt; might finish faster on certain inputs. It just won't do worse than quadratic. Worst-case behaviour is what interviewers care about because best-case behaviour doesn't protect you from the input that breaks the solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five growth classes you'll actually see
&lt;/h2&gt;

&lt;p&gt;Every algorithm you'll meet in an interview lives in one of these.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;n=10&lt;/th&gt;
&lt;th&gt;n=1,000&lt;/th&gt;
&lt;th&gt;n=1,000,000&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;O(1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Constant&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;O(log n)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logarithmic&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;O(n)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;O(n log n)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Linearithmic&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;20,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;O(n²)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quadratic&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;1,000,000,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The jump from &lt;code&gt;O(n)&lt;/code&gt; to &lt;code&gt;O(n²)&lt;/code&gt; is where most interview problems live. A brute-force solution that checks every pair in an array is quadratic. The optimised version using a hash map or two pointers drops it to linear. At a million elements, that's the difference between a million operations and a trillion.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;O(n log n)&lt;/code&gt; is worth a side note. It's the &lt;a href="https://en.wikipedia.org/wiki/Comparison_sort#Number_of_comparisons_required_to_sort_a_list" rel="noopener noreferrer"&gt;theoretical floor for comparison-based sorting&lt;/a&gt;. Merge sort, heapsort, well-implemented quicksort all sit there. The number isn't arbitrary. It falls out of information theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the derivation skill lives
&lt;/h2&gt;

&lt;p&gt;Most Big O guides hand you a lookup table. Binary search is logarithmic, merge sort is linearithmic, hash insert is constant amortised. Fine for reference. Useless when the interviewer hands you a function you've never seen.&lt;/p&gt;

&lt;p&gt;The interview skill is derivation. Counting the dominant operation as a function of input size. The cleanest way to build that skill is to use Big O on code where you don't already know the answer.&lt;/p&gt;

&lt;p&gt;Take a single loop. One pass through &lt;code&gt;n&lt;/code&gt; elements does &lt;code&gt;n&lt;/code&gt; units of work, so it's &lt;code&gt;O(n)&lt;/code&gt;. Two nested loops over the same array multiply: outer runs &lt;code&gt;n&lt;/code&gt; times, inner runs up to &lt;code&gt;n&lt;/code&gt; times, total work is &lt;code&gt;n × n&lt;/code&gt; which is &lt;code&gt;O(n²)&lt;/code&gt;. The inner loop in something like a pair-check usually doesn't run the full &lt;code&gt;n&lt;/code&gt; each time. The first outer pass runs the inner &lt;code&gt;n-1&lt;/code&gt; times, the next runs &lt;code&gt;n-2&lt;/code&gt;, and so on. The sum is &lt;code&gt;n(n-1)/2&lt;/code&gt;, which simplifies to &lt;code&gt;O(n²)&lt;/code&gt; because the constant &lt;code&gt;1/2&lt;/code&gt; and the linear term drop out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The halving pattern, derived from scratch
&lt;/h2&gt;

&lt;p&gt;Take binary search. It's the cleanest example of how &lt;code&gt;O(log n)&lt;/code&gt; actually shows up.&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;binary_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&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;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;target&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;mid&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each loop iteration cuts the search space in half. Start with &lt;code&gt;n&lt;/code&gt; elements, one comparison leaves &lt;code&gt;n/2&lt;/code&gt;, two leave &lt;code&gt;n/4&lt;/code&gt;, three leave &lt;code&gt;n/8&lt;/code&gt;. You stop when the space hits 1. The question is: how many times can you halve &lt;code&gt;n&lt;/code&gt; before reaching 1?&lt;/p&gt;

&lt;p&gt;The answer is &lt;code&gt;log₂(n)&lt;/code&gt;. An array of a million elements needs at most 20 comparisons. A billion needs at most 30. That's why binary search feels almost instant on large inputs.&lt;/p&gt;

&lt;p&gt;The shape generalises. Any loop where the search space shrinks by a constant fraction each step is logarithmic. You'll see the same shape when an index doubles (&lt;code&gt;i *= 2&lt;/code&gt;), when a range halves, when a balanced-tree walk descends one level per iteration. The derivation isn't memorised. It's derived from the mechanic.&lt;/p&gt;

&lt;p&gt;I keep coming back to this one because it's the easiest place to feel the difference between memorising and deriving. Once you've drawn the halving five or six times on a whiteboard, recognising it in unfamiliar code stops being effortful. &lt;a href="https://www.codeintuition.io/courses/searching" rel="noopener noreferrer"&gt;The searching lesson on my own site&lt;/a&gt; walks through the halving invariant frame by frame, then extends the same derivation to predicate search problems where you're binary searching on an answer space instead of an array. Same mechanic, different surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  A five-step derivation protocol
&lt;/h2&gt;

&lt;p&gt;The five steps below work on any code, whether you've seen it before or not. They're the steps I use myself when an interviewer drops a function I don't recognise.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identify the dominant operation.&lt;/strong&gt; Inside the inner loop, the recursive call, the comparison. Whatever you'd count if you were ticking marks on paper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express the iteration count as a function of &lt;code&gt;n&lt;/code&gt;.&lt;/strong&gt; A single loop is &lt;code&gt;n&lt;/code&gt;. Nested loops multiply. A halving loop is &lt;code&gt;log n&lt;/code&gt;. A recursive call that branches twice and reduces by 1 each level builds a tree of depth &lt;code&gt;n&lt;/code&gt;, giving &lt;code&gt;2ⁿ&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sum across the levels.&lt;/strong&gt; Single loop: &lt;code&gt;n × O(1) = O(n)&lt;/code&gt;. Recursive tree: branching factor &lt;code&gt;b&lt;/code&gt;, depth &lt;code&gt;d&lt;/code&gt;, total nodes roughly &lt;code&gt;b^d&lt;/code&gt;. Multiply by the work per node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop constants and lower-order terms.&lt;/strong&gt; &lt;code&gt;3n + 5&lt;/code&gt; becomes &lt;code&gt;O(n)&lt;/code&gt;. &lt;code&gt;n² + n&lt;/code&gt; becomes &lt;code&gt;O(n²)&lt;/code&gt;. The simplification only matters because the notation is about behaviour at large &lt;code&gt;n&lt;/code&gt;, where the highest-order term dominates everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do the same thing for space.&lt;/strong&gt; Track auxiliary memory. Count call-stack frames in recursion. Count the size of any data structure that grows with input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The protocol works on hash-map resize, on BFS over a graph, on string concatenation in a loop, on dynamic programming with memoisation. It works on code you've never seen. That's the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example on recursion
&lt;/h2&gt;

&lt;p&gt;Take naive Fibonacci.&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;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&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;n&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&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="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dominant operation is the recursive call. Each call to &lt;code&gt;fib(k)&lt;/code&gt; makes two more calls. The call tree branches at every level. Depth is roughly &lt;code&gt;n&lt;/code&gt;. Total nodes are roughly &lt;code&gt;2ⁿ&lt;/code&gt;. That's &lt;code&gt;O(2ⁿ)&lt;/code&gt;, which is why naive Fibonacci is unusably slow even for moderate &lt;code&gt;n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Adding memoisation collapses the tree to &lt;code&gt;O(n)&lt;/code&gt; because each subproblem &lt;code&gt;fib(k)&lt;/code&gt; is computed exactly once. The work per subproblem is constant, and there are &lt;code&gt;n&lt;/code&gt; of them. Same recursion, different complexity, derived from the same counting.&lt;/p&gt;

&lt;p&gt;The space complexity of the naive version is &lt;code&gt;O(n)&lt;/code&gt; from the call stack, not &lt;code&gt;O(2ⁿ)&lt;/code&gt; from the tree. At any moment, only one root-to-leaf path is on the stack. The branches aren't all simultaneously in memory. Easy mistake to make. Easy to catch if you're explicit about which operations are happening &lt;em&gt;at the same time&lt;/em&gt; versus &lt;em&gt;across the whole run&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Space complexity, including the parts that get forgotten
&lt;/h2&gt;

&lt;p&gt;Time complexity gets most of the attention. Space gets asked about often enough that ignoring it loses interview points.&lt;/p&gt;

&lt;p&gt;Space complexity measures the additional memory your algorithm uses beyond the input. The input array itself doesn't count toward auxiliary space. Sorting in place is &lt;code&gt;O(1)&lt;/code&gt; auxiliary. Copying the array first is &lt;code&gt;O(n)&lt;/code&gt; auxiliary for the same sort.&lt;/p&gt;

&lt;p&gt;The piece that catches even experienced engineers is the recursive call stack. Every recursive invocation adds a frame. A function that recurses &lt;code&gt;n&lt;/code&gt; levels deep uses &lt;code&gt;O(n)&lt;/code&gt; stack space, even if it doesn't allocate any explicit data structure.&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;sum_recursive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;sum_recursive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&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="c1"&gt;# Time: O(n), Space: O(n) due to call stack
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The iterative version of the same logic uses constant space. That's the concrete reason converting recursion to iteration is sometimes worth the extra code.&lt;/p&gt;

&lt;p&gt;Common space-complexity traps worth knowing by name:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forgetting to count the recursive call stack.&lt;/li&gt;
&lt;li&gt;Counting the input array size as auxiliary space.&lt;/li&gt;
&lt;li&gt;Ignoring that hash maps and sets grow with input.&lt;/li&gt;
&lt;li&gt;Missing that repeated string concatenation in a loop creates &lt;code&gt;O(n²)&lt;/code&gt; total memory because each new string is a fresh allocation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this shows up in interviews
&lt;/h2&gt;

&lt;p&gt;Interviewers don't ask you to recite that merge sort is &lt;code&gt;O(n log n)&lt;/code&gt;. They hand you a function and ask you to analyse it, or they ask you to improve a brute-force &lt;code&gt;O(n²)&lt;/code&gt; solution to &lt;code&gt;O(n)&lt;/code&gt; or &lt;code&gt;O(n log n)&lt;/code&gt;. Derivation, not recall.&lt;/p&gt;

&lt;p&gt;That's where a lot of self-taught preparation falls apart. You can grind hundreds of problems and memorise their complexities. If you haven't drilled the &lt;em&gt;reasoning process&lt;/em&gt; of counting operations as a function of input, novel code still feels opaque. Learning science has a name for the right drill here: elaborative interrogation. Asking yourself why each operation contributes the work it does, rather than accepting the labelled answer.&lt;/p&gt;

&lt;p&gt;Before Big O is derivable, you read a solution and think &lt;em&gt;that looks fast&lt;/em&gt;. After, you look at the same solution and know it's &lt;code&gt;O(n log n)&lt;/code&gt; because you can point at the divide step and the merge step independently. You can explain why adding a hash map drops a nested loop from quadratic to linear. You can evaluate code you've never seen and tell the interviewer how it scales.&lt;/p&gt;

&lt;p&gt;At that point, Big O stops being a label you apply after the fact. It becomes a tool you reason with.&lt;/p&gt;

&lt;p&gt;I wrote a longer version on &lt;a href="https://www.codeintuition.io/blogs/big-o-notation-explained" rel="noopener noreferrer"&gt;my own blog&lt;/a&gt; that includes amortised analysis (the hash-map resize derivation in particular) and the recursion-tree shortcuts for divide-and-conquer.&lt;/p&gt;

&lt;p&gt;What's a problem where the textbook complexity made sense to you only after you traced the work by hand? I'm curious which derivations clicked first for people, and which ones stayed stubborn.&lt;/p&gt;

</description>
      <category>leetcode</category>
      <category>career</category>
      <category>algorithms</category>
      <category>interview</category>
    </item>
    <item>
      <title>BFS vs DFS: The Invariant Decides, Not Your Preference</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Fri, 22 May 2026 19:56:26 +0000</pubDate>
      <link>https://dev.to/codeintuition/bfs-vs-dfs-the-invariant-decides-not-your-preference-4nk4</link>
      <guid>https://dev.to/codeintuition/bfs-vs-dfs-the-invariant-decides-not-your-preference-4nk4</guid>
      <description>&lt;p&gt;Two algorithms visit every node in a graph. Pick whichever you remember and most of the time nothing goes wrong. Then an interview hands you a shortest path problem, you reach for DFS because it's faster to write recursively, and you hand back a path that's valid and twice as long as the optimal one. No error. Just a quietly wrong answer you can't debug with the clock running.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; BFS uses a queue and processes nodes in distance order, so the first time it reaches a node, that path is the shortest in an unweighted graph. DFS uses a stack, commits to one branch fully, and naturally exposes cycles, topological order, and connected components. The data structure creates the guarantee. The guarantee decides which problems each one can solve.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The queue and the stack create different guarantees
&lt;/h2&gt;

&lt;p&gt;BFS and DFS aren't two flavors of the same traversal. They hold different invariants, and the invariant is what decides which problems each one can solve.&lt;/p&gt;

&lt;p&gt;Breadth first search processes every node at distance 1 from the source before any node at distance 2, every node at distance 2 before distance 3, and so on. It uses a queue to hold that order. You get a level by level sweep outward from the start.&lt;/p&gt;

&lt;p&gt;Depth first search drives down one path as far as it goes, then backtracks. It uses a stack, or the call stack when you write it recursively, to remember where it came from. One branch gets fully exhausted before the next one is touched.&lt;/p&gt;

&lt;p&gt;That sounds academic until it costs you a problem. The order each one visits in is not a cosmetic difference. It's the whole reason one of them can answer a question the other can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why BFS finds the shortest path and DFS doesn't
&lt;/h2&gt;

&lt;p&gt;The queue is the entire reason BFS works for shortest paths. A queue is first in, first out. When you enqueue the neighbours at distance &lt;code&gt;d&lt;/code&gt;, they sit behind any remaining distance &lt;code&gt;d-1&lt;/code&gt; nodes and ahead of every future distance &lt;code&gt;d+1&lt;/code&gt; node. The queue enforces distance order without you ever tracking distance explicitly.&lt;/p&gt;

&lt;p&gt;The stack is last in, first out. The most recently pushed node gets processed next, so you keep diving deeper along whichever neighbour you pushed last. DFS might reach a node through a 7 edge path before it ever finds the 2 edge one. BFS can't do that, because it finishes every 2 edge path before it starts any 7 edge path.&lt;/p&gt;

&lt;p&gt;I used to reach for whichever traversal I'd practiced most recently. The first time I watched that break a real solution, it was on a grid.&lt;/p&gt;

&lt;h2&gt;
  
  
  A grid problem where the wrong traversal costs you
&lt;/h2&gt;

&lt;p&gt;Take a 2D grid where &lt;code&gt;0&lt;/code&gt; is passable and &lt;code&gt;1&lt;/code&gt; is a wall. You want the minimum number of steps from the top left corner to the bottom right, moving in four directions. Every move costs the same one step, so BFS's distance guarantee applies directly.&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;min_steps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&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="n"&gt;goal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&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="n"&gt;cols&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;if&lt;/span&gt; &lt;span class="n"&gt;grid&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;goal&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="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&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;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&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="mi"&gt;0&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="n"&gt;seen&lt;/span&gt; &lt;span class="o"&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&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="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;if &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="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;goal&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;dist&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&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="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="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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&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="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="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dc&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;nr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;queue&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;nr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dist&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;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time &lt;code&gt;popleft()&lt;/code&gt; returns the goal, &lt;code&gt;dist&lt;/code&gt; is the shortest distance. Nothing else can reach the goal in fewer steps, because the queue processed all the closer cells first.&lt;/p&gt;

&lt;p&gt;Now swap &lt;code&gt;queue.popleft()&lt;/code&gt; for a stack &lt;code&gt;pop()&lt;/code&gt; and you have DFS. The first time it reaches the goal, the distance could be any valid length, often a wandering one. The code looks almost identical. The guarantee is gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The memory trade nobody mentions
&lt;/h2&gt;

&lt;p&gt;The two algorithms also use memory differently, and the graph's shape decides which is cheaper. BFS holds the entire frontier at once. In a balanced tree with branching factor &lt;code&gt;b&lt;/code&gt; and depth &lt;code&gt;d&lt;/code&gt;, the queue holds &lt;code&gt;O(b^d)&lt;/code&gt; nodes at the widest level. DFS holds at most &lt;code&gt;O(d)&lt;/code&gt; nodes on the stack, one per level of depth. For a deep, narrow graph, DFS is far lighter. For a shallow, wide one, either works.&lt;/p&gt;

&lt;p&gt;DFS also has a recursive form BFS can't match. When you write DFS recursively, the call stack is the stack, so you manage no explicit collection at all. That maps cleanly onto problems that are recursive by nature: trees, nested configurations, expression parsing. BFS has no equivalent, because a queue isn't built into the call stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I decide which one to reach for
&lt;/h2&gt;

&lt;p&gt;The question I ask now is simpler than any rule of thumb: does this problem need a distance guarantee or a structural property? Distance guarantee points at BFS. Structural property, meaning cycles, ordering, or connectivity, points at DFS.&lt;/p&gt;

&lt;p&gt;Reach for BFS when the problem needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shortest path:&lt;/strong&gt; minimum steps in a grid, shortest word transformation, nearest distance to a target, all in an unweighted graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level order processing:&lt;/strong&gt; anything handled layer by layer, like a binary tree level order traversal or finding every node at distance &lt;code&gt;k&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nearest match:&lt;/strong&gt; BFS hits the closest valid node first because it explores in distance order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reach for DFS when the problem needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cycle detection:&lt;/strong&gt; DFS tracks which nodes are currently on the recursion stack, so a back edge to one of them means a cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topological order:&lt;/strong&gt; DFS finish times, reversed, give a valid &lt;a href="https://www.codeintuition.io/courses/graph/XsDP1kvZFP6NR7o9NvZyJ" rel="noopener noreferrer"&gt;topological ordering&lt;/a&gt; for a directed acyclic graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connected components:&lt;/strong&gt; run DFS from an unvisited node, mark everything reachable, repeat from the next unvisited node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path enumeration:&lt;/strong&gt; backtracking and all source to target paths, because DFS explores one complete path before trying alternatives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One trap worth naming: BFS assumes every edge costs the same. The moment edges carry weights, its distance order no longer holds, and you need Dijkstra's algorithm, or Bellman-Ford if weights can go negative.&lt;/p&gt;

&lt;p&gt;I wrote a longer version on &lt;a href="https://www.codeintuition.io/blogs/bfs-vs-dfs" rel="noopener noreferrer"&gt;my own blog&lt;/a&gt; that traces both algorithms step by step on the same six node graph and walks through a directed cycle detection example with code.&lt;/p&gt;

&lt;p&gt;Which graph problem finally made the BFS vs DFS choice obvious for you, the one where picking wrong actually broke your solution rather than just slowing it down?&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>leetcode</category>
      <category>career</category>
      <category>programming</category>
    </item>
    <item>
      <title>Backtracking Algorithm Explained: An N-Queens Walkthrough</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Thu, 21 May 2026 16:03:35 +0000</pubDate>
      <link>https://dev.to/codeintuition/backtracking-algorithm-explained-an-n-queens-walkthrough-9on</link>
      <guid>https://dev.to/codeintuition/backtracking-algorithm-explained-an-n-queens-walkthrough-9on</guid>
      <description>&lt;p&gt;The textbook line on backtracking is "try everything and undo what doesn't work." Accurate, and useless. It tells you nothing about how to write a solution to a problem you've never seen. Backtracking isn't trial and error. It's a depth first walk over a state space tree with a rule for quitting a branch the moment it can't lead anywhere valid.&lt;/p&gt;

&lt;p&gt;Once you see it that way, the trick stops being clever and starts being mechanical. Every backtracking problem you'll meet runs the same loop. The only thing that changes is what you're choosing and what counts as a dead branch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Backtracking builds a solution one decision at a time and walks a state space tree with DFS. When a partial solution breaks a constraint, you abandon that branch and try the next option. The whole algorithm is choose, check, undo. The variation between problems is what you choose and what the constraints are.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What backtracking actually is
&lt;/h2&gt;

&lt;p&gt;Picture a tree where the root is the empty solution. Each edge is a decision: place a queen in a column, add a character to a string, include an element in a subset. Each node is the partial solution you have so far. Leaves are either complete valid answers or dead ends where a constraint already broke.&lt;/p&gt;

&lt;p&gt;Exhaustive search builds every leaf and checks each one after the fact. Backtracking checks on the way down and stops descending the instant a partial answer is doomed. On a board with &lt;code&gt;n&lt;/code&gt; queens, exhaustive search inspects all &lt;code&gt;n^n&lt;/code&gt; configurations. Backtracking inspects a sliver of that, because it never places a queen in a row where a conflict already exists.&lt;/p&gt;

&lt;p&gt;For tiny inputs the difference is academic. Generate every subset of a 10 element array and pruning saves you nothing, there's nothing to prune. The advantage shows up when constraints are tight and the search space is large, which is exactly the shape of the problems interviewers like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The choose, check, undo skeleton
&lt;/h2&gt;

&lt;p&gt;Strip away the problem specifics and every backtracking routine does three things in a loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choose:&lt;/strong&gt; extend the current partial solution with one option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check:&lt;/strong&gt; test whether that extension violates a constraint. If it does, don't recurse into it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undo:&lt;/strong&gt; after the recursive call returns, reverse the choice so the next option starts from a clean state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last step is the one people forget, and it's the one that quietly produces wrong answers instead of crashes. If you append to a path or flip a &lt;code&gt;visited&lt;/code&gt; flag and never reverse it, later branches inherit corrupted state from earlier ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walking N-Queens by hand
&lt;/h2&gt;

&lt;p&gt;N-Queens is the cleanest place to watch this run. Place &lt;code&gt;n&lt;/code&gt; queens on an &lt;code&gt;n x n&lt;/code&gt; board so none attack each other, no shared row, column, or diagonal. The tree has &lt;code&gt;n&lt;/code&gt; levels, one per row, and at each level you pick a column. The branching factor starts at &lt;code&gt;n&lt;/code&gt; and shrinks as conflicts knock out columns.&lt;/p&gt;

&lt;p&gt;Here's a partial trace for &lt;code&gt;n=4&lt;/code&gt;, watching where pruning kicks in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Row 0: try column 0
  Row 1: column 0 -&amp;gt; PRUNE (same column)
  Row 1: column 1 -&amp;gt; PRUNE (diagonal with row 0)
  Row 1: column 2 -&amp;gt; ok, partial [0, 2]
    Row 2: every column conflicts -&amp;gt; BACKTRACK
  Row 1: column 3 -&amp;gt; ok, partial [0, 3]
    Row 2: column 1 -&amp;gt; ok, partial [0, 3, 1]
      Row 3: every column conflicts -&amp;gt; BACKTRACK
    ...all of row 1's options exhausted -&amp;gt; BACKTRACK
Row 0: try column 1
  Row 1: column 3 -&amp;gt; [1, 3]
    Row 2: column 0 -&amp;gt; [1, 3, 0]
      Row 3: column 2 -&amp;gt; [1, 3, 0, 2] -&amp;gt; SOLUTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without pruning you'd evaluate all &lt;code&gt;4^4&lt;/code&gt; = 256 placements. With it, the algorithm touches roughly 20 nodes before the first solution. Early constraint checks did the heavy lifting. The code is just the skeleton with the constraint filled in:&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;solve_n_queens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;solutions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;board&lt;/span&gt; &lt;span class="o"&gt;=&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;is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col&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;prev_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev_col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;board&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;prev_col&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev_row&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev_col&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;backtrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&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;row&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;solutions&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;board&lt;/span&gt;&lt;span class="p"&gt;[:])&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;col&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;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;board&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;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# choose
&lt;/span&gt;                &lt;span class="nf"&gt;backtrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&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="c1"&gt;# explore
&lt;/span&gt;                &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;           &lt;span class="c1"&gt;# undo
&lt;/span&gt;
    &lt;span class="nf"&gt;backtrack&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;solutions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;board.append(col)&lt;/code&gt; is the choose. &lt;code&gt;is_valid(row, col)&lt;/code&gt; is the check that prunes. &lt;code&gt;board.pop()&lt;/code&gt; is the undo. The domain looks completely different on Sudoku or word search, but the three pieces are always sitting right there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three shapes backtracking takes
&lt;/h2&gt;

&lt;p&gt;Backtracking problems aren't all the same. They sort into three shapes by how much you can prune:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/backtracking/dIe_AzPs_9PsI92I4FLHc" rel="noopener noreferrer"&gt;&lt;strong&gt;Unconditional enumeration.&lt;/strong&gt;&lt;/a&gt; Produce every arrangement with no extra rule: all subsets, all letter combinations of a phone number. The tree branches at every position and every leaf is valid output. Nothing to prune, you walk the whole tree.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/backtracking/UUkyIeusIihAiyH8Xz9P8" rel="noopener noreferrer"&gt;&lt;strong&gt;Conditional enumeration.&lt;/strong&gt;&lt;/a&gt; Same branching shape, but a constraint kills branches mid construction: valid parentheses, combinations that sum to a target, permutations of distinct elements. Some branches die before the leaf because the partial answer already broke the rule.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codeintuition.io/courses/backtracking/rblzCNicLv5RvATRMpCWV" rel="noopener noreferrer"&gt;&lt;strong&gt;Backtracking search.&lt;/strong&gt;&lt;/a&gt; Find one or more configurations that satisfy a global constraint: N-Queens, Sudoku, maze pathfinding. Pruning is heaviest here because each placement constrains every later one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason this taxonomy is worth memorising: it tells you how aggressive your pruning should be before you write a line. Search problems want constraint checks at every level. Unconditional enumeration wants none, and adding them just slows you down.&lt;/p&gt;

&lt;p&gt;To see the same skeleton with no pruning at all, here's generating every subset of an array. Every node is already a valid answer, so there's nothing to check:&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;subsets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&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;backtrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;result&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;path&lt;/span&gt;&lt;span class="p"&gt;[:])&lt;/span&gt;          &lt;span class="c1"&gt;# every node is a subset
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&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;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="n"&gt;path&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;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;        &lt;span class="c1"&gt;# choose
&lt;/span&gt;            &lt;span class="nf"&gt;backtrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# explore
&lt;/span&gt;            &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                  &lt;span class="c1"&gt;# undo
&lt;/span&gt;
    &lt;span class="nf"&gt;backtrack&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same three pieces. The &lt;code&gt;is_valid&lt;/code&gt; check from N-Queens is simply absent, because in unconditional enumeration there's no constraint to prune on. Drop the &lt;code&gt;path.pop()&lt;/code&gt; line, though, and you reproduce the most common backtracking bug: every subset after the first comes back polluted with elements left over from an earlier branch. The undo is what keeps one mutable list honest across the whole tree.&lt;/p&gt;

&lt;p&gt;One more thing the shapes tell you is the cost. The worst case for backtracking is exponential, because the tree can have exponentially many nodes. Pruning doesn't change that worst case, it changes how much of it you actually visit. On loose constraints you walk most of the tree anyway. On tight ones like N-Queens you skip the overwhelming majority, which is why the same algorithm feels fast on one problem and hopeless on another.&lt;/p&gt;

&lt;h2&gt;
  
  
  When backtracking is the wrong call
&lt;/h2&gt;

&lt;p&gt;Reaching for backtracking on the wrong problem is a common interview misstep. Three signals tell you it fits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The problem wants all valid configurations, or one valid configuration. "Generate all", "find every", "place the" are tells. If it only wants a count, DP is usually faster.&lt;/li&gt;
&lt;li&gt;Each decision constrains future decisions. Placing an element at A removes options at B. That's a constrained search tree.&lt;/li&gt;
&lt;li&gt;There's no greedy shortcut and no overlapping subproblem to cache. If locally optimal choices give the global optimum, that's greedy. If the same subproblem repeats, that's DP.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the input is large and you can't prune hard, even correct backtracking times out. That's the boundary where the interviewer is usually steering you toward dynamic programming.&lt;/p&gt;

&lt;p&gt;A quick gut check before you commit: estimate the branching factor and the depth. If the tree is roughly &lt;code&gt;b&lt;/code&gt; choices deep &lt;code&gt;d&lt;/code&gt; levels and your constraints don't kill many branches, you're looking at something near &lt;code&gt;b^d&lt;/code&gt; work. When that number is astronomical and the problem only wants a count or a single optimal value, that's the cue to stop and ask whether the states overlap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where backtracking turns into DP
&lt;/h2&gt;

&lt;p&gt;The two are closer than they look. When a backtracking solution explores the same partial state through different decision paths, you're recomputing work. Cache those states and top down DP falls out of the same recursion you already wrote.&lt;/p&gt;

&lt;p&gt;That progression, from raw search to memoised recursion to a table, is one of the most reusable moves in interview prep. It also explains why solid recursion has to come first. Backtracking is recursion with a choose, check, undo wrapper, so if the recursive call still feels shaky, the wrapper won't save you. If you want to drill the pattern shapes directly, &lt;a href="https://www.codeintuition.io/courses/backtracking" rel="noopener noreferrer"&gt;the backtracking pattern lessons&lt;/a&gt; work through each of the three with their own trigger checklist.&lt;/p&gt;

&lt;p&gt;The gap that trips most people up isn't writing the recursion. It's trusting the moment a branch should be abandoned, and that's the part I keep coming back to in my own work.&lt;/p&gt;

&lt;p&gt;I wrote a longer version on &lt;a href="https://www.codeintuition.io/blogs/backtracking-algorithm-explained" rel="noopener noreferrer"&gt;my blog&lt;/a&gt; that walks through the other two shapes with full code, plus the common bugs that make backtracking silently return wrong answers.&lt;/p&gt;

&lt;p&gt;Which backtracking problem finally made the choose, check, undo loop click for you?&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>career</category>
      <category>programming</category>
      <category>leetcode</category>
    </item>
    <item>
      <title>AVL Trees Explained: How Rotations Keep BST Operations O(log n)</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Wed, 20 May 2026 19:50:04 +0000</pubDate>
      <link>https://dev.to/codeintuition/arrays-vs-linked-lists-why-arrays-are-faster-in-practice-3dhj</link>
      <guid>https://dev.to/codeintuition/arrays-vs-linked-lists-why-arrays-are-faster-in-practice-3dhj</guid>
      <description>&lt;p&gt;You learn binary search trees and walk away believing every operation is &lt;code&gt;O(log n)&lt;/code&gt;. It isn't. That guarantee only holds when the tree stays balanced, and a plain BST has no mechanism to enforce that. Insert &lt;code&gt;[1, 2, 3, 4, 5]&lt;/code&gt; into an empty BST and you've built a linked list with extra steps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; An AVL tree adds one rule to a BST. No node's left and right subtrees can differ in height by more than 1. When an insertion breaks the rule, the tree fixes itself through rotations. Every operation stays &lt;code&gt;O(log n)&lt;/code&gt;. The four rotation cases aren't four separate things to memorise. They're two atomic moves combined twice.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The balance rule everything else hangs on
&lt;/h2&gt;

&lt;p&gt;An AVL tree is a self balancing BST where, for every node, the heights of its left and right subtrees differ by at most 1. That's the whole rule. Every operation that preserves the rule preserves &lt;code&gt;O(log n)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You track the rule with the &lt;strong&gt;balance factor&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;balance_factor(node) = height(left_subtree) - height(right_subtree)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A node is balanced when its balance factor is &lt;code&gt;-1&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, or &lt;code&gt;+1&lt;/code&gt;. Zero means both subtrees have the same height. &lt;code&gt;+1&lt;/code&gt; means the left is one level taller. &lt;code&gt;-1&lt;/code&gt; means the right is. When an insertion or deletion pushes any node's balance factor to &lt;code&gt;-2&lt;/code&gt; or &lt;code&gt;+2&lt;/code&gt;, the tree is unbalanced and needs fixing.&lt;/p&gt;

&lt;p&gt;The fix is always a rotation, and it's always local. You don't rebuild the tree. You rearrange 2 or 3 pointers near the imbalance and the invariant snaps back into place.&lt;/p&gt;

&lt;p&gt;This single rule is strict enough to guarantee logarithmic height. An AVL tree with &lt;code&gt;n&lt;/code&gt; nodes has height at most about &lt;code&gt;1.44 log(n)&lt;/code&gt;. Slightly taller than a perfectly balanced tree, but the difference is a constant factor. Every operation traverses from root to leaf, so when height stays logarithmic, so does runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the rule matters more than it looks
&lt;/h2&gt;

&lt;p&gt;A plain BST gives you none of this. Insert the values &lt;code&gt;[1, 2, 3, 4, 5]&lt;/code&gt; into an empty BST. Each value is larger than the last, so every insertion goes right. The resulting tree is a straight line, structurally identical to a linked list. Searching for &lt;code&gt;5&lt;/code&gt; walks all five nodes. &lt;code&gt;O(n)&lt;/code&gt;, not &lt;code&gt;O(log n)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sorted input isn't unusual either. Database indices built from timestamped logs, primary keys assigned by an auto-increment column, sensor readings ordered by time. All of these produce sorted insertion sequences. A plain BST degenerates into a linked list on every one of them.&lt;/p&gt;

&lt;p&gt;Performance depends entirely on height. Balanced, you get &lt;code&gt;O(log n)&lt;/code&gt;. Degenerate, you get &lt;code&gt;O(n)&lt;/code&gt;. Lose control of the height, lose control of the performance. That's the whole reason self balancing exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four rotation cases, and why they're really just two
&lt;/h2&gt;

&lt;p&gt;Every AVL imbalance falls into one of four cases. Each one is resolved by a specific rotation that preserves the BST ordering (&lt;code&gt;left &amp;lt; parent &amp;lt; right&lt;/code&gt;) while restoring the height balance.&lt;/p&gt;

&lt;p&gt;There are only two atomic moves: a left rotation and a right rotation. The four cases are these two moves applied once or twice.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LL case&lt;/strong&gt; (left child of a left-heavy node): single right rotation on the unbalanced node&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RR case&lt;/strong&gt; (right child of a right-heavy node): single left rotation on the unbalanced node&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LR case&lt;/strong&gt; (right child of a left-heavy node): left rotate the left child first to straighten the kink, then right rotate the unbalanced node&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RL case&lt;/strong&gt; (left child of a right-heavy node): right rotate the right child first, then left rotate the unbalanced node&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The single rotations handle the "outside" imbalances. The double rotations handle the "inside" imbalances where a single rotation would just move the heavy side from one position to another without fixing it. The trick is recognising which side of the parent is heavy AND which side of that child is heavy. Those two answers pick the case.&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;right_rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;
    &lt;span class="n"&gt;T3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;T3&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&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;y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what the rotation actually does. It moves 3 pointers and updates 2 heights. That's it. Constant time. The &lt;code&gt;O(log n)&lt;/code&gt; part of an insertion comes from walking down the tree to find the insertion point and walking back up to check balance factors. The rotation itself is free by comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracing an insertion sequence
&lt;/h2&gt;

&lt;p&gt;Walk through inserting &lt;code&gt;[3, 2, 1, 4, 5]&lt;/code&gt; into an empty AVL tree. I find that watching two different rotations fire in the same sequence is what makes the four-case table click.&lt;/p&gt;

&lt;p&gt;Inserting &lt;code&gt;3&lt;/code&gt; creates a single-node tree with balance factor &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inserting &lt;code&gt;2&lt;/code&gt; sends it left of &lt;code&gt;3&lt;/code&gt;. Node &lt;code&gt;3&lt;/code&gt;'s balance factor is &lt;code&gt;+1&lt;/code&gt; (left subtree height 1, right subtree height 0). Still within the rule.&lt;/p&gt;

&lt;p&gt;Inserting &lt;code&gt;1&lt;/code&gt; triggers the first rotation. &lt;code&gt;1&lt;/code&gt; goes left of node &lt;code&gt;2&lt;/code&gt;. Walking back up, node &lt;code&gt;2&lt;/code&gt;'s balance factor is &lt;code&gt;+1&lt;/code&gt; and node &lt;code&gt;3&lt;/code&gt;'s is &lt;code&gt;+2&lt;/code&gt;. That's an LL case (node &lt;code&gt;3&lt;/code&gt; is left-heavy, its left child &lt;code&gt;2&lt;/code&gt; is also left-heavy). A right rotation on node &lt;code&gt;3&lt;/code&gt; puts node &lt;code&gt;2&lt;/code&gt; at the root, node &lt;code&gt;1&lt;/code&gt; on the left, and node &lt;code&gt;3&lt;/code&gt; on the right. All balance factors drop to &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inserting &lt;code&gt;4&lt;/code&gt; goes right from node &lt;code&gt;2&lt;/code&gt; to node &lt;code&gt;3&lt;/code&gt;, then right again to become node &lt;code&gt;3&lt;/code&gt;'s right child. Walking back, node &lt;code&gt;3&lt;/code&gt;'s balance factor is &lt;code&gt;-1&lt;/code&gt;, node &lt;code&gt;2&lt;/code&gt;'s is &lt;code&gt;0&lt;/code&gt;. Within the rule.&lt;/p&gt;

&lt;p&gt;Inserting &lt;code&gt;5&lt;/code&gt; breaks balance again. It lands as node &lt;code&gt;4&lt;/code&gt;'s right child. Walking back up, node &lt;code&gt;4&lt;/code&gt;'s balance factor is &lt;code&gt;-1&lt;/code&gt;, but node &lt;code&gt;3&lt;/code&gt;'s hits &lt;code&gt;-2&lt;/code&gt;. That's an RR case. A left rotation on node &lt;code&gt;3&lt;/code&gt; moves node &lt;code&gt;4&lt;/code&gt; into node &lt;code&gt;3&lt;/code&gt;'s position, with node &lt;code&gt;3&lt;/code&gt; as &lt;code&gt;4&lt;/code&gt;'s left child and node &lt;code&gt;5&lt;/code&gt; as &lt;code&gt;4&lt;/code&gt;'s right child. All balance factors back to &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Final tree: node &lt;code&gt;2&lt;/code&gt; at the root, node &lt;code&gt;1&lt;/code&gt; on the left, node &lt;code&gt;4&lt;/code&gt; on the right, with &lt;code&gt;3&lt;/code&gt; and &lt;code&gt;5&lt;/code&gt; as &lt;code&gt;4&lt;/code&gt;'s children. Height is 2.&lt;/p&gt;

&lt;p&gt;A plain BST with the same insertion order &lt;code&gt;[3, 2, 1, 4, 5]&lt;/code&gt; wouldn't degenerate this badly. But try &lt;code&gt;[1, 2, 3, 4, 5]&lt;/code&gt; and the plain BST becomes a linked list of height 4. The AVL version still has height 2.&lt;/p&gt;

&lt;p&gt;The full insertion algorithm bundles the BST insert with the balance check on the way back up:&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;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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;key&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;bf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;balance_factor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&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;bf&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&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;right_rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&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;bf&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&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;left_rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&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;bf&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;left_rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&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;right_rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&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;bf&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;right_rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&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;left_rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&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;node&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After every insertion you only check balance factors along the path from the inserted node back to the root. At most one rotation (single or double) is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why deletion is harder than insertion
&lt;/h2&gt;

&lt;p&gt;This is the part most beginner tutorials gloss over and the part interviewers like to probe.&lt;/p&gt;

&lt;p&gt;Insertion can break balance at one node along the ancestor path. You rotate once and you're done. Deletion isn't that clean.&lt;/p&gt;

&lt;p&gt;When you remove a node, the height reduction can propagate upward. Fixing the first imbalanced ancestor might shorten that subtree, which then unbalances the next ancestor up. In the worst case you'll rotate at every level from the deleted node's position back to the root. That's &lt;code&gt;O(log n)&lt;/code&gt; rotations instead of the single rotation insertion needs.&lt;/p&gt;

&lt;p&gt;The rotation selection logic stays the same. You still compute &lt;code&gt;balance_factor(node)&lt;/code&gt; and check whether the imbalance is LL, RR, LR, or RL. The difference is that you can't stop after the first fix. You have to keep walking up.&lt;/p&gt;

&lt;p&gt;There's also a subtlety with the node you're actually removing. If the target has two children, you replace it with its in-order successor (smallest in the right subtree) or its in-order predecessor (largest in the left subtree), then delete that node instead. The replacement is identical to plain BST deletion. The AVL-specific work starts afterward, during the upward balance check.&lt;/p&gt;

&lt;p&gt;I'd argue most interview questions won't ask you to implement AVL deletion from scratch. But understanding why deletion is more expensive than insertion is what separates "I've memorised the four cases" from "I've actually thought about the invariant." If an interviewer asks about the tradeoff of stricter balance, deletion cost is the concrete answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to reach for AVL vs Red-Black
&lt;/h2&gt;

&lt;p&gt;In interviews, AVL trees show up two ways: explaining self balancing BSTs conceptually, and implementing balanced insertion from scratch. Red-Black trees show up as a "why don't standard libraries use AVL" question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plain BST.&lt;/strong&gt; Fine when input is random or the problem doesn't require worst-case guarantees. Tree problems on &lt;a href="https://leetcode.com" rel="noopener noreferrer"&gt;LeetCode&lt;/a&gt; typically assume balanced input and don't ask you to maintain balance yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Red-Black tree.&lt;/strong&gt; Relaxes the balance constraint to height up to &lt;code&gt;2 log(n)&lt;/code&gt; in exchange for cheaper insertion and deletion. At most 2 rotations per operation, vs potentially &lt;code&gt;O(log n)&lt;/code&gt; for AVL deletion. Java's &lt;code&gt;TreeMap&lt;/code&gt;, C++'s &lt;code&gt;std::map&lt;/code&gt;, and Linux's &lt;a href="https://docs.kernel.org/scheduler/sched-design-CFS.html" rel="noopener noreferrer"&gt;CFS process scheduler&lt;/a&gt; all use Red-Black trees. So does the Linux kernel's anonymous VMA tree. Most standard libraries do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AVL tree.&lt;/strong&gt; Enforces stricter balance. Maximum height about &lt;code&gt;1.44 log(n)&lt;/code&gt; versus about &lt;code&gt;2 log(n)&lt;/code&gt; for Red-Black. Lookups are faster because the tree is shorter. The tradeoff is that insertions and deletions are slower because AVL trees may need more rotations to maintain the stricter rule.&lt;/p&gt;

&lt;p&gt;Why are AVL trees less common in production despite being older and simpler? Looser balance makes insertion-heavy workloads faster, and most real systems insert and delete more than they search-only.&lt;/p&gt;

&lt;p&gt;For interviews though, AVL trees are the better answer. The rotation logic is more intuitive than Red-Black's five-case colour rules. If an interviewer asks you to implement a self balancing BST from scratch, AVL is the cleaner choice under time pressure. I keep seeing candidates wedge themselves into Red-Black trees and run out of time at the recolouring step. AVL trees let you spend that same time tracing the four cases instead.&lt;/p&gt;

&lt;p&gt;Honest credit where it's due: Red-Black trees aren't "worse." They're optimised for a different workload. The standard library authors knew exactly what they were trading. It's the same engineering call you'd make if you were building a key-value store that inserts billions of times a day. AVL is the wrong choice there. It's only the right choice for read-heavy workloads, or for an interview where the simpler invariant lets you finish before the timer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three mistakes that cost candidates in interviews
&lt;/h2&gt;

&lt;p&gt;These are the patterns I keep seeing on AVL questions, in roughly the order they show up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stale heights after rotation.&lt;/strong&gt; You rotate nodes &lt;code&gt;y&lt;/code&gt; and &lt;code&gt;z&lt;/code&gt; but forget to recalculate their &lt;code&gt;height&lt;/code&gt; fields. The next balance factor check uses stale data and every decision downstream is wrong from that point on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top-down balance checking.&lt;/strong&gt; You insert at a leaf and trace upward to find the first unbalanced ancestor. Starting from the root downward misses the actual imbalance point or applies a rotation at the wrong node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misidentified LR / RL cases.&lt;/strong&gt; Under pressure it's tempting to see a left-heavy node and immediately right rotate. If the left child is right-heavy, a single right rotation makes things worse. Sketching the three nodes on paper before choosing the rotation takes five seconds and avoids a cascading error that's painful to debug mid-interview.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codeintuition's &lt;a href="https://www.codeintuition.io/courses/binary-search-tree" rel="noopener noreferrer"&gt;Binary Search Tree course&lt;/a&gt; walks the balance invariant from first principles. If the four-case table still feels like four separate things to memorise, the BST course traces balance factor computation across multiple tree shapes before any rotation code appears.&lt;/p&gt;

&lt;p&gt;I wrote a longer version on &lt;a href="https://www.codeintuition.io/blogs/avl-tree-explained" rel="noopener noreferrer"&gt;my blog&lt;/a&gt; that walks deletion with the upward rebalance loop and includes the AVL vs Red-Black tradeoff matrix.&lt;/p&gt;

&lt;p&gt;Which AVL rotation case took the longest to internalise for you, and what was the problem or sketch that finally made it click?&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>interview</category>
      <category>career</category>
      <category>leetcode</category>
    </item>
    <item>
      <title>I Mapped 450+ FAANG Problems: Amazon, Google, and Meta Don't Test the Same DSA Patterns</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Tue, 12 May 2026 15:23:46 +0000</pubDate>
      <link>https://dev.to/codeintuition/i-mapped-450-faang-problems-amazon-google-and-meta-dont-test-the-same-dsa-patterns-38df</link>
      <guid>https://dev.to/codeintuition/i-mapped-450-faang-problems-amazon-google-and-meta-dont-test-the-same-dsa-patterns-38df</guid>
      <description>&lt;p&gt;The most common DSA prep mistake I see at the FAANG band isn't undertraining or overtraining. It's training as if Amazon, Google, and Meta all sample from the same pattern bag. They don't. After mapping company tags across 450+ handpicked interview problems, the gap between Amazon's pattern footprint and Google's is larger than most candidates expect, and materially changes how prep time should be allocated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Amazon spans 11+ distinct DSA pattern families, the broadest of any major company. Google has narrower coverage but a distinctive emphasis on &lt;code&gt;predicate search&lt;/code&gt; (binary search on the answer space, not on a sorted array). Seven patterns appear at six or more companies and form the universal baseline that every FAANG candidate has to own before specialising.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The seven patterns every FAANG round assumes you can run
&lt;/h2&gt;

&lt;p&gt;Before company specific differences matter, there's a universal baseline. Seven patterns appear at six or more major companies in the data set. Skipping any of them, regardless of target company, is a structural error in the prep plan.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Company-specific prep matters. But only after the universal baseline is genuinely automatic.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The most heavily tagged is &lt;code&gt;LRU Cache&lt;/code&gt;, at 19 companies. That's every FAANG member, plus DoorDash, Oracle, Zoom, PayPal, Twilio, TikTok, eBay, Yandex, LinkedIn, Zillow, Intuit, and Cloudera. The reason it spreads that far is structural: a single problem tests hash table mechanics, doubly linked list manipulation, and design composition at once. No alternative problem covers the same combination at the same difficulty.&lt;/p&gt;

&lt;p&gt;The remaining six universal patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Counting (hash table):&lt;/strong&gt; 9 companies. Frequency counts, anagram grouping, character buckets. Most broadly tested hash table pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backtracking:&lt;/strong&gt; 8 companies. Generate parentheses through N-Queens. Tests recursive state and pruning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefix Sum:&lt;/strong&gt; 8 companies. Range queries, subarray sums, equilibrium problems. Almost always undertaught relative to its testing rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary Search:&lt;/strong&gt; 7 companies. The classic sorted array variant. Includes rotated array and boundary problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixed Sliding Window:&lt;/strong&gt; 7 companies. Window of fixed size with frequency tracking inside it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variable Sliding Window:&lt;/strong&gt; 6 companies. Different triggers from fixed: a contracting mechanism that has to fire on a condition, not on a counter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prefix Sum is the one that gets shortchanged most. Eight companies tag it. Most prep plans treat it as a footnote because it doesn't have a flagship problem the way LRU Cache or Two Sum do. That's a mismatch worth correcting: it gives disproportionate company coverage relative to the time required to learn it well.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The highest ROI interview patterns are often the ones without famous flagship problems.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Predicate search: the pattern Google tests that nobody calls by name
&lt;/h2&gt;

&lt;p&gt;Google shares the universals, but the pattern that distinguishes Google interviews from the rest is &lt;code&gt;predicate search&lt;/code&gt;. This is binary search applied not to a sorted array but to the answer space itself. You define a range of possible answers, then binary search that range by checking feasibility at each midpoint.&lt;/p&gt;

&lt;p&gt;The classic shape is the &lt;a href="https://www.codeintuition.io/courses/searching/ke266Y-PVh5hOSYdfs0gV" rel="noopener noreferrer"&gt;minimum ship capacity problem&lt;/a&gt;. You're given package weights and &lt;code&gt;D&lt;/code&gt; days. Find the smallest ship capacity that lets you ship everything in &lt;code&gt;D&lt;/code&gt; days.&lt;/p&gt;

&lt;p&gt;Instead of trying every capacity from 1 upward, you frame the search range:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low:&lt;/strong&gt; the heaviest single package (you have to lift it on day one)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High:&lt;/strong&gt; the sum of all weights (one giant day)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then binary search inside that range:&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;min_ship_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="n"&gt;day_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_load&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="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;weights&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;current_load&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;day_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="n"&gt;current_load&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="n"&gt;current_load&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;day_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mechanical shift from classic binary search is small, but the mental shift is large. In classic binary search, the search space is given to you (a sorted array). In predicate search, you construct the search space from the constraints, then run binary search on your own abstraction. You're searching for the minimum value that satisfies a feasibility predicate, not a value already sitting somewhere in the input.&lt;/p&gt;

&lt;p&gt;LeetCode tags these problems "Binary Search" alongside sorted array problems. The tag isn't wrong, but it hides the model shift. If your only model for binary search is "find a value in a sorted array," predicate search will freeze you because the starting assumption doesn't match. Google asks predicate search variants more than any other company in the data set: punctual arrival speed, trip completion frenzy, calculate square root, capacity to ship within D days, all the same shape.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Most candidates know binary search on arrays. Far fewer recognise when the answer itself is the search space.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A useful test before practising binary search problems: can you state, in one sentence, what the search range is and what the feasibility check is? If yes, you're doing predicate search. If you reach for &lt;code&gt;mid = (left + right) // 2&lt;/code&gt; without that explicit framing, you're guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Amazon's pattern coverage is wider than anyone else's
&lt;/h2&gt;

&lt;p&gt;Amazon's problem set covers Counting, Fixed and Variable Sliding Window, Prefix Sum, LRU Cache, Randomised Set, Binary Search, 2D Binary Search, Staircase Search, Maximum Predicate Search, Queue Design, and Backtracking. That's 11+ pattern families against Google's 7-8 and Meta's 6-7.&lt;/p&gt;

&lt;p&gt;The practical effect on prep is concrete. A Google candidate who goes deep on predicate search, counting, and graphs covers a meaningful slice of what they'll actually see. An Amazon candidate who goes equally deep on three families has blind spots across the other eight. The "study three patterns deeply and hope you land in your zone" strategy has a lower hit rate at Amazon than anywhere else.&lt;/p&gt;

&lt;p&gt;The Bar Raiser round amplifies the breadth requirement. The Bar Raiser is an interviewer pulled from outside the hiring team, with veto power, and they're not bound to the team's domain. They can sample any pattern family Amazon tests. If the round happens to land on a category you skipped, the heuristic of "the team usually asks X" doesn't catch you.&lt;/p&gt;

&lt;p&gt;Going deep still matters. But at Amazon the breadth axis carries more weight than at the narrower companies, and the prep allocation should reflect that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meta, Microsoft, Apple: where the rest of the picture sits
&lt;/h2&gt;

&lt;p&gt;Meta concentrates on Sliding Window (both Fixed and Variable), Prefix Sum, Counting, and design problems (LRU Cache, Randomised Set). Maximum Predicate Search shows up too. Compared to Google, Meta places less weight on searching variants and more on hash table depth and design implementation. If Amazon tests breadth and Google tests reasoning depth, Meta wants hash table fluency in a tighter band.&lt;/p&gt;

&lt;p&gt;Microsoft is distinct for &lt;code&gt;2D Binary Search&lt;/code&gt; and &lt;code&gt;Staircase Search&lt;/code&gt;. These are multi dimensional search problems where the input is a sorted matrix and the search has to respect both axes. Most prep plans skip them entirely because they don't appear at Google or Meta. If Microsoft is your target, weight 2D search higher and variable sliding window lower.&lt;/p&gt;

&lt;p&gt;Apple tilts toward fundamentals tested deeply. Five Counting problems carry Apple tags, alongside Binary Search, Prefix Sum, and Backtracking. Apple's data signals a preference for candidates with strong basics over candidates with broad pattern coverage. The advanced design problems (Randomised Set, Queue Design) that Amazon tests don't appear in Apple's tags.&lt;/p&gt;

&lt;p&gt;What a company &lt;em&gt;doesn't&lt;/em&gt; test matters as much as what it does. Every hour spent on patterns your target company doesn't emphasise is an hour that could've gone to one they do. Google shows minimal tags for Queue Design and 2D Binary Search. Meta shows lower coverage of searching variants. Microsoft shows fewer tags for Variable Sliding Window. Apple barely tests advanced design at all. If you're prep-allocated against the wrong company's profile, you're optimising the wrong axis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Allocating prep time once you know your target
&lt;/h2&gt;

&lt;p&gt;Three rules fall out of the data, in this order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cover the universals first, regardless of target.&lt;/strong&gt; LRU Cache, Counting, Backtracking, Prefix Sum, Binary Search, Fixed Sliding Window, Variable Sliding Window. Skip none of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specialise second.&lt;/strong&gt; Predicate search for Google. Design breadth and Bar Raiser follow up depth for Amazon. Design depth and sliding window fluency for Meta. 2D and staircase search for Microsoft. Counting and fundamentals depth for Apple.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cut what doesn't match.&lt;/strong&gt; Targeting Google and not Amazon? Queue Design can wait. Targeting Meta and not Microsoft? Staircase Search can wait. Prep time is finite.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two patterns are worth flagging again. Prefix Sum is undertaught relative to its 8-company tag count. Almost no popular prep plan gives it the time it deserves. And LRU Cache is the one problem you genuinely shouldn't walk into any FAANG round without being able to write cold, including the doubly linked list helpers. Nineteen company tags, no exceptions.&lt;/p&gt;

&lt;p&gt;Most prep platforms organise problems by data structure (&lt;code&gt;Hash Table&lt;/code&gt;, &lt;code&gt;Tree&lt;/code&gt;, &lt;code&gt;Graph&lt;/code&gt;), which buries the company-level signal entirely. The more useful filter is patterns by company tags. Once you can answer "which 6-8 patterns has my target company asked across 450+ problems," the allocation question gets concrete and fast.&lt;/p&gt;

&lt;p&gt;I wrote a longer version with the per company breakdown and the FAQ on &lt;a href="https://www.codeintuition.io/blogs/amazon-vs-google-coding-interview-dsa" rel="noopener noreferrer"&gt;my own blog&lt;/a&gt; covering the patterns each company genuinely deemphasises (Google's quiet skip on Queue Design, Apple's near absence of advanced design).&lt;/p&gt;

&lt;p&gt;If you've interviewed at more than one of these five companies, which pattern came up that you weren't expecting based on the company's reputation?&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>leetcode</category>
      <category>career</category>
      <category>faang</category>
    </item>
    <item>
      <title>The Amazon Bar Raiser Isn't Testing Your Solution. It's Testing the Follow Up.</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Mon, 11 May 2026 14:32:57 +0000</pubDate>
      <link>https://dev.to/codeintuition/the-amazon-bar-raiser-isnt-testing-your-solution-its-testing-the-follow-up-fpn</link>
      <guid>https://dev.to/codeintuition/the-amazon-bar-raiser-isnt-testing-your-solution-its-testing-the-follow-up-fpn</guid>
      <description>&lt;p&gt;You're 15 minutes into an Amazon coding round. You've identified the pattern, your solution handles the examples, you're about to submit. Then the interviewer changes a constraint. Sorted input is now unsorted. The complexity ceiling drops from &lt;code&gt;O(n log n)&lt;/code&gt; to &lt;code&gt;O(n)&lt;/code&gt;. Your original solution collapses, and the rest of the round is now about whether you can reconstruct the approach from the invariant rather than from memory.&lt;/p&gt;

&lt;p&gt;That moment, the constraint shift after the working solution, is where the Bar Raiser's evaluation lives. Most prep treats it as a bonus question. The Bar Raiser treats it as the round.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Amazon's Bar Raiser is an independent interviewer with veto power who tests the same problems as a regular round but probes the follow up much harder. Two things make Amazon prep different from Google or Meta prep: the pattern coverage is broader (eleven plus pattern families vs. six or seven), and the round's evaluation centres on whether you can adapt your solution after a constraint changes, not on whether you can produce the optimal solution first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What the Bar Raiser is, in one paragraph
&lt;/h2&gt;

&lt;p&gt;Amazon's Bar Raiser is an interviewer pulled from outside the hiring team. Their job is to evaluate the candidate against Amazon's company wide hiring bar, independent of how badly the team wants to fill the seat. They can veto a hire that everyone else approved. They can also push a borderline candidate over the line if they see genuine depth.&lt;/p&gt;

&lt;p&gt;In practice, you don't know which of your interviewers is the Bar Raiser. Amazon keeps that ambiguous through the loop. What you do know is that at least one round will be a Bar Raiser round, and that round usually includes a coding problem.&lt;/p&gt;

&lt;p&gt;The problem itself looks the same as any other Amazon technical screen. The difference is what happens after you produce a working answer. A standard interviewer often moves on once you've solved it. A Bar Raiser keeps going. They'll change a constraint, ask you to optimise, ask why your solution is correct (not just whether it works), or walk you through the failure case you didn't test.&lt;/p&gt;

&lt;p&gt;The initial solution gets you to the conversation. The follow up is the conversation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The solution gets you into the round. The adaptation decides the round.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Amazon's pattern coverage is wider
&lt;/h2&gt;

&lt;p&gt;Every FAANG company has favourite pattern categories. Google leans heavily on predicate search (binary search on the answer space). Meta concentrates on sliding window and design. Amazon doesn't have a tight central focus the way those two do. Its problem set spans counting, fixed and variable sliding window, prefix sum, LRU style design, randomised set design, binary search and its 2D / staircase variants, queue design, and backtracking.&lt;/p&gt;

&lt;p&gt;That's eleven plus pattern families versus roughly seven or eight for Google and six or seven for Meta.&lt;/p&gt;

&lt;p&gt;What this means for prep is concrete: the strategy of going deep on two or three pattern families and hoping the round lands in your zone has a lower hit rate at Amazon. A Google candidate who deeply understands predicate search, counting, and graphs covers a meaningful fraction of what they'll see. An Amazon candidate who goes equally deep on three families has blind spots across the other eight.&lt;/p&gt;

&lt;p&gt;The Bar Raiser amplifies this. Because they're not bound to the hiring team's domain, they can pull a problem from any of those families. If the round happens to land on a category you skipped, the safety net of "the team usually asks X" doesn't catch you.&lt;/p&gt;

&lt;p&gt;The broader your pattern coverage, the smaller the chance any single Bar Raiser pick puts you in unfamiliar territory. Going deep matters too, but at Amazon the breadth axis carries more weight than at the narrower companies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the follow up is testing
&lt;/h2&gt;

&lt;p&gt;The interviewer isn't asking the follow up to be cruel. They're testing whether you understood the mechanism of your solution or just the solution itself. If your initial answer was a recalled template for the problem, the constraint change exposes that. If your initial answer was constructed from an invariant, the constraint change is something you can adapt to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Strong candidates optimise from understanding. Weak candidates optimise from memory.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A worked example makes this concrete. LRU Cache is one of Amazon's most reliably tested problems. The standard solution composes a hash map (&lt;code&gt;O(1)&lt;/code&gt; key lookup) with a doubly linked list (&lt;code&gt;O(1)&lt;/code&gt; insert and removal at any node when you have the node reference). The hash map maps key to list node. Operations look like:&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;class&lt;/span&gt; &lt;span class="nc"&gt;LRUCache&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;                     &lt;span class="c1"&gt;# key -&amp;gt; ListNode
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ListNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;            &lt;span class="c1"&gt;# most recently used end
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ListNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;            &lt;span class="c1"&gt;# least recently used end
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&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;key&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_add_to_front&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&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;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ListNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_add_to_front&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;lru&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lru&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lru&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the answer the Bar Raiser expects you to land. Now the follow up: each entry has a TTL. Reads on an expired entry must return &lt;code&gt;-1&lt;/code&gt;. The cache must still operate at expected &lt;code&gt;O(1)&lt;/code&gt; per call.&lt;/p&gt;

&lt;p&gt;The candidate who memorised LRU starts redesigning from zero. The candidate who built it from the invariant asks one question first: which invariant did the doubly linked list maintain, and does that invariant still hold under TTL?&lt;/p&gt;

&lt;p&gt;The list maintained an ordering invariant: nodes near the head were used more recently than nodes near the tail. Eviction removed the tail because tail was the least recently used. With TTL, the eviction criterion changes from "least recently used" to "expired or least recently used." The list ordering by recency is still useful for the second clause, but the first clause needs a different signal. Two choices land:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lazy expiry: on &lt;code&gt;get&lt;/code&gt;, if the node's expiry timestamp has passed, treat it as a miss, remove the node, return &lt;code&gt;-1&lt;/code&gt;. On &lt;code&gt;put&lt;/code&gt;, when the cache is full, evict the tail as before. Simple, keeps the data structure unchanged. Worst case has expired entries lingering until they're touched.&lt;/li&gt;
&lt;li&gt;Eager expiry: maintain a second ordered structure keyed by expiry time (a min heap or a second linked list ordered by expiry). On every &lt;code&gt;get&lt;/code&gt; or &lt;code&gt;put&lt;/code&gt;, sweep expired entries off the front of the expiry structure. More memory, tighter behaviour on memory pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Bar Raiser doesn't necessarily care which one you pick. They care whether you can articulate which invariant you're preserving and which one you're modifying. The candidate who answers "the recency ordering still holds, I'm adding an eviction predicate that checks expiry first" has demonstrated something about reasoning that solving twenty more problems wouldn't have.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three signals the round is graded on
&lt;/h2&gt;

&lt;p&gt;Three threads run through what separates a "hire" rating from a "no hire" in a Bar Raiser coding round, and they correspond to the three places the round lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clean reasoning under loose constraints.&lt;/strong&gt; The problem statement is often deliberately under specified. Inputs might or might not be sorted. Sizes might be zero. Negative numbers might appear. The candidate who immediately asks the clarifying questions, defines assumptions on a whiteboard area, and only then starts coding signals the kind of structured thinking Amazon values. Jumping into code without defining the problem space reads as someone operating from pattern memory rather than from understanding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adaptation across the follow up.&lt;/strong&gt; This is the LRU + TTL example above. The follow up isn't a new problem. It's the same problem with a constraint flipped. The candidate who can isolate which part of the previous solution depended on the changed constraint passes. The candidate who scraps and restarts shows that the previous solution was lookup, not construction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The follow-up is usually testing whether your first solution was constructed or recalled.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Articulation of why.&lt;/strong&gt; Bar Raisers listen for the explanation that goes past "I'm using a hash map here." They want to hear what invariant the hash map maintains, what would break with an array, what tradeoff you accepted. A candidate who can talk through the load bearing reasoning is a candidate who would raise the team's average performance, which is the literal evaluation Amazon writes down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four mistakes the round punishes
&lt;/h2&gt;

&lt;p&gt;These are not coding mistakes. They're prep mistakes that compound under the format.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solving silently.&lt;/strong&gt; You finish in fifteen minutes and present the answer. The Bar Raiser has nothing to evaluate beyond the output. Your reasoning made no observable trace. Narrate the structural choices as you go: why a hash map and not a sorted array, why an &lt;code&gt;O(n)&lt;/code&gt; first pass instead of trying for &lt;code&gt;O(log n)&lt;/code&gt;, what edge case you'll come back to. The round is graded on observable reasoning, not just output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chasing the optimal solution before the working one.&lt;/strong&gt; Some candidates spend twenty minutes thinking before writing a line, hunting for the optimal solution out of the gate. Then the follow up arrives at minute thirty five and there's no time for it. Get a correct &lt;code&gt;O(n^2)&lt;/code&gt; or &lt;code&gt;O(n log n)&lt;/code&gt; working first, confirm edge cases, then improve when the Bar Raiser asks. The follow up is part of the round's grade. Running out of time on it is a soft fail you didn't have to take.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating the follow up as a new problem.&lt;/strong&gt; When the constraint changes, the instinct is to discard the previous solution and start fresh. That's almost always wrong. The follow up is testing whether you understand the structural relationship between the constraint and your approach. Ask which part of your solution depended on the constraint that just changed, then modify only that part. If the original used binary search because the input was sorted and the follow up removes the sort guarantee, you change the search mechanism, not the algorithm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping edge cases until prompted.&lt;/strong&gt; Empty input, single element, duplicate values, integer overflow boundaries. Walking through two of these unprompted before the interviewer asks signals thoroughness and pre empts the follow up that targets the case you skipped. Reactive edge case handling reads as a candidate who optimises for the example and not for the input space.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this changes about how to prep
&lt;/h2&gt;

&lt;p&gt;The standard FAANG prep approach is to go deep on two or three pattern families and hope the interview lands in their zone. That works at companies with narrower coverage. For Amazon's breadth it leaves too many uncovered surfaces, and for the Bar Raiser in particular it leaves you with no recovery path if the chosen pattern isn't one you went deep on.&lt;/p&gt;

&lt;p&gt;Two adjustments help.&lt;/p&gt;

&lt;p&gt;The first is &lt;strong&gt;interleaving over blocking&lt;/strong&gt;. Block practice is a week on sliding window, a week on dynamic programming, a week on graphs. Interleaved practice rotates between pattern families inside the same study session. Cognitive science research on interleaved practice consistently shows that mixing problem types during study improves your ability to identify which pattern applies to a new problem. Mixed practice is harder while you're doing it (you can't lean on the priming of "this week is sliding window, every problem is sliding window") and that's the point. The harder identification work during practice is the same identification work the Bar Raiser's follow up will require.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;practising the follow up explicitly&lt;/strong&gt;. After every problem you solve, ask: what's the constraint change that would invalidate this solution most? Implement the modified version. The constraint change is the rep that builds the adaptation skill, and adaptation is what the Bar Raiser scores. If LRU Cache is the practice problem, "add TTL" is the rep. If sliding window with at most K distinct characters is the problem, "K varies based on the substring contents" is the rep.&lt;/p&gt;

&lt;p&gt;You can do both of these without any platform: a pattern list, a notebook of constraint flips per problem, and a habit of running the second pass before moving on. The reason most prep skips it is that there's no immediate signal it's working. The signal arrives in the round when the Bar Raiser shifts a constraint and you reach for the invariant instead of the template.&lt;/p&gt;

&lt;p&gt;If you're preparing for Amazon and your practice still stops after finding the optimal solution, you're missing the part the Bar Raiser actually evaluates.&lt;/p&gt;

&lt;p&gt;I wrote a longer breakdown covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon's highest frequency pattern families&lt;/li&gt;
&lt;li&gt;how the Bar Raiser evaluates coding rounds&lt;/li&gt;
&lt;li&gt;and the follow up drills that build adaptation under constraint shifts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.codeintuition.io/blogs/amazon-bar-raiser-coding-interview" rel="noopener noreferrer"&gt;Full breakdown here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The day of the loop
&lt;/h2&gt;

&lt;p&gt;A few logistical things candidates often get wrong, worth noting because they're outside your technical preparation but inside the Bar Raiser's evaluation surface.&lt;/p&gt;

&lt;p&gt;The onsite loop is typically four or five rounds across a single day or consecutive virtual sessions. One is system design for senior candidates. Two or three are coding rounds. One is behavioural, focused on the Leadership Principles. The Bar Raiser participates in at least one of these, and you won't know which until after the process ends.&lt;/p&gt;

&lt;p&gt;Coding rounds are about forty five minutes. Five to ten minutes go to introductions and the problem statement. Twenty five to thirty minutes go to the initial solution. The last ten to fifteen are where the follow up happens. If your first solution takes thirty five minutes because you were reaching for the optimal answer from the start, you've already lost the follow up window.&lt;/p&gt;

&lt;p&gt;Each round is evaluated independently. A weak round one doesn't doom you if rounds two and three are strong, and the Bar Raiser considers performance across the loop. Don't mentally rehearse what just happened in the breaks. Reset and walk into the next round.&lt;/p&gt;

&lt;p&gt;The debrief happens without you. All interviewers, including the Bar Raiser, meet afterward. If the hiring manager wants to extend an offer and the Bar Raiser objects, the Bar Raiser's veto stands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three months from now
&lt;/h2&gt;

&lt;p&gt;The Bar Raiser round isn't a fundamentally different format. It's the same problem solving test with a higher evaluation standard and broader pattern coverage. Two things move the needle for it: pattern breadth across families that gets exercised by interleaved practice, and the ability to adapt your solution when the constraint shifts in the follow up.&lt;/p&gt;

&lt;p&gt;I wrote a longer version with the full pattern breakdown across Amazon's tested families on &lt;a href="https://www.codeintuition.io/blogs/amazon-bar-raiser-coding-interview" rel="noopener noreferrer"&gt;my own blog&lt;/a&gt;, including how the loop's other rounds factor into the Bar Raiser's overall judgement.&lt;/p&gt;

&lt;p&gt;What's a follow up question from a real interview that changed how you think about the problem itself, not just the solution?&lt;/p&gt;

</description>
      <category>career</category>
      <category>leetcode</category>
      <category>codinginterview</category>
      <category>aws</category>
    </item>
    <item>
      <title>Arrays vs Linked Lists: Why Big-O Alone Doesn't Explain the Tradeoff</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Sun, 10 May 2026 10:58:45 +0000</pubDate>
      <link>https://dev.to/codeintuition/arrays-vs-linked-lists-why-big-o-alone-doesnt-explain-the-tradeoff-foa</link>
      <guid>https://dev.to/codeintuition/arrays-vs-linked-lists-why-big-o-alone-doesnt-explain-the-tradeoff-foa</guid>
      <description>&lt;p&gt;The complexity table for arrays and linked lists is the most memorised, least understood reference in interview prep. Arrays give &lt;code&gt;O(1)&lt;/code&gt; access. Linked lists give &lt;code&gt;O(1)&lt;/code&gt; insertion. That's the answer on every flashcard. The table doesn't lie. It's incomplete. The missing piece, once you have it, makes every entry in that table feel obvious.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Arrays and linked lists differ for one reason: contiguous memory vs scattered memory.&lt;/li&gt;
&lt;li&gt;Every entry in the complexity table follows from that single fact.&lt;/li&gt;
&lt;li&gt;Two &lt;code&gt;O(n)&lt;/code&gt; operations aren't equal in practice. A million element array scan can often run an order of magnitude faster than the equivalent linked list traversal because of cache locality.&lt;/li&gt;
&lt;li&gt;Linked lists win when your algorithm already holds a pointer to the mutation point. They lose when you have to traverse to find it first.&lt;/li&gt;
&lt;li&gt;The right question on any problem isn't "which has better Big-O?" It's "which operation does this algorithm perform most?"&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What the table tells you, and where it stops
&lt;/h2&gt;

&lt;p&gt;The standard table has six or seven rows: access by index, search, insert at start, insert at end, insert at known position, delete at known position, memory overhead. Each row has two cells. Most engineers memorise the cells and stop there.&lt;/p&gt;

&lt;p&gt;The table is correct. Arrays really do give you &lt;code&gt;O(1)&lt;/code&gt; index access. Linked lists really do give you &lt;code&gt;O(1)&lt;/code&gt; insertion at a known node. The cells aren't wrong. They describe outcomes without explaining why those outcomes happen, which means you can't generalise to a problem you haven't seen.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Memorising complexity tables teaches conclusions. Understanding memory layout teaches reasoning.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your interviewer asks "which would you use here?" on an unfamiliar problem, a memorised table doesn't help. You need the mechanism underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory layout is the root cause
&lt;/h2&gt;

&lt;p&gt;Both data structures store the same thing: a sequence of values. They differ in exactly one way: where those values live.&lt;/p&gt;

&lt;p&gt;An array is a single block of contiguous memory. Element &lt;code&gt;0&lt;/code&gt; sits at the base address. Element &lt;code&gt;1&lt;/code&gt; sits at &lt;code&gt;base + element_size&lt;/code&gt;. Element &lt;code&gt;i&lt;/code&gt; sits at &lt;code&gt;base + (i * element_size)&lt;/code&gt;. The address of any element is a single arithmetic expression. That arithmetic is what makes index access &lt;code&gt;O(1)&lt;/code&gt;. It's also what makes insertion in the middle &lt;code&gt;O(n)&lt;/code&gt;: to make room at index &lt;code&gt;2&lt;/code&gt; of a 5 element array, every element from index &lt;code&gt;2&lt;/code&gt; onward has to shift right.&lt;/p&gt;

&lt;p&gt;A linked list is a chain of nodes that can live anywhere in memory. Node &lt;code&gt;0&lt;/code&gt; might be at address 1000. Node &lt;code&gt;1&lt;/code&gt; at address 7500. Node &lt;code&gt;2&lt;/code&gt; at address 3200. The only way to find node &lt;code&gt;2&lt;/code&gt; is to start at node &lt;code&gt;0&lt;/code&gt; and follow the next pointers. That traversal is what makes index access &lt;code&gt;O(n)&lt;/code&gt;. It's also why insertion at a known node is &lt;code&gt;O(1)&lt;/code&gt;: you allocate a new node, point it at the next node, and update the previous node's pointer. Two pointer assignments. No shifting.&lt;/p&gt;

&lt;p&gt;Every entry in the complexity table is a consequence of these two facts. The math gives you the cells. The layout tells you why.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the same Big-O isn't the same: cache locality
&lt;/h2&gt;

&lt;p&gt;This is what the table can't show. Both arrays and linked lists are technically &lt;code&gt;O(n)&lt;/code&gt; for a full traversal. In practice, an array traversal of a million integers can often run an order of magnitude faster than the equivalent linked list traversal. Same Big-O. Very different wall clock time.&lt;/p&gt;

&lt;p&gt;The reason is the CPU cache.&lt;/p&gt;

&lt;p&gt;When the CPU reads element &lt;code&gt;0&lt;/code&gt; from an array, it doesn't fetch just that one integer. It loads an entire cache line, typically 64 bytes, into the L1 cache. For a 4 byte integer array, that single fetch pulls in the next 15 elements alongside &lt;code&gt;arr[0]&lt;/code&gt;. Iterating through them costs nanoseconds per access because they're already on chip. The CPU prefetcher notices the access pattern and starts loading the next cache line before you ask for it.&lt;/p&gt;

&lt;p&gt;Linked list nodes are scattered across the heap. Each &lt;code&gt;node.next&lt;/code&gt; dereference points at an unrelated address. Each pointer chase is a potential cache miss, and a cache miss is roughly a 100 nanosecond round trip to main memory. Multiply that by a million nodes and the gap is enormous.&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="c1"&gt;# Both loops are O(n). The constants differ by ~10x in practice.
&lt;/span&gt;
&lt;span class="c1"&gt;# Array: cache friendly contiguous reads
&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# next 15 ints already in L1 after the first read
&lt;/span&gt;    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;          

&lt;span class="c1"&gt;# Linked list: cache hostile pointer chases
&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="c1"&gt;# node.next probably triggers a cache miss
&lt;/span&gt;    &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;I keep noticing this is what surprises engineers most when they first see it. They've quoted &lt;code&gt;O(n)&lt;/code&gt; for traversal in both cases for years. The constant factor never came up because Big-O hides it.&lt;/p&gt;

&lt;p&gt;This is the reason &lt;code&gt;ArrayList&lt;/code&gt; in Java and Python's built in &lt;code&gt;list&lt;/code&gt; are array backed. You're paying the occasional &lt;code&gt;O(n)&lt;/code&gt; resize cost in exchange for cache friendly iteration on every operation between resizes. That trade is almost always worth it on real workloads, even ones that involve frequent appends.&lt;/p&gt;

&lt;p&gt;If you mention this in an interview, you've shown that you understand the hardware level, not just the math. Two candidates can both quote &lt;code&gt;O(n)&lt;/code&gt; for traversal. Only one knows that one of those &lt;code&gt;O(n)&lt;/code&gt;s is dramatically slower in practice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Big-O tells you how growth behaves. Hardware tells you what actually feels fast.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When linked lists actually win
&lt;/h2&gt;

&lt;p&gt;Honest credit where it's due: linked lists are the right structure in a narrow set of cases. Don't read the cache argument as "always pick arrays."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mutation at a known position:&lt;/strong&gt; if your algorithm already holds a pointer to the node where you're inserting or deleting, linked lists are &lt;code&gt;O(1)&lt;/code&gt; and arrays are &lt;code&gt;O(n)&lt;/code&gt;. The keyword is "already holds." The standard LeetCode example is &lt;code&gt;LRU Cache&lt;/code&gt;, where a hash map gives you &lt;code&gt;O(1)&lt;/code&gt; access to the relevant node and a doubly linked list gives you &lt;code&gt;O(1)&lt;/code&gt; reordering on every access. Neither structure alone solves the problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pointer only manipulation:&lt;/strong&gt; reversing a segment, splitting a list at a node, merging two sorted sequences. None of these require moving data, they're pure pointer reassignment. The &lt;a href="https://www.codeintuition.io/courses/singly-linked-list" rel="noopener noreferrer"&gt;singly linked list course&lt;/a&gt; on my own site covers seven distinct patterns built on this property.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No reallocation cost:&lt;/strong&gt; dynamic arrays double their capacity when full, which involves copying every element. Linked lists grow one node at a time. For genuinely unbounded streaming workloads with random insertion, the linked list's amortised cost can come out ahead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most frequent mistake I watch beginners make is reaching for a linked list the moment they see "insert" or "delete" in a problem, without checking whether they already hold a pointer to the position. Without that pointer, you traverse first (&lt;code&gt;O(n)&lt;/code&gt;) and then insert (&lt;code&gt;O(1)&lt;/code&gt;). The traversal eats the advantage. Total cost is &lt;code&gt;O(n)&lt;/code&gt;, same as the array shift, with worse constants because of cache misses.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;An &lt;code&gt;O(1)&lt;/code&gt; operation that requires &lt;code&gt;O(n)&lt;/code&gt; setup is still &lt;code&gt;O(n)&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to choose without memorising the table
&lt;/h2&gt;

&lt;p&gt;Once memory layout is your mental model, the choice becomes a small set of questions you can ask on any problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the algorithm need random access by index, or binary search? Pick an array. Linked lists can't do &lt;code&gt;O(log n)&lt;/code&gt; binary search because finding the middle requires traversal.&lt;/li&gt;
&lt;li&gt;Does the algorithm scan the whole structure repeatedly, where speed matters? Pick an array. Cache locality is doing real work in your favour.&lt;/li&gt;
&lt;li&gt;Does the algorithm hold direct pointers to nodes it mutates (head, tail, hash map mapped)? A linked list might genuinely win.&lt;/li&gt;
&lt;li&gt;Does the algorithm reverse, split, or merge segments by relinking? Linked lists are the natural fit.&lt;/li&gt;
&lt;li&gt;Does the algorithm need both random access and frequent reordering at known positions? You probably want both: a hash map of keys to linked list nodes. &lt;code&gt;LRU Cache&lt;/code&gt; is the canonical example.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Almost every interview problem involving a sequence of mutations is either an array problem (because access dominates) or a hash map plus linked list problem (because both operations matter). Pure linked list only problems are surprisingly rare in interview banks. They mostly show up when the problem itself is testing whether you can do pointer manipulation cleanly.&lt;/p&gt;

&lt;p&gt;An aside on the table itself: the complexity tables you see everywhere aren't useless, even if they're incomplete. They're a fast lookup once you know what's underneath. The point isn't to throw the table out. It's to stop treating it as the answer when it's the symptom.&lt;/p&gt;

&lt;p&gt;If Big-O tables still feel like facts you memorise rather than intuitions you can derive, I wrote a longer breakdown covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stacks and queues from the same memory model&lt;/li&gt;
&lt;li&gt;adjacency lists vs matrices&lt;/li&gt;
&lt;li&gt;and why some &lt;code&gt;O(n)&lt;/code&gt; operations feel dramatically faster in practice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.codeintuition.io/blogs/arrays-vs-linked-lists-difference" rel="noopener noreferrer"&gt;Full breakdown here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's a problem where you reached for a linked list and only realised mid implementation that an array would have been cleaner?&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>career</category>
      <category>computerscience</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Am I Ready for FAANG? A Better Test Than Solving More LeetCode</title>
      <dc:creator>Prakhar Srivastava</dc:creator>
      <pubDate>Sat, 09 May 2026 12:40:03 +0000</pubDate>
      <link>https://dev.to/codeintuition/am-i-ready-for-faang-a-better-test-than-solving-more-leetcode-im9</link>
      <guid>https://dev.to/codeintuition/am-i-ready-for-faang-a-better-test-than-solving-more-leetcode-im9</guid>
      <description>&lt;p&gt;You've solved 200 problems. Mediums you've already seen take fifteen minutes. The next one you haven't seen freezes you cold inside of five. And every time you ask yourself if you're ready for a FAANG loop, the honest answer is "I don't know."&lt;/p&gt;

&lt;p&gt;The "I don't know" isn't a feeling problem. It's that you've been asking a feelings question about a performance outcome.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Self assessed readiness is unreliable because confidence swings with every session.&lt;/li&gt;
&lt;li&gt;Solving a familiar problem and constructing a solution to a novel one are different skills.&lt;/li&gt;
&lt;li&gt;The fix is a measurable test: an unseen medium in a pattern family you've studied, under timed conditions, with a hard cap on execution attempts.&lt;/li&gt;
&lt;li&gt;Pass three of those across three different families and you likely have the level of transfer real interviews actually reward. Fail one and you know exactly where to work.&lt;/li&gt;
&lt;li&gt;The signal isn't the count. It's whether the recognition holds when the problem name is hidden.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why "do I feel ready" is the wrong question
&lt;/h2&gt;

&lt;p&gt;Solving a problem you've seen before and reasoning your way through one you haven't are different skills. Practice on the same patterns repeatedly builds recognition. Recognition only fires on shapes you've encountered. The interview gives you a shape you haven't, and asks you to construct the approach from the constraints alone.&lt;/p&gt;

&lt;p&gt;That's the gap between &lt;a href="https://en.wikipedia.org/wiki/Transfer_of_learning" rel="noopener noreferrer"&gt;near transfer and far transfer&lt;/a&gt;. Near transfer is what 300 problems will buy you, applying what you've practised to similar setups. Far transfer is what FAANG selects for, applying what you've understood to genuinely new ones.&lt;/p&gt;

&lt;p&gt;There's a second issue with self assessed confidence. It swings with your last session. Crush five tree problems on Saturday morning and you feel ready. Freeze on an unfamiliar graph problem on Saturday afternoon and the confidence evaporates. Neither data point reflects your stable ability across the families an interview actually draws from.&lt;/p&gt;

&lt;p&gt;The result is a loop. You prep, you feel uncertain, you prep more, you still feel uncertain. The method of evaluation is wrong. You're asking a feelings question about a performance outcome.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Interview readiness isn't confidence. It's repeatable performance under unfamiliar conditions.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The three pattern family test
&lt;/h2&gt;

&lt;p&gt;Readiness is a performance threshold you can measure. The protocol takes about two hours and produces a binary answer.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick an unseen medium from a family you've studied.&lt;/strong&gt; Sliding window, tree traversal, graph BFS, DP subsequence. The problem has to be genuinely novel. You haven't solved it, browsed its discussion thread, or read hints for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solve it under real interview constraints.&lt;/strong&gt; Twenty minute timer. The problem name covered or aliased so you can't reverse engineer the family from the title. No hints. A hard cap on code execution attempts so you can't trial and error your way through. You have to identify the approach, build it, and trace it before running code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeat across two more families you've studied but haven't over practised.&lt;/strong&gt; One pass isn't signal. Three passes across different families confirms the readiness is broad, not narrow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pass means: solve within twenty minutes with fewer than two failed execution attempts. Anything else is useful data.&lt;/p&gt;

&lt;p&gt;If you pass three across different families and you likely have the level of transfer real interviews actually reward. If you fail one, you know precisely where to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that looks like on graph BFS
&lt;/h2&gt;

&lt;p&gt;Pick the worst case for this exercise: a medium graph BFS problem you haven't seen. The constraints describe a grid, an adjacency list, or some traversal where shortest distance is the answer.&lt;/p&gt;

&lt;p&gt;Two minutes in, you've identified the family. Not from the problem title because that's hidden. From the constraints: shortest distance, unweighted edges, layered exploration. That recognition came from training what makes BFS the right approach, not from spotting "BFS" in the problem name.&lt;/p&gt;

&lt;p&gt;The solution builds from BFS's invariant. At any moment, the queue contains every node whose shortest distance from the source is exactly the distance you're currently processing. You aren't recalling "this one used a deque." You're reasoning: enqueue the start at distance zero, expand neighbours level by level, return as soon as you pop the target.&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shortest_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;([(&lt;/span&gt;&lt;span class="n"&gt;start&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="n"&gt;visited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&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;node&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;target&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;dist&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;nbr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&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;nbr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nbr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;queue&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;nbr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dist&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;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before you run anything, you trace it on a four node example. Walk the queue. Check &lt;code&gt;visited&lt;/code&gt;. Verify the returned distance matches what you'd expect for a path you can see in your head. The mental dry run catches bugs the random test and submit loop misses, and it's the exact behaviour interviewers watch for: verifying correctness without leaning on the compiler.&lt;/p&gt;

&lt;p&gt;You submit. It passes. Eight minutes still on the timer. Not because you rushed, but because identification took two minutes instead of fifteen, and the construction followed the invariant rather than trial and error.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Recognition under pressure matters more than recall in comfort.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is what FAANG ready looks like. Not "I feel confident." A repeatable, observable performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  A second pass on a different family
&lt;/h2&gt;

&lt;p&gt;Now you change family. Pick a variable sliding window problem you haven't seen. The constraint shape: a contiguous range over an array or string, a flexible boundary that grows and shrinks, an objective that asks for the longest, shortest, or maximum window meeting some condition.&lt;/p&gt;

&lt;p&gt;The recognition again happens within the first three minutes, before any code. The constraints match the variable sliding window's three triggers, you can name the invariant the window has to maintain, and you write the same expand then contract skeleton you'd write for any problem in the family.&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;variable_window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;init_state&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;right&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;left&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;return&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You fill in &lt;code&gt;init_state&lt;/code&gt;, &lt;code&gt;include&lt;/code&gt;, &lt;code&gt;exclude&lt;/code&gt;, and &lt;code&gt;valid&lt;/code&gt; for the specific problem. The skeleton stays the same. That's the marker of a pattern that's actually generalised in your head: you write the skeleton first, then specialise.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When a pattern generalises, you stop memorising solutions and start specialising frameworks.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you pass this one too, you've got two of three. One more, on a third family you haven't over practised, decides it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you fail the test
&lt;/h2&gt;

&lt;p&gt;Most engineers don't pass all three on the first attempt. That's expected. A clean three for three on the first try usually means the families were too comfortable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One family failed.&lt;/strong&gt; You know the pattern at a surface level but haven't internalised the identification triggers or the construction skeleton. Go back to the foundational material for that family. Don't just grind more problems in it. Study what makes the pattern applicable, the constraint combinations that point to it, the invariant every problem in the family shares. Once you can articulate that without notes, retest with a different unseen problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two families failed.&lt;/strong&gt; You likely have one strong area where you've over practised and shallow gaps everywhere else. Common for engineers who spent months on arrays or trees because the work felt productive. Broaden the coverage. Spend focused time on the families where the understanding is thin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All three failed.&lt;/strong&gt; The preparation has been building near transfer without building far transfer. That's a method gap, not a talent gap. Shift from solving high volumes to studying fewer problems more deeply. Focus on identification and constraint analysis rather than just reaching a correct solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One catch. Don't retake the test with the same problems. A retest on a problem you've already seen, even if you failed it, measures recall instead of reasoning. Find a different unseen problem in the same family.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four signals most engineers use instead
&lt;/h2&gt;

&lt;p&gt;Before the test, it helps to name the signals you've probably been using, and why each one lies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem count.&lt;/strong&gt; Tells you nothing about how the problems were solved. Someone at 120 problems with genuine pattern depth outperforms someone at 400 who relied on hints for half of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topic completion.&lt;/strong&gt; You finished sliding window two months ago and haven't touched it since. Completion isn't retention. Spacing matters. The performance you had on week three doesn't survive without revisits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed on familiar problems.&lt;/strong&gt; Two Sum in two minutes feels like fluency. It's actually retrieval of a stored solution. The moment a novel problem looks similar but has different constraints, the speed evaporates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Peer comparison.&lt;/strong&gt; Your friend got into Google in six months. That ignores their background, their pattern coverage, how they practised, and what level they interviewed for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The three family test bypasses all four. It doesn't care about the count, the completion checkmarks, the recall speed, or anyone else's timeline. It measures one thing: can you construct a solution to a novel problem, under pressure, across families.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the conditions yourself
&lt;/h2&gt;

&lt;p&gt;The hardest part of the test is replicating real interview conditions. Solving at your desk, documentation a tab away, with the timer optional, doesn't replicate a forty five minute FAANG round.&lt;/p&gt;

&lt;p&gt;What you actually need: a source of unseen mediums in the families you've studied (the &lt;a href="https://www.codeintuition.io/courses/array/tFob_VIXJQJ8bUN3hlSMB" rel="noopener noreferrer"&gt;variable sliding window lesson&lt;/a&gt; covers one family if you haven't been through it before), a way to hide the problem name (a friend covering it, or a browser extension that aliases the title), a kitchen timer set to twenty minutes, and the discipline to stop after two failed runs. The conditions matter. The test fails the moment you peek at hints or let the timer slide.&lt;/p&gt;

&lt;p&gt;I keep noticing the same two things across engineers who run this test for the first time. The ones who fail one family and immediately know why aren't far from ready, they're a couple of weeks of focused study away. The ones who fail all three and panic into more volume usually need to step away from the problem bank for a week and re read the foundational material on identification and invariants. The diagnostic is more useful than the score.&lt;/p&gt;

&lt;p&gt;If you're stuck in the “I've solved a lot but still don't know if I'm ready” phase, the problem usually isn't effort.&lt;/p&gt;

&lt;p&gt;It's measurement.&lt;/p&gt;

&lt;p&gt;I wrote a longer breakdown covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;per family readiness signals&lt;/li&gt;
&lt;li&gt;common failure patterns&lt;/li&gt;
&lt;li&gt;and what to fix when one family collapses under pressure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.codeintuition.io/blogs/am-i-ready-for-faang" rel="noopener noreferrer"&gt;Full breakdown here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's the specific moment you knew you weren't ready yet? A particular problem, a frozen minute in a mock, or the cumulative shape of practice that just felt off?&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>leetcode</category>
      <category>career</category>
      <category>interview</category>
    </item>
  </channel>
</rss>
