<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[The Coding Interview Gym: Reviews & Feedback]]></title><description><![CDATA[Annotated breakdowns of real coding interview solutions, highlighting what works, what doesn’t, and how an interviewer would see your approach. Focused written feedback so you improve every week.]]></description><link>https://paulepps.substack.com/s/reviews-and-feedback</link><image><url>https://substackcdn.com/image/fetch/$s_!sUyV!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd0dab41-b251-4b04-8deb-bc6622d47f9c_1024x1024.png</url><title>The Coding Interview Gym: Reviews &amp; Feedback</title><link>https://paulepps.substack.com/s/reviews-and-feedback</link></image><generator>Substack</generator><lastBuildDate>Tue, 12 May 2026 21:06:02 GMT</lastBuildDate><atom:link href="https://paulepps.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Paul Epps]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[paulepps@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[paulepps@substack.com]]></itunes:email><itunes:name><![CDATA[Paul Epps]]></itunes:name></itunes:owner><itunes:author><![CDATA[Paul Epps]]></itunes:author><googleplay:owner><![CDATA[paulepps@substack.com]]></googleplay:owner><googleplay:email><![CDATA[paulepps@substack.com]]></googleplay:email><googleplay:author><![CDATA[Paul Epps]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Reader Solutions Reviewed: Subarray Sum]]></title><description><![CDATA[Canonical solution and common mistakes]]></description><link>https://paulepps.substack.com/p/reader-solutions-reviewed-subarray</link><guid isPermaLink="false">https://paulepps.substack.com/p/reader-solutions-reviewed-subarray</guid><dc:creator><![CDATA[Paul Epps]]></dc:creator><pubDate>Fri, 08 May 2026 04:23:29 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="5472" height="3648" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3648,&quot;width&quot;:5472,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;people sitting on bench near building during night time&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="people sitting on bench near building during night time" title="people sitting on bench near building during night time" srcset="https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1629119882664-a78b644debbe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw3fHxzb2x1dGlvbnMlMjByZXZpZXdlZHxlbnwwfHx8fDE3NzgyMTM3ODh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@introspectivedsgn">Erik Mclean</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>Problem recap</h2><p>You&#8217;re given an integer array <code>nums</code> and an integer <code>k</code>.<br>Return the <strong>number</strong> of <strong>contiguous</strong> subarrays whose sum is exactly <code>k</code>.</p><h3>Example</h3><ul><li><p><code>nums = [1, 2, 3, -2, 5]</code>, <code>k = 3</code></p></li></ul><p>There are 2 valid subarrays: <code>[1, 2]</code>, <code>[3].</code> </p><div><hr></div><h2>Approach 1: Brute force, but with a running sum</h2><p>The na&#239;ve approach is &#8220;all subarrays, sum each,&#8221; which is <em>O(n^3)</em>, but you can upgrade that to <em>O(n^2)</em> by carrying a running sum inside the inner loop.</p><ul><li><p>Fix a start index <code>i</code>.</p></li><li><p>Walk <code>j</code> from <code>i</code> to the end, maintaining <code>current_sum += nums[j]</code>.</p></li><li><p>Each time <code>current_sum == k</code>, increment a counter.</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;a8c5baee-4211-42a2-b3f3-ac3c0ac51091&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from typing import List

def subarray_sum_bruteforce(nums: List[int], k: int) -&gt; int:
    count = 0
    n = len(nums)
    for i in range(n):
        current_sum = 0
        for j in range(i, n):
            current_sum += nums[j]
            if current_sum == k:
                count += 1
    return count</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;c209672a-799f-49d8-a7d3-435e21d61048&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public int subarraySumBruteForce(int[] nums, int k) {
    int count = 0;
    for (int i = 0; i &lt; nums.length; i++) {
        int currentSum = 0;
        for (int j = i; j &lt; nums.length; j++) {
            currentSum += nums[j];
            if (currentSum == k) {
                count++;
            }
        }
    }
    return count;
}</code></pre></div><p>Talking points:</p><ul><li><p>Time: <em>O(n^2)</em>, space: <em>O(1)</em>.</p></li><li><p>Works fine with negatives because it does not rely on any ordering property.</p></li></ul><p><strong>Before reading on, can you think of a way to reuse some of this work to get to linear time?</strong></p><div><hr></div><h2>Approach 2: The &#8220;expanding&#8211;shrinking window&#8221; that breaks with negatives</h2><p>This is the most common incorrect approach.</p><p>The <strong>sliding window with two pointers</strong> works when all numbers are non&#8209;negative:</p><ul><li><p>Keep <code>start</code>, <code>end</code>, and <code>current_sum</code> over <code>nums[start:end)</code>.</p></li><li><p>Expand <code>end</code> while <code>current_sum &lt; k</code>.</p></li><li><p>Shrink from <code>start</code> while <code>current_sum &gt; k</code>.</p></li><li><p>Check for <code>current_sum == k</code> along the way.</p></li></ul><p>This relies critically on the fact that when all numbers are non&#8209;negative:</p><ul><li><p>Increasing the window (move <code>end</code> right) never decreases the sum.</p></li><li><p>Decreasing the window (move <code>start</code> right) never increases the sum.</p></li></ul><p>So you never &#8220;miss&#8221; a candidate by shrinking or expanding.</p><p>Here&#8217;s an <strong>&#8220;only works when all elements are non&#8209;negative&#8221;</strong> cautionary example:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;c8f68b36-853f-4470-a3d0-cd46d5710279&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">// WARNING: Only correct if all nums[i] &gt;= 0
public int subarraySumSlidingWindowNonNegative(int[] nums, int k) {
    int start = 0, currentSum = 0, count = 0;

    for (int end = 0; end &lt; nums.length; end++) {
        currentSum += nums[end];

        while (start &lt;= end &amp;&amp; currentSum &gt; k) {
            currentSum -= nums[start++];
        }

        if (currentSum == k) {
            count++;
        }
    }
    return count;
}</code></pre></div><h3>Why this fails with negatives</h3><p>Give a concrete counterexample:</p><ul><li><p><code>nums = [3, 4, -2]</code>, <code>k = 5</code>.</p></li></ul><p>Correct subarray is <code>[3, 4, -2]</code>. The window algorithm may shrink too early when <code>current_sum &gt; k</code>, but the later negative value could have brought it back down to <code>k</code>. </p><p>Once you discard elements from the left, you cannot recover that subarray. The key invariant &#8220;sum only moves in one direction when expanding or shrinking&#8221; is broken by negative numbers.</p><p><strong>General rule:</strong></p><ul><li><p>Any time your algorithm decides &#8216;I can safely skip all longer windows starting here because the sum is already too big/small,&#8217; it assumes monotonicity. Negatives destroy that.</p></li></ul><p><strong>If sliding window doesn&#8217;t work with negatives, what property must the real solution exploit instead?</strong></p><div><hr></div><h2>Approach 3: Prefix sums + hash map (the correct <em>O(n)</em> running-sum solution)</h2><p><strong>Approach 1</strong> is not wrong, just suboptimal. </p><p>You&#8217;ve discovered one kind of running sum. The final step is to recognize that you don&#8217;t actually need to restart the sum at every i. You can reuse the same running prefix sum for all i simultaneously if you store counts in a map.</p><ul><li><p>Let <code>prefix[i]</code> be the sum of <code>nums[0..i]</code>.</p></li><li><p>The sum of subarray <code>nums[l..r]</code> is <code>prefix[r] - prefix[l-1]</code>.</p></li><li><p>For a subarray ending at index <code>i</code> to have sum <code>k</code>, you need some earlier prefix sum <code>prefix[j]</code> such that:</p></li></ul><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;prefix[i] - prefix[j] = k\n  \\Rightarrow prefix[j] = prefix[i] - k&quot;,&quot;id&quot;:&quot;DVILVEKSMB&quot;}" data-component-name="LatexBlockToDOM"></div><p></p><p>So as you scan from left to right while maintaining a <strong>running prefix sum</strong>, you only need to know:</p><ul><li><p>How many times have you seen the value <code>currentSum - k</code> before?</p></li></ul><p>That count equals the number of subarrays ending at the current index whose sum is <code>k</code>.</p><p>This works with negative numbers, positive numbers, and any mix, because it does not rely on monotonicity, only on the algebraic relationship between prefix sums.</p><p><strong>Key implementation detail:</strong></p><ul><li><p>Initialize the map with <code>sum_frequency[0] = 1</code> so that when <code>currentSum == k</code>, you correctly count subarrays starting at index 0.</p></li></ul><h2>Canonical solutions</h2><p>You can walk through <code>nums = [1, 2, 3, -2, 5]</code>, <code>k = 3</code> step&#8209;by&#8209;step with any of the following solutions to build intuition. </p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;989c48c0-ddad-48fd-a663-8f781534d9d9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from typing import List
from collections import defaultdict

def subarray_sum(nums: List[int], k: int) -&gt; int:
    freq = defaultdict(int)
    freq[0] = 1  # empty prefix

    current_sum = 0
    count = 0

    for x in nums:
        current_sum += x

        # number of previous prefixes we can pair with current_sum to get k
        need = current_sum - k
        count += freq[need]

        # record current prefix sum for future elements
        freq[current_sum] += 1

    return count </code></pre></div><h2>Java</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;ac65604d-2124-4922-bdcd-e72a91948ab7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">import java.util.HashMap;
import java.util.Map;

public int subarraySum(int[] nums, int k) {
    Map&lt;Integer, Integer&gt; freq = new HashMap&lt;&gt;();
    freq.put(0, 1);  // empty prefix

    int currentSum = 0;
    int count = 0;

    for (int x : nums) {
        currentSum += x;

        int need = currentSum - k;
        count += freq.getOrDefault(need, 0);

        freq.put(currentSum, freq.getOrDefault(currentSum, 0) + 1);
    }

    return count;
}</code></pre></div><h2>C#</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;dbaa02a1-5954-4db0-907f-851e3e3d8aed&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">using System.Collections.Generic;

