<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://ekronke.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ekronke.com/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-10-09T21:47:21+02:00</updated><id>https://ekronke.com/feed.xml</id><title type="html">Erik Kronke</title><subtitle>Theoretical computer science student at KTH. Notes and essays on algorithms, complexity, and the mathematics underneath them.</subtitle><author><name>Erik Kronke</name><email>kronke.erik@gmail.com</email></author><entry><title type="html">The birthday problem, and why 64-bit hashes collide sooner than you think</title><link href="https://ekronke.com/2026/birthday-problem-and-hash-collisions/" rel="alternate" type="text/html" title="The birthday problem, and why 64-bit hashes collide sooner than you think" /><published>2026-10-09T00:00:00+02:00</published><updated>2026-10-09T00:00:00+02:00</updated><id>https://ekronke.com/2026/birthday-problem-and-hash-collisions</id><content type="html" xml:base="https://ekronke.com/2026/birthday-problem-and-hash-collisions/"><![CDATA[<p>Put \(n\) people in a room. How large must \(n\) be before two of them probably share a birthday? The answer, 23, surprises people because they compare \(n\) to 365. The right comparison is the number of <em>pairs</em>, \(\binom{n}{2}\), and that grows quadratically.</p>

<h2 id="the-exact-probability">The exact probability</h2>

<p>Assume \(d\) equally likely days.<sup id="fnref:uniform" role="doc-noteref"><a href="#fn:uniform" class="footnote" rel="footnote">1</a></sup> Seat people one at a time; the \(i\)-th person avoids everyone before them with probability \(1 - i/d\). So</p>

\[\Pr[\text{no collision}] \;=\; \prod_{i=0}^{n-1} \left(1 - \frac{i}{d}\right).
\label{eq:exact}\]

<p>Using the inequality \(1 - x \le e^{-x}\), a consequence of convexity,<sup id="fnref:convex" role="doc-noteref"><a href="#fn:convex" class="footnote" rel="footnote">2</a></sup> on each factor of \eqref{eq:exact} and summing the exponents gives a clean upper bound:</p>

\[\Pr[\text{no collision}] \;\le\; \exp\!\left(-\frac{n(n-1)}{2d}\right).
\label{eq:bound}\]

<h2 id="when-a-collision-becomes-likely">When a collision becomes likely</h2>

<div class="theorem" id="thm:sqrt" data-title="Square-root law">
  <p>With \(d\) equally likely values and \(n(n-1) \ge 2d \ln 2\), two of the \(n\) values coincide with probability at least \(1/2\). In particular \(n \approx \sqrt{2d \ln 2} \approx 1.18\sqrt{d}\) suffices.</p>
</div>

<div class="proof">
  <p>By \eqref{eq:bound}, \(\Pr[\text{no collision}] \le \exp(-n(n-1)/2d) \le \exp(-\ln 2) = 1/2\).</p>
</div>

<p>For \(d = 365\) the condition reads \(n(n-1) \ge 505.99\ldots\), and \(n = 23\) is the first value that meets it, since \(23 \cdot 22 = 506\).</p>

<h2 id="try-it">Try it</h2>

<p>Drag the slider to 23 and notice that the curve crosses one half there, then drag it to 57: the probability is already above 99%.</p>

<figure class="figure" id="birthday-fig">
  <svg role="img" aria-label="Probability of a shared birthday as the group grows"></svg>
  <div class="controls">
    <label for="n-slider">People in the room</label>
    <input type="range" id="n-slider" min="2" max="80" value="23" />
    <output for="n-slider" id="n-out"></output>
  </div>
  <figcaption id="n-caption"></figcaption>
</figure>

