<?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"><channel><title><![CDATA[basecase]]></title><description><![CDATA[Code got cheap. Being right didn’t. Writing about AI engineering, distributed systems, algorithms, and product.]]></description><link>https://basecase-utkarsh.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6acae38adacabcce499883ff/0e232f2d-d029-4fc8-a93d-92e4d306e5b0.jpg</url><title>basecase</title><link>https://basecase-utkarsh.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 12:48:36 GMT</lastBuildDate><atom:link href="https://basecase-utkarsh.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Binary search without the off-by-one guesswork]]></title><description><![CDATA[Binary search is easy to explain and easy to get wrong. left <= right or left < right? mid or mid + 1? Return left or right?
Here's my first attempt at LeetCode 981: Time Based Key-Value Store, which ]]></description><link>https://basecase-utkarsh.hashnode.dev/binary-search-without-the-off-by-one-guesswork</link><guid isPermaLink="true">https://basecase-utkarsh.hashnode.dev/binary-search-without-the-off-by-one-guesswork</guid><category><![CDATA[binary search]]></category><category><![CDATA[algorithms]]></category><dc:creator><![CDATA[Utkarsh Shrivastava]]></dc:creator><pubDate>Sun, 11 Oct 2026 02:47:55 GMT</pubDate><content:encoded><![CDATA[<p>Binary search is easy to explain and easy to get wrong. <code>left &lt;= right</code> or <code>left &lt; right</code>? <code>mid</code> or <code>mid + 1</code>? Return <code>left</code> or <code>right</code>?</p>
<p>Here's my first attempt at <a href="https://leetcode.com/problems/time-based-key-value-store/">LeetCode 981: Time Based Key-Value Store</a>, which needs the value with the latest timestamp <code>&lt;= target</code>:</p>
<pre><code class="language-python">def find_value(entries: list[tuple[int, str]], target: int) -&gt; str:
    left, right = 0, len(entries) - 1
 
    while left &lt;= right:
        mid = (left + right) // 2
        timestamp, _ = entries[mid]
 
        if target &lt;= timestamp:
            left = mid
        else:
            right = mid - 1
 
    _, value = entries[left]
    return value
</code></pre>
<p>Three bugs. Here's the framework that prevents all of them.</p>
<h2>1. Turn the array into Y/N</h2>
<p>Ask a yes/no question of each element. In a sorted array, the answers form a block of Ys followed by a block of Ns.</p>
<pre><code class="language-plaintext">timestamps:  1   2   3   4   5   7   9      target = 5
ts &lt;= 5?     Y   Y   Y   Y   Y   N   N
                             ^   ^
                        last Y   first N
</code></pre>
<p>Every binary search finds one of two spots: the <strong>last Y</strong> or the <strong>first N</strong>.</p>
<h2>2. Use one template</h2>
<pre><code class="language-python">left, right = 0, len(arr) - 1
 
while left &lt;= right:
    mid = (left + right) // 2
 
    if is_yes(arr[mid]):
        left = mid + 1     # mid is a Y: boundary is to the right
    else:
        right = mid - 1    # mid is an N: boundary is to the left
</code></pre>
<p>Always step past <code>mid</code>. The window shrinks every loop, so it can't hang.</p>
<h2>3. Read the answer from the end state</h2>
<p>The loop ends with <code>left == right + 1</code>, straddling the boundary:</p>
<ul>
<li><p><code>right</code> = <strong>last Y</strong> (<code>-1</code> if there are no Ys)</p>
</li>
<li><p><code>left</code> = <strong>first N</strong> (<code>len(arr)</code> if there are no Ns) No extra <code>result</code> variable needed.</p>
</li>
</ul>
<h2>Checklist</h2>
<ol>
<li><p><strong>Question?</strong> Phrase it so Ys come first.</p>
</li>
<li><p><strong>Last Y or first N?</strong> Return <code>right</code> or <code>left</code>.</p>
</li>
<li><p><strong>Not found?</strong> Check <code>right &gt;= 0</code> or <code>left &lt; len(arr)</code>.</p>
</li>
</ol>
<h2>Examples</h2>
<p><a href="https://leetcode.com/problems/time-based-key-value-store/"><strong>TimeMap</strong></a><strong>:</strong> latest timestamp <code>&lt;= target</code>. That's the last Y, so use <code>right</code>.</p>
<pre><code class="language-python">def find_value(entries: list[tuple[int, str]], target: int) -&gt; str:
    left, right = 0, len(entries) - 1
 
    while left &lt;= right:
        mid = (left + right) // 2
        timestamp, _ = entries[mid]
 
        if timestamp &lt;= target:    # Y
            left = mid + 1
        else:                      # N
            right = mid - 1
 
    if right &lt; 0:                  # no timestamp &lt;= target
        return ""
 
    _, value = entries[right]      # last Y
    return value
</code></pre>
<p><a href="https://leetcode.com/problems/search-insert-position/"><strong>Search Insert Position</strong></a><strong>:</strong> first element <code>&gt;= target</code>. Ask <code>num &lt; target</code>, so that's the first N: use <code>left</code>.</p>
<pre><code class="language-python">def search_insert(nums: list[int], target: int) -&gt; int:
    left, right = 0, len(nums) - 1
 
    while left &lt;= right:
        mid = (left + right) // 2
 
        if nums[mid] &lt; target:     # Y
            left = mid + 1
        else:                      # N
            right = mid - 1
 
    return left                    # first N
</code></pre>
<p>Same loop. Only the question and the return change.</p>
<p>Python's <a href="https://docs.python.org/3/library/bisect.html"><code>bisect</code></a> module follows the same idea: <code>bisect_left(nums, x)</code> is the first index with <code>nums[i] &gt;= x</code>, and <code>bisect_right(nums, x) - 1</code> is the last index with <code>nums[i] &lt;= x</code>.</p>
<h2>The original bugs</h2>
<ol>
<li><p><strong>Wrong question.</strong> <code>target &lt;= timestamp</code> marks the Ns, then moves right on them.</p>
</li>
<li><p><code>left = mid</code><strong>.</strong> Doesn't step past <code>mid</code>, so it hangs when <code>left == right</code>.</p>
</li>
<li><p><strong>No not-found check.</strong> Reads <code>entries[left]</code> even when nothing qualifies, and crashes on an empty list. Don't memorize where the <code>+ 1</code> goes. Find the Y/N boundary and let the template do the rest.</p>
</li>
</ol>
]]></content:encoded></item></channel></rss>