public int SubarraySum(int[] nums, int k) {
    var freq = new Dictionary&lt;int, int&gt;();
    freq[0] = 1;  // empty prefix

    int currentSum = 0;
    int count = 0;

    foreach (int x in nums) {
        currentSum += x;

        int need = currentSum - k;
        if (freq.TryGetValue(need, out int occ)) {
            count += occ;
        }

        if (freq.ContainsKey(currentSum)) {
            freq[currentSum]++;
        } else {
            freq[currentSum] = 1;
        }
    }

    return count;
}</code></pre></div><h2>TypeScript</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;ddeeb78d-d731-4f05-b40d-47ae84d80171&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">export function subarraySum(nums: number[], k: number): number {
    const freq = new Map&lt;number, number&gt;();
    freq.set(0, 1);  // empty prefix

    let currentSum = 0;
    let count = 0;

    for (const x of nums) {
        currentSum += x;

        const need = currentSum - k;
        if (freq.has(need)) {
            count += freq.get(need)!;
        }

        freq.set(currentSum, (freq.get(currentSum) ?? 0) + 1);
    }

    return count;
} </code></pre></div><p><strong>Complexity talking point:</strong> one pass, constant-time hash operations &#8594; <em>O(n)</em> time, <em>O(n)</em> space in the worst case.</p><div><hr></div><h2>&#8220;Running sum&#8221; vs &#8220;running window&#8221;</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GpRe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GpRe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png 424w, https://substackcdn.com/image/fetch/$s_!GpRe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png 848w, https://substackcdn.com/image/fetch/$s_!GpRe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png 1272w, https://substackcdn.com/image/fetch/$s_!GpRe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GpRe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png" width="1456" height="565" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:565,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120988,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://paulepps.substack.com/i/196859288?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GpRe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png 424w, https://substackcdn.com/image/fetch/$s_!GpRe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png 848w, https://substackcdn.com/image/fetch/$s_!GpRe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png 1272w, https://substackcdn.com/image/fetch/$s_!GpRe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e975d0d-81b7-44d6-a333-b84a7564c5f3_1464x568.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p>&#8220;Running sum&#8221; just means you keep a cumulative total as you iterate; both the <em>O(n^2)</em> brute force and the <em>O(n)</em> prefix-sum+map use a running sum.</p></li><li><p>The flawed pattern is not &#8220;running sum,&#8221; it is &#8220;sliding window that assumes &#8216;if my sum is already too big/small, I can discard this start index forever&#8217;.&#8221;</p></li></ul><div><hr></div><h2>Other common reader mistakes</h2><h3>Using a set instead of counting frequencies</h3><p>Some people get to the prefix-sum idea but stop at &#8220;I just need to know if <code>currentSum - k</code> exists in some set of previous sums.&#8221; That only tells you whether <strong>at least one</strong> subarray ending here sums to <code>k</code>.</p><p>But this problem asks for the <strong>count</strong> of subarrays. There may be multiple previous indices <code>j1</code>, <code>j2</code>, &#8230; with the same prefix sum <code>currentSum - k</code>, and each corresponds to a distinct subarray ending at the current index.</p><p>So a common bug is:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;41d5a188-668f-4221-aadd-c0938bc64aea&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">seen = set()
seen.add(0)
current_sum = 0
count = 0
for x in nums:
    current_sum += x
    if current_sum - k in seen:
        count += 1          # BUG: only adds 1 even if there are multiple matches
    seen.add(current_sum)</code></pre></div><p>The fix is to store <strong>frequencies</strong> and add <code>freq[currentSum - k]</code> rather than just 1.</p><div><hr></div><h3>Forgetting to seed sum 0 in the map</h3><p>Any correct prefix-sum + hash&#8209;map solution needs:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;3828eded-a9d1-491f-85f1-2715dcd73668&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">freq = {0: 1}  # or equivalent in Java/C#/TS</code></pre></div><p>right before the loop.</p><p>A subtle, off&#8209;by&#8209;one&#8209;ish bug: initialize an empty map and only start recording prefix sums inside the loop.</p><p>What goes wrong:</p><ul><li><p>Consider subarrays that start at index 0.</p></li><li><p>For such a subarray ending at index <code>i</code>, you have <code>prefix[i] == k</code>.</p></li><li><p>In the map logic, you&#8217;re looking for <code>prefix[i] - k == 0</code>.</p></li><li><p>If you never seeded <code>sum_frequency[0] = 1</code>, you&#8217;ll miss those subarrays entirely.</p></li></ul><div><hr></div><div class="callout-block" data-callout="true"><p><strong>If you&#8217;re a paid subscriber</strong>, you also get a weekly long-form post and the option to request a private resume/LinkedIn review.</p><p>Next week&#8217;s long-form post is about strings and anagrams: sorted key vs. frequency tuple, prime product hashing trick, and interview traps.</p><p><strong>Not paid yet? You can upgrade here:</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe&quot;,&quot;text&quot;:&quot;Upgrade to paid&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://paulepps.substack.com/subscribe"><span>Upgrade to paid</span></a></p></div><div><hr></div><p><em>&#169; The Coding Interview Gym | paulepps.substack.com</em></p>]]></content:encoded></item><item><title><![CDATA[Reader Solutions Reviewed: Sliding Window]]></title><description><![CDATA[Challenge: Longest Substring Without Repeating Characters]]></description><link>https://paulepps.substack.com/p/reader-solutions-reviewed-sliding</link><guid isPermaLink="false">https://paulepps.substack.com/p/reader-solutions-reviewed-sliding</guid><dc:creator><![CDATA[Paul Epps]]></dc:creator><pubDate>Fri, 01 May 2026 03:01:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!AeaZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AeaZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AeaZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!AeaZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!AeaZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!AeaZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AeaZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png" width="800" height="450" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:597972,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://paulepps.substack.com/i/196059764?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AeaZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!AeaZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!AeaZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!AeaZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3d6f8a2e-5dbb-4e93-8c52-1dcdd252835d_800x450.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Problem recap</h2><p><strong>Interview question</strong><br>Given a string <code>s</code>, return the length of the longest substring without repeating characters.</p><p>Examples:</p><ul><li><p><code>s = "abcabcbb"</code> &#8594; <code>3</code> (answer is <code>"abc"</code>)</p></li><li><p><code>s = "bbbbb"</code> &#8594; <code>1</code> (answer is <code>"b"</code>)</p></li><li><p><code>s = "pwwkew"</code> &#8594; <code>3</code> (answer is <code>"wke"</code>)</p></li></ul><p>Constraints typically look like:</p><ul><li><p><code>1 &lt;= s.length &lt;= 5 * 10^4</code></p></li><li><p>Characters are usually standard ASCII; LeetCode&#8217;s version allows spaces, punctuation, etc.</p></li></ul><div><hr></div><h2>Baseline idea: sliding window over a string</h2><p>Naive thinking says: enumerate all substrings and check them one by one. That&#8217;s <em>O(n^2)</em> substrings and <em>O(n)</em> to check each, which is dead on arrival.</p><p>Instead, this problem is a textbook <strong>variable-size sliding window</strong>:</p><ul><li><p>Maintain a window <code>[left, right]</code> that always has <strong>no duplicates</strong>.</p></li><li><p>Expand <code>right</code> one character at a time.</p></li><li><p>If the new character makes the window invalid (duplicate), move <code>left</code> forward until the window is valid again.</p></li><li><p>Track the best window length seen so far.</p></li></ul><p>All of the implementations below share this same mental model; they only differ in <em>how</em> they detect and fix duplicates (array vs. hash map vs. set).</p><div><hr></div><h2>Approach 1: Hash map frequency (clean, general-purpose)</h2><p>This is the version I&#8217;d expect a strong candidate to reach first in an interview: use a <strong>hash map from character &#8594; count</strong> to track what&#8217;s inside the window.</p><h2>Python (hash map)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;9b00ec5b-ca4c-4adc-9e75-9e116aa8946b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def length_of_longest_substring(s: str) -&gt; int:
    freq = {}          # char -&gt; count in current window
    left = 0
    best = 0

    for right, ch in enumerate(s):
        freq[ch] = freq.get(ch, 0) + 1

        # If ch is now duplicated, shrink from the left
        while freq[ch] &gt; 1:
            left_ch = s[left]
            freq[left_ch] -= 1
            if freq[left_ch] == 0:
                del freq[left_ch]
            left += 1

        best = max(best, right - left + 1)

    return best</code></pre></div><h2>Java (hash map)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;5acd3e6c-1687-4796-aa7d-0d06c97d2e9a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public int lengthOfLongestSubstring(String s) {
    Map&lt;Character, Integer&gt; freq = new HashMap&lt;&gt;();
    int left = 0;
    int best = 0;

    for (int right = 0; right &lt; s.length(); right++) {
        char c = s.charAt(right);
        freq.put(c, freq.getOrDefault(c, 0) + 1);

        while (freq.get(c) &gt; 1) {
            char leftChar = s.charAt(left);
            freq.put(leftChar, freq.get(leftChar) - 1);
            if (freq.get(leftChar) == 0) {
                freq.remove(leftChar);
            }
            left++;
        }

        best = Math.max(best, right - left + 1);
    }

    return best;
}</code></pre></div><h2>C# (dictionary)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;a2209d7f-999b-4252-8793-5535ad917957&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public int LengthOfLongestSubstring(string s) {
    var freq = new Dictionary&lt;char, int&gt;();
    int left = 0;
    int best = 0;

    for (int right = 0; right &lt; s.Length; right++) {
        char c = s[right];
        freq[c] = freq.GetValueOrDefault(c) + 1;

        while (freq[c] &gt; 1) {
            char leftChar = s[left];
            freq[leftChar]--;
            if (freq[leftChar] == 0) {
                freq.Remove(leftChar);
            }
            left++;
        }

        best = Math.Max(best, right - left + 1);
    }

    return best;
}</code></pre></div><p><strong>Complexity</strong></p><ul><li><p>Time: <em>O(n)</em>, each character enters and leaves the window at most once.</p></li><li><p>Space: <em>O(k)</em> where <em>k</em> is the number of distinct characters in the window, <em>k</em>&#8804;128 for ASCII.</p></li></ul><p><strong>When this shines</strong></p><ul><li><p>You want a <strong>generic sliding window template</strong> that adapts to &#8220;at most K occurrences&#8221;, &#8220;at most K distinct characters&#8221;, etc.</p></li><li><p>You&#8217;re not sure about the character set (could be Unicode, emojis, etc.).</p></li></ul><div><hr></div><h2>Approach 2: Hash set + left pointer (simpler state)</h2><p>If all you care about is &#8220;no duplicates at all&#8221;, you don&#8217;t actually need counts&#8212;just <strong>membership</strong>. A hash set stores the characters in the current window.</p><h2>Python (set)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;4efc629e-3d57-44bd-acce-72df5a4ac596&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def length_of_longest_substring_set(s: str) -&gt; int:
    seen = set()
    left = 0
    best = 0

    for right, ch in enumerate(s):
        # If ch is already in the window, move left until it's gone
        while ch in seen:
            seen.remove(s[left])
            left += 1

        seen.add(ch)
        best = max(best, right - left + 1)

    return best</code></pre></div><h2>Java (HashSet)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;f3af8f39-a382-4622-8a2d-ef94bc917a1d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public int lengthOfLongestSubstringSet(String s) {
    Set&lt;Character&gt; seen = new HashSet&lt;&gt;();
    int left = 0;
    int best = 0;

    for (int right = 0; right &lt; s.length(); right++) {
        char c = s.charAt(right);

        while (seen.contains(c)) {
            seen.remove(s.charAt(left));
            left++;
        }

        seen.add(c);
        best = Math.max(best, right - left + 1);
    }

    return best;
}</code></pre></div><h2>C# (HashSet)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;4a7f41ad-ac72-426a-9d88-b1fd9bce28e1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public int LengthOfLongestSubstringSet(string s) {
    var seen = new HashSet&lt;char&gt;();
    int left = 0;
    int best = 0;

    for (int right = 0; right &lt; s.Length; right++) {
        char c = s[right];

        while (seen.Contains(c)) {
            seen.Remove(s[left]);
            left++;
        }

        seen.Add(c);
        best = Math.Max(best, right - left + 1);
    }

    return best;
}</code></pre></div><p><strong>Complexity</strong></p><ul><li><p>Time: <em>O(n)</em> average with set operations expected <em>O(1)</em>.</p></li><li><p>Space: <em>O(k)</em> distinct characters, like the map approach.</p></li></ul><p><strong>When this shines</strong></p><ul><li><p>You want the <strong>simplest readable solution</strong> for this specific problem.</p></li><li><p>You don&#8217;t need frequency counts or more complex constraints.</p></li></ul><div><hr></div><h2>Approach 3: Character index array (true O(1) space for ASCII)</h2><p>In many LeetCode-style constraints, the input is restricted to ASCII. When that&#8217;s true, you can trade generality for a <strong>flat array indexed by character code</strong>, which removes hashing overhead and pins space to a constant.</p><p>Two common patterns:</p><ol><li><p><strong>Last-seen index</strong>: <code>lastIndex[c]</code> = last index where <code>c</code> appeared, or <code>-1</code> if never.</p></li><li><p><strong>Frequency array</strong>: Like the map-based sliding window, but with an array of length 128.</p></li></ol><p>I like the last-seen-index version because it eliminates the inner <code>while</code> loop and keeps the window valid by jumping <code>left</code> forward in one step.</p><h2>Python (last-seen index, ASCII)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;de0d339d-f412-4ee0-a673-9a2f88979060&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def length_of_longest_substring_ascii(s: str) -&gt; int:
    # 128 for standard ASCII; use 256 or 256k if needed
    last_index = [-1] * 128

    left = 0
    best = 0

    for right, ch in enumerate(s):
        code = ord(ch)

        # If we've seen this char inside the current window, move left
        if last_index[code] &gt;= left:
            left = last_index[code] + 1

        last_index[code] = right
        best = max(best, right - left + 1)

    return best</code></pre></div><h2>Java (last-seen index, ASCII)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;8638cccc-b8cf-4bea-86fc-5e571b8e24c1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">public int lengthOfLongestSubstringAscii(String s) {
    int[] lastIndex = new int[128];
    Arrays.fill(lastIndex, -1);

    int left = 0;
    int best = 0;

    for (int right = 0; right &lt; s.length(); right++) {
        char c = s.charAt(right);
        int code = (int) c;

        if (lastIndex[code] &gt;= left) {
            left = lastIndex[code] + 1;
        }

        lastIndex[code] = right;
        best = Math.max(best, right - left + 1);
    }

    return best;
}</code></pre></div><h2>C# (last-seen index, ASCII)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;22157f77-93fd-4e57-beda-be747a79a33b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public int LengthOfLongestSubstringAscii(string s) {
    int[] lastIndex = new int[128];
    Array.Fill(lastIndex, -1);

    int left = 0;
    int best = 0;

    for (int right = 0; right &lt; s.Length; right++) {
        char c = s[right];
        int code = (int)c;

        if (lastIndex[code] &gt;= left) {
            left = lastIndex[code] + 1;
        }

        lastIndex[code] = right;
        best = Math.Max(best, right - left + 1);
    }

    return best;
}</code></pre></div><p><strong>Complexity</strong></p><ul><li><p>Time: <em>O(n)</em>.</p></li><li><p>Space:</p><ul><li><p>Array length is fixed (e.g., 128), so <strong>strictly </strong><em><strong>O(1)</strong></em> with respect to input size.</p></li></ul></li></ul><p><strong>When this shines</strong></p><ul><li><p>You&#8217;re in a <strong>tight performance setting</strong> (competitive programming, very large number of test cases, strict limits).</p></li><li><p>The interviewer has explicitly restricted the alphabet to ASCII or to a small known set.</p></li></ul><div><hr></div><h2>Comparison: map vs. set vs. array</h2><p>Here&#8217;s how these approaches stack up from an interviewer&#8217;s point of view.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jOOo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jOOo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png 424w, https://substackcdn.com/image/fetch/$s_!jOOo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png 848w, https://substackcdn.com/image/fetch/$s_!jOOo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png 1272w, https://substackcdn.com/image/fetch/$s_!jOOo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jOOo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png" width="1456" height="545" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:545,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81894,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://paulepps.substack.com/i/196059764?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jOOo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png 424w, https://substackcdn.com/image/fetch/$s_!jOOo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png 848w, https://substackcdn.com/image/fetch/$s_!jOOo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png 1272w, https://substackcdn.com/image/fetch/$s_!jOOo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f69657a-d60e-48c5-aebe-67a2e1325e56_1470x550.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here <em>k</em> is the number of distinct characters, which is bounded by 128 for ASCII but may be larger in Unicode scenarios.</p><div><hr></div><h2>When does &#8220;O(1) space&#8221; actually matter here?</h2><p>On paper, all three are &#8220;O(n) time, O(1)/O(k) space&#8221;, and it&#8217;s tempting to optimize preemptively. In actual interviews, I&#8217;d bucket it like this:takeuforward+3</p><ol><li><p><strong>Most interviews</strong></p><ul><li><p>The map or set solution is completely fine.</p></li><li><p>The real evaluation is about:</p><ul><li><p>Recognizing the sliding window pattern.</p></li><li><p>Managing indices and invariants cleanly.</p></li><li><p>Explaining why the algorithm is <em>O(n)</em> (each char enters/leaves the window at most once).</p></li></ul></li></ul></li><li><p><strong>Performance-obsessed interviewers / follow-up questions</strong></p><ul><li><p>They may ask: <em>&#8220;Can you do it in O(1) space?&#8221;</em></p></li><li><p>That&#8217;s your cue to say:</p><ul><li><p>&#8220;If we can assume the input is ASCII, we can replace the map with a fixed-size array indexed by character code, which makes the additional space independent of <code>n</code>.&#8221;</p></li></ul></li><li><p>Dropping that shows you can <strong>connect constraints to implementation choices</strong>.</p></li></ul></li><li><p><strong>When not to over-optimize</strong></p><ul><li><p>If your first attempt with the array is buggy or hard to explain, it&#8217;s worse than a clean map-based solution.</p></li><li><p>Most production environments don&#8217;t care about the tiny constant-factor improvement here unless you&#8217;re processing giant logs in a tight loop.</p></li></ul></li></ol><p>A good rule you can even say out loud: <em>&#8220;I&#8217;ll start with the hash map version for clarity, and if we need to squeeze constants or space, we can switch to a fixed-size array given ASCII constraints.&#8221;</em></p><div class="callout-block" data-callout="true"><p><strong>Want to see how interviewers really grade this problem?</strong></p><p>The paid section breaks down why two almost-identical solutions get very different outcomes in real interviews: what strong candidates say about their window invariant, complexity, and follow-ups that upgrades this into a genuine positive signal.</p><p>&#10145;&#65039; Unlock the interviewer&#8217;s rubric for this problem</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe&quot;,&quot;text&quot;:&quot;Unlock the rubric&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://paulepps.substack.com/subscribe"><span>Unlock the rubric</span></a></p></div><h2>Paid-only add-on: How interviewers really grade this problem</h2>
      <p>
          <a href="https://paulepps.substack.com/p/reader-solutions-reviewed-sliding">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Reader Solutions Reviewed: Two Sum ]]></title><description><![CDATA[One-Pass Solutions, Off-by-One Traps, and Hash Collision Edge Cases]]></description><link>https://paulepps.substack.com/p/reader-solutions-reviewed-two-sum</link><guid isPermaLink="false">https://paulepps.substack.com/p/reader-solutions-reviewed-two-sum</guid><dc:creator><![CDATA[Paul Epps]]></dc:creator><pubDate>Fri, 24 Apr 2026 15:02:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!gaRa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gaRa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gaRa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!gaRa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!gaRa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!gaRa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gaRa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png" width="800" height="450" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:478604,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://paulepps.substack.com/i/195182445?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gaRa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!gaRa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!gaRa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!gaRa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17d53f57-cd95-43be-8bff-ead49ba5c5d5_800x450.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Two Sum</h1><p><a href="https://paulepps.substack.com/p/arrays-and-two-pointers-challenge">View original challenge</a></p><p>Two Sum is the problem interviewers use when they want to see how you <em>think</em>. It looks deceptively simple, but the details separating a clean solution from a buggy one are exactly what senior engineers notice. Today we go beyond the basic answer: one-pass hash map, off-by-one errors and hash collision edge cases.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Coding Interview Gym is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>The One-Pass Hash Map Solution</h2><p>Most explanations teach a two-pass approach: first scan the array to populate a map, then scan again to find complements. You don&#8217;t need two passes. You can build the map and check for complements <strong>simultaneously</strong> in a single traversal.</p><p>The insight: when you reach index <code>i</code>, every element before it has already been added to the map. So if the complement of <code>nums[i]</code> is already in the map, you&#8217;ve found your pair. No second pass needed.</p><h3>TypeScript</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;a568431b-016f-4b20-b5b7-a8f55d61cd29&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">function twoSum(nums: number[], target: number): number[] {
  const indexByValue = new Map&lt;number, number&gt;();

  for (let i = 0; i &lt; nums.length; i++) {
    const complement = target - nums[i];

    if (indexByValue.has(complement)) {
      return [indexByValue.get(complement)!, i];
    }

    indexByValue.set(nums[i], i); // store AFTER checking &#8212; crucial
  }
}</code></pre></div><h3>C#</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;a568431b-016f-4b20-b5b7-a8f55d61cd29&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class Solution {
    public int[] TwoSum(int[] nums, int target) {
        var indexByValue = new Dictionary&lt;int, int&gt;(); // value -&gt; index

        for (int i = 0; i &lt; nums.length; i++) {
            int complement = target - nums[i];

            if (indexByValue.TryGetValue(complement, out int complementIndex)) {
                return new int[] { complementIndex, i };
            }

            indexByValue[nums[i]] = i; // store AFTER the check
        }
    }
}</code></pre></div><h2>Java</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;fb6ae866-8d3b-407c-b692-3071ba37c605&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">import java.util.HashMap;
import java.util.Map;

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map&lt;Integer, Integer&gt; indexByValue = new HashMap&lt;&gt;();

        for (int i = 0; i &lt; nums.length; i++) {
            int complement = target - nums[i];

            if (indexByValue.containsKey(complement)) {
                return new int[]{ indexByValue.get(complement), i };
            }

            indexByValue.put(nums[i], i); // store AFTER checking
        }
    }
}</code></pre></div><p>Notice the comment on that last line in each snippet: <strong>"store AFTER checking."</strong> That single ordering decision is where most one-pass bugs are born, and it connects directly to our next section.</p><div><hr></div><h2>Common Off-by-One and Ordering Errors</h2><p>&#8220;Off-by-one&#8221; usually makes people think of loop bounds. In Two Sum, the trickier errors are about <em>when</em> you write to the map relative to when you read from it. Here are the two that I saw most often.</p><div><hr></div><h2>Error 1: Storing before checking (same-element reuse)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;6518c10f-cebc-4be4-a11d-4325320b0e44&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">// BUGGY &#8212; stores num[i] before looking for its complement
for (int i = 0; i &lt; nums.length; i++) {
    indexByValue.put(nums[i], i);        // &#8592; stored first

    int complement = target - nums[i];
    if (indexByValue.containsKey(complement)) {
        return new int[]{ indexByValue.get(complement), i };
    }
}</code></pre></div><p>If <code>target = 6</code> and <code>nums[i] = 3</code>, you store <code>3 &#8594; i</code>, then immediately ask "is 3 in the map?" And find the element you just stored. The problem says you cannot use the same element twice. Storing first breaks that constraint for self-complementary values.</p><p><strong>Fix:</strong> always check before storing, as shown in the correct solutions above.</p><div><hr></div><h2>Error 2: Off-by-one in loop bounds</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;60b08692-a337-4ac0-b744-a6268062948a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// BUGGY &#8212; misses the last element
for (let i = 0; i &lt; nums.length - 1; i++) {</code></pre></div><p>This is the classic off-by-one. Subtracting 1 from the upper bound seems harmless &#8212; you might reason &#8220;I need at least two elements left.&#8221; But in a one-pass hash map approach, you only need the <em>current</em> element and whatever is already in the map. The final element could be the one whose complement is already stored.</p><p><strong>Fix:</strong> iterate to <code>nums.length</code> (TypeScript/Java) or <code>nums.Length</code> (C#), inclusive of the last index.</p><div><hr></div><h2>Hash Collision Edge Cases</h2><p>This is the section almost nobody covers in Two Sum posts, and it&#8217;s the kind of depth that impresses a senior interviewer.</p><h2>What is a hash collision?</h2><p>A hash collision happens when two different keys produce the same hash code, causing them to land in the same underlying bucket. All major hash map implementations handle this with either chaining (linked list of entries per bucket) or open addressing (probe for the next empty slot).</p><p>In average-case analysis, lookup is O(1). In the <strong>worst case</strong>, if every key collides into the same bucket, lookup degrades to <strong>O(n)</strong>, turning your elegant O(n) Two Sum into an accidental O(n&#178;).</p><h2>Does this matter in practice for Two Sum?</h2><p>For most real inputs: no. Java&#8217;s <code>HashMap</code>, C#&#8217;s <code>Dictionary</code>, and JavaScript&#8217;s <code>Map</code> all use robust hash functions for integers that distribute keys well. But here&#8217;s when it <em>does</em> matter:</p><p><strong>1. Adversarial inputs in competitive programming</strong></p><p>Some judges are designed to generate inputs that maximize collisions for <code>HashMap</code> in Java specifically. The fix is to use a randomized hash function or switch to a <code>TreeMap</code> (which uses a balanced BST and gives O(log n) guaranteed lookup, at the cost of O(n log n) total time).</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;e243b865-823d-4078-acf1-86232c18f556&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">// Java: TreeMap gives O(log n) guaranteed lookup, no collision risk
import java.util.TreeMap;

Map&lt;Integer, Integer&gt; indexByValue = new TreeMap&lt;&gt;();</code></pre></div><p><strong>2. Integer overflow when computing the complement</strong></p><p>Not a hash collision, but a closely related &#8220;edge case nobody mentions&#8221;:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;02665346-0cb6-435c-bcef-ebbed7dc26c2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">// nums[i] = Integer.MAX_VALUE = 2,147,483,647
// target   = 0
int complement = target - nums[i]; // = -2,147,483,647 &#10003; fine here

// But: target = Integer.MIN_VALUE, nums[i] = 1
int complement = target - nums[i]; // overflows in Java/C# (int arithmetic)</code></pre></div><p>In Java and C#, <code>int</code> arithmetic wraps silently. The fix is to use <code>long</code> for the complement calculation when inputs can be large:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;8ecd9436-4496-4819-b8b5-6ae9d18868b6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">long complement = (long)target - nums[i];</code></pre></div><p>Mentioning this in an interview separates you from candidates who only think about the algorithm and not the data.</p><div><hr></div><h2>Putting It Together: What &#8220;Production-Ready&#8221; Looks Like</h2><p>Here&#8217;s a C# version that integrates all the guards discussed above:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;b9412ff0-eb7a-4335-b30b-1a2db65a1fcc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">public class Solution {
    public int[] TwoSum(int[] nums, int target) {
        if (nums == null || nums.Length &lt; 2)
            throw new ArgumentException("Input must contain at least two elements.");

        var indexByValue = new Dictionary&lt;int, int&gt;(nums.Length); // pre-size the dict

        for (int i = 0; i &lt; nums.Length; i++) {
            long complement = (long)target - nums[i]; // guard against overflow

            // complement must fit in int range to be a valid array value
            if (complement &gt;= int.MinValue &amp;&amp; complement &lt;= int.MaxValue
                &amp;&amp; indexByValue.TryGetValue((int)complement, out int complementIndex)) {
                return new int[] { complementIndex, i };
            }

            indexByValue[nums[i]] = i; // store AFTER checking
        }

        throw new InvalidOperationException("No solution found.");
    }
}</code></pre></div><p>Notice three upgrades from the interview version:</p><ul><li><p><code>nums.Length</code> passed to the <code>Dictionary</code> constructor pre-allocates capacity and avoids resize operations.</p></li><li><p><code>long</code> arithmetic guards against integer overflow on the complement.</p></li><li><p>Exceptions instead of empty returns give the caller meaningful error signals. (Yes, the challenge guaranteed a unique solution, but that&#8217;s not always the case.</p></li></ul><div><hr></div><h2>Reviewing a Python Solution</h2><p>A full reader submission:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;e0aa9525-5733-4929-accb-5c6b4ba9b8a8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">class Found(Exception): pass
def makesum (target: int, nums: list[int]) :
  try:
    for i in range (len(nums)):
      for j in range (i+1,len(nums)):
        if nums[i]+nums[j] == target :
          raise Found
  except Found :
    return i,j
  return None,None #not reachable as there IS a solution
target = 27
nums = [2,7,11,15,16]
i,j = makesum(target,nums)
print(f"[{i},{j}]")</code></pre></div><p>It works for the given input, but it&#8217;s not a good style or interview-quality solution.</p><h2>What&#8217;s good</h2><ul><li><p>Correct brute-force logic: you check all (<em>i</em>, <em>j</em>) with <em>j</em> &gt; <em>i</em> and return the first pair whose sum hits the target, which is the standard O(n^2) approach.</p></li><li><p>Clear intent: once a pair is found, you stop searching, so you don&#8217;t do unnecessary extra work.</p></li></ul><h2>Main issues</h2><ul><li><p>Misuse of exceptions for control flow</p><ul><li><p>Using <code>raise Found</code> / <code>except Found</code> just to break out of nested loops is considered unpythonic and harder to read than a simple flag or an extracted helper function.</p></li><li><p>It also makes <code>i</code> and <code>j</code> look &#8220;magic&#8221;: they are set in the loop, but returned from inside the <code>except</code> block, which depends on subtle scoping rules rather than explicit data flow.</p></li></ul></li><li><p>Incorrect comment about reachability</p><ul><li><p>The comment <code>#not reachable as there IS a solution</code> encodes a guarantee into the function rather than its caller.</p></li><li><p>In a real codebase, the function should either:</p><ul><li><p>Explicitly document &#8220;assumes a solution exists&#8221;, or</p></li><li><p>Handle the no-solution case gracefully (e.g., return <code>None</code> or raise a meaningful error).</p></li></ul></li></ul></li><li><p>Type hints and interface</p><ul><li><p>Return type is effectively <code>tuple[int, int] | tuple[None, None]</code> but isn&#8217;t annotated.</p></li><li><p>For code clarity, you might either:</p><ul><li><p>Annotate <code>def makesum(...) -&gt; tuple[int, int] | None:</code> and return <code>None</code>, or</p></li><li><p>If you truly assume a solution exists, document that in the docstring and not return the <code>(None, None)</code> sentinel.</p></li></ul></li></ul></li><li><p>Naming and style</p><ul><li><p><code>makesum</code> is vague; something like <code>two_sum_indices</code> or <code>find_two_sum</code> is clearer.</p></li><li><p>PEP 8: spaces after function name before parentheses and around arguments are unusual (<code>def makesum (target: int, nums: list[int]) :</code>).</p></li><li><p>A small thing, but interviewers often care about these details.</p></li></ul></li></ul><h2>How to write a similar brute-force</h2><p>Here&#8217;s a direct, readable O(n^2) brute-force version that keeps the nested-loop idea, without exceptions:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;399ab326-a831-4007-b150-7480e21c696b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from typing import Optional, Tuple

def two_sum_bruteforce(target: int, nums: list[int]) -&gt; Optional[Tuple[int, int]]:
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            if nums[i] + nums[j] == target:
                return i, j
    return None</code></pre></div><p>Usage:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;69add357-c1e8-4186-87da-cd2b9438e7ef&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">target = 27
nums = [2, 7, 11, 15, 16]
result = two_sum_bruteforce(target, nums)

if result is not None:
    i, j = result
    print(f"[{i},{j}]")
else:
    print("No solution found")</code></pre></div><h2>For interviews: show the hash map version</h2><p>Most interviewers expect you to move from this brute-force to an O(n) hash-map solution.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;4c75fd17-ce07-4d5e-8704-e01a6b527226&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from typing import Optional, Tuple

def two_sum(target: int, nums: list[int]) -&gt; Optional[Tuple[int, int]]:
    index_by_value: dict[int, int] = {}
    for i, num in enumerate(nums):
        complement = target - num
        if complement in index_by_value:
            return index_by_value[complement], i
        index_by_value[num] = i
    return None</code></pre></div><div><hr></div><h2>What to Say in an Interview</h2><p>Here&#8217;s the arc that lands well:</p><ol><li><p><strong>Restate the constraints</strong> &#8212; can we use the same element twice? Exactly one solution?</p></li><li><p><strong>Offer the brute force</strong> &#8212; O(n&#178;) time, O(1) space, nested loops.</p></li><li><p><strong>Identify the bottleneck</strong> &#8212; &#8220;We recompute the complement search from scratch every iteration.&#8221;</p></li><li><p><strong>Propose the hash map</strong> &#8212; &#8220;We can cache what we&#8217;ve seen for O(1) lookup.&#8221;</p></li><li><p><strong>Do it in one pass</strong> &#8212; explain the store-after-check ordering.</p></li><li><p><strong>Name the tradeoffs</strong> &#8212; O(n) space, amortized O(1) per operation, worst-case collision behavior.</p></li><li><p><strong>Handle edge cases unprompted</strong> &#8212; duplicate values, integer overflow, self-complementary elements.</p></li></ol><p>That arc shows algorithmic thinking, communication skills, and engineering maturity, all in one problem.</p><div><hr></div><h2>Up Next: Sliding Window</h2><p>Two Sum teaches you to <strong>remember what you&#8217;ve seen</strong> using a hash map. The next pattern takes that idea further: what if instead of a single pair, you need to track a whole <strong>subarray</strong> as it moves through your data?</p><p>That&#8217;s Sliding Window, and it shows up everywhere: longest substring without repeating characters, maximum sum subarray of size k, minimum window substring, and more. It&#8217;s one of the highest-leverage patterns for medium and hard interview problems, and it&#8217;s more nuanced than most posts let on.</p><p>Next week&#8217;s deep-dive (Wednesday for paid subscribers) will cover fixed-window vs. variable-window variants, when to shrink from the left vs. expand from the right, and the exact questions that trip up candidates who think they understand it.</p><p><strong>Subscribe so you don&#8217;t miss it.</strong></p><div><hr></div><p><em>Found this useful? Share it with someone grinding LeetCode right now &#8212; they&#8217;ll thank you later.</em></p><div class="callout-block" data-callout="true"><p><strong>If you&#8217;re a paid subscriber</strong>, you also get a weekly deep&#8209;dive post and the option to request a private resume review.</p><p><strong>Not paid yet? You can upgrade here:</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe&quot;,&quot;text&quot;:&quot;Upgrade to paid&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://paulepps.substack.com/subscribe"><span>Upgrade to paid</span></a></p></div><div><hr></div><p><em>&#169; The Coding Interview Gym | paulepps.substack.com</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Coding Interview Gym is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Review and Feedback: Coin Change and Dynamic Programming]]></title><description><![CDATA[Recursion &#8594; memoization &#8594; tabulation]]></description><link>https://paulepps.substack.com/p/review-and-feedback-coin-change-and</link><guid isPermaLink="false">https://paulepps.substack.com/p/review-and-feedback-coin-change-and</guid><dc:creator><![CDATA[Paul Epps]]></dc:creator><pubDate>Thu, 16 Apr 2026 03:12:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!3Vk0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3Vk0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3Vk0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!3Vk0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!3Vk0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!3Vk0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3Vk0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png" width="800" height="450" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:668442,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://paulepps.substack.com/i/194360853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3Vk0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!3Vk0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!3Vk0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!3Vk0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6b27f5a-fed8-4a4a-88b4-e4bf457c4b90_800x450.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you&#8217;ve been prepping for interviews, you&#8217;ve probably seen <a href="https://paulepps.substack.com/p/dynamic-programming-minimum-coins">Coin Change</a>. It&#8217;s on almost every FAANG short list, and for good reason: it&#8217;s deceptively simple to state, easy to solve naively, and a perfect vehicle for understanding the full arc from brute-force recursion &#8594; memoization &#8594; tabulation.</p><p>Let&#8217;s walk through the whole thing.</p><div><hr></div><h2>The Problem</h2><p>Given an array of <strong>coins[]</strong> of size <strong>n</strong> and a target value <strong>sum</strong>, where <strong>coins[i] </strong>represent the coins of different denominations. You have an <strong>infinite supply</strong> of each of the coins. The task is to find the <strong>minimum </strong>number of coins required to make the given value <strong>sum</strong>. If it is not possible to form the sum using the given coins, return <strong>-1</strong>.---</p><h2>Why Greedy Fails Here</h2><p>Before writing a single line of DP, let&#8217;s address why Greedy is not good here.</p><p><strong>For [1, 5, 10, 25] and amount 30</strong>, greedy works: take the largest coin that fits (25), then take 5. Two coins. Correct.</p><p><strong>For [1, 3, 4] and amount 6</strong>, greedy fails: it takes 4, then two 1s = three coins. But the optimal answer is two 3s = two coins.</p><p><strong>For [1, 20, 25] and amount 40</strong>, greedy fails hard: it takes 25, then can&#8217;t make 15 with {20, 25}, so falls back to fifteen 1s = sixteen coins. The optimal is two 20s = two coins.</p><p>Greedy works for canonical currency systems. The moment denominations are arbitrary, you need DP.</p><div><hr></div><h2>Approach 1: Naive Recursion</h2><p>The cleanest way to see the subproblem structure is top-down recursion with no optimization.</p><p><strong>Recurrence:</strong></p><p>For each coin <code>c</code>, try it if <code>c &lt;= amount</code>, and recurse on <code>amount - c</code>. Take the minimum across all choices.</p><p>(I&#8217;m using Python for all of this week&#8217;s code samples because it&#8217;s easier to see the progression from recursion &#8594; memoization &#8594; tabulation with the same language.)</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;faaccecc-1824-4f43-8e82-7dedb7732986&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def coin_change_naive(coins: list[int], amount: int) -&gt; int:

    def dp(remaining: int) -&gt; int:

        # Base cases

        if remaining == 0:

            return 0

        if remaining &lt; 0:

            return float(&#8217;inf&#8217;)

        # Try every coin and take the minimum

        min_coins = float(&#8217;inf&#8217;)

        for coin in coins:

            result = dp(remaining - coin)

            if result != float(&#8217;inf&#8217;):

                min_coins = min(min_coins, result + 1)

        return min_coins

    result = dp(amount)

    return result if result != float(&#8217;inf&#8217;) else -1
</code></pre></div><p><strong>Time complexity:</strong> Exponential. O(amount^len(coins)). Each call branches into <code>len(coins)</code> subproblems.</p><p><strong>Space complexity:</strong> O(amount) for the recursion call stack.</p><p><strong>Why this is painful:</strong> <code>dp(11)</code> will compute <code>dp(10)</code>, <code>dp(6)</code>, and <code>dp(1)</code> multiple times each. Redundant work piles up fast. For <code>amount = 100</code> with four denominations, you&#8217;ll see millions of calls.</p><div><hr></div><h2>Approach 2: Top-Down DP (Memoization)</h2><p>The fix is straightforward: cache the result of each subproblem so you never recompute it.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;9abdfbd8-24ad-4741-abe1-45b913922d4b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def coin_change_memo(coins: list[int], amount: int) -&gt; int:

    memo = {}

    def dp(remaining: int) -&gt; int:

        # Check cache first

        if remaining in memo:

            return memo[remaining]

        # Base cases

        if remaining == 0:

            return 0

        if remaining &lt; 0:

            return float(&#8217;inf&#8217;)

        # Try every coin and take the minimum

        min_coins = float(&#8217;inf&#8217;)

        for coin in coins:

            result = dp(remaining - coin)

            if result != float(&#8217;inf&#8217;):

                min_coins = min(min_coins, result + 1)

        memo[remaining] = min_coins

        return min_coins

    result = dp(amount)

    return result if result != float(&#8217;inf&#8217;) else -1</code></pre></div><p><strong>Time complexity:</strong> O(amount &#215; len(coins)). Each unique <code>remaining</code> value is computed once, and each computation tries all coins.</p><p><strong>Space complexity:</strong> O(amount) for the memo table + O(amount) for the call stack = O(amount).</p><p>Each entry in the memo table maps a remaining amount to the minimum coins needed. The call stack for <code>dp(11)</code> now only recurses deep once per unique value.</p><p><strong>One risk:</strong> For very large <code>amount</code> values, Python&#8217;s default recursion limit (~1000) can cause a stack overflow. Workaround: <code>sys.setrecursionlimit(amount + 10)</code>, or just use tabulation.</p><div><hr></div><h2>Approach 3: Bottom-Up DP (Tabulation)</h2><p>Instead of recursing top-down and caching, build the answer iteratively from <code>amount = 0</code> upward.</p><p>Key insight: <code>dp[i]</code> = minimum coins to make amount <code>i</code>. Fill the table in order from 0 to <code>amount</code>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;0b4690ee-40d6-4ec4-bd7a-d746bdab1325&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def coin_change_tab(coins: list[int], amount: int) -&gt; int:

    # Initialize table with amount+1 as a sentinel for &#8220;impossible&#8221;

    # (You can never need more than `amount` coins of denomination 1)

    dp = [amount + 1] * (amount + 1)

    dp[0] = 0  # Base case: 0 coins needed to make amount 0

    for i in range(1, amount + 1):

        for coin in coins:

            if coin &lt;= i:

                dp[i] = min(dp[i], dp[i - coin] + 1)

    return dp[amount] if dp[amount] != amount + 1 else -1</code></pre></div><p><strong>Time complexity:</strong> O(amount &#215; len(coins)). Two nested loops.</p><p><strong>Space complexity:</strong> O(amount) = single 1D array. No call stack overhead.</p><div><hr></div><h2>Complexity Summary</h2><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ysli!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ysli!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png 424w, https://substackcdn.com/image/fetch/$s_!ysli!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png 848w, https://substackcdn.com/image/fetch/$s_!ysli!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png 1272w, https://substackcdn.com/image/fetch/$s_!ysli!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ysli!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png" width="1406" height="338" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:338,&quot;width&quot;:1406,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46071,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://paulepps.substack.com/i/194360853?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ysli!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png 424w, https://substackcdn.com/image/fetch/$s_!ysli!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png 848w, https://substackcdn.com/image/fetch/$s_!ysli!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png 1272w, https://substackcdn.com/image/fetch/$s_!ysli!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee219f5-4aab-40c2-89f4-23123eae18c2_1406x338.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>For this problem, tabulation and memoization have identical asymptotic complexity. Tabulation wins in practice for large <code>amount</code> due to no call stack overhead and better cache locality.</p><h2>Test Cases</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;20682412-4836-4709-8e46-990c34e4e1cf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def run_tests():

    fn = coin_change_tab  # swap in any implementation

    # Standard cases

    assert fn([1, 5, 10, 25], 11)  == 2   # 10 + 1

    assert fn([1, 5, 10, 25], 30)  == 2   # 25 + 5

    assert fn([1, 5, 10, 25], 0)   == 0   # base case

    assert fn([1, 5, 10, 25], 1)   == 1   # single coin

    assert fn([1, 5, 10, 25], 25)  == 1   # exact denomination

    # Non-canonical denominations (greedy fails)

    assert fn([1, 3, 4], 6)        == 2   # 3 + 3, not 4+1+1

    assert fn([1, 20, 25], 40)     == 2   # 20 + 20, not 25+15x1

    # Impossible case

    assert fn([2], 3)              == -1  # can&#8217;t make odd amount with only 2s

    # Large amount

    assert fn([1, 5, 10, 25], 100) == 4   # 25 * 4

    print(&#8221;All tests passed.&#8221;)

run_tests()</code></pre></div><h2>Common Pitfalls</h2><h3>1. Confusing bounded vs. unbounded knapsack</h3><p>Coin Change is an <strong>unbounded</strong> problem. You can reuse each denomination any number of times.</p><p>Compare with the <strong>0/1 knapsack</strong> variant: each coin can only be used once. The iteration order and table structure change significantly.</p><p>In the bounded version, you&#8217;d typically iterate over items (coins) in the outer loop and amounts in the inner loop, maintaining a 2D table to track whether each item has been used. In the unbounded version (this problem), you iterate amounts in the outer loop and can reference the same row freely.</p><p><strong>Interview red flag:</strong> Using a 2D table when a 1D table suffices. For unbounded coin change, the 1D bottom-up solution is correct and optimal.</p><h3>2. Not handling the base case dp[0] = 0</h3><p>If <code>dp[0]</code> is not explicitly set to 0, the entire table builds on a wrong foundation. Every subsequent entry will be off by one at minimum.</p><div><hr></div><h2>When to Use Each Approach</h2><p><strong>Use naive recursion</strong> only to sketch the recurrence relation and confirm your base cases before optimizing. Never submit it.</p><p><strong>Use memoization (top-down)</strong> when:</p><ul><li><p>The recurrence is complex and writing it recursively is more natural than iteratively</p></li><li><p>You only need to compute a subset of subproblems (sparse access pattern)</p></li><li><p>You&#8217;re prototyping and want the recursive structure visible for code review</p></li></ul><p><strong>Use tabulation (bottom-up)</strong> when:</p><ul><li><p>You need all subproblems (dense access pattern, as in Coin Change)</p></li><li><p>Stack depth is a concern</p></li><li><p>You want the most cache-friendly, predictable memory behavior</p></li><li><p>You&#8217;re in a systems language where recursion overhead matters</p></li></ul><p>In practice, for most 1D DP interview problems (Coin Change, House Robber, Climbing Stairs), <strong>tabulation is the preferred answer</strong> unless the problem has a sparse subproblem graph.</p><p><em>If this was useful, forward it to an engineer who&#8217;s prepping. Each referral keeps the Gym running.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Coding Interview Gym publishes weekly coding challenges, challenge reviews, and interview tips for engineers preparing for FAANG and top-tier interviews. If you&#8217;re not subscribed yet, join here:</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><p><em>&#169; The Coding Interview Gym | paulepps.substack.com</em></p>]]></content:encoded></item><item><title><![CDATA[3 Behavioral Blunders That Tanked FAANG Offers – And How to Fix Them]]></title><description><![CDATA[Keywords: FAANG behavioral interview mistakes, Amazon Leadership Principles, behavioral interview tips software engineers, STAR method interview, SBI feedback method, how to answer behavioral questions, &#8220;tell me about a time&#8221; interview]]></description><link>https://paulepps.substack.com/p/3-behavioral-blunders-that-tanked</link><guid isPermaLink="false">https://paulepps.substack.com/p/3-behavioral-blunders-that-tanked</guid><dc:creator><![CDATA[Paul Epps]]></dc:creator><pubDate>Fri, 10 Apr 2026 22:05:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!d5H6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F384c0b66-f508-4d22-b7a1-c63b7d95ed3f_1220x558.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Keywords:</strong> FAANG behavioral interview mistakes, Amazon Leadership Principles, behavioral interview tips software engineers, STAR method interview, SBI feedback method, how to answer behavioral questions, &#8220;tell me about a time&#8221; interview</p><div><hr></div><p><em>Reading time: ~8 minutes | Part of The Coding Interview Gym&#8217;s behavioral interview series</em></p><p>Every FAANG interviewer has a version of the same story.</p><p>A candidate walks in with a sky-high LeetCode streak, a polished resume, and strong momentum from the coding rounds. Then comes the behavioral interview. Forty-five minutes later, the candidate walks out &#8212; no offer.</p><p>Not because they couldn&#8217;t code. Not because they lacked experience. But because they made one (or more) of three completely fixable mistakes.</p><p>I&#8217;ve studied interviewer debrief patterns, analyzed feedback from real hiring loops, and coached engineers through these exact pitfalls. What follows are three composite stories, each one recognizable to any engineer who&#8217;s ever gone through a FAANG loop.</p><p>The good news: every blunder has a clean fix you can practice this week.</p><div><hr></div><h2>The Problem with Behavioral Prep</h2><p>Most engineers treat behavioral prep as a box to check. They print the Amazon Leadership Principles, build a stack of polished STAR stories, and rehearse until the answers are smooth.</p><p>FAANG interviewers, especially Amazon Bar Raisers and Google interview committee members, are trained to detect rehearsed perfection. They&#8217;re not looking for a flawless highlight reel. They&#8217;re trying to understand how you actually think, decide, and grow. Authenticity consistently outperforms polish.</p><p>With that in mind, here are the three blunders that most often separate a &#8220;Strong No Hire&#8221; debrief from an offer.</p><div><hr></div><h2>Blunder #1: The Polished Story That Had No Soul (Amazon)</h2><h3>The Story</h3><p>Marcus was methodical. He had 16 STAR stories ready, one for each of Amazon&#8217;s Leadership Principles. Each one was tightly scripted: clear situation, sharp task, decisive action, measurable result.</p><p>In every story, Marcus had saved the day.</p><p>He resolved a production outage before it became a customer incident. He pushed back on a bad architectural decision and turned out to be right. He mentored a junior engineer who went on to crush it at their next review.</p><p>The Amazon Bar Raiser listened politely through two answers. Then she leaned forward.</p><p><em>&#8221;That&#8217;s a great result. Can you walk me through a moment during that project where you weren&#8217;t sure if you were making the right call?&#8221;</em></p><p>Marcus paused. His script didn&#8217;t have that scene.</p><p><em>&#8221;I mean&#8230; it mostly went according to plan.&#8221;</em></p><p>That answer, in an Amazon debrief, is a red flag. <strong>Bias for Action and Earn Trust both require judgment under uncertainty</strong>, and &#8220;it mostly went according to plan&#8221; signals that either the project wasn&#8217;t genuinely hard, or the candidate hasn&#8217;t reflected honestly on it.</p><h3>Why This Tanks Offers</h3><p>Amazon&#8217;s Leadership Principles aren&#8217;t a checklist, they&#8217;re a lens. Bar Raisers specifically probe for moments of friction, doubt and tradeoff. A candidate who presents only highlights signals shallow self-awareness.</p><p>Amazon&#8217;s official interviewer guidance (and publicly documented Bar Raiser training themes) specifically emphasizes <strong>&#8221;Dive Deep&#8221;</strong> and <strong>&#8221;Are Right, A Lot&#8221;</strong> as areas where generic, friction-free stories consistently fail. Interviewers are coached to ask follow-up questions until they find the seams.</p><h3>The Fix: Trade Perfection for Genuine Reflection</h3><p>Pick your three or four most genuinely hard projects, the ones with real ambiguity, real tradeoffs or a real failure mode. For each one, map out:</p><ol><li><p><strong>The moment of highest uncertainty</strong>: what did you not know and how did you decide anyway?</p></li><li><p><strong>The tradeoff you made</strong>: what did you optimize for and what did you sacrifice?</p></li><li><p><strong>What you&#8217;d do differently</strong>: and why the experience changed your thinking.</p></li></ol><p>If your story has no moment of genuine doubt, find a different story. Interviewers aren&#8217;t looking for weakness, they&#8217;re looking for evidence that you can think clearly when the path isn&#8217;t obvious.</p><p>A story that includes <em>&#8221;I wasn&#8217;t sure this was the right call, but here&#8217;s why I made it anyway and here&#8217;s what I learned&#8221;</em> is dramatically more compelling than a story where everything went according to plan.</p><p><strong>Practice prompt:</strong> Pull up your most recent hard project. Write down three things that didn&#8217;t go exactly as expected. Those are the moments your STAR story should highlight.</p><div><hr></div><h2>Blunder #2: &#8220;We Did Everything&#8221; (Meta)</h2><h3>The Story</h3><p>Priya was a strong candidate: five years at a well-regarded mid-size company, solid system design skills, and clear communication in the technical rounds. In the behavioral interview at Meta, she was asked: <em>&#8221;Tell me about a time you drove a significant technical decision under time pressure.&#8221;</em></p><p>Priya had a perfect example. Her team had been debating a data pipeline architecture for two weeks with no resolution. She stepped in, evaluated three options, and pushed the team to a decision that shipped on schedule and improved throughput by 40%.</p><p>But here&#8217;s how she told it:</p><p>*&#8221;We were under a lot of pressure to ship. We spent two weeks debating the architecture. We eventually decided to go with Option B, and we were able to get it shipped on time. We saw a 40% improvement in throughput and the team was really happy with the outcome.&#8221;*</p><p>The interviewer wrote in the debrief: <em>&#8221;Unclear what this candidate personally contributed. No sense of individual ownership or decision-making.&#8221;</em></p><p>Priya had the right story. She delivered it in a way that erased her own role from it.</p><h3>Why This Tanks Offers</h3><p>At Meta (and frankly everywhere in FAANG), behavioral interviews are explicitly designed to assess <em>individual</em> judgment, ownership and impact. When you answer in &#8220;we,&#8221; you&#8217;re doing the equivalent of omitting your name from your own resume.</p><p>This is especially critical at Meta, where <strong>&#8221;Move Fast,&#8221; &#8220;Be Bold,&#8221;</strong> and direct ownership are core cultural values. If an interviewer can&#8217;t identify what <em>you specifically</em> did, they can&#8217;t advocate for you in the debrief.</p><p>This blunder is extremely common among engineers from strong team cultures. The collaborative instinct is real and admirable, but it works against you in a behavioral loop.</p><h3>The Fix: Own the &#8220;I&#8221; Without Erasing the Team</h3><p>The goal isn&#8217;t to pretend you worked alone. It&#8217;s to make your specific contribution visible.</p><p><strong>Replace:</strong></p><ul><li><p>&#8220;We decided to&#8230;&#8221; &#8594; &#8220;I pushed for&#8230; and here&#8217;s why&#8221;</p></li><li><p>&#8220;We struggled with&#8230;&#8221; &#8594; &#8220;I was the one responsible for&#8230;&#8221;</p></li><li><p><em>&#8220;We shipped it&#8230;&#8221; &#8594; &#8220;I drove the final decision on&#8230; and here&#8217;s what that looked like&#8221;</em></p></li></ul><p>A useful frame: <strong>acknowledge the team, claim the action.</strong></p><p><em>&#8221;My team was aligned on the problem but stuck on the solution. I took ownership of the decision by doing X, because I believed Y. I brought the team along by doing Z. The result was&#8230;&#8221;*</em></p><p>You&#8217;re not erasing your teammates, you&#8217;re making your own judgment visible. That&#8217;s exactly what the interviewer needs to evaluate.</p><p><strong>Practice prompt:</strong> Go back through your last three behavioral stories and highlight every instance of &#8220;we.&#8221; For each one, ask: what specifically did <em>I</em> do at that moment? Rewrite those lines in first person.</p><div><hr></div><h2>Blunder #3: The Ramble That Buried the Point (Google)</h2><h3>The Story</h3><p>David was technically excellent. His coding rounds were strong, his system design was sharp, and he had genuinely interesting stories to tell. But in the behavioral round, something strange happened.</p><p>The interviewer asked: <em>&#8221;Tell me about a time you gave difficult feedback to a peer.&#8221;</em></p><p>David started talking. And kept talking. Three minutes in, he was describing the project backstory in detail: the team dynamics, the quarterly goals, the stakeholder pressures. Four minutes in, he was still setting up the situation. The interviewer tried twice to redirect with <em>&#8221;And what did you actually say to them?&#8221;</em> David answered briefly, then pivoted back to more context.</p><p>When the interviewer stopped him at six minutes to move on, she had heard very little about what David actually <em>did</em> or what happened as a result.</p><p>Her debrief note: <em>&#8221;Unable to isolate candidate&#8217;s communication approach. Stories lacked structure. Concern about ability to communicate concisely in leadership contexts.&#8221;</em></p><p>David&#8217;s story had a great answer buried inside it. It never came out.</p><h3>Why This Tanks Offers</h3><p>Unstructured rambling in a behavioral interview signals two things interviewers actively flag: difficulty with concise communication, and low self-awareness about what matters in a story.</p><p>At Google specifically, behavioral interviews are evaluated on <strong>&#8221;Googleyness&#8221;</strong> criteria that include clear and effective communication and the ability to synthesize complex information, not just technical clarity. A candidate who can&#8217;t structure a verbal story raises doubts about their ability to write clear design docs, give crisp feedback and run efficient meetings.</p><p>Google interviewers typically have 5&#8211;7 questions to cover in 45 minutes. A 6-minute unstructured answer doesn&#8217;t just lose points, it eats the time they need to evaluate other dimensions.</p><h3>The Fix: The SBI Method (Out Loud)</h3><p>STAR is the gold standard framework, but for common interpersonal and feedback scenarios, the <strong>SBI method (Situation, Behavior, Impact)</strong> is tighter and forces you to stay concrete.</p><ul><li><p><strong>Situation:</strong> One or two sentences of context. <em>&#8221;During Q3, we were behind on a critical API integration, and I noticed a senior engineer on my team was consistently missing our design review meetings.&#8221;</em></p></li><li><p><strong>Behavior:</strong> What specifically did you observe or do? <em>&#8221;In our next 1:1, I named the pattern directly: &#8216;I&#8217;ve noticed you&#8217;ve missed the last three reviews, and I want to understand what&#8217;s going on.&#8217;&#8221;</em></p></li><li><p><strong>Impact:</strong> What changed as a result? <em>&#8221;He told me he&#8217;d been overwhelmed and hadn&#8217;t felt safe saying so. We restructured his workload. He came back to reviews the following week and ended up presenting the final design.&#8221;</em></p></li></ul><p>That&#8217;s a complete, compelling answer in under 90 seconds.</p><p><strong>The key is practicing out loud.</strong> Reading SBI examples on paper doesn&#8217;t build the habit. Set a timer, speak your answer to the wall (or record yourself on your phone), and stop at 90 seconds. If you can&#8217;t land the impact statement before the timer goes off, your Situation block is too long.</p><p><strong>Practice prompt:</strong> Pick one feedback conversation from the past year. Write it in SBI format. Read it aloud. Time it. Cut until it fits in 90 seconds without losing the core.</p><div><hr></div><h2>The Overarching Lesson: Authenticity Always Outperforms Perfection</h2><p>Here&#8217;s what Marcus, Priya and David all had in common: they were trying to perform a version of competence rather than demonstrate it.</p><p>Marcus performed a highlight reel. Priya performed collaborative humility. David performed thoroughness. None of it landed.</p><p>FAANG interviewers, particularly at senior levels (E5/E6 and above), have evaluated hundreds of candidates. They&#8217;ve heard polished stories. What they&#8217;re genuinely looking for is evidence of how you think when things are hard, unclear or uncomfortable.</p><p>That evidence only comes through when you&#8217;re willing to be honest about:</p><ul><li><p>The decision you made without enough information</p></li><li><p>The friction you created by owning something your team didn&#8217;t agree with</p></li><li><p>The feedback conversation you were nervous to have</p></li></ul><p>These aren&#8217;t weaknesses. They tell an interviewer more about how you&#8217;ll operate at a senior level than any perfectly structured highlight story.</p><p>The fix isn&#8217;t a new framework. It&#8217;s honest reflection about your real experience, structured clearly, and practiced out loud until it sounds like you, not a rehearsed script.</p><div><hr></div><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/oOmWx/3/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/384c0b66-f508-4d22-b7a1-c63b7d95ed3f_1220x558.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f8b0da0e-acde-4e73-82ac-b0c64e801e75_1220x628.png&quot;,&quot;height&quot;:315,&quot;title&quot;:&quot;Quick Reference: The Three Fixes&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/oOmWx/3/" width="730" height="315" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><div><hr></div><h2>What to Do This Week</h2><ol><li><p><strong>Audit your stories.</strong> Pull out your three strongest STAR answers. Does each one include a moment of genuine uncertainty? If not, find a different story or add that moment.</p></li><li><p><strong>Do a &#8220;we&#8221; audit.</strong> Read your stories and circle every &#8220;we.&#8221; Rewrite each one to make your specific contribution visible.</p></li><li><p><strong>Practice SBI out loud.</strong> Pick one interpersonal story: a conflict, a feedback moment, a missed expectation. Write it in SBI format and speak it aloud. Stop at 90 seconds.</p></li></ol><p>You already have the experience. The behavioral interview is about translating that experience into a form that lets an interviewer advocate for you.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Coding Interview Gym publishes weekly coding challenges, challenge reviews, and interview tips for engineers preparing for FAANG and top-tier interviews. If you&#8217;re not subscribed yet, join here:</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><div><hr></div><p><em>&#169; The Coding Interview Gym | paulepps.substack.com</em></p><p></p>]]></content:encoded></item><item><title><![CDATA[Review and Feedback: Minimum Bridges Between Island Cities]]></title><description><![CDATA[Reference implementation and multiple "almost right" solutions]]></description><link>https://paulepps.substack.com/p/review-and-feedback-minimum-bridges</link><guid isPermaLink="false">https://paulepps.substack.com/p/review-and-feedback-minimum-bridges</guid><dc:creator><![CDATA[Paul Epps]]></dc:creator><pubDate>Fri, 03 Apr 2026 06:35:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!KsPF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KsPF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KsPF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png 424w, https://substackcdn.com/image/fetch/$s_!KsPF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png 848w, https://substackcdn.com/image/fetch/$s_!KsPF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png 1272w, https://substackcdn.com/image/fetch/$s_!KsPF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KsPF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png" width="798" height="453" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:453,&quot;width&quot;:798,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:760541,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://paulepps.substack.com/i/192272996?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KsPF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png 424w, https://substackcdn.com/image/fetch/$s_!KsPF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png 848w, https://substackcdn.com/image/fetch/$s_!KsPF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png 1272w, https://substackcdn.com/image/fetch/$s_!KsPF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9316fa1-3d96-48bf-9b97-9e30a189cf92_798x453.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Problem Recap</h2><p>You are given <em>n</em> cities labeled from <em>0</em> to <em>n&#8722;1</em>.<br>Some pairs of cities are connected by roads, and some pairs are connected by bridges.</p><ul><li><p>Roads are free to use.</p></li><li><p>Each bridge crossing costs 1 token, regardless of direction or distance.</p></li></ul><p>You are also given a starting city <code>start</code>, a destination city <code>target</code>, and an integer <code>k</code> representing how many bridge tokens you have.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Coding Interview Gym is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>You can travel:</p><ul><li><p>Along any road edge at zero cost, unlimited times.</p></li><li><p>Along any bridge edge, but in total you may use <strong>at most</strong> <code>k</code> bridges on your entire route.</p></li></ul><p>Return the <strong>minimum number of steps</strong> (edges traversed, roads or bridges) needed to travel from <code>start</code> to <code>target</code> without using more than <code>k</code> bridges. If it is impossible, return <code>-1</code>.</p><p>A &#8220;step&#8221; is traversing a single edge (road or bridge) from one city to a directly connected city.</p><h2>Input format</h2><ul><li><p><code>n</code>: integer, number of cities.</p></li><li><p><code>roads</code>: list of pairs <code>[u, v]</code> meaning there is an undirected road between <code>u</code> and <code>v</code>.</p></li><li><p><code>bridges</code>: list of pairs <code>[u, v]</code> meaning there is an undirected bridge between <code>u</code> and <code>v</code>.</p></li><li><p><code>start</code>: integer in <code>[0, n-1]</code>.</p></li><li><p><code>target</code>: integer in <code>[0, n-1]</code>.</p></li><li><p><code>k</code>: integer, maximum number of bridges you may use.</p></li></ul><h3>Assumptions:</h3><ul><li><p>The graph can be disconnected.</p></li><li><p>There may be both a road and a bridge between the same pair of cities.</p></li><li><p>No self-loops are given, but your code should not rely on that.</p></li><li><p><strong>Constraints:</strong> <em>1&#8804;n&#8804;10^5</em>, total edges up to <em>2&#8901;10^5</em>, <em>0&#8804;k&#8804;300</em>.</p></li></ul><div><hr></div><h2>Think before you code</h2><ol><li><p><strong>What is the graph?</strong></p><ul><li><p>How will you represent roads and bridges in a single adjacency list?</p></li><li><p>What information do you need for each edge?</p></li></ul></li><li><p><strong>What is a state?</strong></p><ul><li><p>Is &#8220;I am at city 7&#8221; enough, or do you also need to know something about how you got there?</p></li><li><p>When are two visits to the same city considered <em>different</em>?</p></li></ul></li><li><p><strong>What algorithm pattern fits?</strong></p><ul><li><p>Every move (road or bridge) costs exactly 1 step. Does this sound weighted or unweighted?</p></li><li><p>Between BFS and DFS, which one naturally gives you a shortest number of steps in an unweighted graph, and why?</p></li></ul></li><li><p><strong>How big is the state space?</strong></p><ul><li><p>Suppose your state is <code>(city, usedBridges)</code>. How many such states are possible in the worst case?</p></li><li><p>What does that imply for time and space complexity?</p></li></ul></li><li><p><strong>Where can this go wrong?</strong></p><ul><li><p>If you only track <code>visited[city]</code>, what kinds of valid paths might you accidentally prune?</p></li><li><p>Try to construct a small example where that mistake returns <code>-1</code> even though a valid path exists.</p></li></ul></li></ol><div><hr></div><h2>Polished reference solution (Python, BFS)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;6bc00101-2348-48de-8407-d1b5c7a72871&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from collections import deque
from typing import List, Tuple

ROAD = 0
BRIDGE = 1

def min_steps_with_bridges(
    n: int,
    roads: List[Tuple[int, int]],
    bridges: List[Tuple[int, int]],
    start: int,
    target: int,
    k: int
) -&gt; int:
    # Build adjacency list: (neighbor, edge_type)
    adj: List[List[Tuple[int, int]]] = [[] for _ in range(n)]
    for u, v in roads:
        adj[u].append((v, ROAD))
        adj[v].append((u, ROAD))
    for u, v in bridges:
        adj[u].append((v, BRIDGE))
        adj[v].append((u, BRIDGE))

    # visited[city][used_bridges] = have we reached this state?
    visited = [[False] * (k + 1) for _ in range(n)]

    q = deque()
    q.append((start, 0, 0))  # (city, used_bridges, distance)
    visited[start][0] = True

    while q:
        city, used, dist = q.popleft()

        if city == target:
            return dist

        for nei, edge_type in adj[city]:
            next_used = used + (1 if edge_type == BRIDGE else 0)
            if next_used &lt;= k and not visited[nei][next_used]:
                visited[nei][next_used] = True
                q.append((nei, next_used, dist + 1))

    return -1
</code></pre></div><ul><li><p>The <strong>graph</strong> is represented as <code>adj[city] = list of (neighbor, type)</code> where <code>type</code> distinguishes roads and bridges.</p></li><li><p>The <strong>state</strong> for BFS is <code>(city, used_bridges)</code>; we track <code>dist</code> separately for convenience.</p></li><li><p>BFS over this expanded state space guarantees the shortest number of steps in an unweighted graph.</p></li><li><p><code>visited[city][used]</code> ensures we do not revisit the same state, while still allowing revisits to the same city with different bridge usage.</p></li></ul><div><hr></div><h2>Polished Java version (for readers who prefer Java)</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;7a3f263e-fd81-4710-a0ff-c9c2ff3029a0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">import java.util.*;

public class MinStepsWithBridges {

    private static final int ROAD = 0;
    private static final int BRIDGE = 1;

    public static int minStepsWithBridges(
            int n,
            List&lt;int[]&gt; roads,
            List&lt;int[]&gt; bridges,
            int start,
            int target,
            int k
    ) {
        List&lt;int[]&gt;[] adj = new ArrayList[n];
        for (int i = 0; i &lt; n; i++) {
            adj[i] = new ArrayList&lt;&gt;();
        }

        for (int[] e : roads) {
            int u = e[0], v = e[1];
            adj[u].add(new int[]{v, ROAD});
            adj[v].add(new int[]{u, ROAD});
        }

        for (int[] e : bridges) {
            int u = e[0], v = e[1];
            adj[u].add(new int[]{v, BRIDGE});
            adj[v].add(new int[]{u, BRIDGE});
        }

        boolean[][] visited = new boolean[n][k + 1];
        Queue&lt;int[]&gt; q = new ArrayDeque&lt;&gt;();
        // state: {city, usedBridges, distance}
        q.offer(new int[]{start, 0, 0});
        visited[start][0] = true;

        while (!q.isEmpty()) {
            int[] cur = q.poll();
            int city = cur[0];
            int used = cur[1];
            int dist = cur[2];

            if (city == target) {
                return dist;
            }

            for (int[] edge : adj[city]) {
                int nei = edge[0];
                int type = edge[1];
                int nextUsed = used + (type == BRIDGE ? 1 : 0);

                if (nextUsed &lt;= k &amp;&amp; !visited[nei][nextUsed]) {
                    visited[nei][nextUsed] = true;
                    q.offer(new int[]{nei, nextUsed, dist + 1});
                }
            }
        }

        return -1;
    }
}
</code></pre></div><p>This follows the standard BFS template described in many interview resources for shortest paths in unweighted graphs.</p><div><hr></div><h2>Weaker solution 1: Wrong visited definition</h2><p>This is the mistake most people make first: they track <code>visited</code> only per city.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;1f8908f0-01c7-43b7-ad5e-f8155fc842ab&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from collections import deque

def min_steps_wrong_visited(
    n, roads, bridges, start, target, k
):
    ROAD, BRIDGE = 0, 1
    adj = [[] for _ in range(n)]
    for u, v in roads:
        adj[u].append((v, ROAD))
        adj[v].append((u, ROAD))
    for u, v in bridges:
        adj[u].append((v, BRIDGE))
        adj[v].append((u, BRIDGE))

    visited = [False] * n  # &lt;== only per city
    q = deque()
    q.append((start, 0, 0))  # city, used_bridges, dist
    visited[start] = True

    while q:
        city, used, dist = q.popleft()
        if city == target:
            return dist

        for nei, edge_type in adj[city]:
            next_used = used + (1 if edge_type == BRIDGE else 0)
            if next_used &lt;= k and not visited[nei]:
                visited[nei] = True
                q.append((nei, next_used, dist + 1))

    return -1
</code></pre></div><p>Why it&#8217;s wrong:</p><ul><li><p>This approach treats &#8220;I reached city 5 with 0 bridges used&#8221; and &#8220;I reached city 5 with 3 bridges used&#8221; as the same situation.</p></li><li><p>In reality, the first is strictly better: you have more flexibility for the rest of the path.</p></li><li><p>Because it sets <code>visited[nei] = True</code> regardless of bridge usage, it can prune away valid solutions.</p></li></ul><div><hr></div><h2>Weaker solution 2: DFS backtracking instead of BFS</h2><p>This version uses DFS with pruning; it <em>can</em> work on small inputs but doesn&#8217;t naturally guarantee shortest paths or good performance.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;d659b7d2-4daf-4ea7-a0ad-27dd7a821af1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def min_steps_dfs_wrong(
    n, roads, bridges, start, target, k
):
    ROAD, BRIDGE = 0, 1
    adj = [[] for _ in range(n)]
    for u, v in roads:
        adj[u].append((v, ROAD))
        adj[v].append((u, ROAD))
    for u, v in bridges:
        adj[u].append((v, BRIDGE))
        adj[v].append((u, BRIDGE))

    best = [float('inf')]
    visited = set()  # tracks only city

    def dfs(city, used, dist):
        if dist &gt;= best[0]:
            return
        if used &gt; k:
            return
        if city == target:
            best[0] = min(best[0], dist)
            return

        visited.add(city)
        for nei, edge_type in adj[city]:
            if nei in visited:
                continue
            dfs(nei,
                used + (1 if edge_type == BRIDGE else 0),
                dist + 1)
        visited.remove(city)

    dfs(start, 0, 0)
    return -1 if best[0] == float('inf') else best[0]
</code></pre></div><ul><li><p>DFS explores one path deeply before trying alternatives, so you can easily walk a long suboptimal path before discovering a shorter one elsewhere.</p></li><li><p>The pruning condition <code>dist &gt;= best[0]</code> only works once <code>best[0]</code> is set; until then you might do a ton of unnecessary work.</p></li><li><p>This still uses a &#8220;city-only&#8221; visited set, so it conflates different resource usages just like the first weak solution.</p></li></ul><div><hr></div><h2>Weaker solution 3: Correct but potential state explosion / timeouts</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;a557843d-bb6b-40ee-a990-fee84465ed10&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from collections import deque

def min_steps_slow(
    n, roads, bridges, start, target, k
):
    ROAD, BRIDGE = 0, 1
    adj = [[] for _ in range(n)]
    for u, v in roads:
        adj[u].append((v, ROAD))
        adj[v].append((u, ROAD))
    for u, v in bridges:
        adj[u].append((v, BRIDGE))
        adj[v].append((u, BRIDGE))

    visited = set()
    q = deque()
    q.append((start, 0, 0))  # city, used_bridges, dist
    visited.add((start, 0))

    while q:
        city, used, dist = q.popleft()
        if city == target:
            return dist

        for nei, edge_type in adj[city]:
            next_used = used + (1 if edge_type == BRIDGE else 0)
            if next_used &lt;= k:
                state = (nei, next_used)
                if state not in visited:
                    visited.add(state)
                    q.append((nei, next_used, dist + 1))

    return -1
</code></pre></div><p></p><ul><li><p>This uses the right state <code>(city, usedBridges)</code> and is functionally correct.</p></li><li><p>However, because it doesn&#8217;t do any additional dominance pruning (e.g., recognizing that reaching <code>(city, used=1)</code> makes <code>(city, used=2)</code> uninteresting for the same or longer distance), it may explore more states than necessary when <code>k</code> is large.</p></li></ul><div><hr></div><h2>Tying everything together</h2><blockquote><p>At first glance, this problem looks like a standard shortest-path question on an unweighted graph. Every move&#8212;road or bridge&#8212;costs exactly one step, so the natural tool is breadth-first search (BFS), which is known to find shortest paths in unweighted graphs in <em>O(n+m)</em> time.courses.</p><p>The twist is the bridge budget <code>k</code>. Reaching a city is no longer a single event; it matters <strong>how many</strong> bridges you&#8217;ve spent getting there. Reaching city 5 after using 0 bridges is strictly better than reaching city 5 after using 3. To capture this, our BFS state is not just <code>city</code>, but <code>(city, usedBridges)</code>.</p><p>Once we make that modeling decision, everything else falls into place. Our graph of states has at most <code>n * (k + 1)</code> nodes&#8212;each original city, expanded across all possible <code>usedBridges</code> values from 0 to <code>k</code>. From a state <code>(city, used)</code> we push neighbors:</p><ul><li><p>If the edge is a road, we move to <code>(neighbor, used)</code>.</p></li><li><p>If the edge is a bridge, we move to <code>(neighbor, used + 1)</code> as long as <code>used + 1 &#8804; k</code>.</p></li></ul><p>Now we can run a completely standard BFS on this expanded graph. We initialize the queue with <code>(start, 0, 0)</code> where the third component is the number of steps so far. We keep a <code>visited[city][usedBridges]</code> table so we never re-enqueue the same state twice. Because every transition costs exactly 1 step, BFS&#8217;s level-order property guarantees that the first time we pop a state whose <code>city == target</code>, we&#8217;ve reached it with the minimum possible number of steps.</p><p><strong>The first weak solution illustrates a classic pitfall:</strong> tracking <code>visited</code> only per city. That works when the only thing that matters is which node you&#8217;re in, but breaks as soon as there&#8217;s an extra resource dimension like &#8220;bridges used,&#8221; &#8220;walls broken,&#8221; or &#8220;keys collected.&#8221; By marking <code>visited[city] = True</code> unconditionally, that solution may discard a later visit to the same city with fewer bridges spent, even though that later visit might lead to the only feasible path to the target.</p><p><strong>The DFS variant shows a different kind of mistake:</strong> choosing the wrong traversal pattern. DFS explores one path to the bitter end before trying alternatives, so it&#8217;s a poor fit for shortest-path problems in general graphs. You can try to patch it with a global <code>best</code> distance and pruning, but there&#8217;s no structural guarantee that you&#8217;ll explore paths in increasing-length order. On larger inputs, this degenerates into expensive backtracking with fragile heuristics, which is exactly why most interview references recommend BFS (or Dijkstra, for weighted graphs) for shortest paths.</p><p><strong>Finally, the &#8220;slow but correct&#8221; BFS</strong> reminds us that correctness is only part of the story. It uses the right state and will produce the right answer, but a naive state-space treatment can be unnecessarily heavy. Thinking about the size and structure of your state space&#8212;how many distinct <code>(city, usedBridges)</code> pairs exist, and which ones are actually worth exploring&#8212;is an important part of designing scalable graph algorithms. This is the same kind of reasoning that leads from plain BFS to 0&#8211;1 BFS or more general resource-constrained shortest path algorithms in advanced settings.</p><p>Taken together, these versions trace a progression that&#8217;s common in strong interview performances: first <strong>get the modeling right</strong>, then <strong>choose the right traversal pattern</strong>, and finally <strong>reason about the complexity of the </strong><em><strong>state space</strong></em>, not just the graph&#8217;s raw node count.</p></blockquote><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Coding Interview Gym is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Week 2: Reviews and Feedback]]></title><description><![CDATA[Recursive Tree Depth: Comparing a Clean Java Solution to an Over&#8209;Engineered Python One]]></description><link>https://paulepps.substack.com/p/week-2-reviews-and-feedback</link><guid isPermaLink="false">https://paulepps.substack.com/p/week-2-reviews-and-feedback</guid><dc:creator><![CDATA[Paul Epps]]></dc:creator><pubDate>Fri, 27 Mar 2026 03:17:25 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!P3Eq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!P3Eq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!P3Eq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png 424w, https://substackcdn.com/image/fetch/$s_!P3Eq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png 848w, https://substackcdn.com/image/fetch/$s_!P3Eq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png 1272w, https://substackcdn.com/image/fetch/$s_!P3Eq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!P3Eq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png" width="800" height="455" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:455,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:715053,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://paulepps.substack.com/i/190959062?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!P3Eq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png 424w, https://substackcdn.com/image/fetch/$s_!P3Eq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png 848w, https://substackcdn.com/image/fetch/$s_!P3Eq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png 1272w, https://substackcdn.com/image/fetch/$s_!P3Eq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b7fbf88-3e37-4330-8bd8-c5775243b62a_800x455.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>1. Restating the challenge</h2><p>Given the root of a binary tree, return its maximum depth (height): the number of nodes along the longest path from the root down to the farthest leaf. An empty tree has depth 0.</p><div><hr></div><h2>2. A clean Java solution</h2><p>This is an &#8220;interview&#8209;ready&#8221; version: short, idiomatic, and closely aligned with the recursive definition of tree height.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Coding Interview Gym is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;693fad0f-b869-47e1-affd-4183f4533c78&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">// Definition for a binary tree node.
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
    }

    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class Solution {
    // Returns the maximum depth (height) of the binary tree.
    public int maxDepth(TreeNode root) {
        // Base case: empty subtree has depth 0
        if (root == null) {
            return 0;
        }

        // Recursively compute depth of left and right subtrees
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);

        // Current node&#8217;s depth = 1 (this node) + deeper of the two subtrees
        return 1 + Math.max(leftDepth, rightDepth);
    }
}</code></pre></div><p><strong>How to explain it in an interview</strong></p><ul><li><p>Think in terms of <strong>subtrees</strong>: the depth of the whole tree is 1 plus the maximum of the depths of the left and right subtrees.</p></li><li><p>The recursion mirrors that definition: if the subtree is empty, the depth is 0; otherwise, recur on children and take <code>1 + max(left, right)</code>.</p></li></ul><div><hr></div><h2>3. A &#8220;not&#8209;so&#8209;good but correct&#8221; Python solution</h2><p>Here&#8217;s a version that technically works, but takes the scenic route and makes the recursion harder than it needs to be. It&#8217;s the kind of thing an unsure candidate might write under pressure.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;4a4a2723-ddea-4355-aa69-fb5b0134350a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from typing import Optional, List, Tuple

class TreeNode:
    def __init__(self, val: int = 0,
                 left: 'Optional[TreeNode]' = None,
                 right: 'Optional[TreeNode]' = None):
        self.val = val
        self.left = left
        self.right = right


def max_depth_bad(root: Optional[TreeNode]) -&gt; int:
    # Handle the trivial case explicitly
    if root is None:
        return 0

    # We'll store (node, depth) pairs for *every* node
    all_nodes: List[Tuple[TreeNode, int]] = []

    def dfs(node: Optional[TreeNode], depth: int) -&gt; None:
        # Redundant base case checks &amp; side effects
        if node is None:
            return

        # Record this node and its depth in a list
        all_nodes.append((node, depth))

        # Recurse on children, but keep incrementing depth manually
        if node.left is not None:
            dfs(node.left, depth + 1)
        else:
            # unnecessary branch
            dfs(None, depth + 1)

        if node.right is not None:
            dfs(node.right, depth + 1)
        else:
            # unnecessary branch
            dfs(None, depth + 1)

    # Start DFS from root at depth 1
    dfs(root, 1)

    # Now compute maximum depth by scanning the list we built
    max_d = 0
    for (_, d) in all_nodes:
        if d &gt; max_d:
            max_d = d

    return max_d</code></pre></div><p><strong>What&#8217;s &#8220;bad&#8221; about it?</strong></p><ul><li><p>It keeps an <strong>extra list of all nodes and depths</strong>, then scans that list to find the max, rather than just returning the depth directly from recursion. This adds both time and space overhead.</p></li><li><p>It makes <strong>unnecessary recursive calls on </strong><code>None</code>, deepening the call stack without any benefit and increasing the chance of hitting recursion&#8209;limit issues on tall trees.</p></li><li><p>The control flow is more complicated than the simple &#8220;1 + max(left, right)&#8221; pattern, which makes it harder to see the recursion invariant.</p></li></ul><div><hr></div><h2>4. Time complexity: same big&#8209;O, very different clarity</h2><p>Both implementations ultimately visit each real node exactly once.</p><ul><li><p>In the Java version, it&#8217;s easy to see: each node does constant work and makes at most two recursive calls, one to each child.neetcode+1</p></li><li><p>In the Python version, we still traverse each node once, but we also keep an <em>O(n)</em> list of <code>(node, depth)</code> pairs and do an extra linear scan at the end. The asymptotic time complexity is <strong>still</strong> <em>O(n)</em>, but with a larger constant factor and more mental overhead.</p></li></ul><p>This is an important interview point: two solutions can share the same big&#8209;O yet differ a lot in <strong>simplicity</strong>, <strong>constants</strong>, and how easy they are to verify.</p><div><hr></div><h2>5. Stack depth and space: what the call stack is doing</h2><p>The clean Java version is also much nicer to reason about in terms of stack depth and auxiliary space.</p><ul><li><p>Every active recursive call corresponds to one node on a single <strong>root&#8209;to&#8209;current&#8209;node path</strong>, so at any moment the number of frames on the stack is at most the tree height.</p></li><li><p>That gives auxiliary space <em>O(h)</em>:</p><ul><li><p>For a balanced tree, <em>h &#8776; log n</em>.</p></li><li><p>For a skewed &#8220;linked&#8209;list&#8209;shaped&#8221; tree, <em>h &#8776; n</em>, so stack space can grow to <em>O(n)</em>.</p></li></ul></li></ul><p>The Python version shares the same recursion behavior, but then <strong>adds</strong> an <code>all_nodes</code> list of size <em>O(n)</em> on top of the <em>O(h)</em> call stack. That means its total extra space is <em>O(n)</em> even on a balanced tree, purely due to the way it&#8217;s structured.guides.</p><div><hr></div><h2>6. Takeaways</h2><ul><li><p>Start from the <strong>mathematical definition</strong>: &#8220;depth of a tree = 0 if empty, else 1 + max(depth of children)&#8221;. Code that literally.</p></li><li><p>Avoid carrying extra global lists or making redundant recursive calls; they rarely help and often make reasoning about complexity harder.</p></li><li><p>When you explain time and space, tie them directly to <strong>&#8220;one visit per node&#8221;</strong> and <strong>&#8220;one stack frame per level down the tree&#8221;</strong>.</p></li></ul><div><hr></div><p>Here&#8217;s an iterative Java section you can drop into the solutions post, contrasting recursion with an explicit data structure.</p><div><hr></div><h2>7. Iterative Java solution: same idea, explicit stack/queue</h2><p>A great follow&#8209;up is: <em>&#8220;What if the tree is so deep that recursion risks stack overflow?&#8221;</em> One answer is to do the same depth&#8209;first search <strong>iteratively</strong> using your own stack.</p><h3>Iterative DFS with an explicit stack</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;0af3c83e-93f6-4489-9f2c-3b2ddd503997&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">import java.util.Stack;
import javafx.util.Pair; // or your own simple Pair class

class SolutionIterativeDFS {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }

        // Stack holds (node, depth) pairs
        Stack&lt;Pair&lt;TreeNode, Integer&gt;&gt; stack = new Stack&lt;&gt;();
        stack.push(new Pair&lt;&gt;(root, 1));

        int maxDepth = 0;

        while (!stack.isEmpty()) {
            Pair&lt;TreeNode, Integer&gt; current = stack.pop();
            TreeNode node = current.getKey();
            int depth = current.getValue();

            // Update answer
            if (depth &gt; maxDepth) {
                maxDepth = depth;
            }

            // Push children with depth + 1
            if (node.left != null) {
                stack.push(new Pair&lt;&gt;(node.left, depth + 1));
            }
            if (node.right != null) {
                stack.push(new Pair&lt;&gt;(node.right, depth + 1));
            }
        }

        return maxDepth;
    }
}</code></pre></div><p>This mirrors the recursive logic: the stack now plays the role of the call stack, and each entry tracks &#8220;which node&#8221; and &#8220;what depth we&#8217;re at.&#8221;</p><h3>How to talk about complexity</h3><ul><li><p><strong>Time</strong>: Iterative DFS still visits every node exactly once and does constant work per node, so it runs in <em>O(n)</em> time.</p></li><li><p><strong>Space</strong>: Iterative DFS uses a stack whose maximum size is proportional to the <strong>height</strong> <em>h</em> of the tree, so <em>O(h)</em> extra space, just like the recursive version.</p></li></ul><blockquote><p>In the recursive version, the call stack is hidden inside the runtime.<br><br>In the iterative version, we build that stack explicitly, but the time and space story is the same: <em>O(n)</em> time, and <em>O(h)</em> for DFS.</p></blockquote><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Coding Interview Gym is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Week 1: Reviews and Feedback]]></title><description><![CDATA[What interviewers really hear]]></description><link>https://paulepps.substack.com/p/week-1-reviews-and-feedback</link><guid isPermaLink="false">https://paulepps.substack.com/p/week-1-reviews-and-feedback</guid><dc:creator><![CDATA[Paul Epps]]></dc:creator><pubDate>Tue, 10 Mar 2026 23:18:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rnpi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rnpi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rnpi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg 424w, https://substackcdn.com/image/fetch/$s_!rnpi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg 848w, https://substackcdn.com/image/fetch/$s_!rnpi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!rnpi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rnpi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg" width="602" height="453.6811594202899" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1104,&quot;resizeWidth&quot;:602,&quot;bytes&quot;:199282,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://paulepps.substack.com/i/190559681?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rnpi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg 424w, https://substackcdn.com/image/fetch/$s_!rnpi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg 848w, https://substackcdn.com/image/fetch/$s_!rnpi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!rnpi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a525819-428d-4df7-9c64-ec0d6c307d7f_1104x832.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This week&#8217;s challenge was about arrays and strings. It&#8217;s a good test for &#8220;talking through tradeoffs&#8221; because it has multiple reasonable solutions.</p><h2>Frequency map + sort</h2><ul><li><p>Build a hashmap <code>count: word -&gt; frequency</code>.</p></li><li><p>Convert entries to a list and sort with custom comparator:</p><ul><li><p>Primary key: frequency descending, secondary key: word ascending.</p></li></ul></li><li><p>Take first <code>k</code>.</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;1b932fa0-b34f-4cda-bc3a-ef8598f4758f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">import java.util.*;

public class TopKFrequentWordsSort {
    public List&lt;String&gt; topKFrequent(String[] words, int k) {
        Map&lt;String, Integer&gt; freq = new HashMap&lt;&gt;();
        for (String w : words) {
            freq.put(w, freq.getOrDefault(w, 0) + 1);
        }

        List&lt;String&gt; candidates = new ArrayList&lt;&gt;(freq.keySet());

        // Sort by: frequency desc, then word asc
        Collections.sort(candidates, (w1, w2) -&gt; {
            int f1 = freq.get(w1);
            int f2 = freq.get(w2);
            if (f1 != f2) {
                return Integer.compare(f2, f1); // higher freq first
            }
            return w1.compareTo(w2);            // lexicographically smaller first
        });

        return candidates.subList(0, k);
    }
}
</code></pre></div><h3>Tradeoffs</h3><ul><li><p>Time: O(n log &#8289;n) due to sorting all unique words; <code>n</code> is number of words or number of unique words depending how you phrase it.</p></li><li><p>Space: O(u) where <code>u</code> is number of unique words.</p></li><li><p>Pros: Very <strong>simple</strong> and easy to implement correctly.</p></li><li><p>Cons: Overkill if <code>k</code> is small and <code>u</code> is large; you pay sort cost for all items.</p></li></ul><div><hr></div><h2>Frequency map + size&#8209;k heap</h2><ul><li><p>Same frequency map.</p></li><li><p>Maintain a min&#8209;heap of size at most <code>k</code>, ordered by:</p><ul><li><p>frequency ascending, and for ties, word descending (so the &#8220;worst&#8221; candidate is at the top).</p></li></ul></li><li><p>Iterate over all unique words:</p><ul><li><p>Push into heap.</p></li><li><p>If heap size exceeds <code>k</code>, pop.</p></li></ul></li><li><p>At the end, heap contains the <code>k</code> best; pop all and reverse to get correct order.</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;3818b120-e92d-4a69-8acc-7706a589fa1d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">import java.util.*;

public class TopKFrequentWordsHeap {
    public List&lt;String&gt; topKFrequent(String[] words, int k) {
        Map&lt;String, Integer&gt; freq = new HashMap&lt;&gt;();
        for (String w : words) {
            freq.put(w, freq.getOrDefault(w, 0) + 1);
        }

        // Min-heap: worst candidate on top
        PriorityQueue&lt;String&gt; pq = new PriorityQueue&lt;&gt;((w1, w2) -&gt; {
            int f1 = freq.get(w1);
            int f2 = freq.get(w2);
            if (f1 != f2) {
                return Integer.compare(f1, f2); // lower freq first
            }
            return w2.compareTo(w1);            // lexicographically larger first
        });

        for (String word : freq.keySet()) {
            pq.offer(word);
            if (pq.size() &gt; k) {
                pq.poll(); // remove worst candidate
            }
        }

        List&lt;String&gt; result = new ArrayList&lt;&gt;();
        while (!pq.isEmpty()) {
            result.add(pq.poll());
        }
        Collections.reverse(result); // heap gives lowest-first, reverse to highest-first
        return result;
    }
}
</code></pre></div><h3>Tradeoffs</h3><ul><li><p>Time: O(u log k).</p><ul><li><p>Better than full sort when <code>k &lt;&lt; u</code>.</p></li></ul></li><li><p>Space: <em>O</em>(<em>u</em>) for the map, <em>O</em>(<em>k</em>) for the heap.</p></li><li><p>Pros: Asymptotically better when <code>k</code> is small, demonstrates priority queues.</p></li><li><p>Cons: Comparator is subtle, correctness bugs are easy; more complex than sorting.</p></li></ul><div><hr></div><p>Here&#8217;s a <strong>size&#8209;k min&#8209;heap</strong> version in Python:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;58718c6a-d60a-4dc3-915c-4264eb7535ae&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">from collections import Counter
import heapq
from typing import List, Tuple

def topKFrequent(words: List[str], k: int) -&gt; List[str]:
    freq = Counter(words)

    # Heap elements: (count, rev_word, word)
    # rev_word = a key that makes lexicographically larger words "smaller"
    # so they rise to the top and are evicted first on ties.
    def rev_key(w: str) -&gt; str:
        # Map each char c to chr(255 - ord(c)) to reverse lex order
        return ''.join(chr(255 - ord(c)) for c in w)

    heap: List[Tuple[int, str, str]] = []

    for word, count in freq.items():
        entry = (count, rev_key(word), word)
        heapq.heappush(heap, entry)
        if len(heap) &gt; k:
            heapq.heappop(heap)

    # Extract words (heap is from worst to best); reverse to get best to worst.
    res = [heapq.heappop(heap)[2] for _ in range(len(heap))]
    return res[::-1]
</code></pre></div><p>In an interview setting, I&#8217;d usually annotate verbally that the key design choice is &#8220;min&#8209;heap holding the top k so far; comparator makes the least desirable candidate sit at the root and be evicted when size exceeds k.&#8221;</p><div><hr></div><h2>Bucket&#8209;style idea</h2><ul><li><p>Because max frequency &#8804; <code>n</code>, you can bucket words by frequency.</p></li><li><p>After building the map, make an array of lists <code>buckets[1..n]</code>, each bucket holds words with that frequency.</p></li><li><p>Traverse buckets from high freq to low, sort each bucket&#8217;s words lexicographically, and accumulate until you have <code>k</code>.</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;csharp&quot;,&quot;nodeId&quot;:&quot;50c1c990-030b-43e1-94f7-bf9c42f51f01&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-csharp">using System;
using System.Collections.Generic;

public class Solution {
    public IList&lt;string&gt; TopKFrequent(string[] words, int k) {
        int n = words.Length;
        var result = new List&lt;string&gt;();

        // 1. Count frequencies
        var count = new Dictionary&lt;string, int&gt;();
        foreach (var w in words) {
            if (!count.ContainsKey(w)) {
                count[w] = 0;
            }
            count[w]++;
        }

        // 2. Buckets: index = frequency, each bucket holds words with that freq
        var buckets = new List&lt;string&gt;[n + 1];
        foreach (var kvp in count) {
            string word = kvp.Key;
            int freq = kvp.Value;
            if (buckets[freq] == null) {
                buckets[freq] = new List&lt;string&gt;();
            }
            buckets[freq].Add(word);
        }

        // 3. Walk buckets from high freq to low
        for (int freq = n; freq &gt; 0 &amp;&amp; result.Count &lt; k; freq--) {
            if (buckets[freq] == null) continue;

            // For equal frequency, words must be in lexicographic order
            buckets[freq].Sort(StringComparer.Ordinal);

            foreach (var word in buckets[freq]) {
                result.Add(word);
                if (result.Count == k) {
                    return result;
                }
            }
        }

        return result;
    }
}
</code></pre></div><h3>Tradeoffs</h3><ul><li><p>Time: <em>O</em>(<em>n </em>+ <em>u </em>+ &#8721;sort(bucket)); in worst case still <em>O</em>(<em>u </em>log <em>u</em>), but can be good when frequencies are spread out.</p></li><li><p>Space: <em>O</em>(<em>n</em>+<em>u</em>).</p></li><li><p>Pros: Great to talk about using constraints (freq bounded by <code>n</code>), shows alternative to comparison sort.</p></li><li><p>Cons: More bookkeeping, less common pattern in standard interviews.</p></li></ul><div><hr></div><h2><strong>How to answer it in an interview</strong></h2><ul><li><p>You can start with a brute&#8209;force / straightforward approach (frequency map + sort).</p></li><li><p>Analyze <strong>time vs space</strong> and when that&#8217;s acceptable.</p></li><li><p>If asked, &#8220;What if <code>k</code> is small and the number of unique words is huge?&#8221; this leads into the heap solution.</p></li><li><p>Compare:</p><ul><li><p>Simplicity / robustness of the sort approach.</p></li><li><p>Performance benefit and added complexity of the heap.</p></li></ul></li><li><p>If asked, &#8220;Can we do better using the fact that frequency is bounded by <code>n</code>?&#8221; this gets you to buckets. In practice, the C# bucket solution was the fastest of all the shown solutions.</p></li></ul><div><hr></div><p>If you joined late or didn&#8217;t submit this week, you can still work through the prompt and compare your answer to the solutions and notes above.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://paulepps.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Coding Interview Gym! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>