<script>
(function () {
  var d = 365, maxN = 80;
  var fig = document.getElementById('birthday-fig');
  var svg = fig.querySelector('svg');
  var slider = document.getElementById('n-slider');
  var out = document.getElementById('n-out');
  var cap = document.getElementById('n-caption');
  var NS = 'http://www.w3.org/2000/svg';
  var dot, guide, x, y;

  function pCollide(n) {
    var p = 1;
    for (var i = 0; i < n; i++) p *= 1 - i / d;
    return 1 - p;
  }
  function el(tag, attrs) {
    var e = document.createElementNS(NS, tag);
    for (var k in attrs) e.setAttribute(k, attrs[k]);
    svg.appendChild(e);
    return e;
  }

  // Draw at the figure's real pixel width, so text stays the same size on any screen.
  function draw() {
    var W = svg.clientWidth || 600;
    var H = Math.round(Math.min(280, Math.max(190, W * 0.45)));
    var L = 44, R = 12, T = 12, B = 30;
    svg.setAttribute('viewBox', '0 0 ' + W + ' ' + H);
    svg.style.height = H + 'px';
    svg.textContent = '';
    x = function (n) { return L + (n - 1) / (maxN - 1) * (W - L - R); };
    y = function (p) { return T + (1 - p) * (H - T - B); };

    [0, 0.5, 1].forEach(function (p) {
      el('line', { x1: L, x2: W - R, y1: y(p), y2: y(p), style: 'stroke: var(--rule)' });
      el('text', { x: L - 8, y: y(p) + 4, 'text-anchor': 'end', style: 'fill: var(--muted); font-size: 13px' })
        .textContent = p * 100 + '%';
    });
    (W < 420 ? [1, 40, 80] : [1, 20, 40, 60, 80]).forEach(function (n) {
      el('text', { x: x(n), y: H - 8, 'text-anchor': 'middle', style: 'fill: var(--muted); font-size: 13px' })
        .textContent = n;
    });

    var pts = [];
    for (var n = 1; n <= maxN; n++) pts.push(x(n).toFixed(1) + ',' + y(pCollide(n)).toFixed(1));
    el('polyline', { points: pts.join(' '), fill: 'none', 'stroke-width': 2.5, style: 'stroke: var(--accent)' });
    guide = el('line', { 'stroke-dasharray': '3 4', style: 'stroke: var(--muted)' });
    dot = el('circle', { r: 6, 'stroke-width': 2, style: 'fill: var(--paper); stroke: var(--accent)' });
    update();
  }

  function update() {
    var n = +slider.value, p = pCollide(n);
    dot.setAttribute('cx', x(n)); dot.setAttribute('cy', y(p));
    guide.setAttribute('x1', x(n)); guide.setAttribute('x2', x(n));
    guide.setAttribute('y1', y(0)); guide.setAttribute('y2', y(p));
    out.textContent = n + ' people';
    var approx = 1 - Math.exp(-n * (n - 1) / (2 * d));
    cap.textContent = 'Chance of a shared birthday: ' + (100 * p).toFixed(1) +
      '% exactly, at least ' + (100 * approx).toFixed(1) + '% by the bound.';
  }

  slider.addEventListener('input', update);
  if (window.ResizeObserver) new ResizeObserver(draw).observe(fig); else window.addEventListener('resize', draw);
  draw();
})();
</script>

<h2 id="why-this-matters-for-hashing">Why this matters for hashing</h2>

<p>Replace “birthdays” with “hash values”. A \(b\)-bit hash has \(d = 2^b\) outputs, so by Theorem \ref{thm:sqrt} a collision is more likely than not after about \(1.18 \cdot 2^{b/2}\) items. A 64-bit hash, which sounds enormous, gets there after roughly \(1.18 \cdot 2^{32} \approx 5\) billion items. That is a normal day for a large database.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">math</span>

<span class="k">def</span> <span class="nf">items_until_likely_collision</span><span class="p">(</span><span class="n">bits</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
    <span class="s">"""Smallest n (as a real number) with n^2 &gt;= 2 * 2**bits * ln 2."""</span>
    <span class="k">return</span> <span class="n">math</span><span class="p">.</span><span class="n">sqrt</span><span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="mi">2</span><span class="o">**</span><span class="n">bits</span> <span class="o">*</span> <span class="n">math</span><span class="p">.</span><span class="n">log</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span>

<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">items_until_likely_collision</span><span class="p">(</span><span class="mi">64</span><span class="p">)</span><span class="si">:</span><span class="p">.</span><span class="mi">3</span><span class="n">e</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>  <span class="c1"># 5.057e+09
</span></code></pre></div></div>

<p>The same square root explains why birthday attacks halve the security of a hash function, and why 128-bit identifiers are the usual choice when collisions must essentially never happen.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:uniform" role="doc-endnote">
      <p>Real birthdays are not uniform. Unevenness only makes collisions more likely, so 23 people is enough in real rooms too. <a href="#fnref:uniform" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:convex" role="doc-endnote">
      <p>The function \(e^{-x}\) is convex, and \(1 - x\) is its tangent line at \(x = 0\), so the line lies below the curve. <a href="#fnref:convex" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Erik Kronke</name><email>kronke.erik@gmail.com</email></author><summary type="html"><![CDATA[Why 23 people are enough for a shared birthday, and why a 64-bit hash already collides after about 5 billion items.]]></summary></entry></feed>