<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://systemhalted.in/feed.xml" rel="self" type="application/atom+xml" /><link href="https://systemhalted.in/" rel="alternate" type="text/html" /><updated>2026-07-21T14:01:49+00:00</updated><id>https://systemhalted.in/feed.xml</id><title type="html">SystemHalted</title><subtitle>SystemHalted is the personal blog of Palak Mathur, covering software engineering, leadership, management, and Emacs, plus the Kartavya Path newsletter.</subtitle><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><entry><title type="html">Review: Software Malpractice in the Age of AI</title><link href="https://systemhalted.in/2026/07/21/review-software-malpractice-in-the-age-of-ai/" rel="alternate" type="text/html" title="Review: Software Malpractice in the Age of AI" /><published>2026-07-21T00:00:00+00:00</published><updated>2026-07-21T00:00:00+00:00</updated><id>https://systemhalted.in/2026/07/21/review-software-malpractice-in-the-age-of-ai</id><content type="html" xml:base="https://systemhalted.in/2026/07/21/review-software-malpractice-in-the-age-of-ai/"><![CDATA[<p>Danny Tobey’s “Software Malpractice in the Age of AI” makes a useful and uncomfortable argument: as software becomes more consequential, the software industry cannot keep pretending that professional responsibility belongs only to older professions such as medicine, law, accounting, and nursing.</p>

<p>The paper surveys legal precedent around software, especially in medicine, and argues that the rise of AI weakens one of the assumptions that has historically protected software vendors from malpractice-style liability. That assumption is that human professionals remain the real decision makers, while software merely assists them.</p>

<p>For ordinary clinical decision-support systems, that line may seem plausible. A doctor reviews the output, applies professional judgment, and accepts or rejects the recommendation. But with narrow AI systems that develop deep expertise in a domain, the relationship becomes less clean. If a system produces a recommendation that a human reviewer cannot meaningfully audit, then “human oversight” can become more procedural than real.</p>

<p>That is the paper’s strongest point. AI makes it harder to rely on the fiction that a human independently reviewed the basis for a software recommendation.</p>

<h2 id="where-i-agree">Where I agree</h2>

<p>I agree with the paper’s central concern. Technology companies and software professionals should not be immune from responsibility simply because the harm is mediated through code.</p>

<p>Software now helps decide medical treatment, credit access, hiring, public benefits, fraud detection, insurance pricing, and many other areas where mistakes can alter a person’s life. When systems operate at that level of consequence, the industry needs a more serious account of duty, care, review, and accountability.</p>

<p>The paper is also right to look at professional malpractice as a useful comparison. Older professions have already built ideas around competence, fiduciary responsibility, licensing, insurance, standards of care, and disciplinary consequences. Software does not need to copy that structure blindly, but it should learn from it.</p>

<h2 id="where-the-analogy-weakens">Where the analogy weakens</h2>

<p>The main weakness is that the doctor-software-engineer analogy does not map cleanly.</p>

<p>A doctor usually owns the professional act of diagnosis or treatment. Even when the doctor consults peers, the final professional decision is attributable to that doctor. Software is rarely produced that way.</p>

<p>A deployed AI product is usually the result of many decisions:</p>

<ul>
  <li>Product defines the user problem and desired behavior.</li>
  <li>Data teams choose or prepare datasets.</li>
  <li>Engineers implement the system.</li>
  <li>Model teams tune behavior.</li>
  <li>Designers shape how users interpret the output.</li>
  <li>Legal and compliance teams approve risk language.</li>
  <li>Executives decide whether the product ships.</li>
</ul>

<p>If harm occurs, it may be impossible or unfair to assign responsibility only to the individual engineer who wrote part of the implementation. The engineer may have seen the risk but lacked authority to block release. Or the risk may have emerged from a product decision, data limitation, or business constraint outside the engineer’s control.</p>

<p>That does not mean nobody is responsible. It means software responsibility has to be organizational, not merely individual.</p>

<h2 id="what-the-paper-leaves-open">What the paper leaves open</h2>

<p>The paper is valuable as a survey and warning, but it does less to explain how responsibility should actually be assigned inside software organizations.</p>

<p>Several questions need more work:</p>

<ul>
  <li>What would a software “standard of care” look like for AI systems?</li>
  <li>Which duties should belong to engineers, product leaders, data scientists, executives, and companies?</li>
  <li>Should licensing apply to all software engineers, or only to those working in high-risk domains?</li>
  <li>How should certifications, professional societies, or bodies such as IEEE shape enforceable practice?</li>
  <li>How do existing frameworks such as GDPR or human-rights declarations become operational inside product development?</li>
</ul>

<p>The strongest version of this argument would move from analogy to mechanism. It would define what competent AI development requires, what evidence must be produced before deployment, and who is accountable when that evidence is ignored.</p>

<h2 id="bottom-line">Bottom line</h2>

<p>“Software Malpractice in the Age of AI” is worth reading because it names a real gap: AI systems are becoming professionally consequential without the professional accountability structure that older high-stakes domains developed over time.</p>

<p>But the solution cannot simply be “treat software engineers like doctors.” Software is too collaborative, too organizational, and too entangled with product and business incentives for that analogy to carry the whole burden.</p>

<p>AI malpractice, if the term is to mean anything useful, has to attach responsibility to the system of production. That includes the engineer, but it also includes the company that creates the incentives, approves the risks, and profits from the deployment.</p>

<h2 id="reference">Reference</h2>

<p>Danny Tobey, “Software Malpractice in the Age of AI: A Guide for the Wary Tech Company”, AIES 2018.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Article Review" /><category term="AI" /><category term="ai" /><category term="law" /><category term="software" /><category term="article-review" /><category term="opinion" /><summary type="html"><![CDATA[A review of Danny Tobey's argument that AI makes software malpractice harder to avoid, and why responsibility in software cannot be modeled exactly like medical malpractice.]]></summary></entry><entry><title type="html">[Book Review] Miner Town: Awakening (Miner Town, #1) by Ankit Saxena</title><link href="https://systemhalted.in/2026/07/20/book-review-miner-town-awakening-by-ankit-saxena/" rel="alternate" type="text/html" title="[Book Review] Miner Town: Awakening (Miner Town, #1) by Ankit Saxena" /><published>2026-07-20T00:00:00+00:00</published><updated>2026-07-20T00:00:00+00:00</updated><id>https://systemhalted.in/2026/07/20/book-review-miner-town-awakening-by-ankit-saxena</id><content type="html" xml:base="https://systemhalted.in/2026/07/20/book-review-miner-town-awakening-by-ankit-saxena/"><![CDATA[<p><em>I received a complimentary copy of this book and am leaving an honest review.</em></p>

<p>Miner Town: Awakening is a dystopian science fiction novel that blends post-apocalyptic survival, coming-of-age, and rebellion against an oppressive system. The world is imaginative and immediately engaging. Miner Town feels harsh, believable, and internally consistent, and the contrast between the mining settlement and the pristine Trinity cities provides a compelling foundation for the story.</p>

<p>The strongest aspect of the novel is its cast. Gage, Camilla, Otto, and Ben each represent different strengths, and their relationships drive much of the narrative. Rather than relying solely on action, the story spends time developing why these characters become who they are.</p>

<p>My main problem is with the prose. There is a vivid metaphor or philosophical reflection in every paragraph. While individually well written, their cumulative effect occasionally slows the narrative and reduces the impact of the strongest moments. The book would hit harder if it were plainer most of the time. Related issue: several pieces of exposition get delivered twice by different characters. Cutting those would help the pace.</p>

<p>Themes of sacrifice and exploitation are handled well, mostly because the author trusts the setting to carry them instead of having someone announce them.</p>

<p>An ambitious debut. If you liked The Hunger Games, you will recognize the genre. I will definitely like to read the next one.</p>

<p>Rating: 4.5/5</p>

<p>Find the book on <a href="https://a.co/d/0aG2qvUs">Amazon</a>.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Books &amp; Reading&quot;]" /><category term="book-review" /><category term="science-fiction" /><category term="dystopian" /><summary type="html"><![CDATA[An honest review of Miner Town Awakening, Ankit Saxena's ambitious dystopian science fiction debut - an imaginative world and a strong cast, held back a little by dense prose.]]></summary></entry><entry><title type="html">Agent Loops as a Team Diagnostic</title><link href="https://systemhalted.in/2026/07/17/agent-loops-as-a-team-diagnostic/" rel="alternate" type="text/html" title="Agent Loops as a Team Diagnostic" /><published>2026-07-17T00:00:00+00:00</published><updated>2026-07-17T00:00:00+00:00</updated><id>https://systemhalted.in/2026/07/17/agent-loops-as-a-team-diagnostic</id><content type="html" xml:base="https://systemhalted.in/2026/07/17/agent-loops-as-a-team-diagnostic/"><![CDATA[<p>In an <a href="/2026/07/03/what-sits-underneath-the-agent-loops-post/">earlier post</a> I wrote about the four kinds of agent loops that Anthropic’s Claude Code team described, and about where the check on the work lives in each one. The taxonomy also supports a practical exercise. A team can read the four types as four questions about its own work, and the answers point to the artifact it should write next.</p>

<p>The first question is what the team still verifies by hand. Every manual verification habit is a candidate skill file. The content is nothing more than the steps a reviewer already performs before trusting a change: what to start, what to click, what to compare, which tests to run. The habit feels too obvious to document, which is exactly why it never gets documented. A useful test for the finished file is whether a new team member could follow it without asking anyone.</p>

<p>The next question is how the team decides that a piece of work is done. If the honest answer is that an experienced person looks at it and says so, the missing artifact is a measurable exit criterion. A worthwhile exercise is to take one recent change that was accepted on judgement and restate the acceptance in checkable terms: a response time under a stated number, a set of tests that must pass, a score from an audit tool. Not every kind of done converts into a measurement, but more of them convert than the first attempt suggests, and each conversion is one less decision that has to be relitigated per change.</p>

<p>Then there are the tasks people do at the same time every day. A morning routine of reading a channel, sweeping a queue, or checking a dashboard is a schedule that exists only in someone’s habits. Writing it down as an actual schedule is the small step. The more valuable step is to ask what change in the world the routine is standing in for, and whether the system where that change happens can announce it. A webhook or a queue alert replaces a guess about timing with a fact about the source.</p>

<p>The last question concerns streams: bug reports, support tickets, dependency alerts, anything that arrives continuously and gets triaged by a person. This is where a proactive routine could stand, and it is also where the diagnostic has to be most careful. The routine needs a scoped goal, a budget, a rate limit, a shutoff, and an owner. The owner is the deciding item. If no one can be named who is responsible when the routine acts wrongly, the routine is not ready to build, whatever the tooling makes possible.</p>

<p>The output of the exercise is a short list: the checks nobody has written down, the criteria that live in one person’s judgement, the schedules that exist as habits, and the streams with no owner for their automation. A team does not need to adopt all four loops, and most should not. It needs to know which artifact on that list to write first.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Technology" /><category term="Software Engineering" /><category term="AI" /><category term="ai" /><category term="agents" /><category term="claude-code" /><category term="engineering-management" /><summary type="html"><![CDATA[Four questions a team can ask about its own work, using Anthropic's agent loop taxonomy, to find the artifact it should write next.]]></summary></entry><entry><title type="html">The 2D↔1D Problem in Text Editors</title><link href="https://systemhalted.in/2026/07/17/the-2d-1d-problem-in-text-editors/" rel="alternate" type="text/html" title="The 2D↔1D Problem in Text Editors" /><published>2026-07-17T00:00:00+00:00</published><updated>2026-07-17T00:00:00+00:00</updated><id>https://systemhalted.in/2026/07/17/the-2d-1d-problem-in-text-editors</id><content type="html" xml:base="https://systemhalted.in/2026/07/17/the-2d-1d-problem-in-text-editors/"><![CDATA[<p>Open a file in any editor and you see a grid: rows of text, a cursor at “line 12, column 5.” Move down and the cursor drops a row. The position is two-dimensional.</p>

<p>But the file underneath is not a grid. A text file is a one-dimensional sequence: a flat run of characters, <code class="language-plaintext highlighter-rouge">h e l l o \n w o r l d</code>, where the newline is a character that means “start drawing on the next row.” The buffer that holds the document in memory is the same, a sequence you index with a single number.</p>

<p>So every editor lives with a permanent translation problem. This post looks at how a few of them solve it: a from-scratch Rust editor I’m building (<a href="https://github.com/systemhalted/textr">textr</a>), the two it is modeled on (<strong>gedit</strong> and <strong>Emacs</strong>), and <strong>VS Code</strong> for a fourth point of comparison.</p>

<ul id="markdown-toc">
  <li><a href="#the-impedance-mismatch" id="markdown-toc-the-impedance-mismatch">The impedance mismatch</a></li>
  <li><a href="#where-it-comes-up-in-textr" id="markdown-toc-where-it-comes-up-in-textr">Where it comes up in textr</a></li>
  <li><a href="#why-not-just-pick-one-and-be-done" id="markdown-toc-why-not-just-pick-one-and-be-done">Why not just pick one and be done?</a></li>
  <li><a href="#how-gedit-does-it-iterators-over-a-tree-cursor-as-mark" id="markdown-toc-how-gedit-does-it-iterators-over-a-tree-cursor-as-mark">How gedit does it: iterators over a tree, cursor-as-mark</a></li>
  <li><a href="#how-emacs-does-it-point-is-a-number-everything-else-is-derived" id="markdown-toc-how-emacs-does-it-point-is-a-number-everything-else-is-derived">How Emacs does it: point is a number, everything else is derived</a></li>
  <li><a href="#how-vs-code-does-it-a-piece-tree" id="markdown-toc-how-vs-code-does-it-a-piece-tree">How VS Code does it: a piece tree</a></li>
  <li><a href="#side-by-side" id="markdown-toc-side-by-side">Side by side</a></li>
  <li><a href="#the-choice-follows-the-data-structure-and-the-dominant-operation" id="markdown-toc-the-choice-follows-the-data-structure-and-the-dominant-operation">The choice follows the data structure and the dominant operation</a></li>
  <li><a href="#notes" id="markdown-toc-notes">Notes</a></li>
  <li><a href="#references" id="markdown-toc-references">References</a></li>
</ul>

<h2 id="the-impedance-mismatch">The impedance mismatch</h2>

<p>Two coordinate systems, describing the same text:</p>

<ul>
  <li><strong>Humans and screens think in 2D:</strong> <code class="language-plaintext highlighter-rouge">(line, column)</code>.</li>
  <li><strong>Files and buffers store 1D:</strong> a single offset into a sequence.</li>
</ul>

<p><img src="/assets/images/2026-07-17-2d-1d-grid.svg" alt="A caret shown in a 2D character grid on the left and mapped by an arrow to its position in a 1D flat, indexed character sequence on the right" />
<em>Figure 1 — The same caret, two coordinate systems. <code class="language-plaintext highlighter-rouge">(line 1, column 1)</code> on the left is flat index <code class="language-plaintext highlighter-rouge">4</code> on the right. The mapping between them is the editor’s job.</em></p>

<p>Bridging those two systems correctly and cheaply is one of the basic jobs of a text editor. Get it wrong and the cursor lands mid-character in a UTF-8 sequence, or an insert meant for one place writes three lines away.</p>

<h2 id="where-it-comes-up-in-textr">Where it comes up in textr</h2>

<p>This shows up in textr’s <code class="language-plaintext highlighter-rouge">View</code>, the cursor model.<sup id="fnref:textr"><a href="#fn:textr" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> The cursor is stored the way a user thinks about it:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">View</span> <span class="p">{</span> <span class="n">line</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span> <span class="n">column</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span> <span class="n">goal_column</span><span class="p">:</span> <span class="nb">usize</span> <span class="p">}</span>
</code></pre></div></div>

<p>That is a 2D coordinate. But the buffer, a <a href="https://github.com/cessen/ropey">ropey</a> rope, has a strictly 1D editing API:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">doc</span><span class="nf">.insert</span><span class="p">(</span><span class="n">char_idx</span><span class="p">,</span> <span class="n">text</span><span class="p">);</span>   <span class="c1">// one index</span>
<span class="n">doc</span><span class="nf">.remove</span><span class="p">(</span><span class="n">char_idx</span><span class="o">..</span><span class="n">end</span><span class="p">);</span>    <span class="c1">// one index</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">insert</code> doesn’t know what a “line” is. To insert a character <em>where the cursor is</em>, I have to convert <code class="language-plaintext highlighter-rouge">(line, column)</code> into a single flat index. That conversion is one function:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">cursor_char_idx</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">doc</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Document</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">usize</span> <span class="p">{</span>
    <span class="n">doc</span><span class="nf">.line_to_char</span><span class="p">(</span><span class="k">self</span><span class="py">.line</span><span class="p">)</span> <span class="o">+</span> <span class="k">self</span><span class="py">.column</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">line_to_char(line)</code> gives the flat index where that line <em>starts</em>; add <code class="language-plaintext highlighter-rouge">column</code> and you have the caret’s absolute position in the whole document. It’s tempting to think <code class="language-plaintext highlighter-rouge">column</code> already <em>is</em> the index — and on line 0 it is, because line 0 starts at 0. But look at <code class="language-plaintext highlighter-rouge">"ab\ncd"</code> with the caret on the <code class="language-plaintext highlighter-rouge">d</code>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>             index:  0   1   2   3   4
             char:   a   b   \n  c   d
                                     ^
   (line 1, column 1)  -&gt;  line_to_char(1) + 1  =  3 + 1  =  4
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">column</code> is <code class="language-plaintext highlighter-rouge">1</code>, but the flat index is <code class="language-plaintext highlighter-rouge">4</code>. <code class="language-plaintext highlighter-rouge">column</code> is “where I am <em>on this line</em>”; the flat index is “where I am <em>in the file</em>.” The rope only understands the second one. (The reverse exists too, <code class="language-plaintext highlighter-rouge">char_to_line(idx)</code>, for when you have a flat position, like a search hit or a mouse click, and need to find the right row.)</p>

<p>Both conversions are cheap on a rope: ropey documents <code class="language-plaintext highlighter-rouge">line_to_char</code> and <code class="language-plaintext highlighter-rouge">char_to_line</code> as <em>O(log n)</em> each, and the <code class="language-plaintext highlighter-rouge">+ column</code> is <em>O(1)</em>, so moving a cursor between the two coordinate systems runs in <em>O(log n)</em> whichever way it goes.<sup id="fnref:ropes"><a href="#fn:ropes" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<h2 id="why-not-just-pick-one-and-be-done">Why not just pick one and be done?</h2>

<p>The harder question is not <em>how</em> to convert but <strong>which representation is the source of truth</strong>, and how it survives edits. There are a handful of strategies:</p>

<ol>
  <li><strong>Store 2D, derive 1D on demand</strong> <em>(textr’s choice)</em>. Keep <code class="language-plaintext highlighter-rouge">(line, column)</code> canonical; compute the flat index whenever the buffer needs it. Never store the index — a stored index goes stale the instant text changes ahead of it. <em>Pro:</em> vertical movement is native (Up/Down, and the “goal column” that remembers your column as you glide over short lines, are 2D ideas). <em>Con:</em> every edit must update the 2D cursor by hand, and 2D positions need clamping.</li>
  <li><strong>Store 1D, derive 2D on demand</strong> <em>(Emacs’s choice)</em>. Keep a single integer offset canonical; compute line and column only to display or move by rows. <em>Pro:</em> dead simple — a position is <em>one number</em>. <em>Con:</em> line and column now cost a computation you mitigate with caches.</li>
  <li><strong>Store both, keep them in sync.</strong> Fast reads either way, but now <em>two</em> things must be updated on <em>every</em> edit or they drift.</li>
  <li><strong>Hand out position <em>objects</em>.</strong> An “iterator” that internally carries both representations. Cheap to read either way — but typically <strong>invalidated by edits</strong>, so you can’t hold one across a modification.</li>
  <li><strong>Persistent marks.</strong> The subtle problem with any raw offset: if I remember “position 487” and someone inserts 10 characters at the top of the file, 487 now points somewhere wrong. A <strong>mark</strong> is a position the buffer <em>itself</em> keeps updated as text moves around it.</li>
</ol>

<p>The choice is not free-floating. It falls out of <strong>what the buffer is made of</strong>:</p>

<p><img src="/assets/images/2026-07-17-buffer-structures.svg" alt="Four buffer data structures — gap buffer, rope, B-tree, and piece table — each annotated with the operations it makes cheap and the coordinate it treats as canonical" />
<em>Figure 2 — The storage structure decides which coordinate is cheap, which in turn pushes the design toward 1D-canonical or 2D-canonical.</em><sup id="fnref:structures"><a href="#fn:structures" class="footnote" rel="footnote" role="doc-noteref">3</a></sup></p>

<p>Strategies 4 and 5 are not hypothetical. gedit uses both: iterators for cheap reads and marks for persistence. Emacs uses markers (strategy 5) alongside its 1D point.</p>

<h2 id="how-gedit-does-it-iterators-over-a-tree-cursor-as-mark">How gedit does it: iterators over a tree, cursor-as-mark</h2>

<p>gedit is built on GTK’s <code class="language-plaintext highlighter-rouge">GtkTextView</code>/<code class="language-plaintext highlighter-rouge">GtkSourceView</code>, backed by a <a href="https://docs.gtk.org/gtk4/class.TextBuffer.html"><code class="language-plaintext highlighter-rouge">GtkTextBuffer</code></a>.<sup id="fnref:gtk"><a href="#fn:gtk" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> Internally that buffer isn’t a flat array — it’s a <strong>tree</strong> (a B-tree) that indexes lines and character/byte offsets, so it can answer “where does line N start?” and “what line is offset K on?” in roughly <em>O(log n)</em>. Both directions of our conversion are cheap by construction.</p>

<p>You never touch raw offsets directly. Instead you work with <a href="https://docs.gtk.org/gtk4/struct.TextIter.html"><code class="language-plaintext highlighter-rouge">GtkTextIter</code></a> — a small stack-allocated struct representing a position, obtained from <em>either</em> coordinate system:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GtkTextIter</span> <span class="n">iter</span><span class="p">;</span>
<span class="n">gtk_text_buffer_get_iter_at_line_offset</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">iter</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">column</span><span class="p">);</span> <span class="c1">// from 2D</span>
<span class="n">gtk_text_buffer_get_iter_at_offset</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">iter</span><span class="p">,</span> <span class="n">char_offset</span><span class="p">);</span>       <span class="c1">// from 1D</span>
</code></pre></div></div>

<p>And crucially, an iter carries <em>both</em> representations at once — you can ask it for its line (<code class="language-plaintext highlighter-rouge">gtk_text_iter_get_line</code>), its column (<code class="language-plaintext highlighter-rouge">gtk_text_iter_get_line_offset</code>), or its absolute char offset (<code class="language-plaintext highlighter-rouge">gtk_text_iter_get_offset</code>). The 2D↔1D conversion is baked <em>into the iterator</em>, powered by that tree. GTK’s answer to “which is the source of truth?” is essentially <strong>“neither — the tree makes both cheap.”</strong></p>

<p>The catch: <strong>a <code class="language-plaintext highlighter-rouge">GtkTextIter</code> is transient — invalidated the moment the buffer is modified.</strong> So you don’t store iters; you re-fetch them.</p>

<p>For positions that must <em>persist</em> across edits, GTK gives you <a href="https://docs.gtk.org/gtk4/class.TextMark.html"><code class="language-plaintext highlighter-rouge">GtkTextMark</code></a>, a named position the buffer maintains automatically. gedit’s cursor is a mark named <code class="language-plaintext highlighter-rouge">"insert"</code> (the selection’s other end is <code class="language-plaintext highlighter-rouge">"selection_bound"</code>). To render the caret, gedit gets an iter at the <code class="language-plaintext highlighter-rouge">"insert"</code> mark and reads its line and column; to type a character, it inserts at that iter. The persistence problem (strategy 5) and the conversion problem (strategy 4) are handled by two separate abstractions.</p>

<p><img src="/assets/images/2026-07-17-gedit-statusbar.png" alt="gedit editing the Markdown source of this post, with &quot;Ln 5, Col 12&quot; shown in the status bar" />
<em>Figure 3 — gedit with this post’s draft open. The status bar reads “Ln 5, Col 12”, the 2D face of the <code class="language-plaintext highlighter-rouge">"insert"</code> mark.</em></p>

<h2 id="how-emacs-does-it-point-is-a-number-everything-else-is-derived">How Emacs does it: point is a number, everything else is derived</h2>

<p>Emacs comes at it from the opposite end. Its buffer is a classic <strong>gap buffer</strong> — one big array of characters with a movable gap where edits happen, which makes insertion and deletion <em>at the cursor</em> very cheap.<sup id="fnref:emacs"><a href="#fn:emacs" class="footnote" rel="footnote" role="doc-noteref">5</a></sup></p>

<p>Position in Emacs is <a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Point.html"><strong>point</strong></a>: a single integer, the character offset of the caret (1-based — <code class="language-plaintext highlighter-rouge">point-min</code> is 1; positions sit <em>between</em> characters). Point is 1D and canonical. Almost every primitive takes or returns a buffer position as a plain integer.</p>

<p>Line and column are <strong>not stored</strong> — they’re computed when asked:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">line-number-at-pos</span><span class="p">)</span>   <span class="c1">; scan / caches to count newlines before point</span>
<span class="p">(</span><span class="nv">current-column</span><span class="p">)</span>       <span class="c1">; scan back to line start, honoring tab-width and char widths</span>
</code></pre></div></div>

<p><a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Columns.html"><code class="language-plaintext highlighter-rouge">current-column</code></a> shows that “column” is subtler than a character count: Emacs makes a tab advance to the next tab stop and accounts for wide characters, so the <em>visual</em> column and the <em>character</em> column differ. (textr, for now, uses character columns, a documented simplification.)<sup id="fnref:unicode"><a href="#fn:unicode" class="footnote" rel="footnote" role="doc-noteref">6</a></sup></p>

<p>Computing line numbers means scanning for newlines — <em>O(distance)</em> — so Emacs keeps caches to amortize it (line-number lookup on a multi-megabyte buffer is a real, historically-tuned concern). For persistence, Emacs has <a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Markers.html"><strong>markers</strong></a> — objects that hold a position and are automatically nudged as text is inserted or deleted before them, with an <a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Marker-Insertion-Types.html">insertion type</a> that decides whether a marker sticks or advances when text lands exactly on it. Same idea as GTK’s marks, different name.</p>

<p>Emacs also separates <strong>character positions from byte positions</strong> (<a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Text-Representations.html"><code class="language-plaintext highlighter-rouge">position-bytes</code></a>) in multibyte buffers, so ordinary code can index by character and never split a multibyte sequence, the same reason textr indexes by <code class="language-plaintext highlighter-rouge">char</code>, never by byte.</p>

<p><img src="/assets/images/2026-07-17-emacs-modeline.png" alt="Emacs editing the same Markdown source, with (12,4) shown in the mode line" />
<em>Figure 4 — Emacs with the same file open. The mode line reads <code class="language-plaintext highlighter-rouge">(12,4)</code>, the line and column derived from point.</em></p>

<h2 id="how-vs-code-does-it-a-piece-tree">How VS Code does it: a piece tree</h2>

<p>VS Code reaches a similar place to textr from a different direction.<sup id="fnref:vscode"><a href="#fn:vscode" class="footnote" rel="footnote" role="doc-noteref">7</a></sup> Its buffer is a <strong>piece tree</strong>: a piece table whose pieces hang off a balanced red-black tree, where each node caches the text length and line-break count of its subtree, so a lookup by line or by offset walks the tree in roughly <em>O(log n)</em> instead of scanning. Its canonical position is 2D, like textr’s — the editor keeps the cursor as a <a href="https://microsoft.github.io/monaco-editor/typedoc/interfaces/IPosition.html"><code class="language-plaintext highlighter-rouge">Position</code></a> of <code class="language-plaintext highlighter-rouge">lineNumber</code> and <code class="language-plaintext highlighter-rouge">column</code>, both 1-based, and the flat offset is derived on demand through <code class="language-plaintext highlighter-rouge">getOffsetAt</code> and its inverse <code class="language-plaintext highlighter-rouge">getPositionAt</code>. The reason for keeping 2D canonical differs from textr’s, though: the whole editor API is written in terms of <code class="language-plaintext highlighter-rouge">Position</code>, so 2D is the natural currency for the interface, not because vertical movement is the hardest job.</p>

<p>One detail sets VS Code apart from the other three. It measures <code class="language-plaintext highlighter-rouge">column</code> and the offset in <strong>UTF-16 code units</strong>, not characters, so an emoji outside the Basic Multilingual Plane counts as two columns and a grapheme built from combining marks spans several units. textr, gedit, and Emacs all index by <code class="language-plaintext highlighter-rouge">char</code> (Unicode scalar values); VS Code indexes by UTF-16 unit, and the two do not agree on where a given column falls.</p>

<h2 id="side-by-side">Side by side</h2>

<table>
  <thead>
    <tr>
      <th> </th>
      <th><strong>textr</strong> (Rust)</th>
      <th><strong>gedit</strong> (GTK/C)</th>
      <th><strong>Emacs</strong> (C/Elisp)</th>
      <th><strong>VS Code</strong> (Monaco/TS)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Buffer structure</td>
      <td>rope (ropey)</td>
      <td>B-tree (<code class="language-plaintext highlighter-rouge">GtkTextBTree</code>)</td>
      <td>gap buffer</td>
      <td>piece tree</td>
    </tr>
    <tr>
      <td>Canonical position</td>
      <td><strong>2D</strong> <code class="language-plaintext highlighter-rouge">(line, column)</code></td>
      <td>a <strong>mark</strong>; read via iters</td>
      <td><strong>1D</strong> <code class="language-plaintext highlighter-rouge">point</code> (integer)</td>
      <td><strong>2D</strong> <code class="language-plaintext highlighter-rouge">Position</code> (line, column)</td>
    </tr>
    <tr>
      <td>2D → 1D</td>
      <td><code class="language-plaintext highlighter-rouge">line_to_char + col</code> — <em>O(log n)</em></td>
      <td>build a <code class="language-plaintext highlighter-rouge">GtkTextIter</code> — <em>O(log n)</em></td>
      <td>rare; <code class="language-plaintext highlighter-rouge">goto-line</code> scans</td>
      <td><code class="language-plaintext highlighter-rouge">getOffsetAt</code> — <em>O(log n)</em></td>
    </tr>
    <tr>
      <td>1D → 2D</td>
      <td><code class="language-plaintext highlighter-rouge">char_to_line</code> — <em>O(log n)</em></td>
      <td>iter carries line + offset</td>
      <td><code class="language-plaintext highlighter-rouge">line-number-at-pos</code> — scan + cache</td>
      <td><code class="language-plaintext highlighter-rouge">getPositionAt</code> — <em>O(log n)</em></td>
    </tr>
    <tr>
      <td>Persist across edits</td>
      <td>recompute from <code class="language-plaintext highlighter-rouge">(line,col)</code></td>
      <td><code class="language-plaintext highlighter-rouge">GtkTextMark</code></td>
      <td>markers</td>
      <td>tracked ranges</td>
    </tr>
    <tr>
      <td>Indexing unit</td>
      <td>chars</td>
      <td>chars (bytes tracked too)</td>
      <td>chars (bytes separate)</td>
      <td>UTF-16 code units</td>
    </tr>
  </tbody>
</table>

<h2 id="the-choice-follows-the-data-structure-and-the-dominant-operation">The choice follows the data structure and the dominant operation</h2>

<p>There is no universally correct answer, only trade-offs that fall out of two things: <strong>what the buffer is good at</strong>, and <strong>which operation you do most</strong>.</p>

<ul>
  <li>Emacs’s gap buffer makes <em>offsets</em> cheap and edits-at-point cheap, so 1D-canonical is the simplest choice; it pays for line and column with scans and caches.</li>
  <li>gedit’s tree makes <em>both</em> directions cheap, so it can afford to hide the whole question behind iterators and lean on marks for persistence.</li>
  <li>textr’s rope also makes both directions cheap (<em>O(log n)</em> either way), so the choice was free. I picked <strong>2D-canonical</strong> because the <code class="language-plaintext highlighter-rouge">View</code>’s busiest and hardest job is <em>vertical</em> movement with a goal column, which is a 2D idea. The representation matches the operation it does most.</li>
  <li>VS Code’s piece tree makes both directions cheap like gedit’s, and it too keeps a 2D <code class="language-plaintext highlighter-rouge">Position</code> canonical — but for a different reason than textr: its whole editor API speaks in positions, so 2D is a matter of interface rather than of any single dominant operation.</li>
</ul>

<p>One point is easy to miss: a raw offset is only correct for one moment. Once the text changes ahead of it, an old index points to the wrong place. That is why converting on demand (textr) or letting the buffer maintain the position (marks and markers) is safer than storing the number and reusing it. textr avoids the problem today because it has a single cursor that updates itself; when it grows multiple cursors or collaborative editing, it will want marks too, for the same reason every editor eventually adopts them.</p>

<hr />

<h2 id="notes">Notes</h2>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">GtkTextIter</code> is a value, not a handle.</strong> It’s valid only until the next buffer mutation; treat it as a snapshot and re-fetch after any edit. Persistent positions are <code class="language-plaintext highlighter-rouge">GtkTextMark</code>s, not stored iters.</li>
  <li><strong>Marker insertion type</strong> decides the tie-break: when text is inserted exactly <em>at</em> a marker, does the marker stay put or advance past the new text? Emacs lets you choose per-marker; it’s the kind of detail that quietly determines whether your saved position feels “before” or “after” an edit.</li>
  <li><strong>Character vs byte positions</strong> are different numbers in any multibyte buffer. textr indexes by <code class="language-plaintext highlighter-rouge">char</code> throughout and never does byte math (ropey also exposes byte-index methods; textr uses only the char ones) — which is why inserting a precomposed <code class="language-plaintext highlighter-rouge">'é'</code> (U+00E9: one <code class="language-plaintext highlighter-rouge">char</code>, two UTF-8 bytes) advances the cursor by exactly one column. A decomposed <code class="language-plaintext highlighter-rouge">'é'</code> (<code class="language-plaintext highlighter-rouge">e</code> + a combining accent) is two chars and would advance by two — so this assumes NFC-normalized input; char indexing is not grapheme indexing.</li>
  <li><strong>ropey’s phantom trailing line.</strong> A rope for <code class="language-plaintext highlighter-rouge">"a\nb\n"</code> reports <em>three</em> lines — <code class="language-plaintext highlighter-rouge">"a\n"</code>, <code class="language-plaintext highlighter-rouge">"b\n"</code>, and a final empty <code class="language-plaintext highlighter-rouge">""</code>. The caret may rest on that empty line but no further; textr’s <code class="language-plaintext highlighter-rouge">line_len_chars</code> helper strips the trailing <code class="language-plaintext highlighter-rouge">\n</code> so “end of line” lands before it, not after.</li>
  <li><strong>“Column” is not “display column.”</strong> This post (and textr, today) uses <em>character</em> columns. A real editor’s visible column has to account for tab stops and wide/zero-width characters — which is exactly what Emacs’s <code class="language-plaintext highlighter-rouge">current-column</code> does and where grapheme-cluster segmentation (UAX #29) eventually comes in.</li>
  <li><strong>textr is a learning project</strong> — a from-scratch <a href="https://gedit-text-editor.org/">gedit</a> clone I’m building to learn Rust, with a headless, UI-agnostic core and thin frontends. The 2D↔1D bridge above is one small, load-bearing piece of its <code class="language-plaintext highlighter-rouge">View</code>.</li>
</ul>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:textr">
      <p>textr’s source — the <code class="language-plaintext highlighter-rouge">View</code> cursor model in <a href="https://github.com/systemhalted/textr/blob/main/crates/core/src/view.rs"><code class="language-plaintext highlighter-rouge">crates/core/src/view.rs</code></a> and the rope wrappers in <a href="https://github.com/systemhalted/textr/blob/main/crates/core/src/document.rs"><code class="language-plaintext highlighter-rouge">crates/core/src/document.rs</code></a>; org-flavored sibling editor <a href="https://github.com/systemhalted/textr-org">textr-org</a>. <a href="#fnref:textr" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ropes">
      <p>ropey <a href="https://docs.rs/ropey">docs</a> (<code class="language-plaintext highlighter-rouge">line_to_char</code>, <code class="language-plaintext highlighter-rouge">char_to_line</code>, <code class="language-plaintext highlighter-rouge">len_chars</code>). On ropes generally: Boehm, Atkinson &amp; Plass, <em>“Ropes: an Alternative to Strings”</em> (Software: Practice and Experience, 1995), <a href="https://doi.org/10.1002/spe.4380251203">doi.org/10.1002/spe.4380251203</a>; Raph Levien, <em>“Rope science”</em> (xi-editor notes), <a href="https://xi-editor.io/docs/rope_science_00.html">xi-editor.io</a>. <a href="#fnref:ropes" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:structures">
      <p><a href="https://en.wikipedia.org/wiki/Gap_buffer">Gap buffer</a> and <a href="https://en.wikipedia.org/wiki/Piece_table">piece table</a> on Wikipedia; VS Code’s <em>“Text Buffer Reimplementation”</em> (their <strong>piece tree</strong> — a piece table backed by a red-black tree): <a href="https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation">code.visualstudio.com</a>. <a href="#fnref:structures" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:gtk">
      <p><a href="https://gedit-text-editor.org/">gedit</a> · <a href="https://gitlab.gnome.org/GNOME/gedit">source</a>; the B-tree that powers <code class="language-plaintext highlighter-rouge">GtkTextBuffer</code>, <a href="https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gtk/gtktextbtree.c"><code class="language-plaintext highlighter-rouge">gtktextbtree.c</code></a>. <a href="#fnref:gtk" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:emacs">
      <p>Emacs <a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Buffer-Internals.html">buffer internals — the gap</a>; source at <a href="https://git.savannah.gnu.org/cgit/emacs.git">git.savannah.gnu.org</a> (<code class="language-plaintext highlighter-rouge">src/buffer.h</code>, <code class="language-plaintext highlighter-rouge">src/insdel.c</code>, <code class="language-plaintext highlighter-rouge">src/marker.c</code>). <a href="#fnref:emacs" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:unicode">
      <p><a href="https://unicode.org/reports/tr29/">UAX #29, <em>Unicode Text Segmentation</em></a> (grapheme clusters); Rust crates <a href="https://docs.rs/unicode-width"><code class="language-plaintext highlighter-rouge">unicode-width</code></a> (display width, UAX #11) and <a href="https://docs.rs/unicode-segmentation"><code class="language-plaintext highlighter-rouge">unicode-segmentation</code></a> (UAX #29). <a href="#fnref:unicode" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:vscode">
      <p>VS Code / Monaco — <code class="language-plaintext highlighter-rouge">Position</code> (<code class="language-plaintext highlighter-rouge">lineNumber</code>/<code class="language-plaintext highlighter-rouge">column</code>, 1-based) in <a href="https://github.com/microsoft/vscode/blob/main/src/vs/editor/common/core/position.ts"><code class="language-plaintext highlighter-rouge">position.ts</code></a> and the <a href="https://microsoft.github.io/monaco-editor/typedoc/interfaces/IPosition.html"><code class="language-plaintext highlighter-rouge">IPosition</code></a> typedoc; <code class="language-plaintext highlighter-rouge">getOffsetAt</code>/<code class="language-plaintext highlighter-rouge">getPositionAt</code> in <a href="https://github.com/microsoft/vscode/blob/main/src/vs/editor/common/model.ts"><code class="language-plaintext highlighter-rouge">model.ts</code></a>; columns measured in UTF-16 code units per <a href="https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.d.ts"><code class="language-plaintext highlighter-rouge">vscode.d.ts</code></a>. <a href="#fnref:vscode" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Computer Science" /><category term="rust" /><category term="text-editors" /><category term="emacs" /><category term="gedit" /><category term="data-structures" /><category term="unicode" /><summary type="html"><![CDATA[Editors show a 2D grid but store text as a 1D sequence. How textr, gedit, and Emacs bridge the gap, and why the choice follows the buffer's data structure.]]></summary></entry><entry><title type="html">JPMS and Cargo: Two Answers to the Same Problem</title><link href="https://systemhalted.in/2026/07/12/jpms-and-cargo-two-answers-to-the-same-problem/" rel="alternate" type="text/html" title="JPMS and Cargo: Two Answers to the Same Problem" /><published>2026-07-12T00:00:00+00:00</published><updated>2026-07-12T00:00:00+00:00</updated><id>https://systemhalted.in/2026/07/12/jpms-and-cargo-two-answers-to-the-same-problem</id><content type="html" xml:base="https://systemhalted.in/2026/07/12/jpms-and-cargo-two-answers-to-the-same-problem/"><![CDATA[<p>Every language with a serious ecosystem eventually has to answer three related questions:</p>

<ol>
  <li>How is code compiled?</li>
  <li>How is it distributed?</li>
  <li>Where is encapsulation enforced?</li>
</ol>

<p>Java accumulated its answers over time. Source files and packages came from the language, JARs became the distribution unit, and JPMS arrived in 2017 to add strong encapsulation and explicit dependency readability.</p>

<p>Rust and Cargo started with a more integrated model, designed together around a standard package and build system.</p>

<p>The two systems answer the same three questions but put the answers in different places. Java spreads them across pieces assembled over two decades — packages and the module system in the language, JARs and Maven outside it — that must stay consistent without anything forcing them to. Rust concentrates compilation and encapsulation in the crate and distribution in the Cargo package, with far less overlap.</p>

<h2 id="the-terms">The terms</h2>

<p>Both toolchains reuse a few of the same words for different things, so it helps to fix the vocabulary before the code.</p>

<p><strong>Rust / Cargo</strong></p>

<ul>
  <li><strong>Crate</strong> — the unit the compiler compiles, and the unit of encapsulation. A crate is either a <em>library crate</em> or a <em>binary crate</em>.</li>
  <li><strong>Package</strong> — what Cargo versions, builds, and publishes, described by <code class="language-plaintext highlighter-rouge">Cargo.toml</code>. A package holds at most one library crate and any number of binary crates.</li>
  <li><strong>Module</strong> (<code class="language-plaintext highlighter-rouge">mod</code>) — a namespace <em>inside</em> a crate. Modules form a tree, and that tree is where visibility is enforced.</li>
  <li><strong>Workspace</strong> — a set of packages that share one <code class="language-plaintext highlighter-rouge">Cargo.lock</code> and one build.</li>
</ul>

<p><strong>Java / Maven / JPMS</strong></p>

<ul>
  <li><strong>Package</strong> — a namespace declared per source file (<code class="language-plaintext highlighter-rouge">package in.systemhalted.gateway.api;</code>).</li>
  <li><strong>Module (JPMS)</strong> — a named group of packages with a <code class="language-plaintext highlighter-rouge">module-info.java</code> that declares what it reads and what it exports. The unit of strong encapsulation.</li>
  <li><strong>Artifact (Maven)</strong> — the versioned, publishable unit, identified by <code class="language-plaintext highlighter-rouge">groupId:artifactId:version</code> and shipped as a JAR.</li>
  <li><strong>JAR</strong> — the packaging and distribution format.</li>
</ul>

<p><strong>Shared</strong></p>

<ul>
  <li><strong>Dependency</strong> — code from another package, artifact, crate, or module that this component uses. Maven resolves and fetches a Java dependency, while JPMS separately determines module readability through <code class="language-plaintext highlighter-rouge">requires</code>. Cargo resolves a Rust dependency and makes the corresponding crate available to the compiler from the same manifest declaration.</li>
</ul>

<p>The words already collide. A Rust <em>crate</em> is closest to a JPMS <em>module</em>, a Rust <em>module</em> to a Java <em>package</em>, and a Cargo <em>package</em> to a Maven <em>artifact</em>. None of these is exact — a crate resembles a JPMS module in encapsulation and dependency structure more than in the mechanics of compilation — and “module” means almost opposite-scale things in the two worlds. The rest of this post lines these up against the three questions and shows where the words look alike but aren’t.</p>

<h2 id="the-same-project-twice">The same project, twice</h2>

<p>Consider a small API gateway library with a public API, a configuration type, and an internal routing engine.</p>

<p>Here is the Java version, built with Maven and JPMS:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gateway/
├── pom.xml
└── src/main/java/
    ├── module-info.java
    └── in/systemhalted/gateway/
        ├── api/
        │   └── RouteHandler.java
        ├── config/
        │   └── GatewayConfig.java
        └── internal/
            └── Router.java
</code></pre></div></div>

<p>And the Rust version:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gateway/
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── api.rs
    ├── api/
    │   └── handlers.rs
    ├── config.rs
    ├── internal.rs
    └── internal/
        └── router.rs
</code></pre></div></div>

<p>Four files reveal most of the architectural difference: <code class="language-plaintext highlighter-rouge">pom.xml</code>, <code class="language-plaintext highlighter-rouge">module-info.java</code>, <code class="language-plaintext highlighter-rouge">Cargo.toml</code>, and <code class="language-plaintext highlighter-rouge">lib.rs</code>. They do not map one-to-one. Understanding why is most of understanding the two systems.</p>

<h2 id="the-four-files">The four files</h2>

<h3 id="pomxml-identity-and-dependencies">pom.xml: identity and dependencies</h3>

<p>The POM belongs to Maven, not to the Java language. It declares the artifact’s coordinates and the dependencies required to build it:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;groupId&gt;</span>in.systemhalted<span class="nt">&lt;/groupId&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>gateway<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;version&gt;</span>1.4.0<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;dependencies&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>com.fasterxml.jackson.core<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>jackson-databind<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;version&gt;</span>2.17.1<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>
<span class="nt">&lt;/dependencies&gt;</span>
</code></pre></div></div>

<p>Maven resolves the dependency, downloads the JAR, and constructs the classpath or module path. <code class="language-plaintext highlighter-rouge">javac</code> knows nothing about the POM.</p>

<h3 id="module-infojava-readability-and-exports">module-info.java: readability and exports</h3>

<p>The module descriptor lives at the root of the Java source tree, at <code class="language-plaintext highlighter-rouge">src/main/java/module-info.java</code>. It is part of the Java language and is compiled by <code class="language-plaintext highlighter-rouge">javac</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">module</span> <span class="n">in</span><span class="o">.</span><span class="na">systemhalted</span><span class="o">.</span><span class="na">gateway</span> <span class="o">{</span>
    <span class="n">requires</span> <span class="n">com</span><span class="o">.</span><span class="na">fasterxml</span><span class="o">.</span><span class="na">jackson</span><span class="o">.</span><span class="na">databind</span><span class="o">;</span>
    <span class="n">exports</span> <span class="n">in</span><span class="o">.</span><span class="na">systemhalted</span><span class="o">.</span><span class="na">gateway</span><span class="o">.</span><span class="na">api</span><span class="o">;</span>
    <span class="n">exports</span> <span class="n">in</span><span class="o">.</span><span class="na">systemhalted</span><span class="o">.</span><span class="na">gateway</span><span class="o">.</span><span class="na">config</span><span class="o">;</span>
    <span class="c1">// in.systemhalted.gateway.internal is not exported</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The descriptor declares two boundaries: which modules this module reads, and which packages it exposes.</p>

<p>Jackson therefore appears twice. The POM tells Maven to obtain the JAR; the descriptor tells JPMS that this module may read it. These are separate systems, and nothing forces them to agree: a dependency can sit on the module path without a matching <code class="language-plaintext highlighter-rouge">requires</code>, or be declared with <code class="language-plaintext highlighter-rouge">requires</code> without being supplied by the build. The two names even differ — the Maven artifactId is <code class="language-plaintext highlighter-rouge">jackson-databind</code>, the JPMS module name <code class="language-plaintext highlighter-rouge">com.fasterxml.jackson.databind</code>. Coordinate and module identity are separate namespaces.</p>

<h3 id="cargotoml-the-package-manifest">Cargo.toml: the package manifest</h3>

<p><code class="language-plaintext highlighter-rouge">Cargo.toml</code> describes the Cargo package:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[</span><span class="n">package</span><span class="k">]</span>
<span class="n">name</span> <span class="o">=</span><span class="w"> </span><span class="s">"gateway"</span>
<span class="n">version</span> <span class="o">=</span><span class="w"> </span><span class="s">"1.4.0"</span>

<span class="k">[</span><span class="n">dependencies</span><span class="k">]</span>
<span class="n">serde_json</span> <span class="o">=</span><span class="w"> </span><span class="s">"1"</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">gateway</code> package here is a single library crate, <code class="language-plaintext highlighter-rouge">src/lib.rs</code>. Add a <code class="language-plaintext highlighter-rouge">src/main.rs</code> and it would hold two crates, a library and a binary. The crate is where visibility and module boundaries apply.</p>

<p>Declaring a dependency in <code class="language-plaintext highlighter-rouge">Cargo.toml</code> both resolves it and makes it available during compilation; Cargo invokes <code class="language-plaintext highlighter-rouge">rustc</code> with the corresponding <code class="language-plaintext highlighter-rouge">--extern</code> arguments. There is no separate language-level file holding a second copy of the dependency declaration.</p>

<h3 id="librs-the-crate-root">lib.rs: the crate root</h3>

<p><code class="language-plaintext highlighter-rouge">lib.rs</code> is not a manifest. It is source code — the root of the library crate and the starting point of the crate’s module tree:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/lib.rs</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">api</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">config</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">internal</span><span class="p">;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">pub mod api;</code> loads <code class="language-plaintext highlighter-rouge">src/api.rs</code> and makes the module publicly reachable; <code class="language-plaintext highlighter-rouge">mod internal;</code> loads <code class="language-plaintext highlighter-rouge">src/internal.rs</code> but does not make it reachable outside the crate.</p>

<p>This is where part of the Java module descriptor’s role moves into source. The descriptor’s <code class="language-plaintext highlighter-rouge">exports in.systemhalted.gateway.api;</code> and the crate root’s <code class="language-plaintext highlighter-rouge">pub mod api;</code> are not exact equivalents, but serve the same purpose: defining the externally reachable surface. In Rust the dependency side lives in <code class="language-plaintext highlighter-rouge">Cargo.toml</code>, the visibility side in ordinary source.</p>

<h2 id="where-the-module-tree-comes-from">Where the module tree comes from</h2>

<p>The deeper difference is not the file names. It is who defines the structure.</p>

<h3 id="java-packages-are-declared-independently">Java packages are declared independently</h3>

<p>Every Java source file declares its package, such as <code class="language-plaintext highlighter-rouge">package in.systemhalted.gateway.internal;</code>. Build tools conventionally mirror that name in the source directory, as <code class="language-plaintext highlighter-rouge">in/systemhalted/gateway/internal/Router.java</code>. But the package declaration defines membership: no parent package registers the class, and no other source file has to mention it.</p>

<p>A Java package is a flat namespace. The dots suggest hierarchy, but there is none: <code class="language-plaintext highlighter-rouge">a.b</code> and <code class="language-plaintext highlighter-rouge">a.b.c</code> are separate packages, and <code class="language-plaintext highlighter-rouge">a.b.c</code> is not semantically nested inside <code class="language-plaintext highlighter-rouge">a.b</code>.</p>

<h3 id="rust-modules-form-an-explicit-tree">Rust modules form an explicit tree</h3>

<p>In Rust, a file does not join the crate merely by existing on disk; a parent module must declare it. In <code class="language-plaintext highlighter-rouge">src/lib.rs</code>, <code class="language-plaintext highlighter-rouge">mod internal;</code> brings <code class="language-plaintext highlighter-rouge">src/internal.rs</code> into the crate. In turn, <code class="language-plaintext highlighter-rouge">src/internal.rs</code>’s <code class="language-plaintext highlighter-rouge">pub mod router;</code> brings <code class="language-plaintext highlighter-rouge">src/internal/router.rs</code> into the tree, and <code class="language-plaintext highlighter-rouge">src/api.rs</code>’s <code class="language-plaintext highlighter-rouge">pub mod handlers;</code> brings <code class="language-plaintext highlighter-rouge">src/api/handlers.rs</code>. Remove the <code class="language-plaintext highlighter-rouge">mod router;</code> declaration and the compiler never reads <code class="language-plaintext highlighter-rouge">router.rs</code>, even if the file remains on disk. The filesystem follows the module tree; it does not create it.</p>

<p>This explains why the layout carries both <code class="language-plaintext highlighter-rouge">internal.rs</code> and an <code class="language-plaintext highlighter-rouge">internal/</code> directory: <code class="language-plaintext highlighter-rouge">internal.rs</code> defines the module, <code class="language-plaintext highlighter-rouge">internal/</code> holds its child modules. A leaf module such as <code class="language-plaintext highlighter-rouge">config</code> needs only <code class="language-plaintext highlighter-rouge">config.rs</code>. The older <code class="language-plaintext highlighter-rouge">internal/mod.rs</code> layout still works, but the <code class="language-plaintext highlighter-rouge">internal.rs</code>-plus-<code class="language-plaintext highlighter-rouge">internal/</code> style is now common.</p>

<h2 id="visibility-follows-the-tree">Visibility follows the tree</h2>

<p>Once the tree exists, visibility is evaluated along paths through it. Consider:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/internal/router.rs</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Router</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>

<span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">rebuild_routes</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="k">pub</span><span class="p">(</span><span class="k">super</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">debug_dump</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="k">pub</span><span class="p">(</span><span class="k">in</span> <span class="k">crate</span><span class="p">::</span><span class="n">internal</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">merge</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">parse_segment</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>

<p>These declarations represent different visibility scopes.</p>

<h3 id="pub-is-bounded-by-the-path">pub is bounded by the path</h3>

<p>This is the first rule that often surprises Java developers. <code class="language-plaintext highlighter-rouge">pub struct Router;</code> does not necessarily make <code class="language-plaintext highlighter-rouge">Router</code> reachable outside the crate; every module along the path must also be public. The full path is <code class="language-plaintext highlighter-rouge">crate::internal::router::Router</code>, but <code class="language-plaintext highlighter-rouge">lib.rs</code> declared <code class="language-plaintext highlighter-rouge">mod internal;</code>, not <code class="language-plaintext highlighter-rouge">pub mod internal;</code>. The private <code class="language-plaintext highlighter-rouge">internal</code> module blocks the path: <code class="language-plaintext highlighter-rouge">Router</code> is public within the visibility that path allows, but the path itself is not externally reachable.</p>

<p>Visibility belongs to both the item and its route through the module tree.</p>

<h3 id="visibility-can-name-an-ancestor">Visibility can name an ancestor</h3>

<p>Rust can express scopes relative to the tree: <code class="language-plaintext highlighter-rouge">pub(crate)</code> is visible throughout the crate, <code class="language-plaintext highlighter-rouge">pub(super)</code> within the parent module and its descendants, <code class="language-plaintext highlighter-rouge">pub(in crate::internal)</code> within the named module and its descendants. The path in <code class="language-plaintext highlighter-rouge">pub(in path)</code> must name an ancestor of the current module. A function inside <code class="language-plaintext highlighter-rouge">crate::internal::router</code> cannot declare <code class="language-plaintext highlighter-rouge">pub(in crate::api)</code>, because <code class="language-plaintext highlighter-rouge">api</code> is a sibling branch, not an ancestor.</p>

<p>Java has no equivalent, because its packages do not form a semantic tree. For top-level types and cross-package access, Java mainly offers <code class="language-plaintext highlighter-rouge">public</code> and package-private; class members also have <code class="language-plaintext highlighter-rouge">private</code> and <code class="language-plaintext highlighter-rouge">protected</code>, but none express visibility to a named package subtree.</p>

<p>Before JPMS, a package named <code class="language-plaintext highlighter-rouge">internal</code> was mostly a warning:</p>

<blockquote>
  <p>This is internal. Please do not use it.</p>
</blockquote>

<p>JPMS made that boundary enforceable across modules by withholding exports. But inside the module, Java remains flat.</p>

<h3 id="visibility-keyword-by-keyword">Visibility, keyword by keyword</h3>

<p>External reachability is path-based in Rust and package-based in JPMS. Each Rust modifier grants a scope defined by the module tree; the nearest Java construct is often not a keyword at all, because Java expresses external reachability through the module descriptor rather than a modifier on the declaration.</p>

<table>
  <thead>
    <tr>
      <th>Rust modifier</th>
      <th>Scope it grants</th>
      <th>Nearest Java equivalent</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">pub</code> on a public path</td>
      <td>outside the crate</td>
      <td><code class="language-plaintext highlighter-rouge">public</code> in an exported package</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">pub</code> behind a private path</td>
      <td>capped by the path</td>
      <td><code class="language-plaintext highlighter-rouge">public</code> in a non-exported package</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">pub(crate)</code></td>
      <td>entire crate</td>
      <td><code class="language-plaintext highlighter-rouge">public</code> in a non-exported package</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">pub(super)</code></td>
      <td>parent module’s subtree</td>
      <td>none</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">pub(in path)</code></td>
      <td>named ancestor’s subtree</td>
      <td>none</td>
    </tr>
    <tr>
      <td>no modifier</td>
      <td>current module and descendants</td>
      <td>package-private, approximately</td>
    </tr>
  </tbody>
</table>

<p>Java approximates <code class="language-plaintext highlighter-rouge">pub(crate)</code> by placing a <code class="language-plaintext highlighter-rouge">public</code> class in a package omitted from exports — not a Java visibility level, but an effect of the module boundary. Rust privacy also differs from Java package privacy: a private Rust item is visible to its defining module and that module’s descendants, while a package-private Java member is visible to all code in the same package.</p>

<h2 id="imports-reveal-the-architecture">Imports reveal the architecture</h2>

<p>The different namespace models also shape imports.</p>

<p>Java:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">in.systemhalted.gateway.api.RouteHandler</span><span class="o">;</span>
</code></pre></div></div>

<p>Rust:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/internal/router.rs</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">collections</span><span class="p">::</span><span class="n">HashMap</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">api</span><span class="p">::</span><span class="n">RouteHandler</span><span class="p">;</span>
</code></pre></div></div>

<p>And from a child module, <code class="language-plaintext highlighter-rouge">use super::Router;</code>.</p>

<p>A Java import aliases a fully qualified name. That name belongs to a global package namespace and says nothing about where the importing class sits relative to the imported one. There is no Java equivalent of <code class="language-plaintext highlighter-rouge">super::</code>, because Java packages have no parent-child relationship.</p>

<p>A Rust <code class="language-plaintext highlighter-rouge">use</code> navigates the module tree:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">crate::</code> starts at the crate root</li>
  <li><code class="language-plaintext highlighter-rouge">self::</code> starts at the current module</li>
  <li><code class="language-plaintext highlighter-rouge">super::</code> starts at the parent module</li>
  <li>an external crate is named directly</li>
</ul>

<p>The module tree is therefore more than an encapsulation mechanism. It is the coordinate system for names throughout the crate.</p>

<h2 id="re-exports-and-transitive-readability">Re-exports and transitive readability</h2>

<p>Rust and JPMS both allow one component’s API to depend on another, but the mechanisms are very different.</p>

<h3 id="jpms-propagates-readability">JPMS propagates readability</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">module</span> <span class="n">in</span><span class="o">.</span><span class="na">systemhalted</span><span class="o">.</span><span class="na">gateway</span><span class="o">.</span><span class="na">api</span> <span class="o">{</span>
    <span class="n">requires</span> <span class="n">transitive</span> <span class="n">in</span><span class="o">.</span><span class="na">systemhalted</span><span class="o">.</span><span class="na">gateway</span><span class="o">.</span><span class="na">types</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Any module that requires <code class="language-plaintext highlighter-rouge">gateway.api</code> also reads <code class="language-plaintext highlighter-rouge">gateway.types</code>, without declaring <code class="language-plaintext highlighter-rouge">gateway.types</code> itself. The dependency remains a separate module: its classes keep their original package and module identity, and consumers can access only the packages that <code class="language-plaintext highlighter-rouge">gateway.types</code> exports. <code class="language-plaintext highlighter-rouge">requires transitive</code> changes the readability graph.</p>

<h3 id="rust-republishes-names">Rust republishes names</h3>

<p>In <code class="language-plaintext highlighter-rouge">src/lib.rs</code>, <code class="language-plaintext highlighter-rouge">pub use gateway_types::RouteConfig;</code> gives consumers a public path through the current crate, <code class="language-plaintext highlighter-rouge">gateway::RouteConfig</code>. Rust can also re-export an item from a private internal module with <code class="language-plaintext highlighter-rouge">pub use crate::internal::router::Router;</code>: the internal module remains private, but <code class="language-plaintext highlighter-rouge">Router</code> becomes available at <code class="language-plaintext highlighter-rouge">gateway::Router</code>.</p>

<p>This works because <code class="language-plaintext highlighter-rouge">Router</code> is public and reachable from <code class="language-plaintext highlighter-rouge">lib.rs</code>. A re-export cannot override privacy; it can only republish an item the re-exporting module can already access.</p>

<p>This gives Rust an important form of indirection: the implementation can live at <code class="language-plaintext highlighter-rouge">crate::internal::router::Router</code> while the public API stays <code class="language-plaintext highlighter-rouge">gateway::Router</code>, and the internal module tree can change without changing the path exposed to consumers.</p>

<p>Java cannot do this directly, because a class’s package is part of its identity.</p>

<h2 id="scaling-up-modules-and-workspaces">Scaling up: modules and workspaces</h2>

<p>Real projects rarely remain a single component. Suppose the gateway is split into two libraries, <code class="language-plaintext highlighter-rouge">gateway-api</code> and <code class="language-plaintext highlighter-rouge">gateway-core</code>.</p>

<h3 id="maven-multi-module-build">Maven multi-module build</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gateway/
├── pom.xml
├── gateway-api/
│   ├── pom.xml
│   └── src/main/java/
│       ├── module-info.java
│       └── in/systemhalted/gateway/api/
└── gateway-core/
    ├── pom.xml
    └── src/main/java/
        ├── module-info.java
        └── in/systemhalted/gateway/core/
</code></pre></div></div>

<p>The root POM aggregates the modules:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;packaging&gt;</span>pom<span class="nt">&lt;/packaging&gt;</span>
<span class="nt">&lt;modules&gt;</span>
    <span class="nt">&lt;module&gt;</span>gateway-api<span class="nt">&lt;/module&gt;</span>
    <span class="nt">&lt;module&gt;</span>gateway-core<span class="nt">&lt;/module&gt;</span>
<span class="nt">&lt;/modules&gt;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">gateway-core</code> declares the Maven dependency in its POM and the JPMS dependency in its module descriptor.</p>

<h3 id="cargo-workspace">Cargo workspace</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gateway/
├── Cargo.toml
└── crates/
    ├── gateway-api/
    │   ├── Cargo.toml
    │   └── src/
    │       └── lib.rs
    └── gateway-core/
        ├── Cargo.toml
        └── src/
            └── lib.rs
</code></pre></div></div>

<p>The root manifest defines the workspace:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[</span><span class="n">workspace</span><span class="k">]</span>
<span class="n">members</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"crates/*"</span><span class="p">]</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">gateway-core</code> declares a path dependency:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[</span><span class="n">dependencies</span><span class="k">]</span>
<span class="n">gateway-api</span> <span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">path</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"../gateway-api"</span><span class="w"> </span><span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">crates/</code> directory is only convention; the <code class="language-plaintext highlighter-rouge">members</code> declaration defines the workspace. Each member remains a normal Cargo package with its own manifest, source tree, and crate root. A workspace adds organization above packages without weakening the boundaries between their crates.</p>

<h3 id="dependency-management">Dependency management</h3>

<p>The Maven and Cargo mappings are close but not identical. A Cargo workspace shares one resolved dependency graph through <code class="language-plaintext highlighter-rouge">Cargo.lock</code>. Centralized dependency declarations belong in <code class="language-plaintext highlighter-rouge">[workspace.dependencies]</code>, and member packages inherit them with <code class="language-plaintext highlighter-rouge">serde = { workspace = true }</code> — closer to Maven’s <code class="language-plaintext highlighter-rouge">dependencyManagement</code> than the lock file itself.</p>

<p>At the build level, both construct a dependency graph and build components in order: <code class="language-plaintext highlighter-rouge">cargo build</code> from the workspace root resembles a Maven reactor build like <code class="language-plaintext highlighter-rouge">mvn package</code> from the parent project.</p>

<p>The architectural consequence is the same: a crate boundary inside a workspace is still a real boundary. <code class="language-plaintext highlighter-rouge">gateway-core</code> sees only what <code class="language-plaintext highlighter-rouge">gateway-api</code> exposes publicly, just as one JPMS module sees only the packages another exports. Splitting a project into crates or JPMS modules is therefore not directory organization; it changes the enforceable architecture.</p>

<h2 id="split-packages-and-legacy-constraints">Split packages and legacy constraints</h2>

<p>JPMS arrived after two decades of classpath-based Java. That history created migration mechanisms and compatibility layers:</p>

<ul>
  <li>automatic modules</li>
  <li>the unnamed module</li>
  <li>classpath fallback</li>
  <li>command-line overrides</li>
  <li>split-package failures</li>
</ul>

<p>A split package occurs when two or more named modules define types in the same Java package. JPMS rejects that arrangement: a given package must belong to a single module. Rust rules out the analogous structure by construction — a Rust module belongs to one crate’s module tree and is introduced by declarations within that crate. No module spans two crates. There is no pre-crate Rust ecosystem to accommodate.</p>

<p>The escape hatch survives, too. Drop a JAR onto the legacy <code class="language-plaintext highlighter-rouge">--class-path</code> instead of the module path and its packages fall back into the unnamed module, outside JPMS entirely — strong encapsulation holds only as long as everyone stays on the module path. Rust has no such fallback: nothing sits outside the crate system, so its boundaries always hold.</p>

<p>This is not evidence that Rust’s designers were smarter; they had a different starting point. But the consequence matters: crate boundaries apply uniformly across the ecosystem, and tools can assume the model is real.</p>

<h2 id="the-runtime-model">The runtime model</h2>

<p>JPMS also governs deep runtime reflection. Java frameworks often inspect constructors, fields, and methods reflectively; whether that access is permitted depends on ordinary access checks and whether the containing package is open. A module can open one package to a specific framework:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">module</span> <span class="n">in</span><span class="o">.</span><span class="na">systemhalted</span><span class="o">.</span><span class="na">gateway</span> <span class="o">{</span>
    <span class="n">opens</span> <span class="n">in</span><span class="o">.</span><span class="na">systemhalted</span><span class="o">.</span><span class="na">gateway</span><span class="o">.</span><span class="na">config</span>
        <span class="n">to</span> <span class="n">com</span><span class="o">.</span><span class="na">fasterxml</span><span class="o">.</span><span class="na">jackson</span><span class="o">.</span><span class="na">databind</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The command line can also modify the boundary with <code class="language-plaintext highlighter-rouge">--add-opens</code>.</p>

<p>Rust has no general runtime reflection facility comparable to Java reflection. Its major metaprogramming mechanisms — procedural macros and derive macros — operate during compilation, expanding into ordinary Rust code that remains subject to the language’s visibility rules. Rust therefore needs no equivalent of JPMS <code class="language-plaintext highlighter-rouge">opens</code>; the problem does not arise, because the runtime model is different.</p>

<h2 id="the-mapping">The mapping</h2>

<table>
  <thead>
    <tr>
      <th>Rust</th>
      <th>Java</th>
      <th>Caveat</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>crate</td>
      <td>JPMS module</td>
      <td>Architectural analogy focused on encapsulation and dependency structure</td>
    </tr>
    <tr>
      <td>Cargo package</td>
      <td>Maven artifact</td>
      <td>Manifest and publishing unit; may contain multiple crates</td>
    </tr>
    <tr>
      <td>workspace</td>
      <td>Maven multi-module build</td>
      <td>Workspace manifest resembles an aggregator parent POM</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">[workspace.dependencies]</code></td>
      <td><code class="language-plaintext highlighter-rouge">dependencyManagement</code></td>
      <td>Centralized dependency declarations</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Cargo.toml</code> dependencies</td>
      <td>POM dependencies plus JPMS <code class="language-plaintext highlighter-rouge">requires</code></td>
      <td>Cargo combines resolution and compiler availability; Java separates them</td>
    </tr>
    <tr>
      <td>Rust module</td>
      <td>Java package</td>
      <td>Rust modules form a tree; Java packages are flat</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">use</code></td>
      <td><code class="language-plaintext highlighter-rouge">import</code></td>
      <td>Rust navigates a module tree; Java aliases package-qualified names</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">pub use</code></td>
      <td>no direct equivalent</td>
      <td>Re-exports create new public paths</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">requires transitive</code></td>
      <td>no direct Rust equivalent</td>
      <td>Propagates module readability</td>
    </tr>
    <tr>
      <td>none</td>
      <td><code class="language-plaintext highlighter-rouge">opens</code>, <code class="language-plaintext highlighter-rouge">--add-opens</code></td>
      <td>Rust has no comparable runtime-reflection model</td>
    </tr>
  </tbody>
</table>

<h2 id="what-each-language-should-envy">What each language should envy</h2>

<p>Return to the three questions. Rust answers all of them inside the language and its build tool, the same way for every crate. Java answers them across pieces assembled over twenty years. That difference is the source of what each side can envy.</p>

<p>Java developers should envy Rust’s uniformity. Every Rust crate, from the standard library to a small third-party library, participates in the same crate and module model. There is no parallel classpath world, no automatic-module transition state, no split-package compatibility problem, and no reflective framework asking for runtime access to private implementation details.</p>

<p>Rust developers should envy the explicit JPMS boundary document. A <code class="language-plaintext highlighter-rouge">module-info.java</code> file states, in one compact artifact:</p>

<ul>
  <li>which modules are readable</li>
  <li>which packages are exported</li>
  <li>which packages are opened</li>
  <li>which services are used or provided</li>
</ul>

<p>Cargo can expose the resolved dependency graph through <code class="language-plaintext highlighter-rouge">cargo metadata</code>, and Rust tooling can resolve public re-exports, but a crate’s external namespace is assembled through source declarations rather than summarized in one descriptor. For architectural governance at module and package granularity, JPMS provides a cleaner artifact.</p>

<p>Neither system describes the complete public API: an exported Java package still has to be inspected for its public classes and methods, and a Rust crate still has to be analyzed for its public items and re-exports. But JPMS makes the high-level boundary unusually explicit.</p>

<p>Rust could make the crate the language’s central architectural unit, and Cargo built its package and dependency model around it: one place answers compilation and encapsulation, one place answers distribution. JPMS had no such freedom. It had to coexist with packages, JARs, the classpath, reflection-heavy frameworks, and two decades of existing code; its answers to the three questions were already spread apart before it arrived.</p>

<p>That historical constraint explains most of the irregularity in JPMS. It also explains most of the uniformity in Cargo.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Computer Science" /><category term="jpms" /><category term="cargo" /><category term="modules" /><category term="encapsulation" /><category term="type-systems" /><category term="java" /><category term="rust" /><category term="programming-languages" /><summary type="html"><![CDATA[The same project built twice, in Java (JPMS + Maven) and Rust (Cargo) — how each answers compilation, distribution, and encapsulation, why a crate maps to a JPMS module and a Rust module to a Java package, and where the analogy breaks down.]]></summary></entry><entry><title type="html">Adopting Agent Loops in Order</title><link href="https://systemhalted.in/2026/07/10/adopting-agent-loops-in-order/" rel="alternate" type="text/html" title="Adopting Agent Loops in Order" /><published>2026-07-10T00:00:00+00:00</published><updated>2026-07-10T00:00:00+00:00</updated><id>https://systemhalted.in/2026/07/10/adopting-agent-loops-in-order</id><content type="html" xml:base="https://systemhalted.in/2026/07/10/adopting-agent-loops-in-order/"><![CDATA[<p>In an <a href="/2026/07/03/what-sits-underneath-the-agent-loops-post/">earlier post</a> I wrote about the four kinds of agent loops that Anthropic’s Claude Code team described, and about where the check on the work lives in each one. That post looked at the loops one at a time. There is also an order to them. Each loop leaves behind an artifact that the next loop depends on, so the sequence in which a team adopts them matters as much as the choice of which ones to use.</p>

<p>The turn-based loop comes first because its artifact is the foundation. Working turn by turn with an agent forces a team to answer a basic question: how do we know a change is good? Writing the answer down as a runnable check produces the skill file. Nothing about this requires autonomy. It only requires noticing what a person does before accepting a result, and recording it.</p>

<p>Second is the goal-based loop, which consumes that check. An exit criterion is only useful if there is a way to evaluate it, and the evaluation is the check the turn-based stage wrote down. Defining the criterion is work of its own. A team has to say what done means in language precise enough for an evaluator to apply, and that precision usually does not exist yet. Producing it is the real output of this stage.</p>

<p>After the criterion is in place, a schedule or an event trigger follows, and this is the rung where teams most often stall. A schedule is the easiest of the four artifacts to create, so it tends to appear early and stay. A prompt that runs every morning feels like automation, and nobody revisits it. The better path is to treat the schedule as provisional from the day it is created, and to replace it with an event trigger when the source system offers one. A calendar entry is a guess about when the world changes. An event is a report that it did.</p>

<p>A proactive routine can only come last, because it consumes everything: the check, the criterion, the trigger, and the operational guardrails around all three. At this stage the question stops being technical. A routine that touches production systems needs an owner, a budget, and a way to be turned off, and those are organisational decisions. A platform team can supply the machinery. Only the owning team can supply the ownership.</p>

<p>Skipping a rung defers work rather than removing it. A proactive routine adopted before the exit criterion exists will need that criterion defined during an incident instead of during design. A goal-based loop adopted before the check is written down will evaluate against an evaluator’s guess. The artifacts get produced either way. The order decides whether they are produced deliberately or under pressure.</p>

<p>So the most capable loop is not the right starting point. The right starting point is the artifact underneath the rung a team is already on, finished and written down before the next step up.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Technology" /><category term="Software Engineering" /><category term="AI" /><category term="ai" /><category term="agents" /><category term="claude-code" /><category term="engineering-management" /><summary type="html"><![CDATA[The four agent loops depend on each other's artifacts. Notes on the order in which a team should adopt them.]]></summary></entry><entry><title type="html">What Sits Underneath the Agent Loops Post</title><link href="https://systemhalted.in/2026/07/03/what-sits-underneath-the-agent-loops-post/" rel="alternate" type="text/html" title="What Sits Underneath the Agent Loops Post" /><published>2026-07-03T00:00:00+00:00</published><updated>2026-07-03T00:00:00+00:00</updated><id>https://systemhalted.in/2026/07/03/what-sits-underneath-the-agent-loops-post</id><content type="html" xml:base="https://systemhalted.in/2026/07/03/what-sits-underneath-the-agent-loops-post/"><![CDATA[<p>Anthropic’s Claude Code team put out a short piece last week describing four kinds of agent loops: turn-based, goal-based, time-based, and proactive. Each type comes with a trigger, a stop condition, and typical use cases. The taxonomy describes surface. It says how the agent runs, how it stops, and what starts it. Underneath it sits a different question: where does the check on the work actually live? Each loop type puts that check in a different place, and the place that holds it is the place a team has to make durable before the next loop up is safe to build.</p>

<p>In a turn-based loop, the check lives in your judgement. The agent gathers context, does the work, checks itself, and hands back a result. You decide whether to accept it, redirect, or discard. Your judgement does not scale past a certain volume of work. What helps is writing the check down as something the agent can run before it comes back to you. Start the dev server, exercise the changed control, take screenshots before and after, run the affected tests. A skill file that encodes these steps moves the check from one person’s head to a file the whole team can read and improve. Every loop above this one assumes that file exists.</p>

<p>A goal-based loop moves the check into the exit criterion. You state what done looks like, and an evaluator model tests the condition each time the agent tries to stop. A measurable criterion, such as a Lighthouse score or a latency threshold on a specific endpoint, stops the loop on something you can defend after the fact. A vague criterion stops the loop early, because the agent convinces itself it is done, or lets it run long, because there is no way to be sure. Most teams do not have exit criteria for their own work written in language a person could act on, let alone an agent. The first attempt at a goal-based loop usually exposes that gap.</p>

<p>With a time-based loop, the trigger carries the check. The same prompt runs every N minutes against the current state of the world: summarising a channel, sweeping a queue, checking a pull request for review comments. What usually breaks here is the interval, not the prompt. A short interval against a slow-moving source wastes tokens, and a long interval against a fast-moving source misses changes that need a response. When the source system can emit an event, such as a webhook or a queue depth alert, an event trigger ties each run to a change that actually happened. A time-based loop works better as a stage on the way to an event-driven one than as a destination.</p>

<p>A proactive loop spreads the check across the whole system around the agent. A routine watches a stream of bug reports, alerts, or tickets, and runs a goal-based loop for each item until someone turns it off. This is a queue consumer whose judgement comes from a model, so it needs the same properties as any production consumer: idempotency, rate limits, spend caps, observability, a shutoff, and an owner. The Anthropic post covers token budgeting and routing routine subtasks to smaller models, which matter for cost. The harder question is ownership. A routine without a clear owner will eventually do the wrong thing with nobody responsible for noticing.</p>

<p>Three of these places build on each other directly. The check written down at the turn-based level is what the goal-based loop evaluates, and the criterion defined at the goal-based level is what the proactive routine applies to each item on the stream. The trigger sits beside this chain rather than inside it: a schedule or an event source decides when the runs happen, not whether their results can be trusted. A proactive routine built on unverified goals does not automate anything useful. It repeats whatever was already broken, at a cost that grows with how often it runs.</p>

<p>So the loops are the visible layer, and the durable artifacts sit underneath them: the skill file, the exit criterion, the trigger, and the observability around the routine. A team can locate its missing artifact by looking at what it still does by hand. If UI changes are verified manually at the end of every session, the skill file has not been written. Case-by-case judgement on whether a fix is done usually means there is no measurable exit criterion. A script that someone remembers to start every morning is a schedule that has not been written down, and after that an event trigger. Hand triage of a steady stream of reports is a routine that has not been built yet. These artifacts outlive the specific prompt, the specific model, and the specific team. Writing them down is the work the loops post points to.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Technology" /><category term="Software Engineering" /><category term="AI" /><category term="ai" /><category term="agents" /><category term="claude-code" /><category term="engineering-management" /><summary type="html"><![CDATA[Anthropic's Claude Code team described four kinds of agent loops. Notes on where the check on the work lives in each, and on the artifacts a team has to make durable underneath them.]]></summary></entry><entry><title type="html">Property-Based Testing: Testing Rules Instead of Examples</title><link href="https://systemhalted.in/2026/07/02/property-based-testing/" rel="alternate" type="text/html" title="Property-Based Testing: Testing Rules Instead of Examples" /><published>2026-07-02T00:00:00+00:00</published><updated>2026-07-02T00:00:00+00:00</updated><id>https://systemhalted.in/2026/07/02/property-based-testing</id><content type="html" xml:base="https://systemhalted.in/2026/07/02/property-based-testing/"><![CDATA[<p>In an <a href="/2026/06/21/types-check-shape-tests-check-behaviour/">earlier post</a> I wrote about a small text editor I am building in Rust, and a <code class="language-plaintext highlighter-rouge">save</code> function that passed its test while being wrong. The test built a document, changed it, saved it, read the file back, and checked that the contents matched. It was green. The implementation dropped the <code class="language-plaintext highlighter-rouge">Result</code> from the disk write, so a failed save would still report success and clear the unsaved-changes flag. The test and the bug lived together without any conflict, because the test only ever ran the happy path.</p>

<p>That post was about the boundary between what a type system can prove and what only a test can. There is a separate limitation worth looking at, one that has little to do with that particular bug. An example test only runs the input I give it. My save test proves something about the string <code class="language-plaintext highlighter-rouge">abcdef</code> written to a temporary file, and nothing about the other inputs the function will see. I chose the input, so the input agrees with me. Writing more example tests does not remove this, because I choose those inputs too.</p>

<h2 id="from-examples-to-a-property">From examples to a property</h2>

<p>Take <code class="language-plaintext highlighter-rouge">insert</code> and <code class="language-plaintext highlighter-rouge">delete</code> on the document model. The usual way to test them is with an example.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[test]</span>
<span class="k">fn</span> <span class="nf">insert_then_delete_restores_buffer</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">doc</span> <span class="o">=</span> <span class="nn">Document</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"hello"</span><span class="p">);</span>
    <span class="n">doc</span><span class="nf">.insert</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s">" world"</span><span class="p">);</span>        <span class="c1">// "hello world"</span>
    <span class="n">doc</span><span class="nf">.delete</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">11</span><span class="p">);</span>              <span class="c1">// back to "hello"</span>
    <span class="nd">assert_eq!</span><span class="p">(</span><span class="n">doc</span><span class="nf">.text</span><span class="p">(),</span> <span class="s">"hello"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This test passes, and it is not a bad test. But look at what it actually asserts: that inserting <code class="language-plaintext highlighter-rouge">" world"</code> at index 5 of <code class="language-plaintext highlighter-rouge">"hello"</code>, and then deleting that range, gives back <code class="language-plaintext highlighter-rouge">"hello"</code>. That is one point in a large space of possible inputs. The rule I care about is not about <code class="language-plaintext highlighter-rouge">"hello"</code> at all. It is this: for any document, inserting a string and then deleting exactly that range returns the original document. I wrote a test about one case of the rule because a single case is the only thing an example test lets me state.</p>

<p>Property-based testing lets me state the rule and let the framework choose the inputs. Instead of picking the input, I describe the range of valid inputs, state what must be true, and the framework generates many cases and tries to find one that fails. In Rust the common tool for this is the <code class="language-plaintext highlighter-rouge">proptest</code> crate<sup id="fnref:proptest"><a href="#fn:proptest" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">proptest</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="nd">proptest!</span> <span class="p">{</span>
    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">insert_then_delete_is_identity</span><span class="p">(</span><span class="n">base</span> <span class="k">in</span> <span class="s">".*"</span><span class="p">,</span> <span class="n">ins</span> <span class="k">in</span> <span class="s">".*"</span><span class="p">,</span> <span class="n">at</span> <span class="k">in</span> <span class="mi">0usize</span><span class="o">..=</span><span class="nn">usize</span><span class="p">::</span><span class="n">MAX</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">doc</span> <span class="o">=</span> <span class="nn">Document</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="o">&amp;</span><span class="n">base</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">at</span> <span class="o">=</span> <span class="n">at</span> <span class="o">%</span> <span class="p">(</span><span class="n">doc</span><span class="nf">.len</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>          <span class="c1">// an offset into the buffer</span>
        <span class="k">let</span> <span class="n">before</span> <span class="o">=</span> <span class="n">doc</span><span class="nf">.text</span><span class="p">()</span><span class="nf">.to_string</span><span class="p">();</span>

        <span class="n">doc</span><span class="nf">.insert</span><span class="p">(</span><span class="n">at</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ins</span><span class="p">);</span>
        <span class="n">doc</span><span class="nf">.delete</span><span class="p">(</span><span class="n">at</span><span class="p">,</span> <span class="n">at</span> <span class="o">+</span> <span class="n">ins</span><span class="nf">.len</span><span class="p">());</span>

        <span class="nd">prop_assert_eq!</span><span class="p">(</span><span class="n">doc</span><span class="nf">.text</span><span class="p">(),</span> <span class="n">before</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">proptest</code> runs this body a few hundred times with different generated values, and the test passes only if the property holds for all of them. I am no longer asserting a fact about <code class="language-plaintext highlighter-rouge">"hello"</code>. I am asserting a fact about <code class="language-plaintext highlighter-rouge">insert</code> and <code class="language-plaintext highlighter-rouge">delete</code>.</p>

<h2 id="reading-the-generator-line">Reading the generator line</h2>

<p>Most of the work in that test is in the signature, so it is worth going through it slowly.</p>

<p>Each <code class="language-plaintext highlighter-rouge">name in strategy</code> clause defines one generated argument. A strategy is the <code class="language-plaintext highlighter-rouge">proptest</code> term for something that knows two things: how to produce a random value, and how to shrink that value toward something smaller when a test fails. On each run, <code class="language-plaintext highlighter-rouge">proptest</code> draws a value from each strategy and calls the body with them. The <code class="language-plaintext highlighter-rouge">in</code> reads a little like the <code class="language-plaintext highlighter-rouge">in</code> of a <code class="language-plaintext highlighter-rouge">for</code> loop, but it is not walking a fixed list. It is drawing from a described set of inputs.</p>

<p><code class="language-plaintext highlighter-rouge">base in ".*"</code> and <code class="language-plaintext highlighter-rouge">ins in ".*"</code> generate strings. The literal <code class="language-plaintext highlighter-rouge">".*"</code> is read as a regular expression, and the strategy produces strings that match it. In that expression <code class="language-plaintext highlighter-rouge">.</code> means any character and <code class="language-plaintext highlighter-rouge">*</code> means zero or more of them, so <code class="language-plaintext highlighter-rouge">".*"</code> matches almost any string: the empty string, <code class="language-plaintext highlighter-rouge">"hello"</code>, a single emoji, a two-byte character such as <code class="language-plaintext highlighter-rouge">"é"</code>, whitespace, or control characters. This wide range is deliberate. <code class="language-plaintext highlighter-rouge">".*"</code> will produce characters that take more than one byte, and those are the inputs I do not think to type by hand, which turns out to matter here. If I wanted a narrower set of inputs I would use a narrower expression, such as <code class="language-plaintext highlighter-rouge">"[a-z]{1,10}"</code> for one to ten lowercase letters, or <code class="language-plaintext highlighter-rouge">"[0-9]+"</code> for a run of digits. Here I want the widest set I can get.</p>

<p><code class="language-plaintext highlighter-rouge">at in 0usize..=usize::MAX</code> generates an integer. The strategy here is an ordinary Rust range. <code class="language-plaintext highlighter-rouge">proptest</code> provides a <code class="language-plaintext highlighter-rouge">Strategy</code> implementation for the standard range types, so a range can be handed to it directly as a generator that produces values inside the range. Reading the bounds from left to right: <code class="language-plaintext highlighter-rouge">0usize</code> is the low end, zero, written as a <code class="language-plaintext highlighter-rouge">usize</code>, which is Rust’s pointer-sized unsigned integer and the type used for indexing. The <code class="language-plaintext highlighter-rouge">..=</code> operator makes the range inclusive, so the upper value is part of the range. <code class="language-plaintext highlighter-rouge">usize::MAX</code> is the largest value a <code class="language-plaintext highlighter-rouge">usize</code> can hold. The <code class="language-plaintext highlighter-rouge">usize</code> suffix on <code class="language-plaintext highlighter-rouge">0usize</code> is not cosmetic. Both ends of a range must have the same type, and without it the compiler cannot tell whether I meant a <code class="language-plaintext highlighter-rouge">u32</code>, an <code class="language-plaintext highlighter-rouge">i64</code>, or something else. So <code class="language-plaintext highlighter-rouge">at</code> is any index from zero to the maximum.</p>

<p>This raises a fair question. The generated integer can be as large as <code class="language-plaintext highlighter-rouge">usize::MAX</code>, about 1.8 billion billion on a 64-bit machine, while a short document has only a handful of positions. A five-byte string like <code class="language-plaintext highlighter-rouge">"hello"</code> has just six offsets, zero through five. So almost every raw value the generator produces lands far past the end of the buffer rather than inside it. Why draw from the whole <code class="language-plaintext highlighter-rouge">usize</code> range at all? The reason is that the three strategies are evaluated independently. The generator for <code class="language-plaintext highlighter-rouge">at</code> does not know how long the <code class="language-plaintext highlighter-rouge">base</code> string it is paired with will be, so it cannot produce an index that is valid for that particular document. The first line of the body deals with this.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">at</span> <span class="o">=</span> <span class="n">at</span> <span class="o">%</span> <span class="p">(</span><span class="n">doc</span><span class="nf">.len</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>   <span class="c1">// an offset into the buffer</span>
</code></pre></div></div>

<p>The modulo maps any generated integer into the range <code class="language-plaintext highlighter-rouge">0..=doc.len()</code>, where <code class="language-plaintext highlighter-rouge">doc.len()</code> is the length of the document in bytes. The range is inclusive at the top because you can insert at the end of the buffer as well as inside it. Generating a wide value and then mapping it into range is a common pattern with <code class="language-plaintext highlighter-rouge">proptest</code>, because it keeps every generated value usable instead of throwing away the ones that fall out of range. There is a more precise tool for producing an index into a generated collection, the <code class="language-plaintext highlighter-rouge">prop_flat_map</code> combinator, which lets one strategy depend on the value another produced. For a single index the modulo is simpler.</p>

<p>So the signature reads as follows: for any strings <code class="language-plaintext highlighter-rouge">base</code> and <code class="language-plaintext highlighter-rouge">ins</code>, and any index <code class="language-plaintext highlighter-rouge">at</code> mapped into range, the body must hold. <code class="language-plaintext highlighter-rouge">proptest</code> runs the body many times with different values, and the test passes only if the property holds for all of them.</p>

<h2 id="when-a-property-fails">When a property fails</h2>

<p>Run this property as written and it does not reach the assertion. It panics, and not on <code class="language-plaintext highlighter-rouge">"hello"</code>. It panics on an input I would not have typed into a test by hand.</p>

<p>A raw generated input is not much use as a bug report. If the framework told me the test failed on a four thousand character random string, that would be a puzzle to work through, not a defect I can read. What makes property-based testing practical is shrinking. When <code class="language-plaintext highlighter-rouge">proptest</code> finds a failing input, it does not report that input directly. It looks for the smallest and simplest input that still fails, using shorter strings, smaller indices, and values closer to zero, and reports that instead. The failure shrinks down to something like this.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>minimal failing input:
    base = "é"
    ins  = ""
    at   = 1

panic: byte index 1 is not a char boundary
</code></pre></div></div>

<p>That is a report I can act on, and the surprise is where the panic comes from. It is not a bug in <code class="language-plaintext highlighter-rouge">insert</code> or <code class="language-plaintext highlighter-rouge">delete</code>. It is my own line, <code class="language-plaintext highlighter-rouge">at % (doc.len() + 1)</code>. In Rust a string index is a byte offset, and <code class="language-plaintext highlighter-rouge">len()</code> returns a length in bytes, so the modulo gives me some offset between zero and the byte length. But not every byte offset is a place you are allowed to edit. An edit must land on a UTF-8 character boundary, and <code class="language-plaintext highlighter-rouge">insert</code> panics when it does not. The character <code class="language-plaintext highlighter-rouge">é</code> is two bytes, so the only valid positions are <code class="language-plaintext highlighter-rouge">0</code> and <code class="language-plaintext highlighter-rouge">2</code>; the offset <code class="language-plaintext highlighter-rouge">1</code> falls inside the character. The generator produced the smallest string and index that break an assumption I had written into the test without noticing it: that any offset from zero to the length is a valid place to insert.</p>

<p>The fix is to generate only offsets that fall on character boundaries. Rust gives me the boundaries through <code class="language-plaintext highlighter-rouge">char_indices</code>, and I add the end of the string as the last position.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">proptest</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">char_boundary_offsets</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">s</span><span class="nf">.char_indices</span><span class="p">()</span>
        <span class="nf">.map</span><span class="p">(|(</span><span class="n">i</span><span class="p">,</span> <span class="n">_</span><span class="p">)|</span> <span class="n">i</span><span class="p">)</span>
        <span class="nf">.chain</span><span class="p">(</span><span class="nn">std</span><span class="p">::</span><span class="nn">iter</span><span class="p">::</span><span class="nf">once</span><span class="p">(</span><span class="n">s</span><span class="nf">.len</span><span class="p">()))</span>
        <span class="nf">.collect</span><span class="p">()</span>
<span class="p">}</span>

<span class="nd">proptest!</span> <span class="p">{</span>
    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">insert_then_delete_is_identity</span><span class="p">(</span><span class="n">base</span> <span class="k">in</span> <span class="s">".*"</span><span class="p">,</span> <span class="n">ins</span> <span class="k">in</span> <span class="s">".*"</span><span class="p">,</span> <span class="n">raw_at</span> <span class="k">in</span> <span class="mi">0usize</span><span class="o">..=</span><span class="nn">usize</span><span class="p">::</span><span class="n">MAX</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">offsets</span> <span class="o">=</span> <span class="nf">char_boundary_offsets</span><span class="p">(</span><span class="o">&amp;</span><span class="n">base</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">at</span> <span class="o">=</span> <span class="n">offsets</span><span class="p">[</span><span class="n">raw_at</span> <span class="o">%</span> <span class="n">offsets</span><span class="nf">.len</span><span class="p">()];</span>   <span class="c1">// a valid edit position</span>

        <span class="k">let</span> <span class="k">mut</span> <span class="n">doc</span> <span class="o">=</span> <span class="nn">Document</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="o">&amp;</span><span class="n">base</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">before</span> <span class="o">=</span> <span class="n">doc</span><span class="nf">.text</span><span class="p">()</span><span class="nf">.to_string</span><span class="p">();</span>

        <span class="n">doc</span><span class="nf">.insert</span><span class="p">(</span><span class="n">at</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ins</span><span class="p">);</span>
        <span class="n">doc</span><span class="nf">.delete</span><span class="p">(</span><span class="n">at</span><span class="p">,</span> <span class="n">at</span> <span class="o">+</span> <span class="n">ins</span><span class="nf">.len</span><span class="p">());</span>

        <span class="nd">prop_assert_eq!</span><span class="p">(</span><span class="n">doc</span><span class="nf">.text</span><span class="p">(),</span> <span class="n">before</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>There is a more general point here, beyond the Unicode detail. A property test does not only run more inputs than an example. It makes me say what a valid input is. Writing the example test, I never had to define the set of legal insertion points, because I only used position 5 of <code class="language-plaintext highlighter-rouge">"hello"</code>, which happens to be legal. The property could not be written until I stated, in code, that a legal position is a character boundary. So the generator did not simply find a case I had forgotten. It exposed an assumption I had never written down, which is what happens when a rule has to hold for every input rather than for the one I picked.</p>

<p>The type system does not close this gap. <code class="language-plaintext highlighter-rouge">String</code> guarantees the buffer is valid UTF-8, but an index into it is a plain <code class="language-plaintext highlighter-rouge">usize</code>, and the type says nothing about whether that <code class="language-plaintext highlighter-rouge">usize</code> lands on a boundary. That check happens at runtime, and until I wrote the property it was happening nowhere in my tests.</p>

<h2 id="where-property-based-testing-helps-and-where-it-does-not">Where property-based testing helps, and where it does not</h2>

<p>It would be easy to read this and decide that property tests should replace example tests. They should not, any more than tests replace types. Each answers a different question, and property testing has a cost. A good property is harder to find than a good example, and a weak property is worse than no test at all.</p>

<p>The cost is in finding the rule. “For input <code class="language-plaintext highlighter-rouge">"hello"</code>, expect <code class="language-plaintext highlighter-rouge">"hello world"</code>” takes a second to write. “For all documents and all edits, this relationship holds” takes real thought, and if the rule is slightly wrong the test is either flaky or empty. The common mistake is a vacuous property, one that cannot fail.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">prop_assert!</span><span class="p">(</span><span class="n">doc</span><span class="nf">.len</span><span class="p">()</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">);</span>       <span class="c1">// len() is usize; always true</span>
</code></pre></div></div>

<p>That runs a few hundred times, passes every time, and checks nothing. A property that is merely true is not the same as a property that constrains the code. The useful properties describe a relationship tight enough that a wrong implementation cannot satisfy it: round-trips such as <code class="language-plaintext highlighter-rouge">decode(encode(x)) == x</code>, idempotence such as <code class="language-plaintext highlighter-rouge">f(f(x)) == f(x)</code>, order-independence where applying two edits in either order gives the same result, and conservation of some quantity such as length or character count. If I cannot name a relationship like that, an example test is the honest choice, and a property adds nothing.</p>

<p>Here is the rule I settled on. A property test earns its place when there is a rule that must hold across many inputs, not a single value I am checking. <code class="language-plaintext highlighter-rouge">save</code> then <code class="language-plaintext highlighter-rouge">load</code> should return the same buffer, and that is a rule. “The About dialog shows version 2.1” is a single value, and writing it as a property gains nothing.</p>

<h2 id="types-examples-and-properties">Types, examples and properties</h2>

<p>I now think of three tools rather than two, each covering what the previous one cannot.</p>

<p>Types rule out illegal shapes before the program runs. A <code class="language-plaintext highlighter-rouge">Result</code> that must be used, an <code class="language-plaintext highlighter-rouge">Option</code> that cannot be read without handling the empty case, a closed enum that forces every branch to be handled. The compiler settles these for every input at once, and this is the cheapest of the three.</p>

<p>Example tests check specific behaviour that the types allow but do not pin down. That inserting <code class="language-plaintext highlighter-rouge">" world"</code> at index 5 of <code class="language-plaintext highlighter-rouge">"hello"</code> gives <code class="language-plaintext highlighter-rouge">"hello world"</code> and not <code class="language-plaintext highlighter-rouge">"worldhello"</code> is a fact about values, and the type checker has no opinion on it. An example fixes one such fact.</p>

<p>Property tests cover the space in between, the behaviour that must hold across all the inputs the type still allows and that no single example can cover. The UTF-8 boundary case sits there. It is legal according to the type, missed by the examples, and wrong in general. The useful thing is to be clear about how behaviour will be evaluated before writing the code, which is the <a href="/2025/12/09/tdd-revisted/">test-first</a> instinct. A property is one clear way to state that evaluation, because it forces me to say what must always be true rather than what happened to be true the one time I ran the code.</p>

<p>The three fit together in order. Push what you can into types, so the compiler proves it for every input. Use examples to record the specific behaviour you have decided on. Use properties for the rules that must hold everywhere and that you would never cover by hand.</p>

<h2 id="summary">Summary</h2>

<p>An example test checks the inputs I thought of. A property-based test states a rule and lets the framework generate inputs that try to break it. That is the difference that matters. On my own I tend to write the tests my code already passes, because I imagine the same cases when I write the code and when I test it. The generator does not share that imagination, and shrinking makes what it finds small enough to read and fix. Writing the property also forces me to say what a valid input is, which is often where the real gap turns out to be. The reason to reach for property testing is not that the code is otherwise untestable. It is that it exercises the inputs I would not have chosen, and in a text editor those are often the inputs a real user will produce.</p>

<h2 id="references-and-notes">References and Notes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:proptest">
      <p><code class="language-plaintext highlighter-rouge">proptest</code> is a property-testing framework for Rust with shrinking driven by its generators. Documentation and the book: <a href="https://proptest-rs.github.io/proptest/">https://proptest-rs.github.io/proptest/</a>. Rust’s other well-known option, <code class="language-plaintext highlighter-rouge">quickcheck</code> (<a href="https://github.com/BurntSushi/quickcheck">https://github.com/BurntSushi/quickcheck</a>), follows the original Haskell QuickCheck more closely: it mirrors that library’s <code class="language-plaintext highlighter-rouge">Arbitrary</code> typeclass as an <code class="language-plaintext highlighter-rouge">Arbitrary</code> trait, so generation and shrinking both live on the type. <code class="language-plaintext highlighter-rouge">proptest</code> instead takes its generator-driven shrinking from Python’s Hypothesis (<a href="https://hypothesis.readthedocs.io/">https://hypothesis.readthedocs.io/</a>), where the strategy that built a value also knows how to shrink it. Both Rust crates descend from Claessen and Hughes, “QuickCheck: A Lightweight Tool for Random Testing of Haskell Programs” (ICFP 2000): <a href="https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf">https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf</a>, the paper that introduced testing stated properties over generated inputs. <a href="#fnref:proptest" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Software Engineering" /><category term="Computer Science" /><category term="rust" /><category term="testing" /><category term="property-based-testing" /><category term="tdd" /><category term="software" /><summary type="html"><![CDATA[An example test checks the inputs you happened to choose. A property-based test states a rule and lets the framework generate inputs that try to break it. Notes from continuing a small text editor in Rust.]]></summary></entry><entry><title type="html">Git Worktree and the Myth of a Single Working Directory</title><link href="https://systemhalted.in/2026/06/25/git-worktree/" rel="alternate" type="text/html" title="Git Worktree and the Myth of a Single Working Directory" /><published>2026-06-25T00:00:00+00:00</published><updated>2026-06-25T00:00:00+00:00</updated><id>https://systemhalted.in/2026/06/25/git-worktree</id><content type="html" xml:base="https://systemhalted.in/2026/06/25/git-worktree/"><![CDATA[<p>While working on a feature recently, I needed to look at something on another branch. Normally this would involve one of the workflows most Git users are familiar with: commit the current changes, stash them, or temporarily abandon the current state and switch branches. None of those options felt appealing. The work was incomplete and I was not ready to commit it. Stashing would have worked, but it felt like introducing additional state that I would need to remember to restore later.</p>

<p>Looking for alternatives, I came across a Git feature that I had heard about before but never used seriously: worktree. At first glance, worktree looks like a convenience feature. It allows multiple working directories to be attached to the same repository so that different branches can be checked out simultaneously. That is useful on its own, but what caught my attention was the implication behind it.</p>

<p>Most Git users, myself included, tend to think of a repository as a working directory that happens to contain a <code class="language-plaintext highlighter-rouge">.git</code> directory. Git does not view things this way. From Git’s perspective, the repository is the object database, and the working directory is one view into that repository. Worktree exists because Git was never fundamentally limited to a single working directory. Most of us simply use it that way.</p>

<h2 id="the-mental-model-most-of-us-carry">The Mental Model Most of Us Carry</h2>

<p>Consider a typical repository:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>my-project/
├── src/
├── docs/
├── pom.xml
└── .git/
</code></pre></div></div>

<p>Most developers naturally view this as a single unit: a repository, a working directory, and a currently checked-out branch. That mental model is not wrong, but it is incomplete. The branch is not stored in the working directory. The commit history is not stored in the working directory. The object database is not stored in the working directory. Those things live inside <code class="language-plaintext highlighter-rouge">.git</code>, and the files visible in the working directory are a projection of a particular commit from the repository.</p>

<p>Once viewed from that perspective, an interesting question emerges: why should a repository be limited to a single projection? Git’s answer is that it isn’t.</p>

<h2 id="creating-a-worktree">Creating a Worktree</h2>

<p>Suppose the current repository is checked out on a feature branch:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git switch feature/customer-notifications
</code></pre></div></div>

<p>A second working directory can be created using:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git worktree add ../customer-notifications-main main
</code></pre></div></div>

<p>Git creates a new directory alongside the existing one:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>projects/
├── customer-notifications/
└── customer-notifications-main/
</code></pre></div></div>

<p>The first directory remains on the feature branch, and the second is checked out on <code class="language-plaintext highlighter-rouge">main</code>. Both can be opened in separate terminals, editors, or IDE windows, and changes in one working tree do not affect the other. From a developer’s perspective, it feels almost like having two clones, except that Git is not creating another repository.</p>

<h2 id="looking-at-what-git-actually-creates">Looking at What Git Actually Creates</h2>

<p>After creating a worktree, the repository structure changes slightly. Inside <code class="language-plaintext highlighter-rouge">.git</code>, a new directory appears:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.git/
├── objects/
├── refs/
├── logs/
└── worktrees/
</code></pre></div></div>

<p>Listing the contents of <code class="language-plaintext highlighter-rouge">.git/worktrees/</code> reveals a subdirectory for each additional worktree, containing files such as <code class="language-plaintext highlighter-rouge">HEAD</code>, <code class="language-plaintext highlighter-rouge">gitdir</code>, <code class="language-plaintext highlighter-rouge">commondir</code>, and <code class="language-plaintext highlighter-rouge">index</code>. The exact contents may vary across Git versions, but the important observation is that Git is maintaining metadata for an additional working tree while continuing to share the underlying repository. This is why creating a worktree is extremely fast. Git is not duplicating the commit graph, objects, tags, or references. It is creating another working directory and associating it with the existing repository.</p>

<h2 id="shared-and-separate-state">Shared and Separate State</h2>

<p>The easiest way to understand worktrees is to identify what is shared and what is independent. The object database, branch references, tags, hooks, and configuration are all shared across worktrees. Each worktree has its own working directory contents, its own index, and its own HEAD pointing somewhere into the shared <code class="language-plaintext highlighter-rouge">refs/heads/</code>. This separation is what allows two branches to be active simultaneously without two repositories, and it has a few practical consequences worth noting.</p>

<p>A <code class="language-plaintext highlighter-rouge">git fetch</code> in any worktree updates the shared refs, so the new commits are immediately visible from every worktree attached to the repository. Hooks installed in <code class="language-plaintext highlighter-rouge">.git/hooks/</code> run regardless of which worktree triggered them, which catches some people out when they expect per-worktree hook behavior. And because branch refs are shared, a branch checked out in one worktree cannot be checked out in another, which is the next thing worth looking at.</p>

<h2 id="why-git-refuses-certain-operations">Why Git Refuses Certain Operations</h2>

<p>Suppose <code class="language-plaintext highlighter-rouge">feature/customer-notifications</code> is already checked out in one worktree. Attempting to check it out in another produces an error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git worktree add ../another-copy feature/customer-notifications
fatal: 'feature/customer-notifications' is already checked out at '/path/to/customer-notifications'
</code></pre></div></div>

<p>This can initially seem restrictive, but the restriction makes sense given the shared-ref design. A branch is a movable reference, and if two working directories could manipulate the same branch independently, Git would have no way to determine the intended state of that reference. The restriction prevents ambiguity and protects branch state from concurrent updates.</p>

<h2 id="worktree-versus-stash">Worktree Versus Stash</h2>

<p>Worktree and stash solve different problems, even though both come up when someone needs to switch context briefly. A stash temporarily preserves uncommitted changes so another task can be performed in the same working directory, while a worktree creates an entirely separate workspace. If I need to quickly pull <code class="language-plaintext highlighter-rouge">main</code>, perform a rebase, and continue working, a stash is reasonable. If I expect to spend hours investigating another branch while keeping the current work untouched, a worktree is the better choice. The distinction is subtle but important: a stash preserves state, while a worktree preserves context.</p>

<h2 id="worktree-versus-another-clone">Worktree Versus Another Clone</h2>

<p>Historically, some developers solved this problem by maintaining multiple clones of the same repository. That approach works, but every clone maintains its own object database, references, and repository metadata, which means fetches must be performed in each clone separately and disk usage grows linearly with the number of copies. Worktrees share those resources. For smaller repositories the difference is negligible, but for larger repositories containing years of history it becomes noticeable. More importantly, worktrees communicate intent. They represent multiple views into the same repository rather than multiple copies of the repository itself.</p>

<h2 id="the-bare-repository-approach">The Bare Repository Approach</h2>

<p>One workflow worth mentioning treats worktrees as the primary interface rather than an occasional convenience. The repository is cloned bare, and every branch the developer wants to work on becomes its own worktree:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone --bare git@github.com:example/project.git project.git
cd project.git
git worktree add ../project-main main
git worktree add ../project-feature feature/customer-notifications
</code></pre></div></div>

<p>There is no “main” working directory in this model. Every working directory is a worktree, and the bare repository at the root contains only the shared state. Some developers organize their worktrees inside a <code class="language-plaintext highlighter-rouge">.worktrees/</code> subdirectory of the project to keep things tidy. Whether this layout is worth adopting depends on how often parallel branches are needed, but it makes Git’s underlying model unusually visible.</p>

<h2 id="managing-worktrees">Managing Worktrees</h2>

<p>Git provides a few commands for managing worktrees. <code class="language-plaintext highlighter-rouge">git worktree list</code> shows all active worktrees and the branches they point to:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/home/palak/projects/my-project          a1b2c3d [feature/customer-notifications]
/home/palak/projects/my-project-main     e4f5g6h [main]
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">git worktree remove &lt;path&gt;</code> removes a worktree. If a worktree directory is removed manually outside of Git, the stale metadata can be cleaned up with <code class="language-plaintext highlighter-rouge">git worktree prune</code>. Most day-to-day usage rarely requires anything beyond these commands.</p>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>Before learning about worktrees, I implicitly assumed that a Git repository and a working directory were inseparable concepts. Git itself makes no such assumption. A repository is an object database containing commits, trees, blobs, references, and metadata, while a working directory is one representation of repository state at a particular point in time. Viewed from that perspective, worktrees feel less like an advanced feature and more like a natural consequence of Git’s architecture.</p>

<p>The feature is useful for avoiding branch switching, preserving context, and keeping parallel streams of work isolated. But the more interesting lesson is the one it reveals about Git itself. Many of the limitations we assume are built into our tools are actually limitations of the mental models we carry around. Git worktree is a small reminder that those two things are not always the same.</p>]]></content><author><name>Palak Mathur</name></author><category term="Technology" /><category term="Software Engineering" /><category term="git" /><category term="version-control" /><category term="software-engineering" /><summary type="html"><![CDATA[While working on a feature recently, I needed to look at something on another branch. Normally this would involve one of the workflows most Git users are familiar with: commit the current changes, stash them, or temporarily abandon the current state and switch branches. None of those options felt appealing. The work was incomplete and I was not ready to commit it. Stashing would have worked, but it felt like introducing additional state that I would need to remember to restore later.]]></summary></entry><entry><title type="html">Types Check Shape, Tests Check Behaviour</title><link href="https://systemhalted.in/2026/06/21/types-check-shape-tests-check-behaviour/" rel="alternate" type="text/html" title="Types Check Shape, Tests Check Behaviour" /><published>2026-06-21T00:00:00+00:00</published><updated>2026-06-25T00:00:00+00:00</updated><id>https://systemhalted.in/2026/06/21/types-check-shape-tests-check-behaviour</id><content type="html" xml:base="https://systemhalted.in/2026/06/21/types-check-shape-tests-check-behaviour/"><![CDATA[<p>I am writing a small text editor in Rust to learn the language – a document model, opening a file, saving a file, etc. While building the <code class="language-plaintext highlighter-rouge">save</code> function I hit a bug that made me reconsider what tests are for and where the compiler takes over.</p>

<p>These are some of the notes.</p>

<h2 id="a-green-test-is-not-a-correct-program">A green test is not a correct program</h2>

<p>The function performed following tasjs - write the buffer to a path, then mark the document as having no unsaved changes.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">fn</span> <span class="nf">save</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Path</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="k">self</span><span class="nf">.text</span><span class="p">());</span>   <span class="c1">// the Result here is silently dropped</span>
    <span class="k">self</span><span class="py">.modified</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</code></pre></div></div>

<p>and I had also written the following test:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[test]</span>
<span class="k">fn</span> <span class="nf">save_writes_contents_and_clears_modified</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">path</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">env</span><span class="p">::</span><span class="nf">temp_dir</span><span class="p">()</span><span class="nf">.join</span><span class="p">(</span><span class="s">"save_test.txt"</span><span class="p">);</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">doc</span> <span class="o">=</span> <span class="nn">Document</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"abc"</span><span class="p">);</span>
    <span class="n">doc</span><span class="nf">.insert</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="s">"def"</span><span class="p">);</span> <span class="c1">// buffer is now "abcdef", with unsaved changes</span>

    <span class="n">doc</span><span class="nf">.save</span><span class="p">(</span><span class="o">&amp;</span><span class="n">path</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="nd">assert_eq!</span><span class="p">(</span><span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="nf">read_to_string</span><span class="p">(</span><span class="o">&amp;</span><span class="n">path</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">(),</span> <span class="s">"abcdef"</span><span class="p">);</span>
    <span class="nd">assert!</span><span class="p">(</span><span class="o">!</span><span class="n">doc</span><span class="nf">.is_modified</span><span class="p">());</span> <span class="c1">// a successful save clears the dirty flag</span>

    <span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="nf">remove_file</span><span class="p">(</span><span class="o">&amp;</span><span class="n">path</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It built a document, made a change, saved it to a temporary file, read the file back, checked that the contents matched, and confirmed the document no longer reported unsaved changes. The test passed, which would normally be the end of it, except that the implementation had a bug on the first line.</p>

<p><code class="language-plaintext highlighter-rouge">std::fs::write</code> returns a <code class="language-plaintext highlighter-rouge">Result</code>, because writing to disk can fail in any of the ordinary ways: the disk fills up, the directory does not exist, the process lacks permission. The function ignored that return value, so a failed write would still fall through to <code class="language-plaintext highlighter-rouge">self.modified = false</code> and then <code class="language-plaintext highlighter-rouge">Ok(())</code>. The editor would report the file as saved, clear the unsaved-changes marker, and lose the user’s work, which is the one bug a text editor cannot ship. The test stayed green the whole time because it only ran the happy path, and a passing happy-path test says nothing about the behaviour on the failure paths it never visits.</p>

<h2 id="the-compiler-reviews-shape-not-intent">The compiler reviews shape, not intent</h2>

<p>I missed the bug, but the compiler flagged it:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>warning: unused `Result` that must be used
</code></pre></div></div>

<p>Rust marks <code class="language-plaintext highlighter-rouge">Result</code> as <code class="language-plaintext highlighter-rouge">#[must_use]</code><sup id="fnref:mustuse"><a href="#fn:mustuse" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, so the lint fires when a fallible call’s return is dropped on the floor. Most languages would stay silent here; Rust does not, and it was right to complain.</p>

<p>The warning is real help, but it is still only a warning. I could have written this instead and quieted it:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">_</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="k">self</span><span class="nf">.text</span><span class="p">());</span>   <span class="c1">// explicitly discard it</span>
<span class="k">self</span><span class="py">.modified</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
<span class="nf">Ok</span><span class="p">(())</span>
</code></pre></div></div>

<p>That compiles cleanly. The type checker is satisfied: the signature returns a <code class="language-plaintext highlighter-rouge">Result</code>, a <code class="language-plaintext highlighter-rouge">Result</code> is returned, the borrow rules<sup id="fnref:borrow"><a href="#fn:borrow" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> hold, the shape of the program is right. What the compiler has no way of checking is what I meant by it – that a failed write must not clear the modified flag, that it must not be reported as success, that in a text editor a dirty buffer is not a boolean but a promise to the user.</p>

<p>This is what the compiler is good at and where it stops. It checks that the types line up, that fallibility is visible in signatures, that a <code class="language-plaintext highlighter-rouge">Result</code> is not accidentally ignored, that the borrows are valid; it does not check that the program does the right thing with any of those things once it has them.</p>

<h2 id="a-regression-test-only-counts-once-you-have-seen-it-fail">A regression test only counts once you have seen it fail</h2>

<p>The fix is one character – the <code class="language-plaintext highlighter-rouge">?</code> operator – which turns the dropped result into a propagated one:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="nf">write</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="k">self</span><span class="nf">.text</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>
<span class="k">self</span><span class="py">.modified</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
<span class="nf">Ok</span><span class="p">(())</span>
</code></pre></div></div>

<p>If the write fails, the function exits before clearing the modified flag. The interesting part is not the fix but the realisation that I had no test that would have caught the bug. Adding <code class="language-plaintext highlighter-rouge">?</code> without adding a test would leave me trusting the code for no reason at all, so I wrote the test that should have existed from the start.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[test]</span>
<span class="k">fn</span> <span class="nf">failed_save_surfaces_error_and_keeps_modified</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">doc</span> <span class="o">=</span> <span class="nn">Document</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"important data"</span><span class="p">);</span>
    <span class="n">doc</span><span class="nf">.insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="s">"!"</span><span class="p">);</span> <span class="c1">// the document now has unsaved changes</span>

    <span class="c1">// a directory that does not exist, so the OS write is forced to fail</span>
    <span class="k">let</span> <span class="n">bad_path</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">env</span><span class="p">::</span><span class="nf">temp_dir</span><span class="p">()</span><span class="nf">.join</span><span class="p">(</span><span class="s">"no_such_dir"</span><span class="p">)</span><span class="nf">.join</span><span class="p">(</span><span class="s">"file.txt"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="n">doc</span><span class="nf">.save</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bad_path</span><span class="p">);</span>

    <span class="nd">assert!</span><span class="p">(</span><span class="n">result</span><span class="nf">.is_err</span><span class="p">());</span>     <span class="c1">// the failure must reach the caller</span>
    <span class="nd">assert!</span><span class="p">(</span><span class="n">doc</span><span class="nf">.is_modified</span><span class="p">());</span>   <span class="c1">// and the buffer must still be dirty</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then I put the bug back, ran the suite, watched this test go red, restored the fix, and watched it go green. Putting the bug back to confirm the test fails for the right reason sounds like ceremony but is not – a regression test you have never seen fail is one you trust by faith, and the first section already showed how little a passing test is worth on its own.</p>

<h2 id="types-and-tests-answer-different-questions">Types and tests answer different questions</h2>

<p>This is the rule I would keep if I could keep only one. A type system is good at making certain kinds of lies impossible: a value that may be absent cannot pretend to be present, a fallible operation cannot pretend to be infallible, a closed set of cases cannot pretend one branch does not exist. That is powerful, but it is structural. Rust can make the failure visible, warn me when I accidentally ignore it, and force the code to admit that saving may fail, but it cannot encode the editor’s rule that if saving fails, the document must remain dirty. That rule lives at the level of behaviour, not shape.</p>

<p>You can push more behaviour into types than that suggests. The typestate pattern<sup id="fnref:typestate"><a href="#fn:typestate" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> encodes a rule into a type, so that an illegal operation does not compile instead of failing a test. The <code class="language-plaintext highlighter-rouge">save</code> bug can be narrowed this way by making the clearing of the modified flag depend on a value that only a successful write can produce:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">Saved</span><span class="p">;</span>   <span class="c1">// nothing outside this module can construct one</span>

<span class="k">fn</span> <span class="nf">write</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Path</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">io</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Saved</span><span class="o">&gt;</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
<span class="k">fn</span> <span class="nf">mark_clean</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">_proof</span><span class="p">:</span> <span class="n">Saved</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="py">.modified</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<p>Now <code class="language-plaintext highlighter-rouge">mark_clean</code> cannot be called without a <code class="language-plaintext highlighter-rouge">Saved</code>, and the only thing that hands one back is a <code class="language-plaintext highlighter-rouge">write</code> that returned <code class="language-plaintext highlighter-rouge">Ok</code>, so the exact bug from the first section is harder to write by accident.<sup id="fnref:receipt"><a href="#fn:receipt" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> The boundary between what a type can prove and what only a test can prove is not fixed; it moves with how much you choose to encode.</p>

<p>What stays out of reach, at least in Rust, is value-level correctness – the relationship between specific input and output values. That needs dependent types, which Rust does not have. The type checker has no opinion on whether inserting <code class="language-plaintext highlighter-rouge">" world"</code> at index 5 of <code class="language-plaintext highlighter-rouge">"hello"</code> gives <code class="language-plaintext highlighter-rouge">"hello world"</code> or <code class="language-plaintext highlighter-rouge">"worldhello"</code>; both are valid <code class="language-plaintext highlighter-rouge">String</code>s, and which one you meant is a question only a test can answer. The instruction that falls out of this is short: do not test what the type system has already made impossible, and do test the behaviour the type system is silent about. A small example came up in the next step. A document needs to remember the file it came from, but a new, untitled buffer has not come from anywhere, so the honest type for that field is <code class="language-plaintext highlighter-rouge">Option&lt;PathBuf&gt;</code>, and Rust will not let you read the path without handling the case where there is none. The “I forgot to check for the missing file” bug is not caught by a test here; the program that contains it does not compile, and writing a test for it would only re-prove what the compiler has already proven.</p>

<h2 id="test-everything-that-can-fail-is-too-blunt">“Test everything that can fail” is too blunt</h2>

<p>I used to carry a simple rule: every operation that can fail gets a failure test. It made sense in the Java world I grew up in, and after the save bug I wrote one for <code class="language-plaintext highlighter-rouge">open</code> too – a test asserting that opening a missing file returns an error. Then I deleted it.</p>

<p>The save failure test is worth keeping, but not because saving can fail. It is worth keeping because <code class="language-plaintext highlighter-rouge">save</code> changes state on the way to succeeding: it clears the modified flag. The assertion in that test that actually matters is not <code class="language-plaintext highlighter-rouge">assert!(result.is_err())</code> but <code class="language-plaintext highlighter-rouge">assert!(doc.is_modified())</code>, which guards the state that a failed save could otherwise leave wrong. <code class="language-plaintext highlighter-rouge">open</code> is different; it builds a value and returns it, changing nothing and leaving nothing wrong when it fails. The only thing a failure test there could check is whether I propagated the error instead of swallowing it, which is thin, with no state behind it to protect.</p>

<p>So the rule sharpened into something narrower: a failure test earns its place in proportion to the state a failure could leave wrong, and where failure only propagates cleanly there is little for it to guard. That is not an argument for testing less out of laziness, but for putting tests where things can actually break instead of spreading them evenly out of habit.</p>

<h2 id="the-point">The point</h2>

<p>This is not an argument against tests or against types, but for knowing which of the two you are leaning on at any given moment. Lean only on tests and you will keep writing assertions to re-establish guarantees a good type system would give you for free, at compile time, for every input. Lean only on types and you will ship code that is well-formed and quietly wrong: green in every structural sense, still losing files. The healthier division is to let the type system carry what it can – null-safety, exhaustiveness, fallibility made visible, and as much of your state machine as you are willing to encode – and to spend your tests on the behaviour that is left over.</p>

<p>That line moves. Typestate carries more than people expect, and dependent types would carry more again, but wherever you draw it for a given program the rule is the same: do not ask a type to prove what only a test can, or a test to re-prove what the type already guarantees. How far the line can move also depends on the language. Rust leans on two guarantees that Java’s type system holds you to less strictly. The first is that absence has a single shape: Rust has no <code class="language-plaintext highlighter-rouge">null</code>, so a value that might not be there is an <code class="language-plaintext highlighter-rouge">Option</code> and the compiler will not let me touch it without handling the empty case, whereas Java’s <code class="language-plaintext highlighter-rouge">Optional</code><sup id="fnref:java"><a href="#fn:java" class="footnote" rel="footnote" role="doc-noteref">5</a></sup> sits beside <code class="language-plaintext highlighter-rouge">null</code> rather than replacing it and leaves <code class="language-plaintext highlighter-rouge">.get()</code> available to learn my mistake at runtime. The second is move semantics: Java can make a proof-of-save <em>exist</em> through a private constructor and a factory, but it cannot make it <em>spent</em>, so nothing would stop me clearing the flag with the same token twice; Rust takes the value away when it is used, and that is the part Java has no equivalent for. In both cases the work does not vanish – it moves from the compiler to my tests and my discipline. The reason to reach for Rust here is not that the editor is otherwise impossible, which it plainly is not, but that a more expressive type system settles more of the program’s correctness before a single test runs.</p>

<h2 id="references-and-notes">References and Notes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:mustuse">
      <p>Rust’s <code class="language-plaintext highlighter-rouge">#[must_use]</code> attribute and the lint behind the warning: <a href="https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute">https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute</a> <a href="#fnref:mustuse" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:borrow">
      <p>References and borrowing in Rust, including the rules the borrow checker enforces: <a href="https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html">https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html</a> <a href="#fnref:borrow" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:typestate">
      <p>Cliff L. Biffle, “The Typestate Pattern in Rust,” a thorough walk through encoding state into types: <a href="https://cliffle.com/blog/rust-typestate/">https://cliffle.com/blog/rust-typestate/</a> <a href="#fnref:typestate" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:receipt">
      <p>This sketch is illustrative, not airtight. The <code class="language-plaintext highlighter-rouge">Saved</code> token is not bound to a particular document or path, so within the module that defines these methods you could still write to one file and clear a different document’s flag. A rigorous version would tie the proof to the instance; the point here is only that the rule can be pushed into the type at all. <a href="#fnref:receipt" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:java">
      <p>Kotlin, on the same JVM, enforces null-safety in its type system, distinguishing <code class="language-plaintext highlighter-rouge">String</code> from <code class="language-plaintext highlighter-rouge">String?</code> and checking it at compile time, which closes the first gap. Java’s sealed types with a pattern-matching <code class="language-plaintext highlighter-rouge">switch</code> give compile-time exhaustiveness over a closed set of cases – the same “every case is handled” guarantee Rust’s enums provide – and it can even encode method-presence typestate, so it carries more behaviour than the body alone might suggest. What stays particular to Rust is linearity: a value that is consumed when it is used. The broader point, that the type system’s power sets how far the line can move, is what holds. <a href="#fnref:java" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Software Engineering" /><category term="Computer Science" /><category term="rust" /><category term="testing" /><category term="type-systems" /><category term="tdd" /><category term="software" /><summary type="html"><![CDATA[Notes from building a small text editor in Rust, and what a green-but-wrong save() function taught me about the line between what a type system can prove and what only a test can.]]></summary></entry><entry><title type="html">Java Generics: The Cost of Type Erasure</title><link href="https://systemhalted.in/2026/05/31/java-generics-type-erasure/" rel="alternate" type="text/html" title="Java Generics: The Cost of Type Erasure" /><published>2026-05-31T00:00:00+00:00</published><updated>2026-05-31T00:00:00+00:00</updated><id>https://systemhalted.in/2026/05/31/java-generics-type-erasure</id><content type="html" xml:base="https://systemhalted.in/2026/05/31/java-generics-type-erasure/"><![CDATA[<p>This post is not an introduction to Java generics. I am assuming you are already familiar with the topic. The core idea I want to capture in this post is that Java checks generic types at compile time but erases much of that information at runtime, and that this changes how code behaves. This runtime behavior is known as type erasure, which means <code class="language-plaintext highlighter-rouge">List&lt;String&gt;</code> and <code class="language-plaintext highlighter-rouge">List&lt;Integer&gt;</code> both become <code class="language-plaintext highlighter-rouge">List</code> at runtime.</p>

<p>This behavior causes some interesting bugs and awkward APIs. We will explore some of them in this post.</p>

<h2 id="1-liststringclass-does-not-exist">1. <code class="language-plaintext highlighter-rouge">List&lt;String&gt;.class</code> does not exist</h2>

<p>We are all familiar with <code class="language-plaintext highlighter-rouge">List.class</code>, <code class="language-plaintext highlighter-rouge">String.class</code>, <code class="language-plaintext highlighter-rouge">Integer.class</code> but <code class="language-plaintext highlighter-rouge">List&lt;String&gt;.class</code> is an illegal construct in Java. At runtime, Java does not have a separate class object for <code class="language-plaintext highlighter-rouge">List&lt;String&gt;</code> versus <code class="language-plaintext highlighter-rouge">List&lt;Integer&gt;</code>.</p>

<p>That is why APIs that often accept this <code class="language-plaintext highlighter-rouge">Class&lt;T&gt; type</code> break down for generic types.</p>

<p>For example, this API looks clean:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="no">T</span> <span class="nf">read</span><span class="o">(</span><span class="nc">String</span> <span class="n">json</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">type</span><span class="o">)</span>
</code></pre></div></div>

<p>It works well for a normal object:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">User</span> <span class="n">user</span> <span class="o">=</span> <span class="n">read</span><span class="o">(</span><span class="n">json</span><span class="o">,</span> <span class="nc">User</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
</code></pre></div></div>

<p>However, the following will be impossible:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="n">users</span> <span class="o">=</span> <span class="n">read</span><span class="o">(</span><span class="n">json</span><span class="o">,</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;.</span><span class="na">class</span><span class="o">);</span>
</code></pre></div></div>
<p>There is no <code class="language-plaintext highlighter-rouge">List&lt;User&gt;.class</code> to pass. You may be tempted to make it work by trying:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="n">users</span> <span class="o">=</span> <span class="n">read</span><span class="o">(</span><span class="n">json</span><span class="o">,</span> <span class="nc">List</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
</code></pre></div></div>
<p>But now the runtime only knows that the target type is <code class="language-plaintext highlighter-rouge">List</code>. It does not know that the list is supposed to contain <code class="language-plaintext highlighter-rouge">User</code> objects.</p>

<p>This is where bugs begin. A JSON library, for example, may deserialize the JSON array into a <code class="language-plaintext highlighter-rouge">List&lt;LinkedHashMap&gt;</code> instead of a <code class="language-plaintext highlighter-rouge">List&lt;User&gt;</code>. The code may compile, but fail later when you try to use one of the elements as a <code class="language-plaintext highlighter-rouge">User</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="n">users</span> <span class="o">=</span> <span class="n">read</span><span class="o">(</span><span class="n">json</span><span class="o">,</span> <span class="nc">List</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> 

<span class="nc">User</span> <span class="n">user</span> <span class="o">=</span> <span class="n">users</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="c1">// may fail at runtime</span>
</code></pre></div></div>

<p>The problem is not that the list may be empty. That would be a normal collection issue. The type erasure problem is that even if the list contains elements, those elements may not be of the type the source code appears to promise.</p>

<p>To solve this, libraries invented alternate ways to carry generic type information into runtime APIs.</p>

<p>Jackson uses <code class="language-plaintext highlighter-rouge">TypeReference&lt;T&gt;</code>:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="n">users</span> <span class="o">=</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">readValue</span><span class="o">(</span><span class="n">json</span><span class="o">,</span> <span class="k">new</span> <span class="nc">TypeReference</span><span class="o">&lt;</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;&gt;()</span> <span class="o">{});</span>
</code></pre></div></div>

<p>Gson uses <code class="language-plaintext highlighter-rouge">TypeToken&lt;T&gt;</code>:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="n">users</span> <span class="o">=</span>
    <span class="n">gson</span><span class="o">.</span><span class="na">fromJson</span><span class="o">(</span><span class="n">json</span><span class="o">,</span> <span class="k">new</span> <span class="nc">TypeToken</span><span class="o">&lt;</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;&gt;()</span> <span class="o">{}.</span><span class="na">getType</span><span class="o">());</span>
</code></pre></div></div>

<p>Spring uses <code class="language-plaintext highlighter-rouge">ParameterizedTypeReference&lt;T&gt;</code>:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;&gt;</span> <span class="n">response</span> <span class="o">=</span>
    <span class="n">restTemplate</span><span class="o">.</span><span class="na">exchange</span><span class="o">(</span>
        <span class="n">url</span><span class="o">,</span>
        <span class="nc">HttpMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">,</span>
        <span class="kc">null</span><span class="o">,</span>
        <span class="k">new</span> <span class="nc">ParameterizedTypeReference</span><span class="o">&lt;</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;&gt;()</span> <span class="o">{}</span>
    <span class="o">);</span>
</code></pre></div></div>

<p>These APIs look awkward because they are working around the same missing runtime concept: a class-like object that represents a fully parameterized generic type. If Java supported class literals for parameterized types such as <code class="language-plaintext highlighter-rouge">List&lt;User&gt;.class</code>, many of these APIs could have been simpler.</p>

<h2 id="2-instanceof-liststring-does-not-work">2. <code class="language-plaintext highlighter-rouge">instanceof List&lt;String&gt;</code> does not work</h2>

<p>Another place where type erasure shows up is runtime type checking. In Java, we commonly use <code class="language-plaintext highlighter-rouge">instanceof</code> to check the type of an object.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">String</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>

<p>But Java won’t let you write</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;)</span> <span class="o">{</span>
 <span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The reason is that <code class="language-plaintext highlighter-rouge">List&lt;String&gt;</code> is not fully available at runtime. After type erasure, the runtime can check whether the <code class="language-plaintext highlighter-rouge">obj</code> is a <code class="language-plaintext highlighter-rouge">List</code>, but it cannot directly check whether it is specifically a <code class="language-plaintext highlighter-rouge">List&lt;String&gt;</code>.</p>

<p>So, Java only allows this:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">List</span><span class="o">&lt;?&gt;)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>This tells us that <code class="language-plaintext highlighter-rouge">obj</code> is some kind of <code class="language-plaintext highlighter-rouge">List</code>. It does not tell us what kind of elements the list contains and we must inspect each element individually, if we really care:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">List</span><span class="o">&lt;?&gt;</span> <span class="n">list</span> <span class="o">&amp;&amp;</span>
    <span class="n">list</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">allMatch</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">class</span><span class="o">::</span><span class="n">isInstance</span><span class="o">))</span> <span class="o">{</span> <span class="c1">//this check returns true for an empty list as well</span>
     <span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>This works, but it is much more verbose than a normal runtime type check. It also changes the nature of the check. We are no longer asking the JVM, “Is this a <code class="language-plaintext highlighter-rouge">List&lt;String&gt;</code>?” We are asking, “Is this a <code class="language-plaintext highlighter-rouge">List</code>, and do all of its current elements happen to be strings?”</p>

<h2 id="3-method-overloads-can-clash-after-erasure">3. Method overloads can clash after erasure</h2>

<p>Java lets you overload methods when their parameters are different. For example, this is fine:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{...}</span>

<span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{...}</span>
</code></pre></div></div>
<p>At runtime these are still different. However, with generics, method overloading gets a little tricky.</p>

<p>You may think that this will work:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">names</span><span class="o">)</span> <span class="o">{...}</span>

<span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">ids</span><span class="o">)</span> <span class="o">{...}</span>
</code></pre></div></div>

<p>But Java rejects this because after type erasure both effectively are the same method:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>void process(List names) {...}
void process(List ids) {...}
</code></pre></div></div>

<p>Both have the same erased signature <code class="language-plaintext highlighter-rouge">void process(List)</code>. The compiler sees the method collision and throws this error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>name clash: process(List&lt;Integer&gt;) and process(List&lt;String&gt;) have the same erasure
</code></pre></div></div>

<p>The workaround usually is to give methods different names <code class="language-plaintext highlighter-rouge">processNames(List&lt;String&gt; names)</code> and <code class="language-plaintext highlighter-rouge">processIds(List&lt;Integer&gt; ids)</code>.</p>

<p>Another option is to introduce wrapper types:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">record</span> <span class="nf">Names</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">names</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">record</span> <span class="nf">Ids</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">ids</span><span class="o">)</span> <span class="o">{}</span>

<span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="nc">Names</span> <span class="n">names</span><span class="o">)</span> <span class="o">{...}</span>

<span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="nc">Ids</span> <span class="n">ids</span><span class="o">)</span> <span class="o">{...}</span>
</code></pre></div></div>
<p>This works because <code class="language-plaintext highlighter-rouge">Names</code> and <code class="language-plaintext highlighter-rouge">Ids</code> are real runtime types. They survive erasure, unlike <code class="language-plaintext highlighter-rouge">List&lt;String&gt;</code> and <code class="language-plaintext highlighter-rouge">List&lt;Integer&gt;</code>.</p>

<h2 id="4-generic-arrays-are-painful">4. Generic arrays are painful</h2>

<p>Type erasure also makes arrays and generics uncomfortable together.</p>

<p>In Java, arrays know their component type at runtime. For example, a <code class="language-plaintext highlighter-rouge">String[]</code> knows that it is an array of <code class="language-plaintext highlighter-rouge">String</code>. If you try to put an <code class="language-plaintext highlighter-rouge">Integer</code> into it, the JVM can detect the problem and throw an <code class="language-plaintext highlighter-rouge">ArrayStoreException</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span><span class="o">[]</span> <span class="n">names</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[</span><span class="mi">10</span><span class="o">];</span>
<span class="nc">Object</span><span class="o">[]</span> <span class="n">values</span> <span class="o">=</span> <span class="n">names</span><span class="o">;</span>

<span class="n">values</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="mi">42</span><span class="o">;</span> <span class="c1">// ArrayStoreException at runtime</span>
</code></pre></div></div>

<p>Generics work differently. A <code class="language-plaintext highlighter-rouge">List&lt;String&gt;</code> does not carry <code class="language-plaintext highlighter-rouge">String</code> as a full runtime type in the same way. After erasure, it is mostly just a <code class="language-plaintext highlighter-rouge">List</code>.</p>

<p>That mismatch is why Java does not allow this:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">T</span><span class="o">[]</span> <span class="n">values</span> <span class="o">=</span> <span class="k">new</span> <span class="no">T</span><span class="o">[</span><span class="mi">10</span><span class="o">];</span> <span class="c1">// illegal</span>
</code></pre></div></div>

<p>The runtime does not know what <code class="language-plaintext highlighter-rouge">T</code> really is, so it cannot create an array with the correct component type.</p>

<p>The workaround is to pass the component type explicitly:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">static</span> <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="no">T</span><span class="o">[]</span> <span class="nf">createArray</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">type</span><span class="o">,</span> <span class="kt">int</span> <span class="n">size</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="o">(</span><span class="no">T</span><span class="o">[])</span> <span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">reflect</span><span class="o">.</span><span class="na">Array</span><span class="o">.</span><span class="na">newInstance</span><span class="o">(</span><span class="n">type</span><span class="o">,</span> <span class="n">size</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Usage:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span><span class="o">[]</span> <span class="n">names</span> <span class="o">=</span> <span class="n">createArray</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="mi">10</span><span class="o">);</span>
<span class="nc">Integer</span><span class="o">[]</span> <span class="n">ids</span> <span class="o">=</span> <span class="n">createArray</span><span class="o">(</span><span class="nc">Integer</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="mi">10</span><span class="o">);</span>
</code></pre></div></div>

<p>The generic type <code class="language-plaintext highlighter-rouge">T</code> is known to the compiler, but not enough is available at runtime. So the API asks the caller to pass a <code class="language-plaintext highlighter-rouge">Class&lt;T&gt;</code> token manually.</p>

<p>This is another cost of type erasure. The type appears obvious in the source code, but runtime code still needs an explicit type descriptor to do the right thing.</p>

<h2 id="why-these-workarounds-work">Why these workarounds work</h2>

<p>Type erasure is not total. Java still records generic type information in the class file, in a metadata section called the <code class="language-plaintext highlighter-rouge">Signature</code> attribute. This is kept for declarations: fields, method parameters and return types, and a class’s generic superclass and interfaces. What gets erased is the type of a value at runtime. A <code class="language-plaintext highlighter-rouge">List&lt;String&gt;</code> object and a <code class="language-plaintext highlighter-rouge">List&lt;Integer&gt;</code> object share the same <code class="language-plaintext highlighter-rouge">List.class</code>, so an object cannot tell you its element type. But a declaration can.</p>

<p>That retained information is readable through reflection. <code class="language-plaintext highlighter-rouge">Field.getGenericType()</code>, <code class="language-plaintext highlighter-rouge">Method.getGenericReturnType()</code>, and <code class="language-plaintext highlighter-rouge">Class.getGenericSuperclass()</code> return a <code class="language-plaintext highlighter-rouge">java.lang.reflect.Type</code>, which can be a <code class="language-plaintext highlighter-rouge">ParameterizedType</code> such as <code class="language-plaintext highlighter-rouge">List&lt;User&gt;</code> rather than a plain <code class="language-plaintext highlighter-rouge">List</code>.</p>

<p>This is the trick behind <code class="language-plaintext highlighter-rouge">TypeReference</code>, <code class="language-plaintext highlighter-rouge">TypeToken</code>, and <code class="language-plaintext highlighter-rouge">ParameterizedTypeReference</code>. Writing <code class="language-plaintext highlighter-rouge">new TypeReference&lt;List&lt;User&gt;&gt;() {}</code> creates an anonymous subclass, and its generic superclass <code class="language-plaintext highlighter-rouge">TypeReference&lt;List&lt;User&gt;&gt;</code> is a declaration. So <code class="language-plaintext highlighter-rouge">List&lt;User&gt;</code> is preserved in that subclass’s metadata, and the library recovers it with <code class="language-plaintext highlighter-rouge">getClass().getGenericSuperclass()</code>:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Type</span> <span class="n">type</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TypeReference</span><span class="o">&lt;</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;&gt;()</span> <span class="o">{}</span>
    <span class="o">.</span><span class="na">getClass</span><span class="o">()</span>
    <span class="o">.</span><span class="na">getGenericSuperclass</span><span class="o">();</span> <span class="c1">// ParameterizedType: List&lt;User&gt;</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>Once your code crosses a runtime boundary, assume the generic type will not be there. Design the API to carry the type explicitly, through a <code class="language-plaintext highlighter-rouge">Class&lt;T&gt;</code> token, a <code class="language-plaintext highlighter-rouge">TypeReference&lt;T&gt;</code>, a wrapper type, or a type tag you store in the data yourself, rather than trusting that the compiler’s view of the type survives into runtime.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Computer Science&quot;]" /><category term="computer-science" /><category term="java" /><category term="type-erasure" /><category term="programming-language" /><summary type="html"><![CDATA[Generic types in Java are excellent for compile-time safety, but weak as runtime type descriptors. Whenever a framework crosses a runtime boundary such as JSON, reflection, dependency injection, messaging, persistence, or RPC, it often needs an explicit replacement for the erased generic type information.]]></summary></entry><entry><title type="html">Renaming a Linux User Without Losing Your Setup</title><link href="https://systemhalted.in/2026/05/30/renaming-a-linux-user-without-losing-your-setup/" rel="alternate" type="text/html" title="Renaming a Linux User Without Losing Your Setup" /><published>2026-05-30T11:00:00+00:00</published><updated>2026-05-30T11:00:00+00:00</updated><id>https://systemhalted.in/2026/05/30/renaming-a-linux-user-without-losing-your-setup</id><content type="html" xml:base="https://systemhalted.in/2026/05/30/renaming-a-linux-user-without-losing-your-setup/"><![CDATA[<p>When I installed Ubuntu on a spare laptop, my intent was to make it usable for my son. 
So, I named the account <code class="language-plaintext highlighter-rouge">old-user</code> (obfuscated for obvious reasons). However, my son
moved on and took my old MacBook Pro, leaving this laptop for me. The first thing I wanted to 
do was personalize it by renaming the account to <code class="language-plaintext highlighter-rouge">systemhalted</code> everywhere: login name, home 
directory, primary group, and the name shown on the GNOME login screen.</p>

<p>I wanted to do this without losing my existing shell configuration, installed tools, IDE state, 
and Claude Code session history.</p>

<p>It turns out a complete rename is very doable. The data-loss fear is mostly misplaced. The real
risk is not deleted files, but stale hard-coded paths. In this post, I cover exactly how I did it.</p>

<h2 id="a-little-gotcha-before-we-begin">A Little Gotcha Before We Begin</h2>

<p><code class="language-plaintext highlighter-rouge">usermod</code> refuses to touch an account that is logged in or has running processes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>usermod: user old-user is currently used by process NNNN
</code></pre></div></div>

<p>Since this was the <strong>only</strong> admin account on the machine, I couldn’t rename it while
logged into it. The fix is a throwaway second admin account that does the surgery while
the real account is fully logged out.</p>

<p>With that, here are the steps to change the identity of an existing user.</p>

<h2 id="step-1--create-a-temporary-admin">Step 1 — Create a temporary admin</h2>

<p>While still logged in as the old user:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>adduser tempadmin
<span class="nb">sudo </span>usermod <span class="nt">-aG</span> <span class="nb">sudo </span>tempadmin
</code></pre></div></div>

<h2 id="step-2--log-out-completely-and-switch">Step 2 — Log out completely and switch</h2>

<p>This part matters: <strong>Log Out</strong> of GNOME — not lock, not “Switch User”. The old session
must actually end. Then log in as <code class="language-plaintext highlighter-rouge">tempadmin</code> at the greeter and open a terminal.</p>

<h2 id="step-3--confirm-the-old-account-is-truly-idle-if-needed-terminate-the-sessions">Step 3 — Confirm the old account is truly idle; if needed terminate the sessions</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">who</span>                                   <span class="c"># old user should not appear</span>
pgrep <span class="nt">-u</span> old-user                     <span class="c"># should print nothing</span>
<span class="nb">sudo </span>loginctl terminate-user old-user 2&gt;/dev/null
pgrep <span class="nt">-u</span> old-user <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"clear - safe to proceed"</span>
</code></pre></div></div>
<p>Proceed to Step 4 if the message says <code class="language-plaintext highlighter-rouge">clear - safe to proceed</code>.</p>

<p>Before renaming, record the account’s current numbers so you can confirm nothing shifted
afterward — and so you’re not assuming mine:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">id </span>old-user        <span class="c"># note the uid=, gid=, and the full groups= list</span>
</code></pre></div></div>

<p>On my machine both the UID and GID were <strong>1000</strong> (the first account the Ubuntu installer
creates). <strong>Yours may differ</strong> — on a multi-user box, a migrated system, or one with service
accounts, the first human user isn’t always 1000. Whatever your values are, they should be
<em>identical</em> before and after; keeping them unchanged is exactly why we never pass <code class="language-plaintext highlighter-rouge">-u</code>.</p>

<h2 id="step-4--the-actual-rename">Step 4 — The actual rename</h2>

<p>Three commands do the core work:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Rename the login AND move/rename the home dir (last arg is the CURRENT name):</span>
<span class="nb">sudo </span>usermod <span class="nt">-l</span> systemhalted <span class="nt">-d</span> /home/systemhalted <span class="nt">-m</span> old-user

<span class="c"># Rename the matching private group; the GID stays unchanged:</span>
<span class="nb">sudo </span>groupmod <span class="nt">-n</span> systemhalted old-user

<span class="c"># Update the display/full name shown at the login screen:</span>
<span class="nb">sudo </span>usermod <span class="nt">-c</span> <span class="s2">"systemhalted"</span> systemhalted
</code></pre></div></div>

<p>Why this is safe: the <strong>UID stays unchanged, 1000 on my machine</strong> — <code class="language-plaintext highlighter-rouge">usermod -l</code> changes the
login name and <code class="language-plaintext highlighter-rouge">-d -m</code> moves the home directory’s contents, but the UID stays put because
I’m not passing <code class="language-plaintext highlighter-rouge">-u</code>. With the numeric owner unchanged, <code class="language-plaintext highlighter-rouge">usermod -m</code> <em>moves</em> the home
directory and tries to adapt ownership, permissions, ACLs, and extended attributes under the
new path — though the manpage notes some cases may still need manual fixing. In the normal
same-UID case, the intent is relocation rather than deletion, but I still treated backup as 
mandatory. The account’s authentication entry is preserved through the rename, so the same 
password keeps working.</p>

<h2 id="step-5--fix-what-usermod-doesnt-touch">Step 5 — Fix what usermod doesn’t touch</h2>

<p>This is where the real work hides. <code class="language-plaintext highlighter-rouge">usermod</code> renames the account, but several things keep
pointing at the old path or the old name.</p>

<p><strong>(a) Hard-coded paths.</strong> A scan of my home turned up ~196 files under <code class="language-plaintext highlighter-rouge">.config</code>,
<code class="language-plaintext highlighter-rouge">.local</code>, and <code class="language-plaintext highlighter-rouge">.claude</code> containing the literal string <code class="language-plaintext highlighter-rouge">/home/old-user</code> — almost all
of it, in my case, JetBrains and VS Code state. Editing them in bulk is a trap:
many are binary or fragile JSON, and a stray <code class="language-plaintext highlighter-rouge">sed</code> can corrupt them. The clean, reversible 
fix is a single compatibility symlink:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo ln</span> <span class="nt">-s</span> /home/systemhalted /home/old-user
</code></pre></div></div>

<p>Now every stale <code class="language-plaintext highlighter-rouge">/home/old-user/...</code> reference resolves transparently. No file edits,
no corruption risk. It’s not entirely free, though — a lingering symlink can confuse
backups, scripts, future users, or security scans — so keep it (documented) until you’re
confident nothing references the old path, then remove it.</p>

<p><strong>(b) The user crontab spool</strong> isn’t auto-renamed (the same goes for <code class="language-plaintext highlighter-rouge">at</code> jobs, if you
use them):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">f</span><span class="o">=</span>/var/spool/cron/crontabs/old-user
<span class="nb">sudo test</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$f</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="nb">sudo mv</span> <span class="s2">"</span><span class="nv">$f</span><span class="s2">"</span> /var/spool/cron/crontabs/systemhalted <span class="se">\</span>
  <span class="o">&amp;&amp;</span> <span class="nb">sudo chown </span>systemhalted:crontab /var/spool/cron/crontabs/systemhalted
</code></pre></div></div>

<p><strong>(c) GNOME’s AccountsService</strong> keeps per-user login-screen prefs keyed by name:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">f</span><span class="o">=</span>/var/lib/AccountsService/users/old-user
<span class="nb">sudo test</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$f</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="nb">sudo mv</span> <span class="s2">"</span><span class="nv">$f</span><span class="s2">"</span> /var/lib/AccountsService/users/systemhalted
</code></pre></div></div>

<p><strong>(d) Claude Code session history.</strong> Claude Code names its project folders after the
working directory. Mine were all <code class="language-plaintext highlighter-rouge">-home-old-user-*</code>, so prior session history was
keyed to the old path:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /home/systemhalted/.claude/projects

<span class="k">for </span>d <span class="k">in</span> <span class="nt">-home-old-user</span><span class="k">*</span><span class="p">;</span> <span class="k">do
  </span><span class="nb">mv</span> <span class="s2">"./</span><span class="nv">$d</span><span class="s2">"</span> <span class="s2">"./</span><span class="k">${</span><span class="nv">d</span><span class="p">/-home-old-user/-home-systemhalted</span><span class="k">}</span><span class="s2">"</span>
<span class="k">done</span>
</code></pre></div></div>

<p>Two gotchas bit me here. First, the directory names start with <code class="language-plaintext highlighter-rouge">-</code>, so every tool treats
them as options — prefix paths with <code class="language-plaintext highlighter-rouge">./</code> (or use <code class="language-plaintext highlighter-rouge">--</code>) or you’ll get a wall of
<code class="language-plaintext highlighter-rouge">invalid option -- 'h'</code>. Second, a <code class="language-plaintext highlighter-rouge">-home-systemhalted</code> folder already existed from a
session run <em>after</em> the home move, so the bare rename would collide. I merged that one
instead of moving it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> <span class="nt">-a</span> <span class="nt">--update</span><span class="o">=</span>none <span class="s2">"./-home-old-user/."</span> <span class="s2">"./-home-systemhalted/"</span> <span class="se">\</span>
  <span class="o">&amp;&amp;</span> <span class="nb">rm</span> <span class="nt">-rf</span> <span class="s2">"./-home-old-user"</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">--update=none</code> means any file that already exists at the destination is left untouched —
it’s never overwritten, regardless of timestamps.</p>

<h2 id="step-6--verify-before-trusting-it">Step 6 — Verify before trusting it</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>getent passwd systemhalted    <span class="c"># home, shell, GECOS all correct</span>
getent group  systemhalted    <span class="c"># same group, GID unchanged (1000 on mine)</span>
<span class="nb">id </span>systemhalted               <span class="c"># the important one</span>
<span class="nb">ls</span> <span class="nt">-ld</span> /home/systemhalted     <span class="c"># owned systemhalted:systemhalted</span>
<span class="nb">ls</span> <span class="nt">-ld</span> /home/old-user         <span class="c"># symlink -&gt; /home/systemhalted</span>
</code></pre></div></div>

<p>The line I cared most about was <code class="language-plaintext highlighter-rouge">id</code>, confirming the renamed account was still in <strong>both</strong>
<code class="language-plaintext highlighter-rouge">sudo</code> and <code class="language-plaintext highlighter-rouge">docker</code> — i.e. admin and container access carried over intact.</p>

<h2 id="step-7--reboot-and-live-in-it">Step 7 — Reboot and live in it</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>reboot
</code></pre></div></div>

<p>Log in as <code class="language-plaintext highlighter-rouge">systemhalted</code> with the old password. Terminal opens in <code class="language-plaintext highlighter-rouge">/home/systemhalted</code>,
the IDEs launch with their state, <code class="language-plaintext highlighter-rouge">docker ps</code> works, my shell tooling works, and Claude
Code shows all the prior history.</p>

<h2 id="step-8--remove-the-temporary-admin">Step 8 — Remove the temporary admin</h2>

<p>Only after the renamed account is confirmed working:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>deluser <span class="nt">--remove-home</span> tempadmin
</code></pre></div></div>

<p>Worth knowing: if <code class="language-plaintext highlighter-rouge">deluser</code> ever runs <em>without</em> <code class="language-plaintext highlighter-rouge">--remove-home</code> (or partially), it leaves
two orphans behind — <code class="language-plaintext highlighter-rouge">/home/tempadmin</code> and <code class="language-plaintext highlighter-rouge">/var/lib/AccountsService/users/tempadmin</code> —
which you then clean up by hand:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo rm</span> <span class="nt">-rf</span> /home/tempadmin
<span class="nb">sudo rm</span> /var/lib/AccountsService/users/tempadmin
</code></pre></div></div>

<h2 id="rollback-just-in-case">Rollback, just in case</h2>

<p>Everything is reversible from the <code class="language-plaintext highlighter-rouge">tempadmin</code> session until you delete it. If anything
looked wrong, I had this ready:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo rm</span> /home/old-user
<span class="nb">sudo </span>usermod <span class="nt">-l</span> old-user <span class="nt">-d</span> /home/old-user <span class="nt">-m</span> systemhalted
<span class="nb">sudo </span>groupmod <span class="nt">-n</span> old-user systemhalted
<span class="nb">sudo </span>usermod <span class="nt">-c</span> <span class="s2">"Old User"</span> old-user
</code></pre></div></div>

<h2 id="important-notes-and-observations">Important Notes and Observations</h2>

<ul>
  <li><strong>No data was lost in my case</strong> — a same-UID rename <em>moves</em> files; it doesn’t delete them by design.</li>
  <li><strong>The danger is stale paths, not lost files</strong> — and one symlink neutralizes all of them.</li>
  <li><strong>Never bulk-<code class="language-plaintext highlighter-rouge">sed</code> IDE state</strong> — it’s binary/JSON and corrupts easily.</li>
  <li><strong>Secrets mostly survive, but check them.</strong> GNOME Keyring is unlocked by your login
password, which didn’t change, so keyring-backed secrets keep working (maybe a one-time
unlock prompt). Saved Wi-Fi depends on setup — NetworkManager profiles may be system-wide,
per-user, or keyring-backed — so verify rather than assume.</li>
  <li><strong>The compatibility symlink is practical, not free.</strong> It saves you from editing hundreds
of files, but document it and remove it once you’re sure nothing uses the old path.</li>
</ul>

<p>The whole thing took one reboot and zero reinstalls. The account that started life as
<code class="language-plaintext highlighter-rouge">old-user</code> is now <code class="language-plaintext highlighter-rouge">systemhalted</code> as far as normal desktop and shell tooling are concerned.
(The kernel itself never cared — to it, credentials are numeric UIDs and GIDs, not names;
the login name was only ever a label on top of UID 1000.)</p>

<hr />

<p><em>Disclaimer: This worked on my single-admin Ubuntu/GNOME machine, and I’m sharing it as a
record of what I did — not as a guaranteed recipe for yours. Renaming a user touches login,
ownership, and system state, so treat every command here as something to understand before
you run it, not to paste blindly. Setups differ (other desktops, network/LDAP accounts,
encrypted or NFS home directories, services running as the user), and any of those can change
the outcome. <strong>Take a backup first, keep the <code class="language-plaintext highlighter-rouge">tempadmin</code> escape hatch until you’ve confirmed
everything works, and proceed at your own risk.</strong> The username here (<code class="language-plaintext highlighter-rouge">old-user</code>) is a
placeholder; the real one has been obfuscated.</em></p>]]></content><author><name>Palak Mathur</name></author><category term="Technology" /><category term="Software Engineering" /><category term="linux" /><category term="ubuntu" /><category term="sysadmin" /><category term="usermod" /><category term="technology" /><category term="software" /><summary type="html"><![CDATA[When I installed Ubuntu on a spare laptop, my intent was to make it usable for my son. So, I named the account old-user (obfuscated for obvious reasons). However, my son moved on and took my old MacBook Pro, leaving this laptop for me. The first thing I wanted to do was personalize it by renaming the account to systemhalted everywhere: login name, home directory, primary group, and the name shown on the GNOME login screen.]]></summary></entry><entry><title type="html">Saving Private Notes</title><link href="https://systemhalted.in/2026/05/08/saving-private-notes/" rel="alternate" type="text/html" title="Saving Private Notes" /><published>2026-05-08T00:00:00+00:00</published><updated>2026-05-08T00:00:00+00:00</updated><id>https://systemhalted.in/2026/05/08/saving-private-notes</id><content type="html" xml:base="https://systemhalted.in/2026/05/08/saving-private-notes/"><![CDATA[<p>I had over 1000 notes in Apple Notes when I finally decided to clean them up. Most were in a folder called “Misc” or sitting loose in the root. Some were years old. A few I did not recognise as mine.</p>

<p>The structure, if you can call it that, was two systems running side by side and neither of them working. I had read about Zettelkasten somewhere, read a book about it, watched some videos, and set up the standard folders — Inbox, Input, Output, Zettel — without ever using them. Alongside that I had an older hierarchy of Work, Personal, and Writing, where I was actually saving things, more or less at random. Whichever folder I thought of first got the note. I had been telling myself this was fine because the notes were captured, and search would do the rest. Search did not do the rest. Most of the time I did not know what to search for.</p>

<h2 id="why-i-finally-bothered">Why I finally bothered</h2>

<p>I am an engineering manager. In a normal week I need notes from team 1:1s, architecture decisions, books I am reading, an AI/ML study plan I run at weekends, ideas for writing, astrophotography logs, and guitar stuff. None of that is unusual. What forced the cleanup was that several times in a couple of months, I needed a specific note before a meeting and could not find it in time.</p>

<p>The problem was not that I had too many notes. It was that I had never decided what the system was supposed to do.</p>

<h2 id="simplification">Simplification</h2>

<p>One Saturday I sat down and wrote out what I actually use notes for: work, personal life, things I am learning, writing I am working on, a daily journal, random ideas, and an archive for things I am done with. That came out as eight buckets, which I numbered so they would sort in the order I wanted:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00 Inbox
10 Work
20 Personal
30 Learning
40 Writing
50 Journal
60 Ideas
99 Archive
</code></pre></div></div>

<p>Each has a few subfolders. Work has Team Management, Meetings, Architecture, and Decisions. Learning has Books, Articles, Videos, Courses, and a Subjects folder where I keep my own synthesis on topics I am studying over time. Writing splits into Ideas, Drafts, Articles, Poetry, Stories, Books, and Published. The old Zettelkasten folders went into Archive. I was not using them, and pretending I would was part of why the system was a mess.</p>

<p>I learned later that what I had built was close to a combination of two methods I had not heard of when I started. PARA (Projects, Areas, Resources, Archives) organises by how actionable something is, not by topic. Johnny Decimal is a numbering scheme that gives every folder a permanent ID. People combine the two, and what I had was a rough version of that combination. It was nice to know but did not change anything. The structure worked because it matched how I think, not because it had a name.</p>

<h2 id="the-rule-that-fixed-the-overlap">The rule that fixed the overlap</h2>

<p>The other thing I had been doing wrong, which the cleanup made obvious, was mixing notes and tasks. Things that belonged in Reminders were living in Apple Notes, and the other way around, and I was sometimes updating both for the same thing without realising it.</p>

<p>The rule I went with is simple. If it has a “done” state, it goes in Reminders. If it does not, it goes in Notes. Tasks, follow-ups, and things to keep an eye on go to Reminders; knowledge, drafts, and references go to Notes. It sounds obvious written down, but I had not been doing it. Once I started, the overlap was gone, and so was most of the duplication that came with it.</p>

<h2 id="what-id-actually-tell-someone">What I’d actually tell someone</h2>

<p>The cleanup itself was not the hard part. The hard part was admitting that the Zettelkasten folders I had set up years earlier were aspirational, not active. I was impressed by how useful it was for Niklas Luhmann and thought it would be useful for me as well in the same way it was for him. However, these folders had been sitting there making the system look organised while making the actual mess worse.</p>

<p>Most of the work was being honest about what I really do with notes — capture things, look them up later, occasionally write something longer — and building the smallest structure that supported those three things. No folders that existed only because some method I had read about said they should.</p>

<p>If you are sitting on your own version of a thousand unfiled notes, do not go looking for a method to copy. Write down what you actually use notes for, build the folders that match, and leave room to fix it later. The other thing — and I am saying this because I ignored my own advice for years — is that half an hour once or twice a week spent clearing out the Inbox goes a long way. The cleanup itself is a one-time event. Keeping it clean is not.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Personal Essays" /><category term="Technology" /><category term="notes" /><category term="productivity" /><category term="apple-notes" /><category term="para" /><category term="johnny-decimal" /><category term="zettelkasten" /><summary type="html"><![CDATA[How I cleaned up a thousand Apple Notes by being honest about what I actually use notes for, building a small folder structure to match, and drawing a hard line between notes and tasks.]]></summary></entry><entry><title type="html">Insight Agents and the End of Dashboard-Driven Analytics</title><link href="https://systemhalted.in/2026/02/06/insight-agents-end-of-dashboards/" rel="alternate" type="text/html" title="Insight Agents and the End of Dashboard-Driven Analytics" /><published>2026-02-06T00:00:00+00:00</published><updated>2026-02-06T00:00:00+00:00</updated><id>https://systemhalted.in/2026/02/06/insight-agents-end-of-dashboards</id><content type="html" xml:base="https://systemhalted.in/2026/02/06/insight-agents-end-of-dashboards/"><![CDATA[<p>I have just finished reading the Amazon Research paper <em>“Insight Agents: An LLM-Based Multi-Agent System for Data Insights”</em><sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, and it is, I think, the first genuinely production-minded attempt I have seen to close the long-standing gap between dashboards that <em>show</em> data and systems that actually help people <em>decide</em> anything on the basis of it. The paper describes a hierarchical multi-agent system that, somewhat unusually, seems to actually understand the chain by which a business question becomes a data query and then becomes an insight, rather than treating that chain as a single LLM prompt to be optimised.</p>

<p>What stood out to me, reading through it, was less the model architecture than the engineering discipline behind the choice of where to use which kind of model. The authors did not, in fact, reach for an LLM as the answer to every problem, which is increasingly unusual in this space. Instead, they appear to have asked, at each stage of the pipeline, what the cheapest model that does the job correctly actually is, and to have used that. Two of the numbers in the paper are particularly striking:</p>

<ul>
  <li>
    <p>An auto-encoder handles intent detection in <strong>0.009 seconds</strong> instead of 1.6 seconds.</p>
  </li>
  <li>
    <p>A fine-tuned BERT model handles routing in <strong>0.3 seconds</strong> instead of ~2 seconds.</p>
  </li>
</ul>

<p>The LLM, in their architecture, only enters the picture once this fast triage stage has narrowed the problem space — at which point it is being used for what large language models are actually good at, which is reasoning over a focused problem and generating narrative explanations for a human reader.</p>

<p>They also, sensibly, avoid the naive Text-to-SQL approach that has become almost a cliché in this area. Instead, they describe an augmented querying flow that brings business context, internal APIs, and a plan-and-execute style of decomposition to bear on the question. The system breaks a query into a sequence of steps, fetches the right data at each step, and produces explanations that a human reader can actually act on, rather than a single SQL query that is correct in the abstract but unhelpful in context. In their reported evaluation, the system holds 90th-percentile latency under roughly 13 seconds, with about 89% relevance and correctness as judged by human raters — which, for an agentic pipeline of this kind, is a meaningfully better number than I would have expected.</p>

<p>The most important idea in the paper, however, is, I think, the one that follows from all of this rather than being stated outright. It is the implicit observation that, in the model described, the user is no longer expected to navigate a dashboard at all. They are expected, instead, to ask questions of a system that already understands the schema, the metrics, the seasonality of the business, and the internal vocabulary that the team uses to talk about what is happening — and to receive, in response, an explanation of what happened and, where the data supports it, why it happened.</p>

<p>This is not, to be clear, an argument for replacing BI teams or data engineers; on the contrary, it is an argument for amplifying them. What this kind of system promises, if it works at the scale claimed, is to turn the underlying data infrastructure into something a non-specialist part of the business can actually query in plain language, and to receive a structured explanation back rather than a chart that someone still has to interpret. The future of analytics, on this view, is less likely to be a continued proliferation of dashboards and more likely to be a steady shift toward conversational understanding layered on top of structured data — with dashboards retained, in the end, mostly for the cases in which a human still wants to look at the underlying time series themselves.</p>

<p>If you are working on analytics platforms, on GenAI agents, or on data products of any meaningful scale, I would encourage you to read the paper end to end. It is, by some distance, the most thoughtful treatment of where to put which kind of intelligence in an agentic data system that I have come across in the last year.</p>

<h2 id="references">References</h2>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Paper, https://arxiv.org/pdf/2601.20048 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Technology" /><category term="Software Engineering" /><category term="Computer Science" /><category term="Data" /><category term="AI" /><category term="analytics" /><category term="agentic-ai" /><category term="genai" /><category term="llm" /><category term="multi-agent-systems" /><category term="data-insights" /><category term="business-intelligence" /><category term="amazon-research" /><summary type="html"><![CDATA[Why Insight Agents signal a shift from dashboard-driven analytics to conversational, agentic systems that prioritize speed, context, and real business understanding.]]></summary></entry><entry><title type="html">Part 8: Big Decimal Rounding Modes - Why HALF_UP Isn’t Always the Answer</title><link href="https://systemhalted.in/2026/02/05/rounding-modes-why-half-up-isnt-always-the-answer/" rel="alternate" type="text/html" title="Part 8: Big Decimal Rounding Modes - Why HALF_UP Isn’t Always the Answer" /><published>2026-02-05T00:00:00+00:00</published><updated>2026-02-05T00:00:00+00:00</updated><id>https://systemhalted.in/2026/02/05/rounding-modes-why-half-up-isnt-always-the-answer</id><content type="html" xml:base="https://systemhalted.in/2026/02/05/rounding-modes-why-half-up-isnt-always-the-answer/"><![CDATA[<p><em>This post is part of my <a href="https://systemhalted.in/categories/#cat-series-4-floating-point-without-tears">Floating Point Without Tears</a> series on how Java numbers misbehave and how to live with them.</em></p>

<p>In <a href="https://systemhalted.in/2026/01/11/kahan-summation-java-streams/">Part 7</a> of this series, we looked at summation error and how Kahan compensation can keep a long reduction from quietly losing low-order bits. This post is about a different and, in some ways, more consequential precision trap: the moment at which a value is rounded down to fewer digits in order to be displayed, stored, or reported.</p>

<p>Rounding looks like a small detail in the code, and it almost never is one. It is the place where many otherwise-correct calculations end up disagreeing with audits, ledgers, and reconciliations, often in ways that are difficult to diagnose because the disagreement only appears at scale. Most teams I have seen pick a rounding mode in roughly the same way that most people pick a film on a streaming service — by going with whichever option happened to be already playing — and in Java, the option that happens to be already playing is very often <code class="language-plaintext highlighter-rouge">HALF_UP</code>, on the implicit assumption that this is “normal” rounding.</p>

<blockquote>
  <p>“We’ll use <code class="language-plaintext highlighter-rouge">HALF_UP</code>. That’s normal rounding. Done.”</p>
</blockquote>

<p>Then a pricing engine ships, or a statement generator, or a ledger, or a tax calculator, or an amortization schedule — and at some point afterwards it becomes apparent that <strong>rounding is policy</strong>, not arithmetic. Or, more precisely, it is arithmetic with consequences, and the choice of which rounding mode to apply turns out to be a product decision wearing mathematical clothing. This post is about how to make that choice deliberately, and about why <code class="language-plaintext highlighter-rouge">HALF_UP</code> is not the universal solvent it is so often assumed to be.</p>

<h2 id="cheat-sheet">Quick Reference: Rounding Mode Cheat Sheet</h2>

<table>
  <thead>
    <tr>
      <th>Mode</th>
      <th>Behavior</th>
      <th>When</th>
      <th>Jump</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">HALF_EVEN</code></td>
      <td>Ties -&gt; nearest even digit</td>
      <td>Aggregates, reducing bias</td>
      <td><a href="#use-half-even">↓</a></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">HALF_UP</code></td>
      <td>Ties -&gt; away from zero</td>
      <td>Schoolbook rounding, retail</td>
      <td><a href="#use-half-up">↓</a></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">HALF_DOWN</code></td>
      <td>Ties -&gt; toward zero</td>
      <td>Policy requires ties toward zero (rare; document it)</td>
      <td><a href="#toolbox">↓</a></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">UP</code></td>
      <td>Always away from zero</td>
      <td>Conservative bounds (never underestimate magnitude)</td>
      <td><a href="#toolbox">↓</a></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DOWN</code></td>
      <td>Always toward zero</td>
      <td>Fee caps, conservative limits</td>
      <td><a href="#use-down">↓</a></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">CEILING</code></td>
      <td>Toward +∞ (1.231 -&gt; 1.24, -1.231 -&gt; -1.23)</td>
      <td>“At least” constraints</td>
      <td><a href="#use-ceiling-floor">↓</a></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">FLOOR</code></td>
      <td>Toward -∞ (1.231 -&gt; 1.23, -1.231 -&gt; -1.24)</td>
      <td>“At most” constraints</td>
      <td><a href="#use-ceiling-floor">↓</a></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">UNNECESSARY</code></td>
      <td>Throw if rounding needed</td>
      <td>Validation, catching assumptions</td>
      <td><a href="#use-unnecessary">↓</a></td>
    </tr>
  </tbody>
</table>

<p><strong>Key rules:</strong></p>
<ul>
  <li>Avoid rounding double when policy matters: use BigDecimal from strings → <a href="#classic-trap">The Classic Trap</a></li>
  <li>Decide <em>when</em> to round, not just <em>how</em> → <a href="#rounding-timing">Rounding Timing</a></li>
  <li>For money, use scaled integers internally → <a href="#money-pattern">Money Pattern</a></li>
</ul>

<hr />

<h2 id="ties">The real problem: ties (the 5s)</h2>

<p>The drama in rounding, almost without exception, is not really about choosing between <code class="language-plaintext highlighter-rouge">2.341</code> and <code class="language-plaintext highlighter-rouge">2.34</code>. It is about <em>ties</em> — values that lie exactly halfway between two representable steps at the precision you are rounding to.</p>

<p>At two decimal places, the troublesome values are the ones that look like this:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">1.005</code></li>
  <li><code class="language-plaintext highlighter-rouge">2.675</code></li>
  <li><code class="language-plaintext highlighter-rouge">10.125</code></li>
</ul>

<p>It is worth being precise about what counts as a tie, because the term is sometimes applied loosely to values that are not, strictly speaking, halfway between anything. A value is a tie at a given scale only when the first discarded digit is exactly <code class="language-plaintext highlighter-rouge">5</code> and all subsequent discarded digits are exactly <code class="language-plaintext highlighter-rouge">0</code>. So <code class="language-plaintext highlighter-rouge">1.005</code> is a tie at two decimal places, and so is <code class="language-plaintext highlighter-rouge">1.00500…</code>, but <code class="language-plaintext highlighter-rouge">1.0051</code> is not a tie — it is, on inspection, closer to <code class="language-plaintext highlighter-rouge">1.01</code> than to <code class="language-plaintext highlighter-rouge">1.00</code>, and any reasonable rounding mode will produce <code class="language-plaintext highlighter-rouge">1.01</code> regardless of how it handles ties.</p>

<p>The reason ties matter so much in practice is that, depending on which way the rounding mode resolves them, the cumulative effect across many operations can be quite different. If you consistently push ties in one direction — say, always upward — you introduce a systematic bias into every aggregate that depends on those rounded values. Sometimes that bias is exactly what the domain wants (retail prices, for instance, often round in a particular direction by convention); often it is not, occasionally it is actually prohibited by regulation or contract, and even when it is none of those things, it is the kind of pattern that tends to make reconciliation teams unhappy when the totals begin to drift.</p>

<h2 id="toolbox">Java’s rounding toolbox</h2>

<p>Java’s standard library, in <code class="language-plaintext highlighter-rouge">java.math.RoundingMode</code>, exposes a fairly complete set of rounding policies. The interesting question, in the end, is rarely which of them is “available” — they all are — but rather which of them matches the contract the domain actually wants enforced. The available modes are:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">HALF_UP</code> - round to nearest; if exactly halfway, round away from zero (1.5 -&gt; 2, -1.5 -&gt; -2)</li>
  <li><code class="language-plaintext highlighter-rouge">HALF_DOWN</code> - round to nearest; if exactly halfway, round toward zero (1.5 -&gt; 1, -1.5 -&gt; -1)</li>
  <li><code class="language-plaintext highlighter-rouge">HALF_EVEN</code> - round to nearest; if exactly halfway, round to the result whose last kept digit is even (banker’s rounding, also called round-half-to-even)</li>
  <li><code class="language-plaintext highlighter-rouge">UP</code> - always away from zero</li>
  <li><code class="language-plaintext highlighter-rouge">DOWN</code> - always toward zero (truncate)</li>
  <li><code class="language-plaintext highlighter-rouge">CEILING</code> - toward positive infinity (-1.231 -&gt; -1.23, 1.231 -&gt; 1.24 at 2dp)</li>
  <li><code class="language-plaintext highlighter-rouge">FLOOR</code> - toward negative infinity (-1.231 -&gt; -1.24, 1.231 -&gt; 1.23 at 2dp)</li>
  <li><code class="language-plaintext highlighter-rouge">UNNECESSARY</code> - throw if rounding would be required (which I have come to think of as a “make bugs loud” mode)</li>
</ul>

<p>It is also worth recalling that calling <code class="language-plaintext highlighter-rouge">setScale(...)</code> without specifying a rounding mode will, if rounding turns out to be required, throw — which is a sensible default but is occasionally surprising:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"1.234"</span><span class="o">).</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span> <span class="c1">// throws ArithmeticException</span></code></pre></figure>

<p>And the most important practical rule, which I find myself repeating in code review more often than any other rule in this area:</p>

<p><strong>If you care about exact decimal policy, avoid rounding a <code class="language-plaintext highlighter-rouge">double</code>. Round a <code class="language-plaintext highlighter-rouge">BigDecimal</code> created from a string (or from an exact integer scale).</strong></p>

<h3 id="classic-trap">The classic trap: <code class="language-plaintext highlighter-rouge">new BigDecimal(double)</code></h3>

<p>This is the trap that catches almost everyone at least once, and it is worth showing in code rather than just describing in prose:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.math.BigDecimal</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.math.RoundingMode</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RoundingTrap</span> <span class="o">{</span>
  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">BigDecimal</span> <span class="n">a</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="mf">1.005</span><span class="o">);</span>               <span class="c1">// from double</span>
    <span class="nc">BigDecimal</span> <span class="n">b</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"1.005"</span><span class="o">);</span>             <span class="c1">// from string</span>

    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_UP</span><span class="o">));</span> <span class="c1">// 1.00</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">b</span><span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_UP</span><span class="o">));</span> <span class="c1">// 1.01</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>The expectation, of course, is that both values round to <code class="language-plaintext highlighter-rouge">1.01</code>. What actually happens is that the first one rounds to <code class="language-plaintext highlighter-rouge">1.00</code>, because <code class="language-plaintext highlighter-rouge">1.005</code> as a binary floating-point value is not, in fact, exactly <code class="language-plaintext highlighter-rouge">1.005</code> — it is a hair less than that, by exactly the small amount that binary floating point cannot quite represent the decimal fraction <code class="language-plaintext highlighter-rouge">0.005</code>. When you then ask <code class="language-plaintext highlighter-rouge">BigDecimal</code> to round that approximate value, it dutifully rounds the value it actually has, which falls just below the tie boundary.</p>

<p>If you find yourself, in a finance system, looking at a result in which <code class="language-plaintext highlighter-rouge">1.005</code> appears to round to <code class="language-plaintext highlighter-rouge">1.00</code> under <code class="language-plaintext highlighter-rouge">HALF_UP</code>, the right interpretation is not that Java is being eccentric. The right interpretation is that you have fed an approximate binary representation into an exact decimal rounding routine, and that the routine has, accurately and unhelpfully, given you the answer that corresponds to the value it was actually given.</p>

<h3 id="valueof">What about <code class="language-plaintext highlighter-rouge">BigDecimal.valueOf()</code>?</h3>

<p>There is a third constructor in this space which sits between the two previous options and which readers trip over often enough that it is worth treating explicitly: <code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(double)</code>.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">a</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="mf">1.005</span><span class="o">);</span>        <span class="c1">// Dangerous: uses exact binary representation</span>
<span class="nc">BigDecimal</span> <span class="n">b</span> <span class="o">=</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mf">1.005</span><span class="o">);</span>    <span class="c1">// Safer: uses Double.toString() internally</span>
<span class="nc">BigDecimal</span> <span class="n">c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"1.005"</span><span class="o">);</span>      <span class="c1">// Safest: exact decimal from string</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(double)</code> will, in most everyday cases, behave as if you had used the string constructor, because it routes the conversion through <code class="language-plaintext highlighter-rouge">Double.toString()</code> internally — and <code class="language-plaintext highlighter-rouge">Double.toString</code> is specified to return the shortest decimal representation that round-trips back to the same double. So when you start from a literal like <code class="language-plaintext highlighter-rouge">1.005</code>, <code class="language-plaintext highlighter-rouge">valueOf</code> gives you the decimal <code class="language-plaintext highlighter-rouge">1.005</code> rather than the binary-exact value, which is what most callers actually wanted.</p>

<p>A reasonable rule of thumb, based on which entry point one is using:</p>

<ul>
  <li>Use <code class="language-plaintext highlighter-rouge">new BigDecimal("...")</code> for literals and external decimal inputs that arrive as strings</li>
  <li>Use <code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(double)</code> only when you already have a double in hand and need the best possible decimal view of it (still risky after computations, but better than <code class="language-plaintext highlighter-rouge">new BigDecimal(double)</code>)</li>
  <li>Never use <code class="language-plaintext highlighter-rouge">new BigDecimal(double)</code> unless you explicitly want the exact binary-to-decimal conversion, which is rarely what callers expect</li>
</ul>

<p>It is worth one further caution. If the double you are wrapping has come out of arithmetic — rather than out of a literal — then the value you are now “viewing” through <code class="language-plaintext highlighter-rouge">valueOf</code> may already be quite far from the decimal you think you started with. <code class="language-plaintext highlighter-rouge">valueOf</code> is an honest decimal view of whatever double it receives; it is not a cleansing ritual that retroactively repairs precision lost earlier in the calculation.</p>

<h2 id="half-up-bias">Why HALF_UP can be the wrong default</h2>

<p><code class="language-plaintext highlighter-rouge">HALF_UP</code> is, in fairness, intuitive. It matches what most of us were taught with a pencil and paper in school, and it has the agreeable property of making the answer easy to predict in any individual case. The trouble is that, when applied repeatedly to many values, it can introduce a systematic <strong>drift</strong>, because every tie is resolved in the same direction.</p>

<p>It is worth pausing for a moment on a small irony of the IEEE 754 standard here. IEEE 754’s default rounding mode for binary floating-point arithmetic is <em>round to nearest, ties to even</em> — which is, in effect, <code class="language-plaintext highlighter-rouge">HALF_EVEN</code>. Each individual <code class="language-plaintext highlighter-rouge">double</code> operation in Java is specified to behave as if its result had been rounded that way. The mild irony, then, is that when developers reach for <code class="language-plaintext highlighter-rouge">BigDecimal</code> precisely because they are now worried about decimal correctness, many of them then choose <code class="language-plaintext highlighter-rouge">HALF_UP</code>, thereby reintroducing the very bias that IEEE 754 had originally been designed to avoid.</p>

<h3 id="bias-demo">Bias demo: HALF_UP vs HALF_EVEN</h3>

<p>Suppose, in order to make this concrete, that you have a population of values which all happen to land exactly on ties at two decimal places. (This kind of thing happens more often than people tend to expect, particularly after divisions and intermediate scaling steps.) The behaviour of the two main candidate rounding modes, side by side, looks like this:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">HALF_UP</code>: <code class="language-plaintext highlighter-rouge">1.005 -&gt; 1.01</code>, <code class="language-plaintext highlighter-rouge">1.015 -&gt; 1.02</code>, <code class="language-plaintext highlighter-rouge">1.025 -&gt; 1.03</code> … always nudging up</li>
  <li><code class="language-plaintext highlighter-rouge">HALF_EVEN</code>: <code class="language-plaintext highlighter-rouge">1.005 -&gt; 1.00</code>, <code class="language-plaintext highlighter-rouge">1.015 -&gt; 1.02</code>, <code class="language-plaintext highlighter-rouge">1.025 -&gt; 1.02</code> … nudging toward whichever side has an even last digit, and so alternating which side “wins” on ties</li>
</ul>

<p>The structural difference is that <code class="language-plaintext highlighter-rouge">HALF_EVEN</code> alternates which direction it pushes ties, while <code class="language-plaintext highlighter-rouge">HALF_UP</code> always pushes them the same way. This is the underlying reason why <strong>banking and accounting systems often prefer <code class="language-plaintext highlighter-rouge">HALF_EVEN</code></strong>: across a large enough population of values, the alternation cancels out into something close to zero net bias, while <code class="language-plaintext highlighter-rouge">HALF_UP</code> accumulates a small but consistent upward drift that becomes visible at scale.</p>

<h3 id="bias-action">The bias in action</h3>

<p>A short program makes the difference clearer than any description does:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.math.BigDecimal</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.math.RoundingMode</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BiasDemonstration</span> <span class="o">{</span>
  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">String</span><span class="o">[]</span> <span class="n">ties</span> <span class="o">=</span> <span class="o">{</span><span class="s">"1.005"</span><span class="o">,</span> <span class="s">"1.015"</span><span class="o">,</span> <span class="s">"1.025"</span><span class="o">,</span> <span class="s">"1.035"</span><span class="o">,</span> <span class="s">"1.045"</span><span class="o">,</span>
                     <span class="s">"1.055"</span><span class="o">,</span> <span class="s">"1.065"</span><span class="o">,</span> <span class="s">"1.075"</span><span class="o">,</span> <span class="s">"1.085"</span><span class="o">,</span> <span class="s">"1.095"</span><span class="o">};</span>
    
    <span class="nc">BigDecimal</span> <span class="n">sumHalfUp</span> <span class="o">=</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">ZERO</span><span class="o">;</span>
    <span class="nc">BigDecimal</span> <span class="n">sumHalfEven</span> <span class="o">=</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">ZERO</span><span class="o">;</span>
    <span class="nc">BigDecimal</span> <span class="n">trueSum</span> <span class="o">=</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">ZERO</span><span class="o">;</span>
    
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Value   -&gt; HALF_UP / HALF_EVEN"</span><span class="o">);</span>
    <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">tie</span> <span class="o">:</span> <span class="n">ties</span><span class="o">)</span> <span class="o">{</span>
      <span class="nc">BigDecimal</span> <span class="n">bd</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="n">tie</span><span class="o">);</span>
      <span class="nc">BigDecimal</span> <span class="n">roundedUp</span> <span class="o">=</span> <span class="n">bd</span><span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_UP</span><span class="o">);</span>
      <span class="nc">BigDecimal</span> <span class="n">roundedEven</span> <span class="o">=</span> <span class="n">bd</span><span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_EVEN</span><span class="o">);</span>
      
      <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">tie</span> <span class="o">+</span> <span class="s">" -&gt; "</span> <span class="o">+</span> <span class="n">roundedUp</span> <span class="o">+</span> <span class="s">" / "</span> <span class="o">+</span> <span class="n">roundedEven</span><span class="o">);</span>
      
      <span class="n">sumHalfUp</span> <span class="o">=</span> <span class="n">sumHalfUp</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">roundedUp</span><span class="o">);</span>
      <span class="n">sumHalfEven</span> <span class="o">=</span> <span class="n">sumHalfEven</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">roundedEven</span><span class="o">);</span>
      <span class="n">trueSum</span> <span class="o">=</span> <span class="n">trueSum</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">bd</span><span class="o">);</span>
    <span class="o">}</span>
    
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">();</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"HALF_UP sum:   "</span> <span class="o">+</span> <span class="n">sumHalfUp</span><span class="o">);</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"HALF_EVEN sum: "</span> <span class="o">+</span> <span class="n">sumHalfEven</span><span class="o">);</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"True sum:      "</span> <span class="o">+</span> <span class="n">trueSum</span><span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">));</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>And the resulting output:</p>

<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">Value   -&gt; HALF_UP / HALF_EVEN
1.005 -&gt; 1.01 / 1.00
1.015 -&gt; 1.02 / 1.02
1.025 -&gt; 1.03 / 1.02
1.035 -&gt; 1.04 / 1.04
1.045 -&gt; 1.05 / 1.04
1.055 -&gt; 1.06 / 1.06
1.065 -&gt; 1.07 / 1.06
1.075 -&gt; 1.08 / 1.08
1.085 -&gt; 1.09 / 1.08
1.095 -&gt; 1.10 / 1.10

HALF_UP sum:   10.55
HALF_EVEN sum: 10.50
True sum:      10.50</code></pre></figure>

<p>Across these ten values, the difference between <code class="language-plaintext highlighter-rouge">HALF_UP</code> and the true sum is five cents. That is, on its own, an unimpressive amount of money. The point of the demonstration, however, is that the same bias scales linearly: across millions of transactions of this kind, the same five-cents-per-ten pattern can become a meaningful, persistent, and entirely systematic drift in favour of one party.</p>

<h3 id="pennies-from-heaven">How HALF_UP can favor one party at scale</h3>

<p>When this kind of pattern shows up in a system that processes millions of transactions in which ties are common, what <code class="language-plaintext highlighter-rouge">HALF_UP</code> does, in effect, is to consistently favour whichever side of the transaction benefits from rounding upward. If you happen to be that party, you may, over time, find that your books “win” fractions of a cent more often than they “lose”; and that is, generally, fine — until regulators, auditors, or customers do their own arithmetic and notice the same pattern from the other side.</p>

<p>Which is to say, the question worth asking is not really “which rounding mode is mathematically correct?”, because none of them is uniquely correct in any abstract sense. The right question, as it almost always is in this kind of design choice, is:</p>

<p><strong>Which rounding policy matches the domain contract?</strong></p>

<h2 id="rounding-timing">Rounding is not only <em>mode</em>. It’s also <em>when</em>.</h2>

<p>There is a second axis to this kind of decision, which gets less attention than it deserves but which often matters at least as much as the choice of mode itself. The axis is <em>when</em> in a calculation rounding actually happens. Two broad designs are common in production code:</p>

<ol>
  <li>Round at every step (easy, often wrong)</li>
  <li>Keep high precision internally, round only at boundaries (harder, usually right)</li>
</ol>

<h3 id="invoices">Example: line items and invoices</h3>

<p>To make the trade-off concrete, consider a fairly standard invoicing setup:</p>

<ul>
  <li>Price per item carries four decimal places of precision</li>
  <li>The currency, for display and settlement, is two decimals</li>
  <li>Taxes are computed on totals rather than per-line-item (which is the more common convention)</li>
</ul>

<p>If rounding happens too early in this kind of calculation, the invoice total can end up disagreeing with what the underlying ledger says it ought to be. The four candidate policies, in this small space, are:</p>

<ul>
  <li>Round each line item to two decimals, then sum the rounded values</li>
  <li>Sum the unrounded line items at full precision, then round once at the total</li>
  <li>Compute tax per line item and then sum the taxes</li>
  <li>Compute tax once on the total, then round the tax once</li>
</ul>

<p>All four of these are, in some sense, “reasonable”, and none of them is wrong in the abstract. Only one of them, however, will actually agree with the rules your business has adopted, and the only way to make sure you have picked the right one is to choose explicitly, document the choice somewhere where it can be referred to later, and write tests that pin the chosen behaviour in place.</p>

<h2 id="money-pattern">A practical Java pattern: Money as scaled integer</h2>

<p>For currency in particular, the simplest representation that is also genuinely correct in most cases is, in my experience, to store amounts as integer minor units — long-typed cents, in the case of a US-style currency — to do all of the internal arithmetic in integers, and to convert back to a decimal representation only at the boundaries where the value is being displayed or transmitted. This pattern avoids a great deal of <code class="language-plaintext highlighter-rouge">BigDecimal</code> overhead, and it eliminates the entire class of bug that arises from rounding the same value twice in slightly different ways during a calculation.</p>

<h3 id="money-type">Simple Money type (cents)</h3>

<p>A reasonable starting point looks something like this:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.math.BigDecimal</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.math.RoundingMode</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Money</span> <span class="o">{</span>
  <span class="kd">private</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">cents</span><span class="o">;</span>

  <span class="kd">private</span> <span class="nf">Money</span><span class="o">(</span><span class="kt">long</span> <span class="n">cents</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">cents</span> <span class="o">=</span> <span class="n">cents</span><span class="o">;</span> <span class="o">}</span>

  <span class="kd">public</span> <span class="kd">static</span> <span class="nc">Money</span> <span class="nf">ofDollars</span><span class="o">(</span><span class="nc">String</span> <span class="n">amount</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">// Parse exact decimal dollars, then scale to cents.</span>
    <span class="c1">// This version rounds permissively; see ofDollarsStrict for validation.</span>
    <span class="nc">BigDecimal</span> <span class="n">bd</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="n">amount</span><span class="o">).</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_EVEN</span><span class="o">);</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">Money</span><span class="o">(</span><span class="n">bd</span><span class="o">.</span><span class="na">movePointRight</span><span class="o">(</span><span class="mi">2</span><span class="o">).</span><span class="na">longValueExact</span><span class="o">());</span>
  <span class="o">}</span>

  <span class="cm">/**
   * Strict version: reject inputs that aren't exactly 2 decimals.
   * @throws ArithmeticException if rounding would be required
   */</span>
  <span class="kd">public</span> <span class="kd">static</span> <span class="nc">Money</span> <span class="nf">ofDollarsStrict</span><span class="o">(</span><span class="nc">String</span> <span class="n">amount</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">BigDecimal</span> <span class="n">bd</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="n">amount</span><span class="o">).</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">UNNECESSARY</span><span class="o">);</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">Money</span><span class="o">(</span><span class="n">bd</span><span class="o">.</span><span class="na">movePointRight</span><span class="o">(</span><span class="mi">2</span><span class="o">).</span><span class="na">longValueExact</span><span class="o">());</span>
  <span class="o">}</span>

  <span class="kd">public</span> <span class="nc">Money</span> <span class="nf">plus</span><span class="o">(</span><span class="nc">Money</span> <span class="n">other</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">Money</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">addExact</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">cents</span><span class="o">,</span> <span class="n">other</span><span class="o">.</span><span class="na">cents</span><span class="o">));</span>
  <span class="o">}</span>

  <span class="cm">/**
   * Multiply by a factor with explicit rounding policy.
   * Notice we round only at the boundary where we return to cents.
   * In production, consider taking a BigDecimal factor to avoid parsing repeatedly.
   */</span>
  <span class="kd">public</span> <span class="nc">Money</span> <span class="nf">times</span><span class="o">(</span><span class="nc">String</span> <span class="n">factor</span><span class="o">,</span> <span class="nc">RoundingMode</span> <span class="n">mode</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">BigDecimal</span> <span class="n">bd</span> <span class="o">=</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">cents</span><span class="o">)</span>
        <span class="o">.</span><span class="na">movePointLeft</span><span class="o">(</span><span class="mi">2</span><span class="o">)</span>
        <span class="o">.</span><span class="na">multiply</span><span class="o">(</span><span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="n">factor</span><span class="o">))</span>
        <span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">mode</span><span class="o">);</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">Money</span><span class="o">(</span><span class="n">bd</span><span class="o">.</span><span class="na">movePointRight</span><span class="o">(</span><span class="mi">2</span><span class="o">).</span><span class="na">longValueExact</span><span class="o">());</span>
  <span class="o">}</span>

  <span class="kd">public</span> <span class="nc">BigDecimal</span> <span class="nf">toBigDecimal</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">cents</span><span class="o">).</span><span class="na">movePointLeft</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span>
  <span class="o">}</span>

  <span class="nd">@Override</span>
  <span class="kd">public</span> <span class="nc">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">toBigDecimal</span><span class="o">().</span><span class="na">toPlainString</span><span class="o">();</span> <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>The pattern at work in this small class is, I think, worth pulling out explicitly. Inputs are parsed from strings rather than from doubles; the scaling is always explicit; the rounding mode is a parameter at any point that policy has to be applied; and the internal representation is integer cents rather than <code class="language-plaintext highlighter-rouge">BigDecimal</code>. The code is a little longer than the obvious naive version would have been, but each of those choices removes a class of bug rather than papering over one.</p>

<p>There is one design choice in <code class="language-plaintext highlighter-rouge">ofDollars</code> worth calling out explicitly: it silently rounds inputs like <code class="language-plaintext highlighter-rouge">"10.129"</code> to <code class="language-plaintext highlighter-rouge">"10.13"</code>. That is sometimes exactly what is wanted — for display amounts, for instance, where the upstream code has already validated the value — but for payments and ledgers it is often safer to use <code class="language-plaintext highlighter-rouge">ofDollarsStrict</code> instead, which simply rejects any input that is not already at exactly two decimals. The strict version forces validation to happen at the point where the value enters the system, rather than letting an off-scale value drift in and quietly become representable.</p>

<p>For multi-currency systems, the natural extension is to add a <code class="language-plaintext highlighter-rouge">Currency</code> field to the type and to ensure that arithmetic across different currencies is rejected outright. The core pattern, however, stays the same: integer minor units internally, with rounding occurring only at clearly identified boundaries.</p>

<h2 id="field-guide">Quick guide: what each rounding mode is “for”</h2>

<p>The following is a field guide rather than a strict ruleset; the right answer in any given system is always whichever mode matches the domain contract, but these are the rough shapes of cases I have run into often enough to recommend by default.</p>

<h3 id="use-half-even">Use HALF_EVEN when…</h3>

<p>You are aggregating a large number of rounded values and you want to minimise systematic bias in the aggregate. This is the right default for most accounting ledgers, for interest accrual across many accounts, and for any large-scale reporting in which the total over many ties is what matters more than the result of any individual rounding step.</p>

<h3 id="use-half-up">Use HALF_UP when…</h3>

<p>The domain explicitly expects “schoolbook” rounding — the kind of rounding most people learned in primary school, where 0.5 always rounds up. This is appropriate for retail display prices, for some tax jurisdictions in which the rule is set by statute, and for human-facing calculations where the convention is part of the user experience and surprising the user with banker’s rounding would, on balance, do more harm than the bias it would prevent.</p>

<h3 id="use-down">Use DOWN when…</h3>

<p>Truncation is explicitly what the policy requires — typically in fee calculations or in conservative estimates where the rule is something like “we may not exceed this cap under any circumstances,” and where rounding in the more usual direction could produce an out-of-bounds value.</p>

<h3 id="use-ceiling-floor">Use CEILING or FLOOR when…</h3>

<p>These differ from <code class="language-plaintext highlighter-rouge">UP</code> and <code class="language-plaintext highlighter-rouge">DOWN</code> in that the direction is interpreted relative to the number line, rather than relative to zero, which makes them the right choice when sign matters:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">CEILING</code> is “toward positive infinity” (-1.231 -&gt; -1.23, 1.231 -&gt; 1.24 at 2dp)</li>
  <li><code class="language-plaintext highlighter-rouge">FLOOR</code> is “toward negative infinity” (-1.231 -&gt; -1.24, 1.231 -&gt; 1.23 at 2dp)</li>
</ul>

<p>These tend to be the right choice for constraints, limits, and compliance rules of the form “must be at least X” or “must be at most Y”.</p>

<h3 id="use-unnecessary">Use UNNECESSARY when…</h3>

<p>You want the program to fail loudly the instant it encounters a value that you did not expect to need rounding. This sounds, when written down, like an irritation; in my experience it is, in fact, an enormously useful debugging and validation aid:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">subtotal</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"12.34"</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">rate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"0.075"</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">tax</span> <span class="o">=</span> <span class="n">subtotal</span><span class="o">.</span><span class="na">multiply</span><span class="o">(</span><span class="n">rate</span><span class="o">);</span>

<span class="c1">// Fail fast if tax isn't exactly representable at 2 decimals as required by policy</span>
<span class="n">tax</span> <span class="o">=</span> <span class="n">tax</span><span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">UNNECESSARY</span><span class="o">);</span></code></pre></figure>

<p>The exception thrown by <code class="language-plaintext highlighter-rouge">UNNECESSARY</code> is not, in this kind of usage, a nuisance to be caught and ignored. It is a spotlight that the runtime is shining on an assumption you had forgotten you were making, and being told about that assumption explicitly is almost always more useful than letting it quietly produce a slightly wrong answer further downstream.</p>

<h2 id="moral">The moral of the story</h2>

<p><code class="language-plaintext highlighter-rouge">HALF_UP</code>, in summary, is not really wrong; it is simply not universal. Rounding modes, taken together, are not “implementation details” of any kind. They are product decisions, made of policy rather than mathematics, and they need to be picked the way that any other policy is picked — explicitly, with tests, and with a paper trail that lets future readers understand why a particular choice was made and what would have to change for the choice to be revisited.</p>

<h2 id="tldr">TL;DR</h2>

<ul>
  <li>Ties (first discarded digit is 5, rest are zeros) are where rounding policy matters most.</li>
  <li><code class="language-plaintext highlighter-rouge">HALF_UP</code> is intuitive but can introduce bias at scale.</li>
  <li><code class="language-plaintext highlighter-rouge">HALF_EVEN</code> often reduces bias for aggregates.</li>
  <li>Avoid rounding <code class="language-plaintext highlighter-rouge">double</code> values when policy matters. Use <code class="language-plaintext highlighter-rouge">BigDecimal</code> created from strings or scaled integers.</li>
  <li>Prefer <code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(double)</code> over <code class="language-plaintext highlighter-rouge">new BigDecimal(double)</code> when you must start from a double - but remember it’s not a cleansing ritual.</li>
  <li>Decide <em>when</em> you round, not just <em>how</em> you round.</li>
  <li>Prefer rounding once at boundaries, not repeatedly in the middle.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">UNNECESSARY</code> to catch “we assumed this would be exact” bugs early.</li>
</ul>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Computer Science" /><category term="Software Engineering" /><category term="Technology" /><category term="Series 4 - Floating Point Without Tears" /><category term="java" /><category term="floating-point" /><category term="bigdecimal" /><category term="rounding" /><category term="finance" /><category term="numerics" /><summary type="html"><![CDATA[Learn why HALF_UP rounding isn't always correct and how to choose a rounding policy in Java (HALF_EVEN, HALF_UP, HALF_DOWN, UP, DOWN, CEILING, FLOOR, UNNECESSARY).]]></summary></entry><entry><title type="html">Part 7: Kahan Summation - A Better sum() for Java Streams</title><link href="https://systemhalted.in/2026/01/11/kahan-summation-java-streams/" rel="alternate" type="text/html" title="Part 7: Kahan Summation - A Better sum() for Java Streams" /><published>2026-01-11T00:00:00+00:00</published><updated>2026-01-11T00:00:00+00:00</updated><id>https://systemhalted.in/2026/01/11/kahan-summation-java-streams</id><content type="html" xml:base="https://systemhalted.in/2026/01/11/kahan-summation-java-streams/"><![CDATA[<p><em>This post is part of my <a href="https://systemhalted.in/categories/#cat-series-4-floating-point-without-tears">Floating Point Without Tears</a> series on how Java numbers misbehave and how to live with them.</em></p>

<p>In <a href="https://systemhalted.in/2026/01/05/defending-against-nan-without-defensive-programming-hell/">Part 6</a> of this series, we looked at how to defend against NaN by validating values at boundaries rather than scattering checks throughout the code. This post is about a different and, in its own way, sneakier kind of failure mode in floating-point arithmetic — the case where your sum is computed correctly at every individual step, every individual operation does exactly what the standard says it should, and the final answer is still meaningfully wrong.</p>

<hr />

<p>There is a quiet bug that hides inside almost every large numerical reduction in production code, and it has, in my experience, almost nothing to do with mistakes in logic and almost everything to do with how floating-point addition is forced to behave. Floating point has fixed precision: every addition rounds back to a fixed number of significant digits, and the bits that do not fit are discarded. Most of the time, the discarded bits are too small to matter. Some of the time — particularly when you add a large number of values together, or when the values you are adding span many orders of magnitude — the cumulative effect of those tiny dropped bits becomes large enough to be visible in the answer.</p>

<p>This post is about the full story of <strong>Kahan summation</strong>, which is a small algorithmic trick with a surprisingly large impact on this kind of error. Kahan summation does not, and cannot, make floating-point arithmetic exact; what it does is keep your running totals from quietly losing meaningful contributions when you add many numbers, by tracking the bits that rounding has thrown away and folding them back into the next operation.</p>

<h2 id="the-innocent-looking-loop">The innocent-looking loop</h2>

<p>The classic numerical-analysis textbook example for this kind of error is, for historical reasons, usually written in Fortran — it is the lingua franca of mid-twentieth-century numerics — and it looks innocuous enough that it is easy to read past it without realising what it is about to do.</p>

<figure class="highlight"><pre><code class="language-fortran" data-lang="fortran"><span class="n">S</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
</span><span class="k">DO</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">I</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">N</span><span class="w">
  </span><span class="n">YI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">...</span><span class="w">
</span><span class="nl">4</span><span class="w"> </span><span class="n">S</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">S</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">YI</span></code></pre></figure>

<p>The whole drama, such as it is, lives in the last line: <code class="language-plaintext highlighter-rouge">S = S + YI</code>. People then drop in a line that, at first reading, sounds slightly mystical:</p>

<blockquote>
  <p>Rounding or truncation in the addition can contribute to a loss of almost $\log_{10}(N)$ significant decimal digits in S.</p>
</blockquote>

<p>The $\log_{10}(N)$ in that sentence does not, however, come from any sort of wizardry. It is simply what happens when fixed precision collides with a running total that keeps growing — the floating-point grid on which the running total can land gets coarser as the total gets larger, and so the smallest things you are still trying to add to it get progressively harder to see.</p>

<h2 id="why-you-can-lose-about-log10n-digits">Why you can lose about log10(N) digits</h2>

<p>Suppose, for the sake of intuition, that we are working in base-10 floating point with <strong>p significant decimal digits</strong>. (IEEE 754 is base 2, but the intuition transfers cleanly enough that working in base 10 is worth it for the explanation.) Suppose further that the <code class="language-plaintext highlighter-rouge">YI</code> values are roughly the same size and mostly the same sign, so that the partial sums genuinely grow rather than cancelling each other out, and let <code class="language-plaintext highlighter-rouge">|Y|</code> denote a typical magnitude.</p>

<p>After <code class="language-plaintext highlighter-rouge">N</code> terms, then, the true sum is roughly:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>S</mi><mtext>true</mtext></msub><mo>≈</mo><mi>N</mi><mo>⋅</mo><mi>Y</mi></mrow><annotation encoding="application/x-tex">S_{\text{true}} \approx N \cdot Y</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.0576em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">true</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">Y</span></span></span></span></span>

<p>And here is the key floating-point constraint to keep in mind: a <code class="language-plaintext highlighter-rouge">p</code>-digit floating number around magnitude <code class="language-plaintext highlighter-rouge">|S|</code> cannot represent arbitrarily small changes. Near <code class="language-plaintext highlighter-rouge">S</code>, the spacing between representable values is approximately:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="normal">Δ</mi><mi>S</mi><mo>≈</mo><mi mathvariant="normal">∣</mi><mi>S</mi><mi mathvariant="normal">∣</mi><mo>⋅</mo><mn>1</mn><msup><mn>0</mn><mrow><mo>−</mo><mi>p</mi></mrow></msup></mrow><annotation encoding="application/x-tex">\Delta S \approx |S| \cdot 10^{-p}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">Δ</span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">∣</span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mord">∣</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8213em;"></span><span class="mord">1</span><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8213em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight">p</span></span></span></span></span></span></span></span></span></span></span></span></span>

<p>It helps to think of $\Delta S$ as the width of the grid lines at the altitude where <code class="language-plaintext highlighter-rouge">S</code> happens to live. When you compute <code class="language-plaintext highlighter-rouge">S = S + YI</code>, the result is rounded back onto that grid, and any increment that is much smaller than $\Delta S$ at that altitude can simply vanish, because there is no representable value between <code class="language-plaintext highlighter-rouge">S</code> and <code class="language-plaintext highlighter-rouge">S + YI</code> in the new precision regime.</p>

<p>By the end of the loop, <code class="language-plaintext highlighter-rouge">|S| \approx N|Y|</code>, so the grid spacing near the final sum is:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="normal">Δ</mi><mi>S</mi><mo>≈</mo><mo stretchy="false">(</mo><mi>N</mi><mi mathvariant="normal">∣</mi><mi>Y</mi><mi mathvariant="normal">∣</mi><mo stretchy="false">)</mo><mtext> </mtext><mn>1</mn><msup><mn>0</mn><mrow><mo>−</mo><mi>p</mi></mrow></msup></mrow><annotation encoding="application/x-tex">\Delta S \approx (N|Y|)\,10^{-p}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">Δ</span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.0713em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mord">∣</span><span class="mord mathnormal" style="margin-right:0.22222em;">Y</span><span class="mord">∣</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">1</span><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8213em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight">p</span></span></span></span></span></span></span></span></span></span></span></span></span>

<p>Comparing the grid spacing to the size of a typical addend gives:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi mathvariant="normal">Δ</mi><mi>S</mi></mrow><mrow><mi mathvariant="normal">∣</mi><mi>Y</mi><mi mathvariant="normal">∣</mi></mrow></mfrac><mo>≈</mo><mi>N</mi><mo>⋅</mo><mn>1</mn><msup><mn>0</mn><mrow><mo>−</mo><mi>p</mi></mrow></msup><mo>=</mo><mn>1</mn><msup><mn>0</mn><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>10</mn></msub><mi>N</mi><mo>−</mo><mi>p</mi></mrow></msup></mrow><annotation encoding="application/x-tex">\frac{\Delta S}{|Y|} \approx N \cdot 10^{-p} = 10^{\log_{10}N - p}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.2963em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">∣</span><span class="mord mathnormal" style="margin-right:0.22222em;">Y</span><span class="mord">∣</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">Δ</span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8213em;"></span><span class="mord">1</span><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8213em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight">p</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8991em;"></span><span class="mord">1</span><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8991em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mop mtight"><span class="mop mtight"><span class="mtight">l</span><span class="mtight">o</span><span class="mtight" style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1944em;"><span style="top:-2.2341em;margin-right:0.0714em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mtight">10</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2659em;"><span></span></span></span></span></span></span><span class="mspace mtight" style="margin-right:0.1952em;"></span><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">p</span></span></span></span></span></span></span></span></span></span></span></span></span>

<p>That ratio, in the end, is what the textbooks are getting at. Multiplying by <code class="language-plaintext highlighter-rouge">N</code> shifts magnitudes by $\log_{10}(N)$ decimal digits, which means that as the running sum grows by a factor of <code class="language-plaintext highlighter-rouge">N</code>, the rounding grid spacing grows by exactly the same factor. Relative to the scale of the things you are still trying to add, you have therefore effectively lost about $\log_{10}(N)$ digits of useful precision. Another way to say the same thing is:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>useful digits left at the addend scale</mtext><mo>≈</mo><mi>p</mi><mo>−</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>10</mn></msub><mi>N</mi></mrow><annotation encoding="application/x-tex">\text{useful digits left at the addend scale} \approx p - \log_{10}N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord text"><span class="mord">useful digits left at the addend scale</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">p</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.9386em;vertical-align:-0.2441em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.207em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">10</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span></span></span></span></span>

<h3 id="a-concrete-gut-punch">A concrete gut punch</h3>

<p>Suppose, to make this less abstract, that you are working with about <code class="language-plaintext highlighter-rouge">p = 7</code> significant decimal digits — roughly single-precision territory — and you sum <code class="language-plaintext highlighter-rouge">N = 10^6</code> numbers, each of size about 1.</p>

<p>$\log_{10}(10^6) = 6$</p>

<p>Which leaves you with roughly:</p>

<p>$7 - 6 = 1$ meaningful decimal digit at the scale of 1</p>

<p>By the time <code class="language-plaintext highlighter-rouge">S</code> reaches around one million, the grid spacing near <code class="language-plaintext highlighter-rouge">S</code> is roughly:</p>

<p>$10^6 \cdot 10^{-7} = 10^{-1} = 0.1$</p>

<p>So adding <code class="language-plaintext highlighter-rouge">0.01</code> to <code class="language-plaintext highlighter-rouge">S</code> at that point can literally do nothing at all — the increment is smaller than the grid spacing in the precision regime where the running total currently lives, and it gets rounded away. The problem, in other words, is not really the single rounding step; it is the cumulative effect of rounding many million times while the running total continues to inflate.</p>

<h2 id="a-quick-demo-sums-that-look-reasonable-and-are-still-wrong">A quick demo: sums that look reasonable and are still wrong</h2>

<p>Here is a small example of the same kind of cancellation, in Java this time:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">1</span><span class="n">e16</span><span class="o">;</span>
<span class="kt">double</span> <span class="n">naive</span> <span class="o">=</span> <span class="o">(</span><span class="n">x</span> <span class="o">+</span> <span class="mf">1.0</span><span class="o">)</span> <span class="o">+</span> <span class="mf">1.0</span> <span class="o">-</span> <span class="n">x</span><span class="o">;</span>   <span class="c1">// commonly prints 0.0</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">naive</span><span class="o">);</span></code></pre></figure>

<p>The two <code class="language-plaintext highlighter-rouge">+ 1.0</code> additions in this expression happen while the running value is around <code class="language-plaintext highlighter-rouge">1e16</code>, and at that scale <code class="language-plaintext highlighter-rouge">1.0</code> is, in fact, smaller than the spacing between representable doubles. The two ones, therefore, fall through the cracks of the grid entirely, and the answer that arrives back is <code class="language-plaintext highlighter-rouge">0.0</code> rather than the <code class="language-plaintext highlighter-rouge">2.0</code> that ordinary arithmetic would have produced.</p>

<p>This is, importantly, not a curious corner case that only shows up in exam questions. If your system does any kind of analytics, pricing, telemetry, risk computation, recommendations, or ranking — anything where <code class="language-plaintext highlighter-rouge">N</code> becomes large in routine operation — this kind of error is the default failure mode hiding inside what looks like a perfectly innocent reduction.</p>

<h2 id="kahan-summation-track-what-gets-dropped">Kahan summation: track what gets dropped</h2>

<p>Kahan summation is, in technical terms, an instance of <strong>compensated summation</strong>. The idea is to keep two running numbers instead of one:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">sum</code>: the running total, the way you would normally maintain it</li>
  <li><code class="language-plaintext highlighter-rouge">c</code>: a compensation term that records what rounding threw away on the previous addition</li>
</ul>

<p>The first holds the running total at full machine precision; the second holds an estimate of the low-order bits that were lost on the most recent addition, so that those lost bits can be folded back into the next addition rather than disappearing forever.</p>

<h3 id="the-algorithm">The algorithm</h3>

<p>Given a new addend <code class="language-plaintext highlighter-rouge">x</code>, the algorithm performs three steps in sequence: it adjusts the addend by what was lost on the previous step, it adds the adjusted addend to the running total, and it then estimates how much was lost on this step and stores that estimate as the new compensation.</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">y = x - c
t = sum + y
c = (t - sum) - y
sum = t</code></pre></figure>

<p>That, taken on its own terms, is the whole trick. It does not change the underlying floating-point rules — additions are still rounded, the precision is still finite — but it changes the way you accumulate so that the rules hurt you less, by keeping a small ledger of what they have already cost you and applying it as a correction on the next operation.</p>

<h2 id="java-implementation-a-small-accumulator-with-big-consequences">Java implementation: a small accumulator with big consequences</h2>

<h3 id="the-accumulator-type">The accumulator type</h3>

<p>A reasonable Java implementation of the algorithm above is a small accumulator object that exposes the operations one would expect of any reduction primitive — adding a single value, combining with another instance of itself, and producing a final result.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">KahanAccumulator</span> <span class="o">{</span>
  <span class="kd">private</span> <span class="kt">double</span> <span class="n">sum</span><span class="o">;</span>
  <span class="kd">private</span> <span class="kt">double</span> <span class="n">c</span><span class="o">;</span> <span class="c1">// compensation for lost low-order bits</span>

  <span class="kd">public</span> <span class="kt">void</span> <span class="nf">add</span><span class="o">(</span><span class="kt">double</span> <span class="n">x</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">double</span> <span class="n">y</span> <span class="o">=</span> <span class="n">x</span> <span class="o">-</span> <span class="n">c</span><span class="o">;</span>
    <span class="kt">double</span> <span class="n">t</span> <span class="o">=</span> <span class="n">sum</span> <span class="o">+</span> <span class="n">y</span><span class="o">;</span>
    <span class="n">c</span> <span class="o">=</span> <span class="o">(</span><span class="n">t</span> <span class="o">-</span> <span class="n">sum</span><span class="o">)</span> <span class="o">-</span> <span class="n">y</span><span class="o">;</span>
    <span class="n">sum</span> <span class="o">=</span> <span class="n">t</span><span class="o">;</span>
  <span class="o">}</span>

  <span class="kd">public</span> <span class="kt">void</span> <span class="nf">combine</span><span class="o">(</span><span class="nc">KahanAccumulator</span> <span class="n">other</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">// Merge another partial sum into this one.</span>
    <span class="c1">// This improves accuracy, but the result is still order-dependent.</span>
    <span class="k">this</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">other</span><span class="o">.</span><span class="na">sum</span><span class="o">);</span>
    <span class="k">this</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">other</span><span class="o">.</span><span class="na">c</span><span class="o">);</span>
  <span class="o">}</span>

  <span class="kd">public</span> <span class="kt">double</span> <span class="nf">value</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">sum</span><span class="o">;</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>One thing worth saying clearly here, before anyone is tempted to oversell what Kahan summation buys: Kahan improves accuracy substantially, but <strong>floating-point addition is still not associative</strong>, which means that parallel reductions running over the same data can still produce different totals from one run to the next, simply because the order in which the partial sums get combined varies. Kahan makes that wobble considerably smaller; it does not make it impossible.</p>

<h2 id="using-it-with-streams">Using it with Streams</h2>

<p>There are two natural ways to integrate this accumulator with the Java Streams API, depending on whether you happen to have a primitive <code class="language-plaintext highlighter-rouge">DoubleStream</code> or a boxed <code class="language-plaintext highlighter-rouge">Stream&lt;Double&gt;</code>.</p>

<h3 id="option-a-doublestreamcollect-best-for-primitive-streams">Option A: DoubleStream.collect (best for primitive streams)</h3>

<p><code class="language-plaintext highlighter-rouge">DoubleStream</code> has its own <code class="language-plaintext highlighter-rouge">collect</code> overload that avoids the boxing cost of the generic <code class="language-plaintext highlighter-rouge">Collector</code> machinery, which makes it the better choice when you already have a primitive stream:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.util.stream.DoubleStream</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Kahan</span> <span class="o">{</span>

  <span class="kd">private</span> <span class="nf">Kahan</span><span class="o">()</span> <span class="o">{}</span>

  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">double</span> <span class="nf">sum</span><span class="o">(</span><span class="nc">DoubleStream</span> <span class="n">stream</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">KahanAccumulator</span> <span class="n">acc</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">collect</span><span class="o">(</span>
        <span class="nl">KahanAccumulator:</span><span class="o">:</span><span class="k">new</span><span class="o">,</span>
        <span class="nl">KahanAccumulator:</span><span class="o">:</span><span class="n">add</span><span class="o">,</span>
        <span class="nl">KahanAccumulator:</span><span class="o">:</span><span class="n">combine</span>
    <span class="o">);</span>
    <span class="k">return</span> <span class="n">acc</span><span class="o">.</span><span class="na">value</span><span class="o">();</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Usage looks like the obvious thing:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">s1</span> <span class="o">=</span> <span class="nc">Kahan</span><span class="o">.</span><span class="na">sum</span><span class="o">(</span><span class="nc">DoubleStream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mf">0.1</span><span class="o">,</span> <span class="mf">0.2</span><span class="o">,</span> <span class="mf">0.3</span><span class="o">));</span>
<span class="kt">double</span> <span class="n">s2</span> <span class="o">=</span> <span class="nc">Kahan</span><span class="o">.</span><span class="na">sum</span><span class="o">(</span><span class="n">myDoubleStream</span><span class="o">.</span><span class="na">parallel</span><span class="o">());</span> <span class="c1">// allowed, order still not fixed</span></code></pre></figure>

<h3 id="option-b-a-collector-nice-ergonomics-for-boxed-streams">Option B: a Collector (nice ergonomics for boxed streams)</h3>

<p>When you already have a <code class="language-plaintext highlighter-rouge">Stream&lt;Double&gt;</code> for some reason — typically because the upstream code is using boxed types — a <code class="language-plaintext highlighter-rouge">Collector</code> is more convenient and idiomatic:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.util.stream.Collector</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">KahanCollectors</span> <span class="o">{</span>

  <span class="kd">private</span> <span class="nf">KahanCollectors</span><span class="o">()</span> <span class="o">{}</span>

  <span class="kd">public</span> <span class="kd">static</span> <span class="nc">Collector</span><span class="o">&lt;</span><span class="nc">Double</span><span class="o">,</span> <span class="nc">KahanAccumulator</span><span class="o">,</span> <span class="nc">Double</span><span class="o">&gt;</span> <span class="nf">kahanSummingDouble</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nc">Collector</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
        <span class="nl">KahanAccumulator:</span><span class="o">:</span><span class="k">new</span><span class="o">,</span>
        <span class="o">(</span><span class="n">acc</span><span class="o">,</span> <span class="n">x</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">acc</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">x</span><span class="o">),</span>
        <span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="o">{</span> <span class="n">a</span><span class="o">.</span><span class="na">combine</span><span class="o">(</span><span class="n">b</span><span class="o">);</span> <span class="k">return</span> <span class="n">a</span><span class="o">;</span> <span class="o">},</span>
        <span class="nl">KahanAccumulator:</span><span class="o">:</span><span class="n">value</span>
    <span class="o">);</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Usage, again, is the usual thing:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">s</span> <span class="o">=</span> <span class="n">myStreamOfDoubles</span><span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">KahanCollectors</span><span class="o">.</span><span class="na">kahanSummingDouble</span><span class="o">());</span></code></pre></figure>

<h2 id="correctness-what-to-test-and-what-not-to-promise">Correctness: what to test, and what not to promise</h2>

<p>It is worth being precise, when introducing this kind of utility into a codebase, about what it does and does not promise, because the difference between the two is the source of most production surprises in this space.</p>

<h3 id="what-you-should-promise">What you should promise</h3>

<ul>
  <li>Better accuracy than naive summation for large <code class="language-plaintext highlighter-rouge">N</code> and wide dynamic ranges</li>
  <li>Explicit behavior you control and can review in code</li>
</ul>

<h3 id="what-you-should-not-promise">What you should not promise</h3>

<ul>
  <li>Bit-for-bit deterministic results in parallel streams</li>
  <li>Exactness for money or decimal accounting</li>
</ul>

<h3 id="junit-test-idea-compare-against-a-higher-precision-reference">JUnit test idea: compare against a higher-precision reference</h3>

<p><code class="language-plaintext highlighter-rouge">BigDecimal</code> is not, strictly speaking, a perfect oracle for binary floating point — it represents numbers in base 10 — but it is a very useful reference when you want to confirm that one summation strategy is drifting more than another, since <code class="language-plaintext highlighter-rouge">BigDecimal</code> arithmetic has effectively unlimited precision for the operations involved here.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">static</span> <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span><span class="o">.*;</span>
<span class="kn">import</span> <span class="nn">java.math.BigDecimal</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Random</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.stream.DoubleStream</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">KahanAccumulatorTest</span> <span class="o">{</span>

  <span class="nd">@Test</span>
  <span class="kt">void</span> <span class="nf">kahan_is_usually_better_than_naive_on_wide_range_data</span><span class="o">()</span> <span class="o">{</span>
    <span class="nc">Random</span> <span class="n">r</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Random</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>

    <span class="kt">double</span><span class="o">[]</span> <span class="n">xs</span> <span class="o">=</span> <span class="nc">DoubleStream</span><span class="o">.</span><span class="na">generate</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="o">{</span>
      <span class="kt">double</span> <span class="n">sign</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">nextBoolean</span><span class="o">()</span> <span class="o">?</span> <span class="mf">1.0</span> <span class="o">:</span> <span class="o">-</span><span class="mf">1.0</span><span class="o">;</span>
      <span class="kt">double</span> <span class="n">mag</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">pow</span><span class="o">(</span><span class="mf">10.0</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="mi">20</span><span class="o">)</span> <span class="o">-</span> <span class="mi">10</span><span class="o">);</span> <span class="c1">// 1e-10 .. 1e9</span>
      <span class="k">return</span> <span class="n">sign</span> <span class="o">*</span> <span class="n">mag</span> <span class="o">*</span> <span class="n">r</span><span class="o">.</span><span class="na">nextDouble</span><span class="o">();</span>
    <span class="o">}).</span><span class="na">limit</span><span class="o">(</span><span class="mi">200_000</span><span class="o">).</span><span class="na">toArray</span><span class="o">();</span>

    <span class="kt">double</span> <span class="n">naive</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">;</span>
    <span class="nc">KahanAccumulator</span> <span class="n">kahan</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">KahanAccumulator</span><span class="o">();</span>
    <span class="nc">BigDecimal</span> <span class="n">ref</span> <span class="o">=</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">ZERO</span><span class="o">;</span>

    <span class="k">for</span> <span class="o">(</span><span class="kt">double</span> <span class="n">x</span> <span class="o">:</span> <span class="n">xs</span><span class="o">)</span> <span class="o">{</span>
      <span class="n">naive</span> <span class="o">+=</span> <span class="n">x</span><span class="o">;</span>
      <span class="n">kahan</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">x</span><span class="o">);</span>
      <span class="n">ref</span> <span class="o">=</span> <span class="n">ref</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">x</span><span class="o">));</span>
    <span class="o">}</span>

    <span class="c1">// Reference converted back to double so we compare at double resolution.</span>
    <span class="kt">double</span> <span class="n">reference</span> <span class="o">=</span> <span class="n">ref</span><span class="o">.</span><span class="na">doubleValue</span><span class="o">();</span>

    <span class="kt">double</span> <span class="n">errNaive</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">naive</span> <span class="o">-</span> <span class="n">reference</span><span class="o">);</span>
    <span class="kt">double</span> <span class="n">errKahan</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">kahan</span><span class="o">.</span><span class="na">value</span><span class="o">()</span> <span class="o">-</span> <span class="n">reference</span><span class="o">);</span>

    <span class="c1">// In rare adversarial sequences Kahan can be slightly worse, but typically it's much better.</span>
    <span class="n">assertTrue</span><span class="o">(</span><span class="n">errKahan</span> <span class="o">&lt;=</span> <span class="n">errNaive</span> <span class="o">*</span> <span class="mf">2.0</span><span class="o">,</span>
        <span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"Kahan error (%.2e) vs naive (%.2e)"</span><span class="o">,</span> <span class="n">errKahan</span><span class="o">,</span> <span class="n">errNaive</span><span class="o">));</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<h2 id="performance-what-it-costs">Performance: what it costs</h2>

<p>Naive summation does exactly one addition per element. Kahan summation, by comparison, does a small handful of extra operations per element — three subtractions and one extra addition, on top of the one that would otherwise have been there. That is, in the end, a small constant factor, and in the great majority of analytics pipelines I have worked on, the accuracy gain is worth far more than the marginal CPU cost.</p>

<p>If you do want to measure the overhead carefully, the right tool to reach for is JMH, and the comparison worth running is between four variants:</p>

<ul>
  <li>the naive loop</li>
  <li><code class="language-plaintext highlighter-rouge">DoubleStream.sum()</code></li>
  <li>the Kahan loop directly</li>
  <li>Kahan via <code class="language-plaintext highlighter-rouge">DoubleStream.collect</code></li>
</ul>

<p>The relative numbers, in my experience, depend more on the surrounding pipeline and the JIT’s behaviour than on the algorithm itself, but the overhead of Kahan tends to be in single-digit percent territory for typical workloads.</p>

<h2 id="when-not-to-use-kahan">When NOT to use Kahan</h2>

<p>Kahan is genuinely useful, but it is not the right tool in every situation. There are at least three categories in which I would specifically avoid reaching for it:</p>

<ul>
  <li><strong>Financial calculations.</strong> Use <code class="language-plaintext highlighter-rouge">BigDecimal</code> for money. Kahan does not give you decimal semantics, and the kinds of rounding rules that finance and accounting actually require live in a different problem entirely.</li>
  <li><strong>Tiny datasets.</strong> When <code class="language-plaintext highlighter-rouge">N</code> is small, the cumulative error of naive summation is usually well below any threshold that matters, and the overhead of Kahan, however small, is rarely worth carrying for the negligible benefit.</li>
  <li><strong>Hard real-time or tight latency budgets.</strong> Profile first; the overhead is usually small but it is not zero, and in the kind of code that has a hard deadline every iteration, even a few nanoseconds per element can add up to meaningful time.</li>
</ul>

<h2 id="variants-worth-mentioning-neumaier-and-pairwise-summation">Variants worth mentioning: Neumaier and pairwise summation</h2>

<p>Kahan is, although a good default, not the only available compensated-summation technique. It is worth at least naming a couple of the alternatives, both because they sometimes behave better than Kahan on particular kinds of data and because anyone seriously interested in numerical accuracy will encounter them sooner or later in the literature.</p>

<p>Neumaier summation is a small modification of Kahan that handles a specific case more gracefully: the case in which the next addend is, in magnitude, larger than the current running sum, which is a situation Kahan can mishandle in a few corner cases.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// Neumaier variant</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">NeumaierAccumulator</span> <span class="o">{</span>
  <span class="kd">private</span> <span class="kt">double</span> <span class="n">sum</span><span class="o">;</span>
  <span class="kd">private</span> <span class="kt">double</span> <span class="n">c</span><span class="o">;</span>

  <span class="kd">public</span> <span class="kt">void</span> <span class="nf">add</span><span class="o">(</span><span class="kt">double</span> <span class="n">x</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">double</span> <span class="n">t</span> <span class="o">=</span> <span class="n">sum</span> <span class="o">+</span> <span class="n">x</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">sum</span><span class="o">)</span> <span class="o">&gt;=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">x</span><span class="o">))</span> <span class="o">{</span>
      <span class="n">c</span> <span class="o">+=</span> <span class="o">(</span><span class="n">sum</span> <span class="o">-</span> <span class="n">t</span><span class="o">)</span> <span class="o">+</span> <span class="n">x</span><span class="o">;</span>
    <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
      <span class="n">c</span> <span class="o">+=</span> <span class="o">(</span><span class="n">x</span> <span class="o">-</span> <span class="n">t</span><span class="o">)</span> <span class="o">+</span> <span class="n">sum</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="n">sum</span> <span class="o">=</span> <span class="n">t</span><span class="o">;</span>
  <span class="o">}</span>

  <span class="kd">public</span> <span class="kt">double</span> <span class="nf">value</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">sum</span> <span class="o">+</span> <span class="n">c</span><span class="o">;</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Pairwise summation, also known as tree reduction, takes a different approach: rather than tracking compensation explicitly, it reduces error growth by combining numbers of similar magnitude together, so that the running sum never gets too far ahead of the values still being added. Some stream implementations may, behind the scenes, do something resembling this internally; the principal advantage of writing your own is that the behaviour is then explicit, reviewable, and not subject to silent change between JDK versions.</p>

<h2 id="when-to-use-what">When to use what</h2>

<p>Pulling all of the above together, the general guidance I would offer is roughly this. Use <code class="language-plaintext highlighter-rouge">BigDecimal</code> when you need decimal semantics, and you mean it; use naive summation when <code class="language-plaintext highlighter-rouge">N</code> is small, the magnitudes of the addends are similar, and you genuinely do not care about a few low-order bits; and use Kahan or Neumaier when:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">N</code> is large (thousands to millions of values)</li>
  <li>values span many orders of magnitude</li>
  <li>small contributions matter</li>
  <li>you want better accuracy without dragging BigDecimal everywhere</li>
</ul>

<h3 id="a-note-on-bigdecimal-vs-kahan-so-we-dont-mix-the-tools">A note on BigDecimal vs Kahan (so we don’t mix the tools)</h3>

<p>It is worth being explicit about this distinction, because I have seen it confused often enough that the confusion is, by itself, a recurring source of bugs. <strong><code class="language-plaintext highlighter-rouge">BigDecimal</code> is about decimal <em>semantics</em></strong> — money, accounting, “pennies must add up,” explicit rounding rules — and <strong>Kahan is about <em>accumulation error</em> in <code class="language-plaintext highlighter-rouge">double</code></strong> when <code class="language-plaintext highlighter-rouge">double</code> is genuinely the right representation for the values being summed (metrics, measurements, statistics, ML features, telemetry) but naive summation is bleeding low-order bits over a long enough reduction.</p>

<p>The practical rule of thumb is straightforward enough:</p>

<ul>
  <li>If your requirements mention <strong>cents, statements, taxes, interest, regulatory accuracy, or mandated rounding policies</strong> → use <strong>BigDecimal</strong> (and decide scale + rounding mode explicitly).</li>
  <li>If your requirements mention <strong>large aggregates, mixed magnitudes, order sensitivity, or “why did parallel give a different total?”</strong> → keep <strong>double</strong>, but upgrade the summation (<strong>Kahan / Neumaier / pairwise</strong>), and be explicit about ordering if determinism matters.</li>
</ul>

<p>One small but persistent footgun while we are on the subject: <code class="language-plaintext highlighter-rouge">BigDecimal</code> only stays “decimal-correct” if you construct it correctly. Prefer parsing from a decimal string, or <code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(double)</code>, over <code class="language-plaintext highlighter-rouge">new BigDecimal(double)</code>, which faithfully captures the binary representation of the double — including, of course, all of its decimal infidelity.</p>

<h2 id="closing">Closing</h2>

<p>Floating point, on its own terms, is not really broken; it is simply finite. The problem is not that rounding happens — rounding is what makes finite-precision arithmetic possible in the first place — but that rounding can happen many millions of times across a single reduction, while the running total continues to grow, and the cumulative effect of all those small dropped bits can become large enough to be wrong in ways that matter.</p>

<p>What Kahan summation does, in the end, is to keep a small ledger of what rounding has thrown away and to fold those discarded bits back into the next addition, so that the long-run drift stays bounded rather than accumulating without limit. The code change involved is genuinely small; the conceptual shift, however — from treating summation as a single line of code to treating it as a deliberate numerical algorithm — is the part that, I think, is worth taking seriously the next time you find yourself reaching for <code class="language-plaintext highlighter-rouge">.sum()</code> over a few million doubles.</p>

<hr />

<h2 id="references">References</h2>

<ul>
  <li>W. Kahan, “Pracniques: Further remarks on reducing truncation errors,” Communications of the ACM, 8(1), Jan. 1965.
DOI (ACM Digital Library): https://dl.acm.org/doi/10.1145/363707.363723</li>
</ul>

<p>Open PDF (hosted copy): https://convexoptimization.com/TOOLS/Kahan.pdf</p>

<p>Metadata entry (Semantic Scholar): https://www.semanticscholar.org/paper/Pracniques%3A-further-remarks-on-reducing-truncation-Kahan/672a99813f52aed720d3508d6be7db461328b064</p>

<ul>
  <li>
    <p>Prof Kahan’s Assorted Notes, https://people.eecs.berkeley.edu/~wkahan/</p>
  </li>
  <li>
    <p>N. J. Higham, “The Accuracy of Floating Point Summation”, SIAM Journal on Scientific Computing (1993). https://doi.org/10.1137/0914050</p>
  </li>
  <li>
    <p>DoubleStream.sum(): https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/DoubleStream.html#sum()</p>
  </li>
  <li>
    <p>DoubleSummaryStatistics: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/DoubleSummaryStatistics.html</p>
  </li>
</ul>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Computer Science" /><category term="Software Engineering" /><category term="Technology" /><category term="Series 4 - Floating Point Without Tears" /><category term="java" /><category term="floating-point" /><category term="ieee-754" /><category term="kahan" /><category term="numerical-analysis" /><category term="streams" /><category term="collector" /><summary type="html"><![CDATA[Learn why naive summation loses digits and how Kahan compensated summation helps. Includes Java Stream integration, test strategies, and when to use BigDecimal instead.]]></summary></entry><entry><title type="html">Stereotypes Are Lazy Maps</title><link href="https://systemhalted.in/2026/01/10/stereotypes-are-lazy-maps/" rel="alternate" type="text/html" title="Stereotypes Are Lazy Maps" /><published>2026-01-10T00:00:00+00:00</published><updated>2026-01-10T00:00:00+00:00</updated><id>https://systemhalted.in/2026/01/10/stereotypes-are-lazy-maps</id><content type="html" xml:base="https://systemhalted.in/2026/01/10/stereotypes-are-lazy-maps/"><![CDATA[<p>A stereotype is, in essence, a shortcut: a way of thinking about people in groups so that one does not have to do the considerably harder work of thinking about them as individuals. A generalization is, technically, a slightly more careful cousin of the same instinct — a guess about a population, ideally informed by some kind of evidence — but in practice the two tend to blur into each other, and both can be dangerously tempting when you are tired, when you are annoyed, or when you are trying to compress the human universe into a single sentence in order to win an argument on the internet. The trouble is that a shortcut, however efficient, is never quite the same thing as the truth, and when the shortcut is being applied to several million people at once, the gap between the two can become very large.</p>

<p>This post, in the modern fashion, started its life with a social media thread. I saw a post about “student drivers” which had, by the time I came across it, evolved into a sweeping generalization about an entire people; someone, apparently in response, had then attempted to generalize Indians in one long, unpunctuated breath, and the resulting exchange was almost a small textbook in the failure mode I want to write about here. The irony, in particular, was not subtle: the corrective offered to a sweeping generalization about one group was, somehow, a sweeping generalization about another group, on the apparent assumption that two equal and opposite errors might add up to honesty.</p>

<p>I should say, before going further, that this is not really a post about Indians, or Americans, or any one group in particular. It is a post about a habit of mind that I think we have, collectively, normalized — the habit of reducing millions of people to a neat sentence and then calling it “an observation.”</p>

<h2 id="the-world-is-too-large-for-our-brains">The world is too large for our brains</h2>

<p>It is worth starting with the underlying mechanism, because the instinct that produces stereotypes is, by itself, neither malicious nor unusual. Human brains are, before they are anything else, pattern machines. We find faces in clouds, infer intent from a raised eyebrow, and build small working models of reality more or less continuously, because raw reality, in its full detail, is far too large to actually hold in mind. That model-building instinct is not in itself evil; it is, in a real sense, a survival adaptation, and most of the time it serves us reasonably well.</p>

<p>The trouble begins, however, when we start to mistake the model for the world. A stereotype, viewed from this angle, is what happens when you take a small sample of human behaviour, add a generous amount of emotion, mix in a few viral anecdotes, and then quietly export the resulting impression as if it were a universal law about an entire population. It tends to feel efficient, satisfying, and like a kind of closure on a question that previously was open — and all three of those feelings, while pleasant, are also exactly what makes the move so easy to perform without noticing. It is, in the end, the principal way in which we end up misreading entire peoples.</p>

<h2 id="indians-are-is-a-sentence-that-breaks-under-its-own-weight">“Indians are…” is a sentence that breaks under its own weight</h2>

<p>India, to take the example I happen to know best, is not a monolith. It is a continent-sized civilization with many languages, many histories, many moral philosophies, many economic realities, and many different ways of being human, none of which fit comfortably under a single noun. When someone confidently begins a sentence with “Indians are…”, and then completes it with any predicate at all, what they are usually saying — whether they realise it or not — is something closer to “I met some Indians once,” or “I saw a few videos,” or “I had one bad experience,” or “I read a thread on the internet that made me feel righteous, and I am now extrapolating from it.”</p>

<p>The mental move that follows is the one I find most worth flagging, because it is so common and so frictionless that it is usually invisible to the person making it. The brain takes an anecdote — a single experience, a small sample, a memorable encounter — and quietly upgrades it into an identity, into a property of an entire group. That is not insight in any meaningful sense; it is laziness wearing the costume of insight, and it is unusually difficult to argue with, because the speaker can always retreat to “but I am only describing what I have actually seen.”</p>

<h2 id="the-equal-and-opposite-mistake">The equal and opposite mistake</h2>

<p>There is a pattern that tends to follow almost mechanically from the kind of move described above, and it is worth treating on its own terms because it is, in some ways, more pernicious than the original. The pattern is this: the response to “Indians are X” is almost never “let us not generalize about people in groups,” but is instead “well, Americans are Y.”</p>

<p>This looks, on its surface, like an attempt at fairness — turning the same lens back on the speaker — but in practice it is just the same error doing a second lap. The United States, after all, is the third-largest country in the world by population, and it contains multitudes: regions that feel like different planets, communities built up from different waves of migration, value systems that clash with each other on a daily basis, and identities that do not fit into any clean box that anyone has yet managed to draw. Broad generalizations about Americans are, on close inspection, every bit as unfair as broad generalizations about Indians, and for exactly the same reasons.</p>

<p>What tends to happen in these exchanges, then, is not a productive conversation but a steady collapse into noise, in which one stereotype is met with its opposite, that opposite is met with another, and by the time anyone has stopped to think, the actual question — whatever it might originally have been — has been buried under a small mountain of confidently stated nonsense. The result, in any sense that the word ought to mean, is not justice; it is just more of the same kind of laziness, moving faster.</p>

<h2 id="the-small-loud-group-problem">The “small loud group” problem</h2>

<p>A reasonable-sounding defence of the move I have been describing goes something like this: “I am not generalizing about everyone. I am only talking about a certain kind of person.” That defence is sometimes legitimate, and it is worth taking seriously, because there is no question that social media has a way of amplifying the most extreme voices in any group, that outrage tends to function as the algorithmic fuel of these platforms, and that the loudest voices therefore get the microphone far more often than their actual representativeness in the underlying population would warrant.</p>

<p>What this defence misses, however, is that adult conversation about groups requires adult precision about which claim, exactly, is being made — and there is a meaningful difference between two claims that often get confused for each other. The first is “some people are doing this harmful thing”; the second is “this group is like this.” The first is a claim about behaviour, and it has the useful property of being debatable, measurable, challengeable, and refinable in the light of new evidence. The second is a claim about identity, and what it does, often without the speaker quite noticing, is to turn an observed behaviour into an essential property of the group itself — at which point it becomes very difficult to discuss without sounding either dismissive of the original observation or apologetic on behalf of an entire population.</p>

<p>That distinction — between behaviour and identity — is, I think, the line that matters most in these conversations. It is also, in my experience, the line that is most reliably crossed without anyone quite admitting that they have crossed it.</p>

<h2 id="why-stereotypes-feel-so-good">Why stereotypes feel so good</h2>

<p>It is worth being honest, at this stage, about why stereotypes are so persistent, despite the fact that almost everyone, when asked directly, will agree that they are unreliable. The honest answer, I think, is that they offer at least three things which the internet, in its current form, particularly rewards: a feeling of certainty about a complicated subject (“I understand what is going on here”), a substantial saving of time and mental effort (“I do not need to do the work”), and a kind of moral permission (“I am now justified in judging”). Each of those payoffs is, on its own, a real human need, and a stereotype tends to provide all three at once and very cheaply.</p>

<p>Real understanding, by comparison, is comparatively slow. It requires the friction of saying “I might be wrong about this,” and of then doing the harder work of going out and gathering better evidence than the small sample one started with. Philosophers have a name for the disposition involved here — they call it epistemic humility<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> — and it amounts, in the end, to the recognition that our knowledge of any given group of people is always incomplete, our samples always limited, and our confidence in our conclusions always more provisional than we tend to act as if it is. It is, almost by definition, the opposite of the kind of reaction that social media tends to reward, and it is, in any case, considerably harder than typing a hot take.</p>

<h2 id="a-better-way-to-speak">A better way to speak</h2>

<p>If the goal is to be able to criticize an observed pattern without flattening millions of people into cardboard cutouts, there are a few defaults that I have, over time, come to find useful. Most of them are not original to me, and most of them sound obvious when written down — but, as with most discipline, the difficulty is not in the principles themselves but in remembering to apply them at the moment one is most tempted not to.</p>

<ul>
  <li>Speak about specific behaviors, not identities.</li>
  <li>Use “some” and “in my experience” like seatbelts.</li>
  <li>Separate “what I saw” from “what is true.”</li>
  <li>Ask whether your sample is representative or just memorable.</li>
  <li>When you feel righteous, pause. Righteousness is not a fact-checker.</li>
</ul>

<p>What unifies most of these, I think, is the underlying observation that, in conversations about groups, precision is a form of kindness. If the goal is to be understood, and to leave the other person with a clearer view of the world rather than a more flattering view of themselves, then the additional words spent saying “some” rather than “all,” or “in my experience” rather than “obviously,” are not weakness or hedging — they are the difference between a sentence that can be true and one that essentially cannot.</p>

<h2 id="immigrants-live-inside-the-blur">Immigrants live inside the blur</h2>

<p>I should also say, more personally, that immigrants tend to occupy a particular kind of space in these conversations, and it is one that I have come to understand from the inside. You learn, over the years, to love the country you live in, while also carrying — quietly, most of the time — the experience of being misread by it. You become, in effect, bilingual not only in language but in assumptions, and you spend a non-trivial amount of mental energy translating between the two on any given day.</p>

<p>The forms of that misreading vary considerably. Sometimes you are treated, by complete strangers, as an ambassador for a population of more than a billion people, and asked questions that no individual could possibly be qualified to answer. Sometimes you are treated as an exception to whatever is being said about your group at the time — “you’re not like the others” — which is meant generously and is, in its own way, almost as unkind as the original generalization, because it tacitly accepts the generalization while granting you a personal exemption from it. And sometimes you are simply reduced to a meme, which is at least faster.</p>

<p>Through all of that, you still get up in the morning and show up. You contribute, you build, you teach, you try to belong somewhere without having to dissolve into it in order to do so. And it is from inside that experience, more than anything else, that I want to insist that when someone stereotypes a group, what they are doing is not really “just words.” A stereotype, in practice, is a small social verdict, and small social verdicts have a way of sticking.</p>

<h2 id="the-point">The point</h2>

<p>It would be, of course, somewhat awkward to end an essay against generalizations with a generalization of my own, and so I want to be careful to avoid the easy line “everyone stereotypes,” which is, in its own quiet way, exactly the kind of move I have spent the last several sections complaining about.</p>

<p>What I will say instead is something more local. After the thread I described at the beginning, I also received a number of kind messages from people in my local community, offering support and, in several cases, apologizing “on behalf of” others. The intent in those messages was generous, and I genuinely appreciated each of them. But I have to admit that, even within that generosity, I felt the shadow of the same underlying mistake — the assumption that there is, somewhere, a “them” coherent enough to speak for. The truth, when one looks closely, is that there usually is not, and that the most useful thing one can do in response to bad generalizations from one direction is not to speak more confidently from the other, but to refuse the framing altogether.</p>

<p>We do not, in the end, defeat stereotypes by pretending that differences between people and groups do not exist; that is its own form of dishonesty, and a particularly fragile one at that. We defeat them, to the extent that we defeat them at all, by being honest about complexity — by trading lazy certainty for careful clarity, by resisting the cheap thrill of the sweeping sentence, and by remembering, especially when it is most tempting to forget, that every “they” we are tempted to talk about is, on closer inspection, made of millions of individual “someones,” each of whom would, given the chance, object to whatever we were about to say about them collectively. The world, in other words, is genuinely detailed; and being honest about that detail, even when it makes our sentences longer, is what taking other people seriously actually looks like.</p>

<h2 id="references-and-notes">References and Notes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Epistemic humility: https://en.wikipedia.org/wiki/Epistemic_humility <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Society &amp; Economy" /><category term="Personal Essays" /><category term="stereotypes" /><category term="generalization" /><category term="immigrants" /><category term="critical-thinking" /><category term="social-media" /><summary type="html"><![CDATA[A short essay on why stereotypes feel efficient but fail under scrutiny, and how to talk about groups: Indians, Americans, immigrants, anyone, without flattening millions of people into a single sentence.]]></summary></entry><entry><title type="html">Part 6: Defending Against NaN Without Defensive Programming Hell</title><link href="https://systemhalted.in/2026/01/05/defending-against-nan-without-defensive-programming-hell/" rel="alternate" type="text/html" title="Part 6: Defending Against NaN Without Defensive Programming Hell" /><published>2026-01-05T00:00:00+00:00</published><updated>2026-01-05T00:00:00+00:00</updated><id>https://systemhalted.in/2026/01/05/defending-against-nan-without-defensive-programming-hell</id><content type="html" xml:base="https://systemhalted.in/2026/01/05/defending-against-nan-without-defensive-programming-hell/"><![CDATA[<p><em>This post is part of my <a href="https://systemhalted.in/categories/#cat-series-4-floating-point-without-tears">Floating Point Without Tears</a> series on how Java numbers misbehave and how to live with them.</em></p>

<p>When IEEE 754 arithmetic encounters an operation for which there is no real-number answer — dividing zero by zero, taking the square root of a negative number, and so on — it does not throw an exception. Instead, it produces a special value called NaN, short for “Not a Number,” and the program continues running as if nothing in particular had happened. This is, as a piece of language design, both a strength and a quiet curse: it allows numerical code to keep flowing in the presence of locally invalid operations, but it also means that NaN tends to slip downstream silently and only surface much later, in log files, in metrics, and on dashboards, long after the operation that actually produced it has scrolled out of view.</p>

<p>The temptation, on first encountering this behaviour, is to start sprinkling <code class="language-plaintext highlighter-rouge">if (isNaN)</code> checks throughout the codebase as a defence. This post is, in a sense, about doing the opposite of that — about defending against NaN structurally, using a small number of checks placed deliberately at the right boundaries, rather than scattering a defensive layer through every function in the system.</p>

<h2 id="the-shape-of-the-beast">The shape of the beast</h2>

<p>NaN, as the name suggests, is what IEEE 754 hands back from operations that have no meaningful real-number answer. The canonical examples are familiar enough — dividing <code class="language-plaintext highlighter-rouge">0.0</code> by <code class="language-plaintext highlighter-rouge">0.0</code>, taking the square root of a negative number, taking the logarithm of a negative number — but there is a fourth source that is, in practice, more important than any individual mathematical case: any operation that already involves a NaN will itself produce a NaN. NaN is contagious, in both the best and the worst senses of the word, and that contagion is the mechanism by which a single invalid operation upstream can quietly poison everything that flows from it.</p>

<p>Some of the operations that produce NaN are:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.	0.0 / 0.0  
2.	Math.sqrt(-1.0)  
3.	Math.log(-1.0)  
4.	Any operation that already contains NaN, because NaN is contagious in the best and worst ways  
</code></pre></div></div>

<p>Java follows IEEE 754 in all of this. The JVM does not, for the most part, throw an exception when a floating-point operation is invalid; it produces NaN or Infinity and lets the program continue, which is another way of saying that what you are looking at is a silent error.</p>

<p>There is one further property of NaN that catches almost everyone the first time they encounter it. NaN is not equal to anything, not even to itself.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">x</span> <span class="o">=</span> <span class="nc">Double</span><span class="o">.</span><span class="na">NaN</span><span class="o">;</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x</span> <span class="o">==</span> <span class="n">x</span><span class="o">);</span>              <span class="c1">// false</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">isNaN</span><span class="o">(</span><span class="n">x</span><span class="o">));</span>     <span class="c1">// true</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="o">);</span>              <span class="c1">// false</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">);</span>              <span class="c1">// false</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x</span> <span class="o">==</span> <span class="mi">0</span><span class="o">);</span>             <span class="c1">// false</span></code></pre></figure>

<p>NaN, in other words, does not really behave like a value in the ordinary sense; it is more accurately understood as a signal that has, by the design of the floating-point system, been forced to masquerade as a value. The fact that it wears that disguise convincingly is the source of most of the practical trouble that NaN goes on to cause in production systems.</p>

<h2 id="defensive-programming-hell-or-what-i-call-checkpoint-syndrome">Defensive programming hell, or what I call “Checkpoint Syndrome”</h2>

<p>The instinctive reaction to discovering NaN in production is, I think, entirely understandable. You find a NaN in a log, trace it back to a particular code path, add an <code class="language-plaintext highlighter-rouge">isNaN</code> check there, deploy the fix, and move on. The trouble is that, almost without exception, the next NaN that shows up will not be in the same place — it will be in some other path that touches the same data — and so another check goes in. Repeat this process for a few months and you end up with a codebase in which essentially every function is doing its own defensive validation of every input it receives, often with slightly different responses to the same underlying condition.</p>

<p>The result tends to look something like this:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">price</span> <span class="o">=</span> <span class="n">computePrice</span><span class="o">(</span><span class="n">input</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">isNaN</span><span class="o">(</span><span class="n">price</span><span class="o">)</span> <span class="o">||</span> <span class="nc">Double</span><span class="o">.</span><span class="na">isInfinite</span><span class="o">(</span><span class="n">price</span><span class="o">))</span> <span class="o">{</span>
    <span class="c1">// shrug, return 0?</span>
<span class="o">}</span></code></pre></figure>

<p>This is what I have come to think of as Checkpoint Syndrome, and the problem with it goes well beyond the visual noise. It is something closer to a small architectural disaster: the checks have a way of spreading everywhere, and yet, despite their ubiquity, they almost never point at the real cause of the bug, because by the time NaN reaches the function being checked, the operation that originally produced it is several layers upstream and is no longer visible at the call site. Each defensive site is also forced to make its own decision about what to do when the check fails, and those decisions tend to drift apart over time, so a single class of upstream bug ends up being silently handled in a dozen inconsistent ways throughout the codebase.</p>

<p>Worst of all — and this is where Checkpoint Syndrome most reliably produces actual financial bugs — the easiest “fix” at any individual site is to convert the invalid value to zero. That has the convenient property of making the immediate symptom go away, while quietly turning “we do not know” into “definitely zero” in every downstream calculation that follows.</p>

<p>The antidote to all of this is not, as it might first appear, to add more checks. The antidote is to place fewer checks but to place them where they actually matter — at the boundaries where invalid values either enter the system or are first produced, once, deliberately, and with a documented response.</p>

<h2 id="the-core-principle-validate-at-the-edges-compute-in-the-middle">The core principle: validate at the edges, compute in the middle</h2>

<p><img src="/assets/images/2026-01-05-core-principles.png" alt="Diagram showing validation at boundaries and a clean computation core." /></p>

<p>Most NaN outbreaks begin at boundaries:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.	Parsing and deserialization (CSV, JSON, user input, partner payloads)  
2.	Sensor-style data (telemetry, percentages, rates)  
3.	Divide by something that might be zero or missing  
4.	"This should never happen" conversions (and then it happens)  
</code></pre></div></div>

<p>The most useful thing you can do in response to this, in my experience, is to establish a single simple contract that the rest of the system can rely on: inside the computation core, all doubles are finite unless explicitly documented otherwise. The computation core is the part of the system that should be allowed to be blissfully boring — the pure math, the algorithms, the business logic that operates on already-validated inputs — and it is the place where you most want to be able to reason about correctness without simultaneously doing border control.</p>

<p>The practical consequence of that contract is that NaN handling becomes concentrated in a small number of choke points, rather than spread thinly across the codebase.</p>

<h2 id="pattern-1-finite-by-default-as-a-guardrail">Pattern 1: “Finite by default” as a guardrail</h2>

<p>The simplest expression of this discipline is a small helper that asserts finiteness explicitly:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">static</span> <span class="kt">double</span> <span class="nf">requireFinite</span><span class="o">(</span><span class="kt">double</span> <span class="n">x</span><span class="o">,</span> <span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(!</span><span class="nc">Double</span><span class="o">.</span><span class="na">isFinite</span><span class="o">(</span><span class="n">x</span><span class="o">))</span> <span class="o">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="n">name</span> <span class="o">+</span> <span class="s">" must be finite, got "</span> <span class="o">+</span> <span class="n">x</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="n">x</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">Double.isFinite()</code> is, for this purpose, the only check you actually need. It returns <code class="language-plaintext highlighter-rouge">true</code> only when its argument is neither NaN nor Infinity, which is exactly what “a normal, usable number” means in most contexts. There is rarely any value in writing two separate checks for <code class="language-plaintext highlighter-rouge">isNaN</code> and <code class="language-plaintext highlighter-rouge">isInfinite</code> when a single call to <code class="language-plaintext highlighter-rouge">isFinite</code> captures both conditions.</p>

<p>The pattern, then, is to use <code class="language-plaintext highlighter-rouge">requireFinite</code> at public boundaries and at layer transitions, rather than inside every private method:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kt">double</span> <span class="nf">monthlyPayment</span><span class="o">(</span><span class="kt">double</span> <span class="n">principal</span><span class="o">,</span> <span class="kt">double</span> <span class="n">annualRate</span><span class="o">,</span> <span class="kt">int</span> <span class="n">months</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">requireFinite</span><span class="o">(</span><span class="n">principal</span><span class="o">,</span> <span class="s">"principal"</span><span class="o">);</span>
    <span class="n">requireFinite</span><span class="o">(</span><span class="n">annualRate</span><span class="o">,</span> <span class="s">"annualRate"</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">months</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="o">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">IllegalArgumentException</span><span class="o">(</span><span class="s">"months must be positive"</span><span class="o">);</span>

    <span class="kt">double</span> <span class="n">r</span> <span class="o">=</span> <span class="n">annualRate</span> <span class="o">/</span> <span class="mf">12.0</span><span class="o">;</span>

    <span class="c1">// Standard annuity formula:</span>
    <span class="c1">//   P = L * [r(1+r)^n] / [(1+r)^n - 1]</span>
    <span class="c1">// Rewritten with a negative exponent:</span>
    <span class="c1">//   P = L * r / (1 - (1+r)^(-n))</span>
    <span class="c1">// Same math, often friendlier numerically for large n.</span>
    <span class="k">return</span> <span class="n">principal</span> <span class="o">*</span> <span class="n">r</span> <span class="o">/</span> <span class="o">(</span><span class="mf">1.0</span> <span class="o">-</span> <span class="nc">Math</span><span class="o">.</span><span class="na">pow</span><span class="o">(</span><span class="mf">1.0</span> <span class="o">+</span> <span class="n">r</span><span class="o">,</span> <span class="o">-</span><span class="n">months</span><span class="o">));</span>
<span class="o">}</span></code></pre></figure>

<p>What you get from this approach is, I think, three things worth having at the same time. The failure becomes loud and early, rather than silent and downstream. The body of the computation stays free of validation noise. And when the exception does fire, it points at a real contract violation — a specific named argument coming in non-finite — rather than at some mysterious symptom many layers later in the call stack.</p>

<h2 id="pattern-2-separate-invalid-from-zero-with-a-result-type">Pattern 2: Separate “invalid” from “zero” with a result type</h2>

<p>There are situations in which you cannot reasonably throw — typically because the caller is processing a stream of inputs and needs to continue regardless of whether any individual one is valid, while still being able to tell which ones were not. In those situations, the right move is usually to represent the distinction explicitly in the return type, rather than overloading a numeric value to mean both “result” and “no result.”</p>

<p>In Java, you can use a sealed interface to construct a true disjunctive type, of which I have written a <a href="https://systemhalted.in/2025/11/25/disjuntive-types/">longer post</a> for the full story:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">sealed</span> <span class="kd">interface</span> <span class="nc">CalcResult</span> <span class="n">permits</span> <span class="nc">CalcResult</span><span class="o">.</span><span class="na">Valid</span><span class="o">,</span> <span class="nc">CalcResult</span><span class="o">.</span><span class="na">Invalid</span> <span class="o">{</span>
    
    <span class="kd">record</span> <span class="nf">Valid</span><span class="o">(</span><span class="kt">double</span> <span class="n">value</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">CalcResult</span> <span class="o">{</span>
        <span class="kd">public</span> <span class="nc">Valid</span> <span class="o">{</span>
            <span class="c1">// Enforce finite values in Valid variant</span>
            <span class="k">if</span> <span class="o">(!</span><span class="nc">Double</span><span class="o">.</span><span class="na">isFinite</span><span class="o">(</span><span class="n">value</span><span class="o">))</span> <span class="o">{</span>
                <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Valid result must be finite"</span><span class="o">);</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>
    
    <span class="kd">record</span> <span class="nf">Invalid</span><span class="o">(</span><span class="nc">String</span> <span class="n">reason</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">CalcResult</span> <span class="o">{}</span>
    
    <span class="c1">// Convenience factory methods</span>
    <span class="kd">static</span> <span class="nc">Valid</span> <span class="nf">ok</span><span class="o">(</span><span class="kt">double</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">Valid</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
    <span class="o">}</span>
    
    <span class="kd">static</span> <span class="nc">Invalid</span> <span class="nf">failed</span><span class="o">(</span><span class="nc">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">Invalid</span><span class="o">(</span><span class="n">reason</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>With a type of this shape in place, NaN is no longer a stealth signal hiding inside a numeric channel. An invalid result is a first-class outcome of its own, the <code class="language-plaintext highlighter-rouge">Valid</code> variant cannot be constructed with a non-finite value, and the compiler can be made to enforce exhaustive handling at every consumption site.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">CalcResult</span> <span class="nf">safeDivide</span><span class="o">(</span><span class="kt">double</span> <span class="n">a</span><span class="o">,</span> <span class="kt">double</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(!</span><span class="nc">Double</span><span class="o">.</span><span class="na">isFinite</span><span class="o">(</span><span class="n">a</span><span class="o">)</span> <span class="o">||</span> <span class="o">!</span><span class="nc">Double</span><span class="o">.</span><span class="na">isFinite</span><span class="o">(</span><span class="n">b</span><span class="o">))</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">CalcResult</span><span class="o">.</span><span class="na">failed</span><span class="o">(</span><span class="s">"non-finite input"</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">b</span> <span class="o">==</span> <span class="mf">0.0</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">CalcResult</span><span class="o">.</span><span class="na">failed</span><span class="o">(</span><span class="s">"division by zero"</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="nc">CalcResult</span><span class="o">.</span><span class="na">ok</span><span class="o">(</span><span class="n">a</span> <span class="o">/</span> <span class="n">b</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// Pattern matching is exhaustive - compiler forces you to handle both cases</span>
<span class="nc">CalcResult</span> <span class="n">result</span> <span class="o">=</span> <span class="n">safeDivide</span><span class="o">(</span><span class="mf">10.0</span><span class="o">,</span> <span class="mf">2.0</span><span class="o">);</span>
<span class="k">switch</span> <span class="o">(</span><span class="n">result</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">case</span> <span class="nc">CalcResult</span><span class="o">.</span><span class="na">Valid</span><span class="o">(</span><span class="kt">double</span> <span class="n">v</span><span class="o">)</span> <span class="o">-&gt;</span> 
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Result: "</span> <span class="o">+</span> <span class="n">v</span><span class="o">);</span>
    <span class="k">case</span> <span class="nc">CalcResult</span><span class="o">.</span><span class="na">Invalid</span><span class="o">(</span><span class="nc">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">-&gt;</span> 
        <span class="nc">System</span><span class="o">.</span><span class="na">err</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Failed: "</span> <span class="o">+</span> <span class="n">reason</span><span class="o">);</span>
    <span class="c1">// No default needed - this is exhaustive</span>
<span class="o">}</span></code></pre></figure>

<p>The exact shape of the type is, in the end, less important than the discipline it expresses. What matters is that “invalid” is treated as a first-class outcome that the type system insists be handled, rather than as a special value that callers can quietly forget to check for.</p>

<h2 id="pattern-3-domain-types-that-make-nan-impossible">Pattern 3: Domain types that make NaN impossible</h2>

<p>It is also worth observing that many of the doubles in business systems are not really “real numbers in the wild.” They are money, rates, counts, durations, and percentages — values which have far more structure than <code class="language-plaintext highlighter-rouge">double</code> is capable of representing, and which therefore tend to make poor candidates for raw <code class="language-plaintext highlighter-rouge">double</code> storage in the first place.</p>

<p>A few examples of the better choice in each case:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.	Money: store cents as long, or use BigDecimal where precision matters  
2.	Counts: long or int  
3.	Percentages: maybe basis points as int (one basis point = 0.01%, so 550 bps = 5.50%)  
4.	Durations: java.time.Duration  
</code></pre></div></div>

<p>Tightening the types in this way reduces the surface area on which NaN can even appear in the first place. This is, in the end, the most reliable form of NaN defence available, and it has the additional virtue of being entirely structural: it is enforced by the compiler, rather than by the discipline of the next person to touch the code. A <code class="language-plaintext highlighter-rouge">long</code>, after all, simply cannot hold NaN — the language will not allow it.</p>

<h2 id="pattern-4-decide-where-infinity-is-acceptable">Pattern 4: Decide where Infinity is acceptable</h2>

<p>Part 5 of this series covered Infinity and signed zero in some detail. The practical question, at this stage, is what to do about Infinity in everyday code, and the answer turns mostly on whether your domain has any legitimate use for it.</p>

<p>Infinity is sometimes a meaningful signal:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.	"Unlimited" limit  
2.	"Unbounded" score  
3.	A mathematical asymptote that you intentionally model  
</code></pre></div></div>

<p>If your domain does not explicitly accept Infinity as a meaningful value, however, the right thing to do is to treat it exactly as you would treat NaN — reject it at the boundary, using <code class="language-plaintext highlighter-rouge">Double.isFinite()</code>. This is precisely why the <code class="language-plaintext highlighter-rouge">requireFinite</code> helper above checks for finiteness rather than for <code class="language-plaintext highlighter-rouge">isNaN</code> alone: it enforces the rule “no NaN, no Infinity, full stop” in a single line. In the rare cases in which you genuinely need to distinguish between NaN and Infinity, you can still do so by checking each separately, but for the great majority of code, the only useful distinction is finite versus not-finite.</p>

<p>The bug pattern to be alert to is the case where Infinity is tolerated accidentally — quietly admitted into the computation core because no one thought to reject it — and is then multiplied or scaled into a very large number that happens to look plausible at a glance. That is, in effect, how nonsense becomes confident-looking nonsense, and it is harder to catch downstream than NaN, because at least NaN propagates obviously.</p>

<h2 id="pattern-5-centralize-sanitization-but-do-not-lie">Pattern 5: Centralize sanitization, but do not lie</h2>

<p>There are also situations in which sanitization is genuinely required, particularly when dealing with messy external data over which you have no control. The two principles I would offer, having been bitten by both, are: do the sanitization once, in a centralised place, and be honest about what you actually did.</p>

<p>The dangerous form of sanitization is the one that looks reasonable at first glance:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">safe</span> <span class="o">=</span> <span class="nc">Double</span><span class="o">.</span><span class="na">isFinite</span><span class="o">(</span><span class="n">x</span><span class="o">)</span> <span class="o">?</span> <span class="n">x</span> <span class="o">:</span> <span class="mf">0.0</span><span class="o">;</span></code></pre></figure>

<p>The problem with this pattern is that it converts “we do not know” into “definitely zero”, and zero is rarely a neutral value in any system that does arithmetic on it. A safer set of options is to either drop the offending datapoint outright (and to count it, so the volume of dropped values is observable), to mark the result as invalid using one of the result types described above, or to fall back to a documented default that has actual meaning in the domain — and, in any case, to log what happened so that the upstream cause can eventually be addressed.</p>

<p>A common example in aggregation is something like this:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="nf">averageFinite</span><span class="o">(</span><span class="kt">double</span><span class="o">[]</span> <span class="n">xs</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">double</span> <span class="n">sum</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">;</span>
    <span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>

    <span class="k">for</span> <span class="o">(</span><span class="kt">double</span> <span class="n">x</span> <span class="o">:</span> <span class="n">xs</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">isFinite</span><span class="o">(</span><span class="n">x</span><span class="o">))</span> <span class="o">{</span>
            <span class="n">sum</span> <span class="o">+=</span> <span class="n">x</span><span class="o">;</span>
            <span class="n">n</span><span class="o">++;</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="c1">// Honest answer: no valid data means undefined result.</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">n</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="k">return</span> <span class="nc">Double</span><span class="o">.</span><span class="na">NaN</span><span class="o">;</span>

    <span class="k">return</span> <span class="n">sum</span> <span class="o">/</span> <span class="n">n</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<p>The honesty of the function consists in the last detail: when there is no valid data to average, the function returns NaN rather than zero. NaN is, in this case, the right answer — it is exactly what “undefined” means — and silently substituting zero would, once again, be a way of pretending to know something the function actually does not.</p>

<h2 id="third-party-libraries-when-nan-arrives-by-mail">Third-party libraries: when NaN arrives by mail</h2>

<p>Not every NaN is born in your own code. Some of them are delivered.</p>

<p>The most common cause of imported NaN is calling a math function that can legitimately return NaN for part of its domain — <code class="language-plaintext highlighter-rouge">Math.sqrt</code>, <code class="language-plaintext highlighter-rouge">Math.log</code>, and so on — and then forgetting that the moment you call out to such a function you have, in effect, crossed a boundary again, and the same boundary discipline applies. The fix is to validate the result immediately, at the point of crossing, while you still have full context about what was being computed:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">result</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">sqrt</span><span class="o">(</span><span class="n">userInput</span><span class="o">);</span>

<span class="c1">// JDK math functions and third-party libs can return NaN.</span>
<span class="c1">// Validate the output right away, at the point of crossing.</span>
<span class="n">requireFinite</span><span class="o">(</span><span class="n">result</span><span class="o">,</span> <span class="s">"sqrt result"</span><span class="o">);</span></code></pre></figure>

<p>The pattern generalises beyond JDK math: any time you call out to code you do not control — libraries, services, model inference endpoints, partner data feeds — the return value is best treated as a fresh boundary, with the same kind of validation you would apply to any other external input. The principle being preserved here is simply that the computation core gets to assume validated inputs, and any time something crosses into the core from elsewhere, the responsibility for validation falls on the crossing point.</p>

<h2 id="finding-the-first-nan-not-the-last-one">Finding the first NaN, not the last one</h2>

<p>There is a particular tragedy that tends to play out in long-lived systems, in which NaN is only detected at the very end of a long chain of transformations — in a report, on a dashboard, or in a downstream consumer — and the team then has to work backwards through ten transformations to find the operation that originally produced it. By that point, the NaN you are looking at is the smoke; the fire was several layers upstream, and the hard part of the bug is reconstructing how the smoke got to where it is now.</p>

<p>Two practical habits help with this:</p>

<h3 id="add-tripwire-assertions-in-debug-builds">Add “tripwire assertions” in debug builds</h3>

<p>In places where NaN should never legitimately exist, it is worth asserting that fact explicitly during development and in tests, so that the first appearance of NaN fails loudly rather than silently propagating:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">static</span> <span class="kt">void</span> <span class="nf">assertFinite</span><span class="o">(</span><span class="kt">double</span> <span class="n">x</span><span class="o">,</span> <span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(!</span><span class="nc">Double</span><span class="o">.</span><span class="na">isFinite</span><span class="o">(</span><span class="n">x</span><span class="o">))</span> <span class="o">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">AssertionError</span><span class="o">(</span><span class="n">name</span> <span class="o">+</span> <span class="s">" became non-finite: "</span> <span class="o">+</span> <span class="n">x</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>This is particularly worthwhile after major computation steps in numerically sensitive algorithms, where the cost of the assertion is negligible and the benefit of catching the first bad value early is considerable.</p>

<h3 id="log-with-context-once-not-everywhere">Log with context once, not everywhere</h3>

<p>If the system needs observability into NaN-related rejections — and most production systems eventually do — the right place for that observability is at the boundary where the rejection happens, because that is where the original context still exists. An input adapter is a good example:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="nf">parseRate</span><span class="o">(</span><span class="nc">String</span> <span class="n">raw</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">double</span> <span class="n">x</span> <span class="o">=</span> <span class="nc">Double</span><span class="o">.</span><span class="na">parseDouble</span><span class="o">(</span><span class="n">raw</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(!</span><span class="nc">Double</span><span class="o">.</span><span class="na">isFinite</span><span class="o">(</span><span class="n">x</span><span class="o">))</span> <span class="o">{</span>
        <span class="c1">// log raw payload id, customer id, partner id, etc.</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"rate must be finite"</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="n">x</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<p>This is the point at which you still have access to the raw payload, the source identity, and any correlating identifiers; once the value has flowed through several layers of the application, all of that context tends to be lost. Logging at the boundary, rather than at the point of eventual symptom, is therefore both cheaper and more useful.</p>

<h2 id="a-small-nan-hygiene-checklist">A small NaN hygiene checklist</h2>

<p>By way of summary, the practices that have served me well in dealing with NaN in long-running systems are roughly these:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.	Use Double.isFinite at boundaries where values enter your system or cross layers
2.	Keep computation code clean; assume finite inputs inside the core
3.	Do not convert invalid to zero unless the domain definition says it is correct
4.	Prefer domain types over raw doubles when the value is not truly "a real number"
5.	When you must degrade gracefully, return an explicit invalid result, not a silent sentinel
6.	Instrument the boundary where you reject or drop invalids, so you can find the upstream cause
</code></pre></div></div>

<p>NaN, taken on its own terms, is not really an enemy. It is the floating-point system’s way of telling you that something invalid happened upstream and that the math, in good faith, could not produce a real-number answer. The defensive task is therefore not to suppress that signal everywhere it appears, but to listen to it where it first appears, deal with it deliberately at the boundary, and let the rest of the system rely on the contract that the boundary enforces. Validate at the edges, keep the core clean, and resist, as steadily as you can, the temptation to silently turn unknown into zero.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Computer Science" /><category term="Software Engineering" /><category term="Technology" /><category term="Series 4 - Floating Point Without Tears" /><category term="java" /><category term="floating-point" /><category term="ieee-754" /><category term="nan" /><category term="validation" /><category term="reliability" /><summary type="html"><![CDATA[Learn how to handle NaN and Infinity in Java without scattering if (isNaN) checks everywhere. Five practical patterns: boundary validation with Double.isFinite(), result types, domain types, centralized sanitization, and detecting NaN at its source. Avoid Checkpoint Syndrome and keep your computation code clean.]]></summary></entry><entry><title type="html">A quiet rebuild: assets, webcmd, and accessibility</title><link href="https://systemhalted.in/2026/01/03/quiet-rebuild-assets-webcmd-accessibility/" rel="alternate" type="text/html" title="A quiet rebuild: assets, webcmd, and accessibility" /><published>2026-01-03T00:00:00+00:00</published><updated>2026-01-03T00:00:00+00:00</updated><id>https://systemhalted.in/2026/01/03/quiet-rebuild-assets-webcmd-accessibility</id><content type="html" xml:base="https://systemhalted.in/2026/01/03/quiet-rebuild-assets-webcmd-accessibility/"><![CDATA[<p>Over the last day or so, I made a round of changes to this site that will not look like much from the outside, but that I think materially improve how it loads, how it behaves, and how it feels to use — especially for readers who navigate by keyboard or rely on assistive technology. None of this is glamorous work, but the kind of unglamorous maintenance that quietly removes friction is, in my experience, often the thing that makes a site usable over the long run. What follows is a short walkthrough of what changed and why I thought it was worth doing.</p>

<h2 id="1-consolidated-assets-into-one-place">1) Consolidated assets into one place</h2>

<p>The site had grown to keep static files in two different locations — <code class="language-plaintext highlighter-rouge">public/</code> and <code class="language-plaintext highlighter-rouge">assets/</code> — and the inconsistency had begun to cause exactly the sort of small problems that consistency is supposed to prevent: occasional broken paths, references that pointed to the wrong directory, and a small but real amount of cognitive overhead every time I had to remember which folder a particular file lived in.</p>

<p>I have now merged everything into <code class="language-plaintext highlighter-rouge">assets/</code>. CSS, JavaScript, and the favicon files have all moved over from <code class="language-plaintext highlighter-rouge">public/</code>, every template has been updated to reference <code class="language-plaintext highlighter-rouge">/assets/...</code>, and the web manifest along with the webcmd page have been corrected to match. The reader-facing benefit is mostly negative — fewer broken resources where there is no good reason for any to break — but the structural simplification also makes future maintenance easier, which I expect will matter more over time than any single fix in this round.</p>

<h2 id="2-modernized-the-favicon-setup">2) Modernized the favicon setup</h2>

<p>While I was tidying up <code class="language-plaintext highlighter-rouge">assets/</code>, I also took the opportunity to modernise the favicon. The site now serves an SVG favicon as the primary asset, with the old ICO file still in place as a fallback for older browsers that do not yet handle SVG icons gracefully. The <code class="language-plaintext highlighter-rouge">&lt;link rel="icon"&gt;</code> tags in the templates were updated accordingly. SVG is crisp at any size and looks visibly sharper on modern displays, while the ICO fallback preserves compatibility for the small but persistent set of browsers that still need it.</p>

<h2 id="3-webcmd-was-modernised">3) Webcmd was modernised</h2>

<p>The <code class="language-plaintext highlighter-rouge">/webcmd/</code> page is still the command-line interface to the site that it always was, but it had drifted out of sync with the rest of the site in a few small ways. It is now using the default site layout and theme, which means it inherits the Nord palette and reads consistently with everything else. The help output, which was previously a wall of text, is now properly semantic — lists and headings rather than line-by-line plaintext — which makes it easier to scan and renders in two columns on desktop while collapsing sensibly on mobile.</p>

<p>A couple of smaller fixes also went in. The <code class="language-plaintext highlighter-rouge">find</code> command, which had quietly been listed under the wrong section, is now grouped with the other Searches commands where it belongs. The navigation commands (<code class="language-plaintext highlighter-rouge">ph</code>, <code class="language-plaintext highlighter-rouge">p</code>, <code class="language-plaintext highlighter-rouge">pi</code>, <code class="language-plaintext highlighter-rouge">pr</code>) now use relative URLs, which means they continue to work as expected when I am testing locally on <code class="language-plaintext highlighter-rouge">localhost</code> — previously they would silently rewrite to the production host, which made local testing more annoying than it had any reason to be.</p>

<h2 id="4-accessibility-pass-wcag-oriented">4) Accessibility pass (WCAG-oriented)</h2>

<p>I also did a focused accessibility sweep across the main site templates, with WCAG 2.1 AA as the target — the <code class="language-plaintext highlighter-rouge">jsgames/</code> directory is excluded for now, since it is a different problem with a different set of constraints.</p>

<p>The work was less about adding any one big feature and more about closing a number of small gaps. There is now a “Skip to content” link at the top of every page, and a proper <code class="language-plaintext highlighter-rouge">&lt;main&gt;</code> landmark wrapping the primary content, both of which make keyboard and screen-reader navigation considerably faster. The sidebar toggle correctly updates <code class="language-plaintext highlighter-rouge">aria-expanded</code> and is now operable from the keyboard rather than only by mouse. The search overlay now traps focus while it is open and restores focus to the original element when it is closed, which is what a modal dialog should do but which my old implementation was not doing. The “Annotate me” control, which had been a clickable <code class="language-plaintext highlighter-rouge">&lt;span&gt;</code> for historical reasons, is now a real <code class="language-plaintext highlighter-rouge">&lt;button&gt;</code>. And the focus styles across the site have been tightened up so that keyboard users can actually see where they are at any given time.</p>

<p>The cumulative effect, for readers using assistive technology, should be noticeably more predictable behaviour throughout the site, and a meaningful reduction in the number of places where keyboard navigation hits an unexpected dead end.</p>

<h2 id="5-reduced-disqus-noise-on-non-post-pages">5) Reduced Disqus noise on non-post pages</h2>

<p>Disqus comment-count scripts were, until this round, being loaded on every page of the site, including pages that do not and never will have comments. They are now only loaded on posts that explicitly allow comments. This means there is less third-party JavaScript running on the home page, archive pages, and other non-post views, which both improves page-load performance modestly and reduces the slightly disorienting experience of seeing comment-related widgets briefly flicker on pages that have no comments to count.</p>

<h2 id="6-new-documentation">6) New documentation</h2>

<p>Finally, in the interest of keeping all of the above sustainable rather than letting it bit-rot, I wrote a couple of internal documents to capture the conventions I have adopted. <code class="language-plaintext highlighter-rouge">docs/webcmd.md</code> explains how the command engine works and how to add new commands without re-learning the system every time. <code class="language-plaintext highlighter-rouge">docs/accessibility.md</code> captures the accessibility conventions I am now trying to maintain, along with a small checklist of things to verify when adding new templates. The README has been updated to point to both, so that next-me, or anyone else who eventually pokes at the codebase, has a fighting chance of doing it correctly.</p>

<hr />

<h3 id="in-short">In short</h3>

<p>Taken individually, none of these changes is a headline. Taken together, however, they leave the site noticeably more consistent, more accessible, and easier to extend — which is, I think, what most maintenance work ought to look like when it is going well.</p>

<p>If you notice anything that looks broken or behaves oddly after this round of changes, please do let me know. Otherwise, I will keep working through the small stuff in the background, on the assumption that the cumulative result of doing so over time tends to be worth more than any single big feature would have been.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Personal Essays" /><category term="Technology" /><category term="jekyll" /><category term="webcmd" /><category term="accessibility" /><category term="ux" /><category term="maintenance" /><summary type="html"><![CDATA[Over the last day or so, I made a round of changes to this site that will not look like much from the outside, but that I think materially improve how it loads, how it behaves, and how it feels to use — especially for readers who navigate by keyboard or rely on assistive technology. None of this is glamorous work, but the kind of unglamorous maintenance that quietly removes friction is, in my experience, often the thing that makes a site usable over the long run. What follows is a short walkthrough of what changed and why I thought it was worth doing.]]></summary></entry><entry><title type="html">Revisiting India’s Post-Harvest Supply Chain (2011 → 2026)</title><link href="https://systemhalted.in/2026/01/03/revisiting-post-harvest-supply-chain-2011-2026/" rel="alternate" type="text/html" title="Revisiting India’s Post-Harvest Supply Chain (2011 → 2026)" /><published>2026-01-03T00:00:00+00:00</published><updated>2026-01-03T00:00:00+00:00</updated><id>https://systemhalted.in/2026/01/03/revisiting-post-harvest-supply-chain-2011-2026</id><content type="html" xml:base="https://systemhalted.in/2026/01/03/revisiting-post-harvest-supply-chain-2011-2026/"><![CDATA[<p>In April 2011, I wrote a short post pointing at a problem that has stayed with me ever since: India’s price signals tend to die on the way back to the farm. The original post is still up at <a href="https://systemhalted.in/2011/04/26/whats-wrong-with-our-post-harvest-agricultural-supply-chain/">What’s wrong with our post-harvest agricultural supply chain?</a>, and it was, in honesty, more of a finger pointed at a wound than a worked argument.</p>

<p>The wound it pointed at had been described well in an <em>Economic Times</em> editorial titled <a href="https://economictimes.indiatimes.com/opinion/et-editorial/the-pm-gets-it-right/articleshow/7499198.cms">“The PM gets it right” (Feb 15, 2011)</a>. The diagnosis there was simple and, I think, still essentially correct: we had modernised inputs but not marketing, and what existed between the farmer and the consumer was an inefficient chain in which retail price spikes rarely sent any of that upside back to the farm. The editorial was making a policy argument, but underneath it was an economics one — that the marketing layer was, on balance, doing more harm to the producer than the production layer was doing good.</p>

<p>Fifteen years later, I want to revisit that post with newer facts and a clearer view of what has changed and what has not. A fair amount of policy has happened since then, and a fair amount of physical infrastructure has been built; whether either of these has reached the smallest farmer in the chain is the harder question, and the one I want to think about here.</p>

<h2 id="where-the-value-leaks">Where the value leaks</h2>

<p>The way I have come to think about India’s post-harvest problem is as a collision between two things that do not get along: produce that decays quickly, and a chain that coordinates poorly. Most of the country’s marketed agricultural output passes through a long, multi-step path — harvesting, sorting, grading, transport, wholesale, retail — and each step adds time, friction, and another opportunity for the farmer’s bargaining position to deteriorate. Because the produce itself is decaying with every hour spent in the chain, the side with the least leverage, which is almost always the farmer, has the strongest incentive to settle quickly, on whatever terms are available.</p>

<p>Four chokepoints, in particular, decide how much value reaches the producer. The first is <strong>time</strong>, in the most physical sense: perishable produce punishes delay, and any link in the chain that adds hours adds losses. The second is <strong>scale</strong>, because a single farmer with a single field has very little leverage when negotiating with aggregators, traders, or retail buyers, while a collective with many fields and a shared facility has meaningfully more. The third is <strong>credit</strong>, since being able to wait — to not sell on the day of harvest at whatever price the market is offering — is fundamentally a financial capability, and a farmer without access to short-term finance is, in practice, a price-taker. The fourth is <strong>information</strong>, because opaque price signals get manipulated, and even where transparency exists it is uneven across mandis, states, and crops.</p>

<p>Back in 2011, I quoted a line from that editorial that I think is still worth repeating, because it captures all four of these in a single sentence: the linkage between the farmer and the consumer is inefficient, wasteful, and subject to manipulation, so shortages trigger hoarding and price spikes at the consumer end without sending those higher prices back to the farmer. The economic point underneath that sentence is unkind but accurate. When the price signal does not reach the farm, the incentive to invest in better acreage, better inputs, or better husbandry erodes, and the long-run consequence is lower productivity than we should otherwise be capable of.</p>

<p>So what has actually changed between 2011 and 2026? There is enough to take seriously, and not enough yet to be complacent about. The next sections walk through what I think are the five most consequential changes — what each is, what it has and has not done, and where it sits relative to the chokepoints described above.</p>

<p>If you grant the four chokepoints described above, then it follows that perishables are not really being bought and sold in the way other goods are; they are being raced against spoilage, and most of what we call the “post-harvest system” is really an attempt to manage that race. Cold chain, warehousing, credit, and transparent markets are, on close inspection, all doing the same underlying work — they are buying time, either physically by slowing decay or financially by reducing the pressure to sell at any price today.</p>

<p>That, viewed in this way, is the main story of India’s post-harvest sector since 2011. We have begun, at significant scale though still unevenly, to build and to finance time. The five sections that follow are the five places where I think this is most visible.</p>

<h2 id="change-1-we-started-paying-for-the-boring-parts">Change 1: We started paying for the boring parts</h2>

<p>The Agriculture Infrastructure Fund (AIF) is a government financing facility launched in 2020-21 to support post-harvest and farm-gate infrastructure through interest subvention and credit guarantee support. The instrument is worth dwelling on for a moment, because the choice to attack the problem with a financing facility — rather than with another round of input subsidy — is itself a quiet acknowledgement that the bottleneck has shifted from the farm to everything that happens after it.</p>

<p>As of June 30, 2025, the Press Information Bureau (PIB) reported ₹66,310 crore sanctioned under AIF for 1,13,419 projects, mobilising ₹1,07,502 crore of investment, including 2,454 cold storage projects.</p>

<p>This is not a philosophical shift so much as a cash-flow one, and supply chains, for all their physical hardware, ultimately run on cash flow. If a sorting yard, a small cold room, or a pack-house cannot be built because the cooperative or entrepreneur behind it cannot get five-year money on reasonable terms, the fact that we know cold chain is good for the country is of no use to anyone.</p>

<h2 id="change-2-cold-chain-scaled-and-policy-got-more-specific">Change 2: Cold chain scaled, and policy got more specific</h2>

<p>The Ministry of Food Processing Industries (MoFPI) runs the Integrated Cold Chain and Value Addition Infrastructure (ICCVAI) scheme under the Pradhan Mantri Kisan Sampada Yojana (PMKSY).</p>

<p>As of June 2025, 395 integrated cold chain projects had been approved since 2008, with 291 operational, creating preservation capacity of 25.52 lakh metric tonnes (LMT) per year and processing capacity of 114.66 LMT per year. MoFPI also notes a key policy shift in June 2022: support for fruit and vegetable cold chain projects under that scheme component was discontinued, and the sector was shifted to Operation Greens.</p>

<p>In plain terms, the government has stopped pretending that every crop has the same bottlenecks. Grains, oilseeds, fruits, and vegetables have very different storage curves, very different price cycles, and very different value chains, and a single instrument was never going to serve all of them well. Splitting fruits and vegetables out into a more specialised programme is overdue, and is the sort of unglamorous, mid-level policy adjustment that does not make headlines but probably matters more than most things that do.</p>

<p>For a farmer growing perishables, cold chain is what turns the choice from “sell today or lose everything” into “sell when the price is reasonable” — a small change in framing that completely changes the bargaining position of the producer, and is, in fact, the most direct attack on the perishability chokepoint that any policy lever can deliver.</p>

<h2 id="change-3-markets-became-more-digital-and-more-visible">Change 3: Markets became more digital, and more visible</h2>

<p>The electronic National Agriculture Market (e-NAM) integrates regulated wholesale markets (mandis) into an online trading platform.</p>

<p>PIB reported that 1,522 mandis were integrated as of June 30, 2025, with 1,79,41,613 farmers and 4,518 Farmer Producer Organisations (FPOs) registered, and total traded value of ₹4,39,941 crore recorded on the platform.</p>

<p>If 2011 was about price signals being weak, then e-NAM is the most credible attempt to date to strengthen them. A digital common market, in a country with the geographical and linguistic spread of India, matters more than its physical equivalent ever could, because it lets a buyer in one state discover a price in another without either of them needing to travel, and because it exposes the spread between mandis to the producer in a way that previously sat with traders alone.</p>

<p>It is worth being honest about what this can and cannot do, however. A stronger price signal does not automatically fix the physical chain underneath it. A dashboard, however well-designed, cannot refrigerate produce, and digital trading does not, by itself, move a truck across a state line. What e-NAM is good at is reducing one specific chokepoint — information — and that is enough to make it worthwhile, but it cannot reasonably be expected to do work that other parts of the system have to do.</p>

<h2 id="change-4-farmer-collectivisation-moved-from-slogan-to-infrastructure">Change 4: Farmer collectivisation moved from slogan to infrastructure</h2>

<p>A Farmer Producer Organisation (FPO) is a farmer collective that can aggregate produce, negotiate, and invest in shared capabilities.</p>

<p>The “Formation and Promotion of 10,000 FPOs” scheme hit the 10,000 milestone by Feb 2025, according to PIB. The same release describes equity grants and credit guarantee cover supporting thousands of FPOs.</p>

<p>This matters more than the headline number suggests, because it is the most direct assault on the scale chokepoint described earlier. A single farmer is, by virtue of being one farmer, forced to accept the chain as it is — they cannot afford a sorting line, they cannot finance a cold room, and they cannot threaten to walk away from a buyer because they have nowhere else for their crop to go. A collective changes all three of those conditions simultaneously: it can build shared infrastructure that no individual member could afford, it can absorb buyer-side delays because it is not financially fragile in the same way, and it can negotiate as a credible counterparty rather than as a price-taker.</p>

<p>Produce handled by an FPO can therefore be graded, packed, stored, and sold with leverage that no individual farmer would have on their own — same crop, same season, but a meaningfully different position in the chain.</p>

<h2 id="change-5-storage-plus-credit-got-sharper">Change 5: Storage plus credit got sharper</h2>

<p>If, in 2011, the farmer’s biggest enemy was forced timing, then by 2026 the problem has slowly mutated into a different one: whether waiting can be financed at all. Storage, in physical terms, has been built; what was missing for a long time was a way to convert that storage into liquidity for the farmer who owned the produce sitting in it.</p>

<p>The Warehousing Development and Regulatory Authority (WDRA) oversees warehousing regulation and electronic warehouse receipt systems, and a Parliamentary Standing Committee report notes the steady growth of pledge finance against electronic Negotiable Warehouse Receipts (eNWRs). The same report notes the launch on 04-03-2024 of e-Kisan Upaj Nidhi, a digital gateway developed by WDRA in association with NABARD (National Bank for Agriculture and Rural Development) and a task force in SBI (State Bank of India), to connect eNWR with onboarded banks.</p>

<p>Of all the changes since 2011, this is the most direct antidote to the original problem, because if a farmer can store and borrow against the stored produce, they are no longer forced to sell at the worst possible moment. The eNWR-plus-credit stack is, in effect, the financial glue that makes the physical investment in cold chain and warehousing actually reach the producer rather than the trader.</p>

<h2 id="the-uncomfortable-part-loss-is-still-huge">The uncomfortable part: loss is still huge</h2>

<p>Even with better financing, more cold chain, more digital markets, and stronger farmer collectives, the scale of loss in the system remains uncomfortably large.</p>

<p>A 2024 policy brief by the Indian Council for Research on International Economic Relations (ICRIER) cites a NABARD Consultancy Services (NABCONS) 2020-2022 study estimating food loss in India at about ₹1.53 trillion (USD 18.5 billion) annually due to post-harvest losses (PHL). The brief also makes the broader argument that, on the margin, reducing PHL is often more cost-effective than producing more food only to lose more of it.</p>

<p>So we have, in fairness, built much of the scaffolding that the 2011 critique implicitly demanded. Yet we are still losing value at industrial scale, and the gap between scaffolding and outcomes is where the next decade of work has to happen — not in announcing new schemes, but in making the schemes that already exist actually reach the smallest producer in the chain.</p>

<h2 id="a-2026-reading">A 2026 reading</h2>

<p>The 2011 editorial argued that policy must take marketing as seriously as production. I agreed with that then, and I still agree with it now, but with a slight update to the framing. India’s post-harvest problem is best understood as a systems problem, in which incentives, physics, and finance combine — usually against the weakest player in the chain. It is rarely a single villain that captures the value; it is the cumulative friction of a chain in which every link is a little tilted against the producer.</p>

<p>Each of the four chokepoints I named earlier maps, more or less, onto one of the policy levers that has emerged in the years since 2011. Perishability — the time problem — is being addressed by cold chain investment under ICCVAI/PMKSY and, increasingly, by Operation Greens. The scale problem is being addressed by the FPO programme and the slow consolidation of farmer collectives. The credit problem is being addressed by the AIF on the infrastructure side and by the WDRA’s eNWR-plus-e-Kisan Upaj Nidhi stack on the working-capital side. And the information problem is being addressed, imperfectly but steadily, by e-NAM. None of these levers is a complete solution on its own, but together they describe a more coherent post-harvest stack than India had at any point before 2011.</p>

<p>The honest test, however, is not whether the schemes exist; it is whether the smallest farmer in the chain still has to make the panic-sale choice on the day of harvest. As long as the answer for too many of them is yes, the work the 2011 editorial called for is unfinished, regardless of how good the dashboards look. If, fifteen years from now, the farmer finally has the same thing the consumer has had all along — a real choice about when and to whom to sell — then the work begun in 2020-21 will have done what it set out to do. We are not there yet, but for the first time in a long while, we are at least pointed in the right direction.</p>

<hr />

<h2 id="references-links">References (links)</h2>

<ol>
  <li>
    <p>Original 2011 post on SystemHalted:<br />
<a href="https://systemhalted.in/2011/04/26/whats-wrong-with-our-post-harvest-agricultural-supply-chain/">https://systemhalted.in/2011/04/26/whats-wrong-with-our-post-harvest-agricultural-supply-chain/</a></p>
  </li>
  <li>
    <p>Quoted editorial (2011, original link from my post):<br />
<a href="https://economictimes.indiatimes.com/opinion/et-editorial/the-pm-gets-it-right/articleshow/7499198.cms">https://economictimes.indiatimes.com/opinion/et-editorial/the-pm-gets-it-right/articleshow/7499198.cms</a></p>
  </li>
  <li>
    <p>PIB note on Agriculture Infrastructure Fund (AIF) status (as of 30 June 2025):<br />
<a href="https://www.pib.gov.in/PressNoteDetails.aspx?ModuleId=3&amp;NoteId=154999">https://www.pib.gov.in/PressNoteDetails.aspx?ModuleId=3&amp;NoteId=154999</a></p>
  </li>
  <li>
    <p>PIB release on e-NAM registrations and traded value (as of 30 June 2025):<br />
<a href="https://www.pib.gov.in/PressReleasePage.aspx?PRID=2151361">https://www.pib.gov.in/PressReleasePage.aspx?PRID=2151361</a></p>
  </li>
  <li>
    <p>PIB release on the 10,000 Farmer Producer Organisations (FPOs) milestone (Feb 28, 2025):<br />
<a href="https://pib.gov.in/PressReleasePage.aspx?PRID=2106913">https://pib.gov.in/PressReleasePage.aspx?PRID=2106913</a></p>
  </li>
  <li>
    <p>Integrated Cold Chain and Value Addition Infrastructure (ICCVAI) status note (PDF; includes June 2025 stats):<br />
<a href="https://static.pib.gov.in/WriteReadData/specificdocs/documents/2025/oct/doc20251029679501.pdf">https://static.pib.gov.in/WriteReadData/specificdocs/documents/2025/oct/doc20251029679501.pdf</a></p>
  </li>
  <li>
    <p>Parliamentary Standing Committee report (PDF; mentions eNWR pledge finance and e-Kisan Upaj Nidhi launch):<br />
<a href="https://sansad.in/getFile/lsscommittee/Consumer%20Affairs%2C%20Food%20and%20Public%20Distribution/18_Consumer_Affairs_Food_and_Public_Distribution_2.pdf?source=loksabhadocs">https://sansad.in/getFile/lsscommittee/Consumer%20Affairs%2C%20Food%20and%20Public%20Distribution/18_Consumer_Affairs_Food_and_Public_Distribution_2.pdf?source=loksabhadocs</a></p>
  </li>
  <li>
    <p>ICRIER Policy Brief 20 (PDF) summarising the NABCONS 2020-2022 loss estimate:<br />
<a href="https://icrier.org/pdf/Policy_Brief_20.pdf">https://icrier.org/pdf/Policy_Brief_20.pdf</a></p>
  </li>
  <li>
    <p>WDRA page describing e-Kisan Upaj Nidhi:<br />
<a href="https://wdra.gov.in/web/wdra/e-kisan-upaj-nidhi">https://wdra.gov.in/web/wdra/e-kisan-upaj-nidhi</a></p>
  </li>
</ol>

<hr />

<h2 id="notes">Notes</h2>

<h3 id="abbreviations-introduced-in-this-post">Abbreviations introduced in this post</h3>

<ul>
  <li><strong>AIF</strong>: Agriculture Infrastructure Fund</li>
  <li><strong>PIB</strong>: Press Information Bureau</li>
  <li><strong>MoFPI</strong>: Ministry of Food Processing Industries</li>
  <li><strong>PMKSY</strong>: Pradhan Mantri Kisan Sampada Yojana</li>
  <li><strong>ICCVAI</strong>: Integrated Cold Chain and Value Addition Infrastructure</li>
  <li><strong>LMT</strong>: lakh metric tonnes</li>
  <li><strong>e-NAM</strong>: electronic National Agriculture Market</li>
  <li><strong>Mandi</strong>: regulated wholesale agricultural market</li>
  <li><strong>FPO</strong>: Farmer Producer Organisation</li>
  <li><strong>WDRA</strong>: Warehousing Development and Regulatory Authority</li>
  <li><strong>eNWR</strong>: electronic Negotiable Warehouse Receipt</li>
  <li><strong>NABARD</strong>: National Bank for Agriculture and Rural Development</li>
  <li><strong>SBI</strong>: State Bank of India</li>
  <li><strong>NABCONS</strong>: NABARD Consultancy Services</li>
  <li><strong>ICRIER</strong>: Indian Council for Research on International Economic Relations</li>
  <li><strong>PHL</strong>: post-harvest losses</li>
</ul>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Politics &amp; Governance" /><category term="agriculture" /><category term="india" /><category term="supply-chain" /><category term="post-harvest" /><category term="cold-chain" /><category term="mandis" /><category term="farmers" /><category term="policy" /><summary type="html"><![CDATA[A 2026 revisit of a 2011 note: what has actually changed in India's post-harvest supply chain since 2011, and what still leaks value.]]></summary></entry><entry><title type="html">Hello, 2026: Shipping Hope in Small Commits</title><link href="https://systemhalted.in/2026/01/01/happy-new-year-2026/" rel="alternate" type="text/html" title="Hello, 2026: Shipping Hope in Small Commits" /><published>2026-01-01T00:00:00+00:00</published><updated>2026-01-01T00:00:00+00:00</updated><id>https://systemhalted.in/2026/01/01/happy-new-year-2026</id><content type="html" xml:base="https://systemhalted.in/2026/01/01/happy-new-year-2026/"><![CDATA[<p>Happy New Year. I have always liked New Year more than most calendar events, mostly because it is the one socially acceptable time of year to sit down and rethink your defaults in public. There is something quietly freeing about the fact that no one expects a ticket, a sprint plan, or a roadmap from a New Year reflection — what is asked for, at most, is a little honesty.</p>

<h2 id="what-actually-ran">What actually ran</h2>

<p>A year, in the end, does not care very much about intent. What it does pay attention to is what actually ran. You can mean well for months on end and still ship nothing of real value, and you can equally well ship one small thing each week and find, by the time December comes around, that you have quietly built up a pile of evidence you did not realise you were accumulating.</p>

<p>My goal for 2026, then, is not reinvention. I am old enough now to mistrust grand reinventions, which I have rarely seen survive contact with February. What I want instead is evidence — a steady, unspectacular sequence of small, honest commits, in code and in everything else.</p>

<h2 id="what-i-am-optimizing-for">What I am optimizing for</h2>

<p>What I do not want, on the other hand, is a year that looks impressive from a distance and feels hollow up close. I have had years like that before, and they leave very little behind that is worth keeping. What I want instead is a year in which the work is sturdy, the learning is deliberate, the writing is honest, and my attention — increasingly the scarcest resource of all — is mine to direct, rather than something rented out to whatever happens to be loud at the moment.</p>

<p>I would rather end the year with fewer open loops and more finished sentences than the other way around.</p>

<h2 id="building-without-worshipping-the-build">Building, without worshipping the build</h2>

<p>Engineers, I have noticed, tend to confuse motion for progress. Activity feels like virtue, busy easily passes for important, and the small thrill of being needed in several places at once does a remarkably good job of disguising itself as actual work. I have been guilty of this often enough to recognise it the moment it begins to happen.</p>

<p>This year I want more stillness around the work — less frantic context-switching, and more of the kind of deep, uninterrupted time in which an idea actually becomes real. Real, after all, is the only feature that users can actually use, and it is a feature that almost never emerges from a calendar full of fifteen-minute fragments.</p>

<h2 id="a-small-promise-to-myself">A small promise to myself</h2>

<p>A few specific commitments, in plain English. I want to read on purpose this year, rather than leaving it to the algorithm to decide what I think about. I want my learning to have a map, however rough — a sense of where I am going and why I am going there — and I want to keep writing even on the days when the mood for it has gone elsewhere, because craft, in my experience, is what is left over after motivation has left the room.</p>

<p>I also want to treat health as a prerequisite for all of the above, rather than as something I will get to once the work calms down. Work, I have learned the hard way, never calms down on its own; it has to be either kept in proportion deliberately, or accepted on its own terms.</p>

<p>I want to be ambitious without being brittle, which is to say I want to aim for the kind of progress that survives a bad day, a bad week, and the occasional bad month, rather than the kind of progress that depends on everything going well to even register.</p>

<p>And, on a more concrete note, I want to finally ship Consumption Backlog this year — and an iOS version of it, too, if I can find the time.</p>

<h2 id="closing-thoughts">Closing thoughts</h2>

<p>If you are reading this, my hope for you is that 2026 brings you, in some combination, three things: something to learn that changes how you see the world, someone to love who makes the days a little lighter, and something to build that makes you quietly proud when no one in particular is watching. Those are the three I have come to think matter most, in roughly that order, though the order has a way of shuffling depending on the year.</p>

<p>Happy New Year. To whatever extent one of those three is within reach today, that is probably the right place to start, and small is probably the right size to start at.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Personal Essays" /><category term="new-year" /><category term="reflection" /><category term="software-engineering" /><category term="writing" /><category term="learning" /><summary type="html"><![CDATA[A New Year note: small, consistent commits, and what I want 2026 to actually be.]]></summary></entry><entry><title type="html">Discipline First: A Trust Pipeline for AI-Assisted Coding</title><link href="https://systemhalted.in/2025/12/31/discipline-first-trust-pipeline-for-ai-assisted-coding/" rel="alternate" type="text/html" title="Discipline First: A Trust Pipeline for AI-Assisted Coding" /><published>2025-12-31T00:00:00+00:00</published><updated>2025-12-31T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/31/discipline-first-trust-pipeline-for-ai-assisted-coding</id><content type="html" xml:base="https://systemhalted.in/2025/12/31/discipline-first-trust-pipeline-for-ai-assisted-coding/"><![CDATA[<p>Vibe coding is not a software engineering paradigm. It’s a mood. Engineering is what makes it ship.</p>

<p>My core claim is simple: engineers who set up a task with clear instructions, a thin prototype, and hard guardrails tend to have a better experience with AI-assisted coding, and they tend to keep their good faith in the tools. Engineers who don’t, often walk away with confusion, rewrites, and a lingering sense that the agent is “untrustworthy.” And that’s why the same tool creates opposite stories, depending on who’s holding it.</p>

<p>This post is my framework, <strong><a href="https://github.com/systemhalted/DisciplineFirst">Discipline First: a trust pipeline for AI-assisted coding</a></strong>. It’s a small kit you can use immediately: an Agent Brief that makes intent hard to misread, guardrails that make failures visible early, and a one-week experiment that turns belief into evidence.</p>

<h2 id="the-axis-that-matters">The axis that matters</h2>

<p>The agent isn’t the methodology. Your engineering habits are.</p>

<p>I’m going to be deliberately boring about definitions, because the labels are less important than the axis. Whether you call it vibe coding, agentic coding, or AI-assisted coding, the same split shows up: disciplined delivery versus undisciplined delivery. The tools can draft code, refactor code, even propose architectures, but they can’t rescue a vague task from its own vagueness.</p>

<p>Discipline is what turns “the agent wrote something” into “the system changed, and we can explain why, prove it works, and undo it safely if it doesn’t.”</p>

<h2 id="four-engineers-walk-into-the-same-tool">Four engineers walk into the same tool</h2>

<p>And once you see it that way, four kinds of engineers show up around AI-assisted coding.</p>

<p>The <strong>Skeptic</strong> is driven by quality, security, and maintainability. They are not anti tool. They are pro standards. They will try these tools in a sandbox, or allow them under strict review, and they trust what they can validate through time-tested discipline: tests, contracts, architecture checks, and clean interfaces. Their posture is “prove it,” and their lingering doubt is usually about authorship. They suspect only human engineers can reliably meet that bar.</p>

<p>The <strong>Dismisser</strong> opts out early. Sometimes that’s reflexive, sometimes it’s reasoned: they’ve seen bad suggestions, security risks, legal uncertainty, vendor lock-in, or unreviewable diffs and they decided the trade is not worth it. Their posture is still “already decided,” but the <em>why</em> matters. You don’t convert a Dismisser by arguing about models or demos. You convert them by giving them control of the bar: let them define the quality gates, then run a small, measured experiment in their own codebase that either meets the bar or fails honestly.</p>

<p>The <strong>Viber</strong> loves speed. They ship fast, accept large diffs, and skip the guardrails that make code testable and observable. Their posture is “speed is truth.” To be fair, that posture has a place: spikes, prototypes, throwaway demos, learning a new stack. The problem is when the same vibe crosses the border into production. That’s where “it seems to work” quietly becomes regressions, mystery failures, and eroded trust. The point of Discipline First is not to shame exploration. It’s to prevent avoidable damage when the stakes are real.</p>

<p>The <strong>Disciplined Builder</strong> loves AI-assisted coding and still engineers hard. Small tasks, small diffs, acceptance criteria, tests, verification loops, rules files, and a security mindset. They do not care whether the code was written by a human or an agent. They care that it is explainable, reviewable, testable, and observable. Their posture is simple: trust is built.</p>

<p>The only difference between the Skeptic and the Builder is what they believe about that last mile. The Skeptic thinks only humans can deliver it consistently. The Builder has learned how to make the agent earn it.</p>

<h2 id="discipline-first-is-xp-with-a-faster-pair">Discipline First is XP with a faster pair</h2>

<p>Discipline First is not a new religion. It is <strong>Extreme Programming</strong> adapted to a world where your pair can write code at absurd speed. Extreme Programming starts with <strong>Values</strong>, because Values guide <strong>Principles</strong>, and Principles are what make <strong>Practices</strong> hold up under pressure.</p>

<p>The Values are <strong>Communication, Simplicity, Feedback, Courage, and Respect</strong>.</p>

<p>Communication becomes explicit intent: the Agent Brief and rules files are how you communicate without mind reading. Simplicity becomes a principle you enforce: assume simplicity, slice work into small tasks, keep diffs small, keep releases small. Feedback becomes rapid and objective: failing tests and continuous integration tell the truth early. Courage becomes disciplined restraint: stop the agent when it starts guessing, delete generated code when it bloats, and ship in increments so reality can correct you fast. Respect becomes engineering for humans: keep standards non negotiable, make changes transparent, and leave behind code that future people can understand, test, observe, and safely change.</p>

<p>From those Values and Principles, the Practices follow naturally: pair programming with the agent as the pair and the human as the driver, test driven development as the spec the agent must satisfy, continuous integration as the always on referee, and small releases as the safest way to turn speed into reliability.</p>

<h2 id="the-discipline-first-kit">The Discipline First kit</h2>

<h3 id="1-the-agent-brief">1) The Agent Brief</h3>

<p>Think of the Agent Brief as a PRD that’s small enough to fit in your head, but sharp enough that the agent can’t “creatively interpret” it.</p>

<ol>
  <li>Goal</li>
  <li>Non-goals</li>
  <li>Constraints</li>
  <li>Interfaces</li>
  <li>Acceptance criteria</li>
  <li>Risks</li>
  <li>Test plan</li>
  <li>Observability</li>
  <li>Dependencies</li>
  <li>Recovery and blast radius</li>
</ol>

<p>Here’s a concrete example (for one service):</p>

<p><strong>Goal:</strong> Add rate limiting to <code class="language-plaintext highlighter-rouge">POST /payments</code> to reduce abuse and protect downstream dependencies.<br />
<strong>Non-goals:</strong> No UI changes. No new auth scheme. No changes to other endpoints.<br />
<strong>Constraints:</strong> Must not change the public API contract. Must keep p95 latency impact under 5%.<br />
<strong>Interfaces:</strong> <code class="language-plaintext highlighter-rouge">POST /payments</code> only; configuration via env var <code class="language-plaintext highlighter-rouge">PAYMENTS_RATE_LIMIT_RPS</code>.<br />
<strong>Acceptance criteria:</strong> Requests above limit return <code class="language-plaintext highlighter-rouge">429</code> with standard error body; limits are per-customer; logs include rate-limit decision.<br />
<strong>Risks:</strong> False positives blocking legit traffic; misconfigured limits; uneven behavior across instances.<br />
<strong>Test plan:</strong> Add functional tests for 200, 429, and boundary conditions; include concurrency test; all tests deterministic in CI.<br />
<strong>Observability:</strong> Emit metric <code class="language-plaintext highlighter-rouge">payments.rate_limited.count</code>; structured log <code class="language-plaintext highlighter-rouge">rate_limit_decision</code> with customer id hash; dashboard alert on spikes.<br />
<strong>Dependencies:</strong> Redis (or in-memory) limiter library already approved; no new infrastructure.<br />
<strong>Recovery and blast radius:</strong> Feature flag the limiter; default off; rollback is flag flip; document emergency disable procedure.</p>

<p>Then three hard rules.</p>

<ol>
  <li>Do not change public APIs unless explicitly permitted.</li>
  <li>Prefer the smallest diff that can satisfy the brief.</li>
  <li>Stop and ask when uncertain.</li>
</ol>

<h3 id="2-guardrails">2) Guardrails</h3>

<p>Not vibes. Guardrails.</p>

<p>Unit tests and functional tests. Contract checks. Architecture checks. Code review. Small tasks. CI as referee.</p>

<p>If an agent can ship code faster than a human, your only sane response is to make the truth show up faster than the code. We have already seen public “trust cliff” moments when autonomy meets weak guardrails.<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<p>Small diffs are not about being precious. They are about control. A small change is easier to review, easier to reason about, easier to test, and easier to roll back. Agents tend to expand scope unless you constrain them, so “small diffs” is both a safety boundary and a way to keep context from exploding.</p>

<h3 id="3-durable-instructions">3) Durable instructions</h3>

<p>Modern agentic tooling is quietly reinventing the same old idea: durable project instructions.</p>

<p>Rules files in agentic IDEs make behavior persistent across prompts.<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><br />
Repo-level instruction files like AGENTS.md make “how to behave here” predictable.<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup><sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup><br />
For conventions that should survive editors and IDEs, EditorConfig gives you portable, version controlled style rules.<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup><sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup><sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup></p>

<p>Practical rule of thumb:</p>

<p>Use EditorConfig for formatting and style that must survive editors and IDEs.<br />
Use Rules files for tool-specific behavior. What to include in context, how to respond, what not to touch.<br />
Use AGENTS.md for repo-wide agent behavior. Setup commands, test commands, conventions, safety boundaries.</p>

<p>If you only do one thing, do AGENTS.md plus your Agent Brief template. That creates a stable baseline even when prompts change.</p>

<h2 id="a-one-week-experiment-tests-only">A one-week experiment (tests only)</h2>

<p>Pick one service. Pick one real user workflow in that service. Then write missing functional tests until the behavior is pinned down.</p>

<p>You will know it’s working when:</p>

<p>The tests fail for the wrong behavior and pass for the right behavior.<br />
They run repeatedly without flakes.<br />
They do not depend on environment quirks or real external systems.<br />
CI stays green and the suite stays fast enough to run often.<br />
Any production code change made only for testability is small and justified.<br />
A human reviewer can read the tests as a spec and agree they capture real behavior.</p>

<p>Measure the boring things: how many rewrites the agent needed, how many times you had to restate intent, how large the diffs got, how much review effort it took, and how quickly you got to “tests green.”</p>

<h2 id="when-discipline-first-is-overkill-and-when-it-breaks">When Discipline First is overkill (and when it breaks)</h2>

<p>Discipline First is the safest on ramp for production work. It is not mandatory ceremony for everything.</p>

<p>It is overkill for throwaway prototypes, exploratory spikes, and one-off scripts where the cost of failure is low and the code has no future. In those contexts, “vibes first” can be a valid way to learn quickly.</p>

<p>Discipline First breaks down in predictable ways:</p>

<p>The Brief rots. Intent changes but the kit does not.<br />
Tests become performative. They pass but don’t pin behavior.<br />
Guardrails turn into friction without signal. Slow, flaky, or mis-scoped checks.<br />
The agent is allowed to widen scope. Diffs balloon, review becomes theater.</p>

<p>The fix is the same as always: tighten the slice, keep the checks honest, and scale the process to the risk.</p>

<h2 id="why-measure-at-all">Why measure at all</h2>

<p>Because productivity gains are not guaranteed.</p>

<p>One of the clearest public data points so far is a randomized controlled trial from METR (published July 2025) studying experienced open source developers working in codebases they already knew. When AI tools were allowed, developers expected big speedups and later felt faster, but measured completion time was slower on average in that setting.<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup><sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">10</a></sup><sup id="fnref:11"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">11</a></sup></p>

<p>That result does not mean “AI slows everyone down.” Tools and workflows change fast, task types vary wildly, and many teams report real gains. It means your intuition is not an instrument. Discipline First is not “trust the agent.” It’s “instrument the work.”</p>

<h2 id="close-the-loop-for-each-persona">Close the loop for each persona</h2>

<p>For the Skeptic: run one Discipline First experiment on tests only in a sandboxed branch.</p>

<p>For the Dismisser: pick one internal task, define your own quality gates, and let the experiment decide.</p>

<p>For the Viber: no big diffs, and every change must come with a failing test first.<sup id="fnref:12"><a href="#fn:12" class="footnote" rel="footnote" role="doc-noteref">12</a></sup></p>

<p>For the Builder: make discipline the default - publish the Agent Brief template, standardize rules files, and optimize the workflow so the safest path is the easiest path.</p>

<h2 id="sources-and-footnotes">Sources and Footnotes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Replit incident reporting (Business Insider): https://www.businessinsider.com/replit-ceo-apologizes-ai-coding-tool-delete-company-database-2025-7 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>Replit CEO statement (X): https://x.com/amasad/status/1943062428929892384 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>Cursor Rules documentation: https://cursor.com/docs/context/rules <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>OpenAI Codex guidance for AGENTS.md: https://developers.openai.com/codex/guides/agents-md/ <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p>OpenAI “Introducing Codex” (see “How Codex Works”): https://openai.com/index/introducing-codex/ <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>EditorConfig homepage: https://editorconfig.org/ <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p>EditorConfig specification: https://spec.editorconfig.org/ <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>JetBrains IDE support for EditorConfig: https://www.jetbrains.com/help/idea/editorconfig.html <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p>METR study write-up (July 2025): https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/ <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10">
      <p>METR paper on arXiv: https://arxiv.org/abs/2507.09089 <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:11">
      <p>Reuters coverage of the METR study: https://www.reuters.com/business/ai-slows-down-some-experienced-software-developers-study-finds-2025-07-10/ <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:12">
      <p>Martin Fowler on TDD: https://martinfowler.com/articles/is-tdd-dead/ <a href="#fnref:12" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Technology" /><category term="Software Engineering" /><category term="ai-assisted-coding" /><category term="agentic-coding" /><category term="vibe-coding" /><category term="extreme-programming" /><category term="xp" /><category term="tdd" /><category term="continuous-integration" /><category term="testing" /><category term="guardrails" /><category term="engineering-discipline" /><summary type="html"><![CDATA[AI-assisted coding is a force multiplier. This post argues that disciplined engineering practices, rooted in Extreme Programming, are what make agentic workflows trustworthy and shippable.]]></summary></entry><entry><title type="html">Part 5: NaN, Infinity and the Rules of Weird Math</title><link href="https://systemhalted.in/2025/12/25/nan-infinity-weird-math-rules/" rel="alternate" type="text/html" title="Part 5: NaN, Infinity and the Rules of Weird Math" /><published>2025-12-25T00:00:00+00:00</published><updated>2025-12-25T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/25/nan-infinity-weird-math-rules</id><content type="html" xml:base="https://systemhalted.in/2025/12/25/nan-infinity-weird-math-rules/"><![CDATA[<p><em>This post is part of my <a href="https://systemhalted.in/categories/#cat-series-4-floating-point-without-tears">Floating Point Without Tears</a> series on how Java numbers misbehave and how to live with them.</em></p>

<p>Floating point math is fast, useful, and occasionally haunted. Not philosophically, literally haunted, as in values that aren’t equal to themselves.</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">NaN != NaN</code> evaluates to <code class="language-plaintext highlighter-rouge">true</code></p>
</blockquote>

<p>IEEE 754 formalizes that haunting by defining special values that let computations continue while still signaling trouble. They look like broken math until you realize they’re doing damage control.</p>

<h2 id="why-special-values-exist-in-floating-point">Why Special Values Exist in Floating Point</h2>

<p>In Part 1 of this series<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, we saw how floating-point numbers work: they use a fixed number of bits to represent the sign, exponent, and significand (mantissa). This representation has limits:</p>

<ul>
  <li><strong>Largest representable number</strong>: Around <code class="language-plaintext highlighter-rouge">1.8 × 10^308</code> for doubles (<code class="language-plaintext highlighter-rouge">Double.MAX_VALUE</code>)</li>
  <li><strong>Smallest positive normalized number</strong>: Around <code class="language-plaintext highlighter-rouge">2.2 × 10^-308</code> (<code class="language-plaintext highlighter-rouge">Double.MIN_NORMAL</code>)</li>
  <li><strong>Smallest positive nonzero number</strong>: Around <code class="language-plaintext highlighter-rouge">4.9 × 10^-324</code> (<code class="language-plaintext highlighter-rouge">Double.MIN_VALUE</code>)</li>
  <li><strong>Precision</strong>: Limited by machine epsilon (about 2.22 × 10^-16 near 1.0, i.e., Math.ulp(1.0) = the gap between 1.0 and the next larger representable double)</li>
</ul>

<p>But what happens when you compute something that <em>exceeds</em> these limits?</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">huge</span> <span class="o">=</span> <span class="mi">1</span><span class="n">e308</span><span class="o">;</span>
<span class="kt">double</span> <span class="n">overflow</span> <span class="o">=</span> <span class="n">huge</span> <span class="o">*</span> <span class="mi">10</span><span class="o">;</span>            <span class="c1">// Exceeds max value</span>
<span class="kt">double</span> <span class="n">gradual</span> <span class="o">=</span> <span class="nc">Double</span><span class="o">.</span><span class="na">MIN_NORMAL</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span> <span class="c1">// Becomes subnormal (gradual underflow)</span>
<span class="kt">double</span> <span class="n">underflow</span> <span class="o">=</span> <span class="nc">Double</span><span class="o">.</span><span class="na">MIN_VALUE</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span> <span class="c1">// Falls below min value → +0.0</span>
<span class="kt">double</span> <span class="n">undefined</span> <span class="o">=</span> <span class="mf">0.0</span> <span class="o">/</span> <span class="mf">0.0</span><span class="o">;</span>           <span class="c1">// Mathematically meaningless</span></code></pre></figure>

<p>IEEE 754 could have made these operations:</p>
<ol>
  <li>Throw exceptions (slow, interrupts computation)</li>
  <li>Wrap around to negative values (confusing, hides errors)</li>
  <li>Return arbitrary garbage (dangerous)</li>
</ol>

<p>Instead, it reserves special bit patterns in the exponent field to represent <em>infinity</em> and <em>NaN</em>. These aren’t normal numbers; they are sentinel values that signal “something unusual happened, but computation can continue.”</p>

<h3 id="the-bit-pattern-trick">The Bit Pattern Trick</h3>

<p>A double uses 11 bits for the exponent. IEEE 754 reserves special patterns for edge cases:</p>

<table>
  <thead>
    <tr>
      <th>Exponent bits</th>
      <th>Significand (fraction)</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>all 0s</td>
      <td>all zeros</td>
      <td>±0.0 (sign bit determines +/−)</td>
    </tr>
    <tr>
      <td>all 0s</td>
      <td>non-zero</td>
      <td>subnormal numbers</td>
    </tr>
    <tr>
      <td>1..2046</td>
      <td>any</td>
      <td>normal numbers</td>
    </tr>
    <tr>
      <td>all 1s (2047)</td>
      <td>all zeros</td>
      <td>±Infinity (sign bit determines +/−)</td>
    </tr>
    <tr>
      <td>all 1s (2047)</td>
      <td>non-zero</td>
      <td>NaN</td>
    </tr>
  </tbody>
</table>

<p>This means you can check for special values with simple bit operations - no exceptions, no branching overhead in critical inner loops. In Java you normally just use Double.isNaN(x) and Double.isInfinite(x), which are implemented efficiently under the hood.</p>

<h2 id="the-problem-what-should-math-return-when-it-breaks">The Problem: What Should Math Return When It Breaks?</h2>

<p>Now that we understand <em>why</em> special values exist (to handle edge cases without crashing), let’s see <em>when</em> they appear.</p>

<p>Consider calculating the average price change across a portfolio:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">totalChange</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">validStocks</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>

<span class="k">for</span> <span class="o">(</span><span class="nc">Stock</span> <span class="n">stock</span> <span class="o">:</span> <span class="n">portfolio</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">double</span> <span class="n">change</span> <span class="o">=</span> <span class="n">stock</span><span class="o">.</span><span class="na">getCurrentPrice</span><span class="o">()</span> <span class="o">-</span> <span class="n">stock</span><span class="o">.</span><span class="na">getPreviousPrice</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">isValidChange</span><span class="o">(</span><span class="n">change</span><span class="o">))</span> <span class="o">{</span>
        <span class="n">totalChange</span> <span class="o">+=</span> <span class="n">change</span><span class="o">;</span>
        <span class="n">validStocks</span><span class="o">++;</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="kt">double</span> <span class="n">avgChange</span> <span class="o">=</span> <span class="n">totalChange</span> <span class="o">/</span> <span class="n">validStocks</span><span class="o">;</span> <span class="c1">// What if validStocks is 0?</span></code></pre></figure>

<p>If no stocks had valid data, you’re dividing <code class="language-plaintext highlighter-rouge">0.0 / 0</code>. Should your program:</p>
<ul>
  <li>Crash immediately?</li>
  <li>Return <code class="language-plaintext highlighter-rouge">0.0</code> and pretend the average is zero (which is a lie)?</li>
  <li>Return something that screams “this value is meaningless”?</li>
</ul>

<p>IEEE 754 chose option three. It invented special values so errors can propagate visibly instead of silently corrupting results downstream.</p>

<h2 id="ieee-754-special-values">IEEE 754 Special Values</h2>

<p>IEEE 754 defines a few “not-a-normal-number” values so computations can keep going in a principled way instead of crashing or silently inventing garbage.</p>

<h3 id="1-signed-zero-00">1. Signed Zero: ±0.0</h3>

<p>Before we dive into infinity, there’s a subtle detail: IEEE 754 has both <code class="language-plaintext highlighter-rouge">+0.0</code> and <code class="language-plaintext highlighter-rouge">-0.0</code>. They both print as <code class="language-plaintext highlighter-rouge">0.0</code> and compare as equal, but they behave differently in division:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">posZero</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">;</span>
<span class="kt">double</span> <span class="n">negZero</span> <span class="o">=</span> <span class="o">-</span><span class="mf">0.0</span><span class="o">;</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">posZero</span> <span class="o">==</span> <span class="n">negZero</span><span class="o">);</span>  <span class="c1">// true</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="mf">1.0</span> <span class="o">/</span> <span class="n">posZero</span><span class="o">);</span>       <span class="c1">// Infinity</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="mf">1.0</span> <span class="o">/</span> <span class="n">negZero</span><span class="o">);</span>       <span class="c1">// -Infinity</span></code></pre></figure>

<p>Signed zero exists so that <code class="language-plaintext highlighter-rouge">1.0 / (tiny positive number → 0)</code> gives <code class="language-plaintext highlighter-rouge">+∞</code> while <code class="language-plaintext highlighter-rouge">1.0 / (tiny negative number → 0)</code> gives <code class="language-plaintext highlighter-rouge">−∞</code>. It preserves the direction you approached zero from, which matters for continuity and limit-style reasoning (aka Calculus).</p>

<h3 id="2-infinity--and-">2. Infinity: +∞ and −∞</h3>

<p>Infinity appears when a finite result cannot be represented, or when you divide a nonzero number by zero.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">posInf</span> <span class="o">=</span> <span class="mf">1.0</span> <span class="o">/</span> <span class="mf">0.0</span><span class="o">;</span>   <span class="c1">// +Infinity</span>
<span class="kt">double</span> <span class="n">negInf</span> <span class="o">=</span> <span class="o">-</span><span class="mf">1.0</span> <span class="o">/</span> <span class="mf">0.0</span><span class="o">;</span>  <span class="c1">// -Infinity</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">posInf</span><span class="o">);</span>                  <span class="c1">// Infinity</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">negInf</span><span class="o">);</span>                  <span class="c1">// -Infinity</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">isInfinite</span><span class="o">(</span><span class="n">posInf</span><span class="o">));</span> <span class="c1">// true</span></code></pre></figure>

<p>Infinity participates in ordering as you’d expect:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">+∞</code> is greater than every finite number</li>
  <li><code class="language-plaintext highlighter-rouge">−∞</code> is smaller than every finite number</li>
</ul>

<p>Arithmetic is mostly “limit-like”:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">finite + (+∞) = +∞</code></li>
  <li><code class="language-plaintext highlighter-rouge">positive × (+∞) = +∞</code></li>
  <li><code class="language-plaintext highlighter-rouge">negative × (+∞) = −∞</code></li>
</ul>

<p>But some combinations are undefined and produce NaN:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">(+∞) + (−∞) = NaN</code></li>
  <li><code class="language-plaintext highlighter-rouge">(+∞) × 0.0 = NaN</code></li>
  <li><code class="language-plaintext highlighter-rouge">(+∞) / (+∞) = NaN</code></li>
</ul>

<h3 id="3-nan-not-a-number">3. NaN: Not a Number</h3>

<p>NaN means “this result is undefined,” like <code class="language-plaintext highlighter-rouge">0.0 / 0.0</code> or <code class="language-plaintext highlighter-rouge">√(−1)</code>. Once NaN enters a computation, it spreads into any operation involving NaN to produce a NaN:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">nan</span> <span class="o">=</span> <span class="mf">0.0</span> <span class="o">/</span> <span class="mf">0.0</span><span class="o">;</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">nan</span><span class="o">);</span>           <span class="c1">// NaN</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">nan</span> <span class="o">+</span> <span class="mi">5</span><span class="o">);</span>       <span class="c1">// NaN</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">nan</span> <span class="o">*</span> <span class="mi">2</span><span class="o">);</span>       <span class="c1">// NaN</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">sqrt</span><span class="o">(</span><span class="n">nan</span><span class="o">));</span> <span class="c1">// NaN</span></code></pre></figure>

<p>This “contagious” behavior is intentional. If a value is undefined, any result built on it should also be undefined.</p>

<h4 id="the-weird-rule-nan--nan">The Weird Rule: NaN ≠ NaN</h4>

<p>The key rule that feels like a logic prank but is actually a safety feature:</p>

<p><strong>NaN is not equal to anything, including itself.</strong></p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">nan</span> <span class="o">=</span> <span class="nc">Double</span><span class="o">.</span><span class="na">NaN</span><span class="o">;</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">nan</span> <span class="o">==</span> <span class="n">nan</span><span class="o">);</span>        <span class="c1">// false (!)</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">nan</span> <span class="o">!=</span> <span class="n">nan</span><span class="o">);</span>        <span class="c1">// true</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">nan</span> <span class="o">&lt;</span> <span class="mf">5.0</span><span class="o">);</span>         <span class="c1">// false</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">nan</span> <span class="o">&gt;=</span> <span class="mf">5.0</span><span class="o">);</span>        <span class="c1">// false</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">isNaN</span><span class="o">(</span><span class="n">nan</span><span class="o">));</span> <span class="c1">// true (correct way)</span></code></pre></figure>

<p>Why? Because NaN means “undefined result,” and you can’t meaningfully compare undefined values. NaN is unordered by design. There can be many NaN bit patterns (IEEE 754 supports “signaling” and “quiet” NaNs with different payloads), but Java generally treats them as “some NaN” unless you inspect raw bits with <code class="language-plaintext highlighter-rouge">Double.doubleToRawLongBits()</code>.</p>

<p>Think of it like asking whether two error messages are “the same error.” Even if both say “Error,” you don’t know if they represent the same underlying problem. The comparison itself is meaningless.</p>

<p><strong>Never check for NaN with <code class="language-plaintext highlighter-rouge">==</code>. Use <code class="language-plaintext highlighter-rouge">Double.isNaN(x)</code> instead.</strong></p>

<h4 id="nan-and-sorting-two-different-worlds">NaN and Sorting: Two Different Worlds</h4>

<p>This is where things get interesting. Java has <em>two</em> ways to compare doubles, and they behave differently with NaN:</p>

<p><strong>IEEE 754 comparisons</strong> (<code class="language-plaintext highlighter-rouge">==</code>, <code class="language-plaintext highlighter-rouge">&lt;</code>, <code class="language-plaintext highlighter-rouge">&lt;=</code>, etc.):</p>
<ul>
  <li>Any comparison with NaN returns <code class="language-plaintext highlighter-rouge">false</code></li>
  <li>These are what you use in <code class="language-plaintext highlighter-rouge">if</code> statements</li>
</ul>

<p><strong>Java’s total order</strong> (<code class="language-plaintext highlighter-rouge">Double.compare()</code>, <code class="language-plaintext highlighter-rouge">Double.compareTo()</code>)<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>:</p>
<ul>
  <li>NaN is considered greater than all other values, including <code class="language-plaintext highlighter-rouge">+∞</code></li>
  <li>All NaNs are considered equal</li>
  <li>This is what <code class="language-plaintext highlighter-rouge">Arrays.sort()</code> and <code class="language-plaintext highlighter-rouge">Arrays.binarySearch()</code> use</li>
</ul>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span><span class="o">[]</span> <span class="n">values</span> <span class="o">=</span> <span class="o">{</span><span class="mf">3.0</span><span class="o">,</span> <span class="nc">Double</span><span class="o">.</span><span class="na">NaN</span><span class="o">,</span> <span class="mf">1.0</span><span class="o">,</span> <span class="mf">2.0</span><span class="o">};</span>
<span class="nc">Arrays</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">values</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Arrays</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">values</span><span class="o">));</span> 
<span class="c1">// [1.0, 2.0, 3.0, NaN] -- guaranteed by Java spec</span>

<span class="c1">// IEEE comparison says "not sorted":</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">values</span><span class="o">[</span><span class="mi">2</span><span class="o">]</span> <span class="o">&lt;=</span> <span class="n">values</span><span class="o">[</span><span class="mi">3</span><span class="o">]);</span> <span class="c1">// false (3.0 &lt;= NaN is false)</span>

<span class="c1">// Total order says "sorted":</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">compare</span><span class="o">(</span><span class="n">values</span><span class="o">[</span><span class="mi">2</span><span class="o">],</span> <span class="n">values</span><span class="o">[</span><span class="mi">3</span><span class="o">])</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="o">);</span> <span class="c1">// true</span></code></pre></figure>

<p><strong>The gotcha:</strong> If you validate sortedness using <code class="language-plaintext highlighter-rouge">&lt;=</code>, you’ll get false negatives when NaN is present. If you need to check ordering, use <code class="language-plaintext highlighter-rouge">Double.compare(a, b) &lt;= 0</code> instead.</p>

<p>IEEE comparisons answer “is this mathematically ordered?” while Java’s total order answers “can we put these in a consistent sequence for sorting?” The Arrays Javadoc basically says exactly that: &lt; is not a total order for doubles, so sorting uses the total order from Double.compareTo.</p>

<p>The good news: <code class="language-plaintext highlighter-rouge">Arrays.sort()</code> and <code class="language-plaintext highlighter-rouge">Arrays.binarySearch()</code> work correctly with NaN because they use the total order internally. The NaN will consistently end up at the end of the array.</p>

<h2 id="quick-reference-comparison-table">Quick Reference: Comparison Table</h2>

<table>
  <thead>
    <tr>
      <th>Operation</th>
      <th>Result</th>
      <th>Reason</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">NaN == NaN</code></td>
      <td><code class="language-plaintext highlighter-rouge">false</code></td>
      <td>IEEE comparison: unordered</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">NaN != NaN</code></td>
      <td><code class="language-plaintext highlighter-rouge">true</code></td>
      <td>Same reason</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">NaN &lt; 5.0</code></td>
      <td><code class="language-plaintext highlighter-rouge">false</code></td>
      <td>NaN fails all IEEE comparisons</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">NaN &gt;= 5.0</code></td>
      <td><code class="language-plaintext highlighter-rouge">false</code></td>
      <td>Same</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Double.compare(NaN, 5.0)</code></td>
      <td><code class="language-plaintext highlighter-rouge">&gt; 0</code></td>
      <td>Total order: NaN &gt; everything</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">∞ &gt; 1e308</code></td>
      <td><code class="language-plaintext highlighter-rouge">true</code></td>
      <td>Infinity is greater than all finite values</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">0.0 == -0.0</code></td>
      <td><code class="language-plaintext highlighter-rouge">true</code></td>
      <td>Signed zeros compare equal</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">1.0 / 0.0</code></td>
      <td><code class="language-plaintext highlighter-rouge">+∞</code></td>
      <td>Division by zero produces infinity</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">0.0 / 0.0</code></td>
      <td><code class="language-plaintext highlighter-rouge">NaN</code></td>
      <td>Indeterminate form</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">∞ − ∞</code></td>
      <td><code class="language-plaintext highlighter-rouge">NaN</code></td>
      <td>Undefined operation</td>
    </tr>
  </tbody>
</table>

<h2 id="practical-takeaways">Practical Takeaways</h2>

<ol>
  <li><strong>Never check NaN with <code class="language-plaintext highlighter-rouge">==</code></strong>. Use <code class="language-plaintext highlighter-rouge">Double.isNaN(x)</code>.</li>
  <li><strong>Log early for special values</strong> when debugging “impossible” totals:</li>
</ol>

<figure class="highlight"><pre><code class="language-java" data-lang="java">   <span class="k">if</span> <span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">isNaN</span><span class="o">(</span><span class="n">result</span><span class="o">))</span> <span class="o">{</span>
       <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"NaN detected at step X"</span><span class="o">);</span>
   <span class="o">}</span>
   <span class="k">if</span> <span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">isInfinite</span><span class="o">(</span><span class="n">result</span><span class="o">))</span> <span class="o">{</span>
       <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Infinity detected at step X"</span><span class="o">);</span>
   <span class="o">}</span>
   </code></pre></figure>

<ol>
  <li><strong>Use the right comparison for the job</strong>: <code class="language-plaintext highlighter-rouge">==</code> for value equality, <code class="language-plaintext highlighter-rouge">Double.compare()</code> for ordering.</li>
  <li><strong>Understand the contagion</strong>: Once NaN enters your calculations, it spreads. Trace backward to find the division by zero or invalid operation that spawned it.</li>
</ol>

<h2 id="the-philosophical-bit">The Philosophical Bit</h2>

<p>NaN isn’t a bug. It is math raising its hand and saying, politely but firmly:</p>

<blockquote>
  <p>“I can’t promise anything from here.”</p>
</blockquote>

<p>When your balance sheet shows NaN, don’t curse floating point. Ask what division by zero or invalid square root you missed three steps ago. The special values aren’t betraying you; they’re the only honest answer to questions that have no answer.</p>

<p>In the next post, we’ll look at how to actually <em>handle</em> these special cases in production code without littering your logic with endless <code class="language-plaintext highlighter-rouge">isNaN()</code> checks.</p>

<h2 id="references-and-notes">References and Notes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="/2025/12/04/ieee-754-doubles/">Part 1: IEEE 754 Doubles - The Numbers That Lie With A Straight Face</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Double.html">Java’s Total Order</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Computer Science&quot;, &quot;Software Engineering&quot;, &quot;Technology&quot;, &quot;Series 4 - Floating Point Without Tears&quot;]" /><category term="java" /><category term="floating-point" /><category term="ieee-754" /><category term="double" /><category term="machine-epsilon" /><category term="ulp" /><category term="numerics" /><category term="NaN" /><category term="infinity" /><summary type="html"><![CDATA[In IEEE 754 floating point, there are special values (NaN, +∞, −∞) that follow rules that look like broken logic, until you realize they're trying to protect you from lying math.]]></summary></entry><entry><title type="html">Part 4: Machine Epsilon - The Smallest Change a Double Can See</title><link href="https://systemhalted.in/2025/12/23/machine-epsilon-double-precision-grid/" rel="alternate" type="text/html" title="Part 4: Machine Epsilon - The Smallest Change a Double Can See" /><published>2025-12-23T00:00:00+00:00</published><updated>2025-12-23T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/23/machine-epsilon-double-precision-grid</id><content type="html" xml:base="https://systemhalted.in/2025/12/23/machine-epsilon-double-precision-grid/"><![CDATA[<p><em>This post is part of my <a href="https://systemhalted.in/categories/#cat-series-4-floating-point-without-tears">Floating Point Without Tears</a> series on how Java numbers misbehave and how to live with them.</em></p>

<p>In my post on associativity and reduce<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, we saw something that feels like a prank. In Example 5, adding 1.0 to 1e16 did not change the value at all.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">1</span><span class="n">e16</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x</span> <span class="o">+</span> <span class="mf">1.0</span> <span class="o">==</span> <span class="n">x</span><span class="o">);</span> <span class="c1">// true</span></code></pre></figure>

<p>That is not Java being cheeky. That is IEEE-754 being literal. A double does not live on a continuous number line. It lives on a grid.</p>

<p>This post answers one question:</p>

<p><strong>How fine is the grid?</strong></p>

<h2 id="machine-epsilon-and-the-first-rung-above-10">Machine epsilon and the first rung above 1.0</h2>

<p>One common definition of machine epsilon (ε) is:</p>

<p><em>The smallest ε &gt; 0 such that 1.0 + ε ≠ 1.0 for double.</em></p>

<p>This is “the gap from 1.0 to the next representable double above it”.</p>

<p><strong>Note:</strong> Some references use ε to mean 2⁻⁵³, which is half this gap and represents the maximum relative rounding error for a correctly rounded operation. In this post, ε means the “next representable number above 1.0” definition, which is 2⁻⁵².</p>

<h3 id="finding-ε-in-java">Finding ε in Java</h3>

<p>Here is a loop that finds that smallest nudge.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MachineEpsilon</span> <span class="o">{</span>
  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">double</span> <span class="n">eps</span> <span class="o">=</span> <span class="mf">1.0</span><span class="o">;</span>

    <span class="k">while</span> <span class="o">(</span><span class="mf">1.0</span> <span class="o">+</span> <span class="o">(</span><span class="n">eps</span> <span class="o">/</span> <span class="mf">2.0</span><span class="o">)</span> <span class="o">!=</span> <span class="mf">1.0</span><span class="o">)</span> <span class="o">{</span>
      <span class="n">eps</span> <span class="o">/=</span> <span class="mf">2.0</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"epsilon = "</span> <span class="o">+</span> <span class="n">eps</span><span class="o">);</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Typical output on an IEEE-754 JVM:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">epsilon = 2.220446049250313E-16</code></pre></figure>

<p>That value is exactly 2⁻⁵². Since powers of two are exactly representable in binary floating point, there is no approximation error in storing this value. The decimal string 2.220446049250313E-16 is just how Java renders that exact binary value for display, rounded to about 15 decimal digits.<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<p><strong>One quick trap:</strong> <code class="language-plaintext highlighter-rouge">Double.MIN_VALUE</code> is not machine epsilon. <code class="language-plaintext highlighter-rouge">Double.MIN_VALUE</code> is the smallest positive double near zero (about 5×10⁻³²⁴). Machine epsilon is about spacing near 1.0.</p>

<h2 id="the-ladder-model">The ladder model</h2>

<p>Picture a ladder laid across the number line.</p>

<p>Near 1.0, the rungs are extremely close together. As the numbers get bigger, the rungs spread out.</p>

<p>Machine epsilon tells you the rung spacing near 1.0. But what you usually want is the spacing near whatever value you are actually using.</p>

<p>That spacing is called <strong>ULP</strong>, short for <strong>Unit in the Last Place</strong>.</p>

<p>Java gives it to you with <code class="language-plaintext highlighter-rouge">Math.ulp(x)</code>.</p>

<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 920 380" role="img" aria-label="Double precision grid spacing near 1.0 vs near 1e16" preserveAspectRatio="xMidYMid meet" style="max-width: 100%; height: auto; display: block;">
  <style>
    .title { font: 700 18px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; fill: #111; }
    .label { font: 13px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; fill: #222; }
    .small { font: 12px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; fill: #222; }
    .tick { stroke: #111; stroke-width: 2; }
    .axis { stroke: #111; stroke-width: 2.5; }
    .ghost { stroke: #888; stroke-width: 2; stroke-dasharray: 5 5; }
    .note { font: 12px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; fill: #333; }
    .box { fill: #fafafa; stroke: #ddd; stroke-width: 1.5; }
  </style>

  <text x="20" y="28" class="title">Double is a grid: spacing depends on magnitude</text>

  <!-- Panel boxes -->
  <rect x="20" y="50" width="880" height="130" rx="10" class="box" />
  <rect x="20" y="195" width="880" height="150" rx="10" class="box" />

  <!-- Panel 1: near 1.0 -->
  <text x="40" y="78" class="label">Zoom near 1.0</text>
  <text x="40" y="98" class="note">Adjacent representable values are extremely close: spacing ≈ ε = 2⁻⁵² ≈ 2.22e−16</text>

  <!-- Axis 1 -->
  <line x1="60" y1="135" x2="860" y2="135" class="axis" />
  <text x="60" y="158" class="small">…</text>

  <!-- Dense ticks -->
  <line x1="430" y1="118" x2="430" y2="152" class="tick" />
  <text x="410" y="112" class="small">1.0</text>

  <line x1="380" y1="122" x2="380" y2="148" class="tick" />
  <line x1="405" y1="122" x2="405" y2="148" class="tick" />
  <line x1="455" y1="122" x2="455" y2="148" class="tick" />
  <line x1="480" y1="122" x2="480" y2="148" class="tick" />

  <!-- epsilon arrow -->
  <line x1="430" y1="165" x2="455" y2="165" stroke="#111" stroke-width="2" />
  <polygon points="455,165 448,161 448,169" fill="#111" />
  <text x="470" y="169" class="small">ε (next rung)</text>

  <text x="780" y="158" class="small">…</text>

  <!-- Panel 2: near 1e16 -->
  <text x="40" y="223" class="label">Zoom near 1e16</text>
  <text x="40" y="243" class="note">Here the grid is coarse: ulp(1e16) = 2.0, so +1.0 lands between rungs and vanishes</text>

  <!-- Axis 2 -->
  <line x1="60" y1="275" x2="860" y2="275" class="axis" />

  <!-- Rungs: 1e16, 1e16+2, 1e16+4 -->
  <line x1="360" y1="258" x2="360" y2="292" class="tick" />
  <text x="310" y="252" class="small">1e16</text>

  <line x1="460" y1="258" x2="460" y2="292" class="tick" />
  <text x="425" y="252" class="small">+2</text>

  <line x1="560" y1="258" x2="560" y2="292" class="tick" />
  <text x="525" y="252" class="small">+4</text>

  <!-- The missing +1 (ghost tick) -->
  <line x1="410" y1="258" x2="410" y2="292" class="ghost" />

  <!-- ulp arrow (moved down so it doesn't collide with +1 text) -->
  <line x1="360" y1="323" x2="460" y2="323" stroke="#111" stroke-width="2" />
  <polygon points="460,323 453,319 453,327" fill="#111" />
  <text x="470" y="327" class="small">ulp = 2.0</text>

  <!-- +1 annotation (moved further down) -->
  <text x="392" y="345" class="small">+1</text>
  <text x="430" y="345" class="small">does not exist</text>

  <!-- Summary callout (pushed down and within new height) -->
  <text x="40" y="372" class="note">Takeaway: doubles keep ~53 bits of precision, so absolute spacing grows as numbers grow.</text>
</svg>

<h2 id="how-spacing-grows-with-magnitude">How spacing grows with magnitude</h2>

<p>Let’s sample the grid at a few scales.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UlpSpacing</span> <span class="o">{</span>
  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">double</span><span class="o">[]</span> <span class="n">xs</span> <span class="o">=</span> <span class="o">{</span><span class="mf">1.0</span><span class="o">,</span> <span class="mf">10.0</span><span class="o">,</span> <span class="mi">1</span><span class="n">e8</span><span class="o">,</span> <span class="mi">1</span><span class="n">e16</span><span class="o">};</span>

    <span class="k">for</span> <span class="o">(</span><span class="kt">double</span> <span class="n">x</span> <span class="o">:</span> <span class="n">xs</span><span class="o">)</span> <span class="o">{</span>
      <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="s">"x=%-8s  ulp(x)=%s%n"</span><span class="o">,</span> <span class="n">x</span><span class="o">,</span> <span class="nc">Math</span><span class="o">.</span><span class="na">ulp</span><span class="o">(</span><span class="n">x</span><span class="o">));</span>
    <span class="o">}</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Typical output:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">x=1.0      ulp(x)=2.220446049250313E-16
x=10.0     ulp(x)=1.7763568394002505E-15
x=1.0E8    ulp(x)=1.4901161193847656E-8
x=1.0E16   ulp(x)=2.0</code></pre></figure>

<p>That last line is the whole “Example 5” mystery solved:</p>

<p><strong>Around 1e16, the grid spacing is 2.0.</strong></p>

<p>So 1e16 + 1.0 lands between rungs and rounds back to 1e16. But 1e16 + 2.0 is exactly one rung up.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">big</span> <span class="o">=</span> <span class="mi">1</span><span class="n">e16</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">big</span> <span class="o">+</span> <span class="mf">1.0</span> <span class="o">==</span> <span class="n">big</span><span class="o">);</span> <span class="c1">// true</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">big</span> <span class="o">+</span> <span class="mf">2.0</span> <span class="o">==</span> <span class="n">big</span><span class="o">);</span> <span class="c1">// false</span></code></pre></figure>

<h2 id="powers-of-two-where-spacing-jumps">Powers of two: where spacing jumps</h2>

<p>ULP does not grow smoothly. It jumps at powers of two.</p>

<p>Right below 2ᵏ, spacing is one value. At 2ᵏ, spacing doubles.</p>

<p>That is why comparisons that seem symmetric can behave oddly if two values straddle a power-of-two boundary. If you’re comparing values near different powers of two, their ULPs can differ by a factor of 2.</p>

<h2 id="near-zero-subnormals-exist-and-they-are-weird">Near zero: subnormals exist and they are weird</h2>

<p>For very small magnitudes below <code class="language-plaintext highlighter-rouge">Double.MIN_NORMAL</code> (approximately 2.225×10⁻³⁰⁸), double switches to subnormal (also called denormal) representation.</p>

<p><strong>What changes in subnormal land:</strong></p>

<p>Normal doubles have an implicit leading <code class="language-plaintext highlighter-rouge">1.</code> in the mantissa:</p>
<ul>
  <li>value = (1.fraction) × 2^exponent</li>
  <li>This gives you full precision</li>
</ul>

<p>Subnormals drop that leading <code class="language-plaintext highlighter-rouge">1.</code>:</p>
<ul>
  <li>value = (0.fraction) × 2^(minExponent)</li>
  <li>You lose precision gradually as you approach zero</li>
</ul>

<p><strong>Why they exist:</strong></p>

<p>Without subnormals, there would be a hard cliff from tiny normal numbers straight to 0.0. Subnormals provide <em>gradual underflow</em> - a ramp instead of a cliff.</p>

<p><strong>Key differences:</strong></p>

<ul>
  <li>Spacing becomes constant at approximately 5×10⁻³²⁴ (the value of <code class="language-plaintext highlighter-rouge">Double.MIN_VALUE</code>) rather than scaling with magnitude</li>
  <li>Arithmetic can be slower on some CPUs</li>
  <li>Relative precision is much worse (you may have only a few significant bits left)</li>
</ul>

<p>Everything in the range <code class="language-plaintext highlighter-rouge">(0, Double.MIN_NORMAL)</code> is subnormal. That’s the range from about 4.9×10⁻³²⁴ up to about 2.225×10⁻³⁰⁸.</p>

<p>Most business code never goes near subnormals. Numerical code sometimes does. It is worth knowing that floating-point has an emergency mode near zero that trades precision for continuity.</p>

<h2 id="why-tiny-increments-vanish-when-numbers-get-big">Why tiny increments vanish when numbers get big</h2>

<p>When your running total grows large enough, the local rung spacing can become bigger than the increments you are adding.</p>

<p>So “add a million tiny things to a huge sum” eventually turns into “add nothing, repeatedly,” because the tiny things fall between rungs and get rounded away.</p>

<p>That is not philosophical. It is mechanical.</p>

<h2 id="why-equality-checks-on-doubles-are-dicey">Why equality checks on doubles are dicey</h2>

<p>Sometimes two values that “should be different” land on the same rung. Sometimes two values that “should be equal” get rounded at different times and land on adjacent rungs.</p>

<p>So <code class="language-plaintext highlighter-rouge">==</code> is only safe when you mean exact equality:</p>

<ul>
  <li>Comparing to exact literals: <code class="language-plaintext highlighter-rouge">0.0</code>, <code class="language-plaintext highlighter-rouge">1.0</code>, <code class="language-plaintext highlighter-rouge">-1.0</code></li>
  <li>Checking for special values: infinities, or <code class="language-plaintext highlighter-rouge">Double.isNaN(x)</code></li>
  <li>Comparing sentinel values or results from identical deterministic operations</li>
  <li>Loop counters stored as doubles (though you should use integers instead)</li>
</ul>

<p>When you need “close enough,” you need a rule that matches your domain.</p>

<p>A common practical pattern is absolute tolerance near zero plus relative tolerance for scale.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">DoubleCompare</span> <span class="o">{</span>
  <span class="kd">private</span> <span class="nf">DoubleCompare</span><span class="o">()</span> <span class="o">{}</span> <span class="c1">// prevent instantiation</span>

  <span class="cm">/**
   * Check if two doubles are nearly equal using absolute and relative tolerance.
   * 
   * @param a first value
   * @param b second value
   * @param absTol absolute tolerance (try 1e-9 for many applications)
   * @param relTol relative tolerance (try 1e-9 for many applications)
   * @return true if values are within tolerance
   */</span>
  <span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">nearlyEqual</span><span class="o">(</span><span class="kt">double</span> <span class="n">a</span><span class="o">,</span> <span class="kt">double</span> <span class="n">b</span><span class="o">,</span> <span class="kt">double</span> <span class="n">absTol</span><span class="o">,</span> <span class="kt">double</span> <span class="n">relTol</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">isNaN</span><span class="o">(</span><span class="n">a</span><span class="o">)</span> <span class="o">||</span> <span class="nc">Double</span><span class="o">.</span><span class="na">isNaN</span><span class="o">(</span><span class="n">b</span><span class="o">))</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">a</span> <span class="o">==</span> <span class="n">b</span><span class="o">)</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span> <span class="c1">// handles infinities and exact matches</span>

    <span class="kt">double</span> <span class="n">diff</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">a</span> <span class="o">-</span> <span class="n">b</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">diff</span> <span class="o">&lt;=</span> <span class="n">absTol</span><span class="o">)</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>

    <span class="kt">double</span> <span class="n">maxAbs</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">max</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">a</span><span class="o">),</span> <span class="nc">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">b</span><span class="o">));</span>
    <span class="k">return</span> <span class="n">diff</span> <span class="o">&lt;=</span> <span class="n">relTol</span> <span class="o">*</span> <span class="n">maxAbs</span><span class="o">;</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p><strong>Choosing tolerance values:</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">absTol</code> should match the minimum meaningful difference in your domain. For scientific data measured to 3 decimal places, maybe <code class="language-plaintext highlighter-rouge">1e-3</code>. For pixel coordinates, maybe <code class="language-plaintext highlighter-rouge">0.5</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">relTol</code> is typically something like <code class="language-plaintext highlighter-rouge">1e-9</code> (about 9 decimal digits of agreement) for general use, or <code class="language-plaintext highlighter-rouge">1e-6</code> if you’re being more lenient.</li>
  <li>These comparisons are slower than <code class="language-plaintext highlighter-rouge">==</code>. If you’re comparing millions of values in performance-critical code, measure the cost.</li>
</ul>

<p>This is not the only strategy, but it is harder to misuse than an “ULPs everywhere” helper.</p>

<h2 id="why-parallel-reductions-can-drift">Why parallel reductions can drift</h2>

<p>Parallel reductions regroup operations. Floating-point addition is not associative, so regrouping changes when rounding happens.</p>

<p>Here is the smallest “this is why” example.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="n">e16</span><span class="o">;</span>

<span class="kt">double</span> <span class="n">left</span>  <span class="o">=</span> <span class="o">(</span><span class="n">a</span> <span class="o">+</span> <span class="mf">1.0</span><span class="o">)</span> <span class="o">+</span> <span class="mf">1.0</span><span class="o">;</span>   <span class="c1">// first +1 vanishes, then second +1 vanishes</span>
<span class="kt">double</span> <span class="n">right</span> <span class="o">=</span> <span class="n">a</span> <span class="o">+</span> <span class="o">(</span><span class="mf">1.0</span> <span class="o">+</span> <span class="mf">1.0</span><span class="o">);</span>   <span class="c1">// (1.0 + 1.0) becomes 2.0, which moves one ULP</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">left</span> <span class="o">==</span> <span class="n">right</span><span class="o">);</span> <span class="c1">// false</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">left</span><span class="o">);</span>          <span class="c1">// 1.0E16</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">right</span><span class="o">);</span>         <span class="c1">// 1.0000000000000002E16</span></code></pre></figure>

<p>Same values, different grouping, different result. That is the reason parallel sums can drift when the data has large magnitudes or mixed scales.</p>

<h2 id="the-takeaway">The takeaway</h2>

<p>A double gives you roughly the same number of significant bits everywhere (about 15-16 decimal digits), not the same absolute resolution everywhere.<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup></p>

<p>Machine epsilon tells you the first rung above 1.0.</p>

<p><code class="language-plaintext highlighter-rouge">Math.ulp(x)</code> tells you the rung spacing where you are standing.</p>

<p>And that is why, at 1e16, adding 1.0 is like whispering into a hurricane.</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="/2025/11/28/associativity-identity-folding/">Part 2: Associativity, Identity, and Folding - Why Your reduce Keeps Biting You</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>How is  2.220446049250313E-16 = 2⁻⁵²?
  2⁻⁵² = 1 / 2⁵² = 1 / 4,503,599,627,370,496</p>

      <p>1 ÷ 4,503,599,627,370,496 = 0.00000000000000022204460492503130808472633361816…<br />
 In scientific notation: 2.2204460492503130808… × 10⁻¹⁶<br />
 The displayed value 2.220446049250313E-16 is this value rounded to 15-16 significant decimal digits for display. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>For deeper reading, see David Goldberg’s classic paper, <a href="https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html">“What Every Computer Scientist Should Know About Floating-Point Arithmetic”</a>.* <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Computer Science&quot;, &quot;Software Engineering&quot;, &quot;Technology&quot;, &quot;Series 4 - Floating Point Without Tears&quot;]" /><category term="java" /><category term="floating-point" /><category term="ieee-754" /><category term="double" /><category term="machine-epsilon" /><category term="ulp" /><category term="numerics" /><summary type="html"><![CDATA[How fine is the double-precision grid, and why does 1.0 vanish next to 1e16?]]></summary></entry><entry><title type="html">Part 3: BigDecimal - When Doubles Aren’t Enough</title><link href="https://systemhalted.in/2025/12/22/java-bigdecimal-vs-double/" rel="alternate" type="text/html" title="Part 3: BigDecimal - When Doubles Aren’t Enough" /><published>2025-12-22T00:00:00+00:00</published><updated>2025-12-22T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/22/java-bigdecimal-vs-double</id><content type="html" xml:base="https://systemhalted.in/2025/12/22/java-bigdecimal-vs-double/"><![CDATA[<p><em>This post is part of my <a href="https://systemhalted.in/categories/#cat-series-4-floating-point-without-tears">Floating Point Without Tears</a> series on how Java numbers misbehave and how to live with them.</em></p>

<p>In my earlier post on <a href="/2025/12/04/ieee-754-doubles/">IEEE 754 doubles</a> I showed how a tiny Java example could break your intuition about numbers. The JVM was not being sloppy. It was faithfully following the floating point rules. The surprise came from my mental model, not from the hardware.</p>

<p>BigDecimal is Java’s answer to a different problem: <em>what if I actually need decimal correctness, not fast binary approximation?</em> It is the type you reach for when cents matter, reconciliation matters, or auditors matter.</p>

<p>It is less magical than it looks.</p>

<p><strong>TL;DR:</strong> Use <code class="language-plaintext highlighter-rouge">new BigDecimal("0.1")</code> for decimal values in your code. Only use <code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(double)</code> when you’re already stuck with a double from external sources.</p>

<h2 id="quick-reference">Quick Reference</h2>

<p>Before we dive in, here’s what you need to remember:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">  
<span class="c1">// ✓ Correct ways to create BigDecimal for money  </span>
<span class="k">new</span> <span class="nf">BigDecimal</span><span class="o">(</span><span class="s">"0.1"</span><span class="o">);</span>                   <span class="c1">// String literal  </span>
<span class="k">new</span> <span class="nf">BigDecimal</span><span class="o">(</span><span class="s">"123.45"</span><span class="o">);</span>                <span class="c1">// String literal  </span>
<span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mi">10</span><span class="o">).</span><span class="na">movePointLeft</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span> <span class="c1">// 10 × 10^-1 = 1.0  </span>

<span class="c1">// ✗ Wrong for hardcoded decimal values  </span>
<span class="k">new</span> <span class="nf">BigDecimal</span><span class="o">(</span><span class="mf">0.1</span><span class="o">);</span>        <span class="c1">// Exposes the binary approximation as decimal</span></code></pre></figure>

<h2 id="doubles-speak-binary-your-domain-speaks-decimal">Doubles speak binary, your domain speaks decimal</h2>

<p><code class="language-plaintext highlighter-rouge">double</code> is brilliant for physics, graphics, simulations, and anything where small error is acceptable. It is terrible at representing human money. The root cause is simple. Doubles are binary fractions. Money is decimal.</p>

<p><code class="language-plaintext highlighter-rouge">0.1</code> rupee or dollar has no exact representation in binary floating point. When you write:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">x</span> <span class="o">=</span> <span class="mf">0.1</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x</span><span class="o">);</span>              <span class="c1">// prints 0.1 (canonical string)</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="s">"%.20f%n"</span><span class="o">,</span> <span class="n">x</span><span class="o">);</span>    <span class="c1">// 0.10000000000000000555...</span></code></pre></figure>

<p>you are already off by a tiny amount, even though the default printout shows <code class="language-plaintext highlighter-rouge">0.1</code>. Most of the time you are happy to ignore that tiny tail (technically binary approximation). But then you sum millions of rows, or reorder operations, or start comparing for equality, and the tail starts wagging the dog.</p>

<p>BigDecimal cuts across this by working in base 10.</p>

<h2 id="bigdecimals-mental-model">BigDecimal’s mental model</h2>

<p>Conceptually, a BigDecimal is two things glued together:</p>

<ol>
  <li>An integer representing all the digits, without any decimal point.</li>
  <li>A scale that says where the decimal point lives.</li>
</ol>

<p>Formally:</p>

<p><code class="language-plaintext highlighter-rouge">value = unscaledValue × 10^(-scale)</code></p>

<p>So:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">amount</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"123.45"</span><span class="o">);</span></code></pre></figure>

<p>internally becomes:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">unscaledValue = 12345</code></li>
  <li><code class="language-plaintext highlighter-rouge">scale = 2</code></li>
  <li>logical value = <code class="language-plaintext highlighter-rouge">12345 × 10^-2 = 123.45</code></li>
</ul>

<p>Because the unscaled integer is exact, decimal values like <code class="language-plaintext highlighter-rouge">0.1</code>, <code class="language-plaintext highlighter-rouge">0.01</code>, <code class="language-plaintext highlighter-rouge">1234567890.12</code> are also exact. There is no “closest representable value” the way there is with <code class="language-plaintext highlighter-rouge">double</code>. You only lose information when you explicitly ask BigDecimal to round (via <code class="language-plaintext highlighter-rouge">MathContext</code> or <code class="language-plaintext highlighter-rouge">setScale</code>).</p>

<h3 id="how-the-jdk-actually-stores-it">How the JDK actually stores it</h3>

<p>That is the spec view. Under the hood in OpenJDK, the class looks roughly like this:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">BigDecimal</span> <span class="kd">extends</span> <span class="nc">Number</span>
        <span class="kd">implements</span> <span class="nc">Comparable</span><span class="o">&lt;</span><span class="nc">BigDecimal</span><span class="o">&gt;</span> <span class="o">{</span>

    <span class="c1">// Compact form when it fits in a long</span>
    <span class="kd">private</span> <span class="kd">transient</span> <span class="kt">long</span> <span class="n">intCompact</span><span class="o">;</span>

    <span class="c1">// Full form when it doesn't</span>
    <span class="kd">private</span> <span class="nc">BigInteger</span> <span class="n">intVal</span><span class="o">;</span>

    <span class="c1">// Digits after the decimal point</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">scale</span><span class="o">;</span>

    <span class="c1">// Cached number of significant digits</span>
    <span class="kd">private</span> <span class="kd">transient</span> <span class="kt">int</span> <span class="n">precision</span><span class="o">;</span>

    <span class="c1">// Marker for "no compact long, use intVal instead"</span>
    <span class="kd">static</span> <span class="kd">final</span> <span class="kt">long</span> <span class="no">INFLATED</span> <span class="o">=</span> <span class="nc">Long</span><span class="o">.</span><span class="na">MIN_VALUE</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<p>So BigDecimal actually has two representations for the unscaled value:</p>

<ul>
  <li><strong>Compact mode</strong>: if the unscaled integer fits in a 64-bit <code class="language-plaintext highlighter-rouge">long</code>, it lives in <code class="language-plaintext highlighter-rouge">intCompact</code> and <code class="language-plaintext highlighter-rouge">intVal</code> is <code class="language-plaintext highlighter-rouge">null</code>. This is the fast path for “small enough” numbers.</li>
  <li><strong>Inflated mode</strong>: if it does not fit, <code class="language-plaintext highlighter-rouge">intCompact</code> is set to <code class="language-plaintext highlighter-rouge">INFLATED</code> and the digits live in <code class="language-plaintext highlighter-rouge">intVal</code> as a <code class="language-plaintext highlighter-rouge">BigInteger</code>.</li>
</ul>

<p>This optimization means small monetary amounts stay fast, while supporting arbitrarily large values when needed.</p>

<p>Your <code class="language-plaintext highlighter-rouge">123.45</code> example fits happily in compact form:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">intCompact = 12345L</code></li>
  <li><code class="language-plaintext highlighter-rouge">intVal = null</code></li>
  <li><code class="language-plaintext highlighter-rouge">scale = 2</code></li>
</ul>

<h2 id="never-construct-bigdecimal-from-a-double">Never construct BigDecimal from a double</h2>

<p>A classic foot-gun looks like this:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">a</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="mf">0.1</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">b</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"0.1"</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span> <span class="c1">// 0.1000000000000000055511151231257827021181583404541015625</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">b</span><span class="o">);</span> <span class="c1">// 0.1</span></code></pre></figure>

<p>The first line takes the <em>binary</em> double for <code class="language-plaintext highlighter-rouge">0.1</code> and converts it directly into an exact decimal. The double is already an approximation, so you get the full fraction printed out.</p>

<p>The second line parses the string <code class="language-plaintext highlighter-rouge">"0.1"</code> as a decimal value. There is no binary detour, so you get exactly one tenth.</p>

<p>You have not “fixed” the double by wrapping it in a BigDecimal. You have just made its approximation painfully visible.</p>

<h2 id="what-about-bigdecimalvalueof-and-canonical-strings">What about BigDecimal.valueOf() and canonical strings?</h2>

<p>This is where people get confused:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">a</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="mf">0.1</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">b</span> <span class="o">=</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mf">0.1</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
<span class="c1">// 0.1000000000000000055511151231257827021181583404541015625</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">b</span><span class="o">);</span>
<span class="c1">// 0.1</span></code></pre></figure>

<p>Same literal <code class="language-plaintext highlighter-rouge">0.1</code>, two different worlds.</p>

<p>The difference is that <code class="language-plaintext highlighter-rouge">valueOf</code> goes through the <strong>canonical decimal string</strong> of the double.</p>

<h3 id="route-1-new-bigdecimal01">Route 1: <code class="language-plaintext highlighter-rouge">new BigDecimal(0.1)</code></h3>

<p>This constructor works directly from the binary bits of the double:</p>

<ul>
  <li>The double for <code class="language-plaintext highlighter-rouge">0.1</code> is not exactly one tenth.</li>
  <li>It is some messy binary fraction very close to 0.1.</li>
  <li><code class="language-plaintext highlighter-rouge">new BigDecimal(double)</code> asks: “What is the exact decimal value of this binary fraction?”</li>
</ul>

<p>So you see the full binary approximation:</p>

<blockquote>
  <p>0.1000000000000000055511151231257827021181583404541015625</p>
</blockquote>

<p>Ugly, but honest.</p>

<h3 id="route-2-bigdecimalvalueof01-and-canonical-decimal-strings">Route 2: <code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(0.1)</code> and canonical decimal strings</h3>

<p><code class="language-plaintext highlighter-rouge">valueOf</code> takes a different path:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="nc">BigDecimal</span> <span class="nf">valueOf</span><span class="o">(</span><span class="kt">double</span> <span class="n">val</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">val</span><span class="o">));</span>
<span class="o">}</span></code></pre></figure>

<p>The key piece here is <code class="language-plaintext highlighter-rouge">Double.toString(val)</code>. That method does not dump all the internal bits. Instead, it produces the <strong>canonical decimal string</strong> for that double:</p>

<blockquote>
  <p>The shortest decimal string that, if you parse it back with <code class="language-plaintext highlighter-rouge">Double.parseDouble</code>, gives you exactly the same double bits.</p>
</blockquote>

<p>In code, it guarantees:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">x</span> <span class="o">=</span> <span class="o">...;</span>
<span class="nc">String</span> <span class="n">s</span> <span class="o">=</span> <span class="nc">Double</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">x</span><span class="o">);</span>
<span class="kt">double</span> <span class="n">y</span> <span class="o">=</span> <span class="nc">Double</span><span class="o">.</span><span class="na">parseDouble</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>

<span class="k">assert</span> <span class="nc">Double</span><span class="o">.</span><span class="na">doubleToLongBits</span><span class="o">(</span><span class="n">x</span><span class="o">)</span> <span class="o">==</span> <span class="nc">Double</span><span class="o">.</span><span class="na">doubleToLongBits</span><span class="o">(</span><span class="n">y</span><span class="o">);</span></code></pre></figure>

<p>For the double that represents <code class="language-plaintext highlighter-rouge">0.1</code>, that canonical string happens to be:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">Double</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="mf">0.1</span><span class="o">);</span> <span class="c1">// "0.1"</span></code></pre></figure>

<p>So the pipeline for <code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(0.1)</code> is:</p>

<ol>
  <li>Start from the binary double for 0.1.</li>
  <li>Turn it into its canonical decimal string <code class="language-plaintext highlighter-rouge">"0.1"</code> – a decimal string with just enough digits to round back to the same double (i.e., to distinguish it from adjacent doubles).</li>
  <li>Feed that string into <code class="language-plaintext highlighter-rouge">new BigDecimal("0.1")</code>.</li>
</ol>

<p>Result: an exact decimal 0.1, not the giant tail.</p>

<p>So you can summarise it like this:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">new BigDecimal(0.1)</code> = “give me the exact decimal value of this weird binary fraction”.</li>
  <li><code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(0.1)</code> = “give me the exact decimal value of the canonical string <code class="language-plaintext highlighter-rouge">\"0.1\"</code> for this double”.</li>
</ul>

<p>The <strong>rounding error</strong> happened earlier, when you chose a <code class="language-plaintext highlighter-rouge">double</code> to represent 0.1 at all. <code class="language-plaintext highlighter-rouge">valueOf</code> doesn’t fix that choice, but it gives you a clean, canonical decimal view of that double instead of the raw fraction.</p>

<h3 id="when-to-use-valueof">When to use <code class="language-plaintext highlighter-rouge">valueOf()</code></h3>

<p><code class="language-plaintext highlighter-rouge">valueOf</code> is useful, and often preferred, in three situations:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// Fine for integers</span>
<span class="nc">BigDecimal</span> <span class="n">cents</span> <span class="o">=</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mi">12345</span><span class="o">);</span> <span class="c1">// 12345</span>

<span class="c1">// When you're stuck with a double from legacy code</span>
<span class="kt">double</span> <span class="n">legacyPrice</span> <span class="o">=</span> <span class="n">thirdPartyApi</span><span class="o">.</span><span class="na">getPrice</span><span class="o">();</span>   <span class="c1">// You can't control this</span>
<span class="nc">BigDecimal</span> <span class="n">price</span> <span class="o">=</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">legacyPrice</span><span class="o">)</span>
    <span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_UP</span><span class="o">);</span>          <span class="c1">// Accept the loss, make it explicit</span>

<span class="c1">// When building decimal values programmatically from integers</span>
<span class="nc">BigDecimal</span> <span class="n">tenth</span> <span class="o">=</span> <span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="mi">1</span><span class="o">).</span><span class="na">movePointLeft</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>  <span class="c1">// Start from exact integer 1</span></code></pre></figure>

<p>But for <strong>hardcoded monetary values</strong> in your own code, skip doubles entirely and use string literals:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">price</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"0.10"</span><span class="o">);</span></code></pre></figure>

<p>The real rule is about <strong>where the value originates</strong>:</p>

<ul>
  <li>If the value is born in your domain as a decimal (prices, rates, balances), create it from a decimal representation (<code class="language-plaintext highlighter-rouge">String</code>, <code class="language-plaintext highlighter-rouge">long</code> + scale).</li>
  <li>If the value is already stuck in a <code class="language-plaintext highlighter-rouge">double</code>, use <code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(double)</code> and treat that conversion as a boundary where precision may already have been lost.</li>
</ul>

<p>BigDecimal will not magically repair a bad choice of primitive type.</p>

<h3 id="quick-comparison">Quick comparison</h3>

<table>
  <thead>
    <tr>
      <th>Expression</th>
      <th>Result</th>
      <th>Use Case</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">new BigDecimal("0.1")</code></td>
      <td>Exact decimal 0.1</td>
      <td>Hardcoded money</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">new BigDecimal(0.1)</code></td>
      <td>0.10000000000…05511 (binary approximation)</td>
      <td>Never use this</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(0.1)</code></td>
      <td>0.1 (canonical string)</td>
      <td>When stuck with double</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">BigDecimal.valueOf(1, 1)</code></td>
      <td>0.1 (1 × 10^-1)</td>
      <td>Programmatic creation</td>
    </tr>
  </tbody>
</table>

<h2 id="exact-sums-predictable-cents">Exact sums, predictable cents</h2>

<p>Here’s a comparison showing why BigDecimal matters for financial code:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// With doubles - unpredictable</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Double</span><span class="o">&gt;</span> <span class="n">doubleAmounts</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
    <span class="mf">10000000000000000.00</span><span class="o">,</span>
    <span class="mf">1.00</span><span class="o">,</span> <span class="mf">1.00</span><span class="o">,</span> <span class="mf">1.00</span><span class="o">,</span> <span class="mf">1.00</span>
<span class="o">);</span>
<span class="kt">double</span> <span class="n">doubleSum</span> <span class="o">=</span> <span class="n">doubleAmounts</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
    <span class="o">.</span><span class="na">mapToDouble</span><span class="o">(</span><span class="nl">Double:</span><span class="o">:</span><span class="n">doubleValue</span><span class="o">).</span><span class="na">sum</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">doubleSum</span><span class="o">);</span> <span class="c1">// 1.0000000000000004E16</span></code></pre></figure>

<p>The result is mathematically correct (10^16 + 4), but the representation shows how rounding noise creeps in when you mix huge and small magnitudes in binary floating point. At this scale, many consecutive integers are not exactly representable as double, so tiny adjustments end up living in the low bits and surfacing as …0004E16.</p>

<p>Now compare with BigDecimal:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// With BigDecimal - exact</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">BigDecimal</span><span class="o">&gt;</span> <span class="n">amounts</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
    <span class="k">new</span> <span class="nf">BigDecimal</span><span class="o">(</span><span class="s">"10000000000000000.00"</span><span class="o">),</span>
    <span class="k">new</span> <span class="nf">BigDecimal</span><span class="o">(</span><span class="s">"1.00"</span><span class="o">),</span>
    <span class="k">new</span> <span class="nf">BigDecimal</span><span class="o">(</span><span class="s">"1.00"</span><span class="o">),</span>
    <span class="k">new</span> <span class="nf">BigDecimal</span><span class="o">(</span><span class="s">"1.00"</span><span class="o">),</span>
    <span class="k">new</span> <span class="nf">BigDecimal</span><span class="o">(</span><span class="s">"1.00"</span><span class="o">)</span>
<span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">sum</span> <span class="o">=</span> <span class="n">amounts</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
    <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">ZERO</span><span class="o">,</span> <span class="nl">BigDecimal:</span><span class="o">:</span><span class="n">add</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">sum</span><span class="o">);</span> <span class="c1">// 10000000000000004.00</span></code></pre></figure>

<p>No matter how you reorder the BigDecimal list, you will get the same <code class="language-plaintext highlighter-rouge">10000000000000004.00</code>. There is no hidden rounding based on magnitude, because the arithmetic is done on the unscaled integers.</p>

<p>You pay for this determinism. BigDecimal operations are typically 10-100× slower than double, depending on the values involved. But when you reconcile two systems and everything lines up to the last cent, you know where the extra CPU cycles went.</p>

<h2 id="scale-rounding-and-the-joy-of-being-explicit">Scale, rounding, and the joy of being explicit</h2>

<p>With doubles, rounding is automatic and mostly invisible. With BigDecimal, rounding is very much your problem.</p>

<p>Imagine you need to divide 1 rupee into 3 equal parts:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">one</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"1.00"</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">three</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"3.00"</span><span class="o">);</span>

<span class="nc">BigDecimal</span> <span class="n">each</span> <span class="o">=</span> <span class="n">one</span><span class="o">.</span><span class="na">divide</span><span class="o">(</span><span class="n">three</span><span class="o">);</span> <span class="c1">// Kaboom: ArithmeticException</span></code></pre></figure>

<p>The exception is deliberate. <code class="language-plaintext highlighter-rouge">1 / 3</code> in decimal form is <code class="language-plaintext highlighter-rouge">0.3333…</code> forever. BigDecimal refuses to guess how many digits you want. You must say how you want the result rounded.</p>

<p>There are two approaches, and knowing when to use which matters.</p>

<h3 id="mathcontext-for-intermediate-calculations">MathContext: For intermediate calculations</h3>

<p>Use <code class="language-plaintext highlighter-rouge">MathContext</code> when you need to control significant digits during computation:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// Calculate pi as 22/7 with 10 significant digits</span>
<span class="nc">MathContext</span> <span class="n">mc</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MathContext</span><span class="o">(</span><span class="mi">10</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_UP</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">pi</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"22"</span><span class="o">).</span><span class="na">divide</span><span class="o">(</span>
    <span class="k">new</span> <span class="nf">BigDecimal</span><span class="o">(</span><span class="s">"7"</span><span class="o">),</span> 
    <span class="n">mc</span>
<span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">pi</span><span class="o">);</span> <span class="c1">// 3.142857143</span></code></pre></figure>

<h3 id="setscale-for-final-results">setScale: For final results</h3>

<p>Use <code class="language-plaintext highlighter-rouge">setScale</code> when you need to control decimal places for presentation or storage:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// Round a calculated price to 2 decimal places for currency</span>
<span class="nc">BigDecimal</span> <span class="n">rawPrice</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"12.3456"</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">price</span> <span class="o">=</span> <span class="n">rawPrice</span><span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_UP</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">price</span><span class="o">);</span> <span class="c1">// 12.35</span></code></pre></figure>

<h3 id="the-pattern-that-works">The pattern that works</h3>

<p>For currency, a clean approach is:</p>

<ol>
  <li>Decide how many decimal places your business uses (usually 2 for most currencies)</li>
  <li>Store all monetary values with that scale</li>
  <li>When you need intermediate higher precision, use a <code class="language-plaintext highlighter-rouge">MathContext</code> locally</li>
  <li>Bring the value back to your standard scale at the boundaries</li>
</ol>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">rate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"0.0525"</span><span class="o">);</span> <span class="c1">// 5.25% interest rate</span>
<span class="nc">BigDecimal</span> <span class="n">principal</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"1000.00"</span><span class="o">);</span>

<span class="c1">// Higher precision for calculation</span>
<span class="nc">MathContext</span> <span class="n">mc</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MathContext</span><span class="o">(</span><span class="mi">10</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_UP</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">interest</span> <span class="o">=</span> <span class="n">principal</span><span class="o">.</span><span class="na">multiply</span><span class="o">(</span><span class="n">rate</span><span class="o">,</span> <span class="n">mc</span><span class="o">);</span>

<span class="c1">// Round to cents for storage</span>
<span class="n">interest</span> <span class="o">=</span> <span class="n">interest</span><span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_UP</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">interest</span><span class="o">);</span> <span class="c1">// 52.50</span></code></pre></figure>

<h2 id="equals-is-not-the-same-as-compareto">Equals is not the same as compareTo</h2>

<p>There is a subtle trap buried in BigDecimal’s API:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"1.0"</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">y</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"1.00"</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">y</span><span class="o">));</span>    <span class="c1">// false</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="n">y</span><span class="o">));</span> <span class="c1">// 0</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">equals</code> cares about both value and scale. The unscaled integer is <code class="language-plaintext highlighter-rouge">10</code> vs <code class="language-plaintext highlighter-rouge">100</code>, scale is <code class="language-plaintext highlighter-rouge">1</code> vs <code class="language-plaintext highlighter-rouge">2</code>, so the objects are not “equal”.</p>

<p><code class="language-plaintext highlighter-rouge">compareTo</code> cares only about numeric value. From that point of view they are both exactly one, so the comparison says zero.</p>

<p>If you put BigDecimal keys into a <code class="language-plaintext highlighter-rouge">HashMap</code> or <code class="language-plaintext highlighter-rouge">HashSet</code>, you are using <code class="language-plaintext highlighter-rouge">equals</code>. If you put them in a <code class="language-plaintext highlighter-rouge">TreeMap</code> or <code class="language-plaintext highlighter-rouge">TreeSet</code>, you are using <code class="language-plaintext highlighter-rouge">compareTo</code>. That difference has bitten enough people that the Javadoc has an explicit warning<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>.</p>

<h3 id="what-to-do-about-it">What to do about it</h3>

<p>For financial applications, you typically want value-based comparison:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// Use compareTo for all business logic</span>
<span class="k">if</span> <span class="o">(</span><span class="n">price</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="n">threshold</span><span class="o">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">applyDiscount</span><span class="o">();</span>
<span class="o">}</span>

<span class="c1">// Or normalize scale before storing in collections</span>
<span class="nc">BigDecimal</span> <span class="n">normalized</span> <span class="o">=</span> <span class="n">value</span><span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">UNNECESSARY</span><span class="o">);</span>
<span class="n">priceSet</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">normalized</span><span class="o">);</span></code></pre></figure>

<h2 id="common-mistakes">Common mistakes</h2>

<p>Beyond the double constructor trap, watch out for these.</p>

<h3 id="using--for-comparison">Using == for comparison</h3>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// Wrong</span>
<span class="k">if</span> <span class="o">(</span><span class="n">price</span> <span class="o">==</span> <span class="n">threshold</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>

<span class="c1">// Right</span>
<span class="k">if</span> <span class="o">(</span><span class="n">price</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="n">threshold</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span></code></pre></figure>

<h3 id="forgetting-rounding-mode">Forgetting rounding mode</h3>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// Throws ArithmeticException</span>
<span class="nc">BigDecimal</span> <span class="n">result</span> <span class="o">=</span> <span class="n">amount</span><span class="o">.</span><span class="na">divide</span><span class="o">(</span><span class="n">three</span><span class="o">);</span>

<span class="c1">// Specify your intent</span>
<span class="nc">BigDecimal</span> <span class="n">result</span> <span class="o">=</span> <span class="n">amount</span><span class="o">.</span><span class="na">divide</span><span class="o">(</span><span class="n">three</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">HALF_UP</span><span class="o">);</span></code></pre></figure>

<h3 id="mixing-scales-carelessly">Mixing scales carelessly</h3>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">a</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"1.0"</span><span class="o">);</span>   <span class="c1">// scale 1</span>
<span class="nc">BigDecimal</span> <span class="n">b</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"2.00"</span><span class="o">);</span>  <span class="c1">// scale 2</span>
<span class="nc">BigDecimal</span> <span class="n">sum</span> <span class="o">=</span> <span class="n">a</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">b</span><span class="o">);</span>              <span class="c1">// scale 2 (max of the two)</span>

<span class="c1">// Result may have unexpected scale; normalize when it matters</span>
<span class="c1">// Note: UNNECESSARY throws ArithmeticException if rounding would be needed</span>
<span class="n">sum</span> <span class="o">=</span> <span class="n">sum</span><span class="o">.</span><span class="na">setScale</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="nc">RoundingMode</span><span class="o">.</span><span class="na">UNNECESSARY</span><span class="o">);</span></code></pre></figure>

<h2 id="when-should-you-actually-use-bigdecimal">When should you actually use BigDecimal?</h2>

<p>BigDecimal is not a “better double”. It is a different tool.</p>

<p>Reach for BigDecimal when:</p>

<ul>
  <li>You are working with money, interest rates, exchange rates, or anything that must reconcile to the cent or paise</li>
  <li>You are implementing rules that are written in decimal terms by humans and regulators, not in binary terms by hardware engineers</li>
  <li>You care more about correctness and determinism than raw speed</li>
</ul>

<p>Stay with doubles when:</p>

<ul>
  <li>You are doing heavy numeric computing, simulations, statistics, graphics, or ML workloads where small rounding error is acceptable and performance dominates</li>
  <li>You are counting in powers of two, not powers of ten</li>
  <li>The measurements themselves are imprecise (sensor readings, physical measurements)</li>
</ul>

<p>You can always convert between the two worlds at clearly defined boundaries.</p>

<h2 id="closing-thought">Closing thought</h2>

<p>BigDecimal is not slow magic. It is a disciplined refusal to lie about decimals.</p>

<p>Doubles take a binary view of the universe and do their best to approximate your decimal stories. BigDecimal takes your decimal stories literally and forces you to be explicit about where information is lost.</p>

<p>Neither is the “right” choice in isolation. The trick is to know which world you are in.</p>

<h2 id="references">References</h2>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>BigDecimal Java 17 JavaDocs: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/math/BigDecimal.html#equals(java.lang.Object) <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Computer Science&quot;, &quot;Software Engineering&quot;, &quot;Technology&quot;, &quot;Series 4 - Floating Point Without Tears&quot;]" /><category term="java" /><category term="bigdecimal" /><category term="ieee-754" /><category term="money" /><category term="numeric-precision" /><summary type="html"><![CDATA[Why BigDecimal exists, how it really works, and when you should reach for it instead of double.]]></summary></entry><entry><title type="html">Reforming the Security Council Without Breaking Trust</title><link href="https://systemhalted.in/2025/12/22/reforming-unsc-without-breaking-trust/" rel="alternate" type="text/html" title="Reforming the Security Council Without Breaking Trust" /><published>2025-12-22T00:00:00+00:00</published><updated>2025-12-22T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/22/reforming-unsc-without-breaking-trust</id><content type="html" xml:base="https://systemhalted.in/2025/12/22/reforming-unsc-without-breaking-trust/"><![CDATA[<p>I have written about United Nations Security Council reforms before, and the older I get, the more I realize this is not a topic that rewards anger. It rewards clarity.</p>

<p>The Security Council was designed in a specific world. The year was 1945. The problem statement was simple and terrifying: prevent another world war. The veto was not invented as a moral idea. It was a stability mechanism. If the strongest powers of that era were expected to participate in a system, they needed assurance that the system would not be used against what they consider existential interests. You may dislike that logic, but you cannot pretend it is irrational. It is the kind of logic that keeps institutions alive.</p>

<p>Still, a system can be historically justified and yet feel increasingly mismatched to the world it is supposed to manage. When that mismatch grows, two things happen.</p>

<p>First, decisions start looking less legitimate to those who are asked to accept them.</p>

<p>Second, countries start routing around the institution when it feels slow, unpredictable, or politically costly.</p>

<p>Both outcomes are bad. Legitimacy without power becomes poetry. Power without legitimacy becomes noise. The world needs less noise.</p>

<p>So the best way to talk about Security Council reform is not as a fight between “the powerful” and “the rest.” The best way is to ask a practical question.</p>

<p>How do we keep the Council authoritative enough that major powers remain invested in it, while making it representative enough that the wider world respects it?</p>

<p>That question matters because reform that ignores incentives will not produce a better Security Council. It will produce an ignored Security Council. And an ignored Security Council is not a victory for democracy. It is a victory for chaos.</p>

<p>So my goal here is not to write a fantasy about “abolishing the veto.” Charter change is hard, and the Charter is designed to be hard to change. The goal is more modest and more practical.</p>

<p>Make obstruction more accountable, more legible, and harder to perform casually, without pretending we can enforce good faith.</p>

<p>I will do three things for each proposal.</p>

<ol>
  <li>Describe what happens today.</li>
  <li>Define a concrete additional process.</li>
  <li>Explain why it helps, including where it can still fail.</li>
</ol>

<h2 id="1-make-veto-use-more-accountable-not-weaker">1. Make veto use more accountable, not weaker</h2>

<h3 id="what-happens-today">What happens today</h3>

<p>A substantive Council decision needs at least nine affirmative votes and no negative vote from any permanent member. One negative vote by a permanent member blocks the draft. That is the veto. (UN Charter, Article 27)<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></p>

<p>After the veto, accountability is mostly reputational.</p>

<p>Yes, the veto is public. Voting records exist. Explanations may be offered in the chamber. But the content is unstructured and often optimized for politics, not clarity.</p>

<p>Since 2022, there is also a formal spotlight mechanism. When a veto is cast, the General Assembly President must convene a debate within ten working days. The Assembly “invites” the Council to submit a special report on the veto at least 72 hours before the debate. (UNGA resolution 76/262)<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> The important word there is “invites.” It raises political cost, but it does not force precision, and it does not force participation. Analysts have noted that the resolution does not impose obligations on the vetoing state to even show up.<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup></p>

<h3 id="what-i-propose">What I propose</h3>

<p>A veto should trigger a short, mandatory, structured “Veto Brief” filed as an official UN document within 48 hours.</p>

<p>Not an essay. A template with hard edges.</p>

<p>The brief must include:</p>

<ol>
  <li>
    <p>Pinpoint objection<br />
Identify exactly which operative paragraphs are unacceptable.</p>
  </li>
  <li>
    <p>Factual predicates<br />
List the key factual claims the veto relies on, written as testable statements.</p>
  </li>
  <li>
    <p>Principle being invoked<br />
Cite Charter articles if needed, but state the real test in plain language. The Charter citation supports the argument. It cannot replace the argument.</p>
  </li>
  <li>
    <p>Acceptable path to “yes”<br />
Provide at least one concrete amendment or alternative text that would remove the need for a veto.</p>
  </li>
  <li>
    <p>Review trigger<br />
If the veto is conditional, state the conditions and a review date for reconsideration.</p>
  </li>
</ol>

<p>Then, within seven days, the Council schedules a short “Veto Consequences” session. Ten minutes for the vetoing member to present the brief, then a time-boxed round of questions from elected members. No moral theatre required. Just structured daylight.</p>

<p>Finally, add one small but sharp piece of enforcement that costs almost nothing.</p>

<p>If the vetoing member does not file the brief, that non-submission is recorded in the Council’s official meeting record, and it is flagged in the General Assembly debate convened under 76/262.<sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> The veto remains valid, but the refusal becomes part of the permanent archive and part of the public debate.</p>

<p>To prevent a perfunctory brief that merely checks boxes, the Council President should record whether the brief is <strong>responsive</strong> to the template (i.e., it contains a concrete “path to yes” and specific factual predicates, not generic slogans). A non-responsive brief is treated like a non-submission for the purposes of the General Assembly debate, where <strong>the adequacy of the brief becomes a formal topic</strong>–members can explicitly challenge missing predicates, evasive language, or the absence of any acceptable alternative.<sup id="fnref:2:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<h3 id="why-it-helps-and-where-it-can-fail">Why it helps, and where it can fail</h3>

<p>The honest criticism is correct: “What if powerful states don’t care?”</p>

<p>Some don’t. History is not shy about that.</p>

<p>But accountability is not only about changing minds. It is also about changing the friction profile of a behavior.</p>

<p>This mechanism raises the cost of vetoing in three ways that do not require new UN spending.</p>

<ol>
  <li>
    <p>It shifts effort onto the actor choosing the veto.<br />
The UN does not hire a new bureaucracy. The vetoing mission uses its existing staff to produce a short document.</p>
  </li>
  <li>
    <p>It removes the fog that makes performative vetoes easy.<br />
If you must state what you would accept, you cannot veto and disappear into slogans.</p>
  </li>
  <li>
    <p>It makes patterns visible over time.<br />
A veto is a single moment. A trail of structured briefs becomes a story.</p>
  </li>
</ol>

<p>A concrete example shows the dysfunction today.</p>

<p>On Syria, vetoes have been repeatedly used to block action on drafts concerning the conflict and humanitarian mechanisms. Security Council Report notes that since 2011, Russia cast 19 vetoes, with 14 on Syria, and that eight of nine Chinese vetoes in that period were on Syria.<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> The UN’s own research guide lists vetoed Syria-related drafts across multiple years.<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup></p>

<p>A structured veto brief would not magically unlock consensus, but it would force two things that are often missing.</p>

<p>First, explicit alternative text, on the record, each time.</p>

<p>Second, explicit factual predicates, which can be challenged publicly in the General Assembly debate already mandated after a veto.<sup id="fnref:2:3"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<p>Failure mode still exists. A determined vetoing member can absorb reputational costs. But even then, the system gains something it currently lacks: a clear, testable record of what was blocked and what compromise was refused.</p>

<p>That clarity matters to history, to diplomacy, and to any future negotiation.</p>

<h2 id="2-build-a-norm-of-restraint-for-mass-atrocities-and-make-the-norm-operational">2. Build a norm of restraint for mass atrocities, and make the norm operational</h2>

<h3 id="what-happens-today-1">What happens today</h3>

<p>There is no binding rule preventing veto use in situations involving genocide, crimes against humanity, or war crimes.</p>

<p>There are voluntary initiatives.</p>

<p>The ACT Code of Conduct asks states to support timely and decisive action to prevent or end mass atrocity crimes, and calls on Council members not to vote against credible action aimed at stopping them.<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup> There is also the France–Mexico initiative calling for voluntary restraint on veto use in mass atrocity situations.<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup></p>

<p>These matter. But voluntary norms have a familiar weakness: they work best when they are not needed.</p>

<h3 id="what-i-propose-1">What I propose</h3>

<p>Convert “restraint” from a moral appeal into a repeatable procedure.</p>

<p>Create a “Mass Atrocity Track” for draft resolutions explicitly aimed at preventing or halting atrocity crimes.</p>

<p>If a draft is placed on this track, then any veto triggers two additional obligations inside the veto brief.</p>

<ol>
  <li>
    <p>Civilian impact claim<br />
A short statement explaining why the vetoing member believes the draft would worsen civilian protection outcomes, or violate a principle in a way that outweighs the harm of inaction.</p>
  </li>
  <li>
    <p>Alternative protection path<br />
A concrete alternative proposal that still targets civilian protection, even if it changes the means.</p>
  </li>
</ol>

<p>Then impose one simple process requirement.</p>

<p>Within 14 days of a veto on the atrocity track, the Council must vote on at least one alternative draft addressing the same civilian protection objective, even if it is imperfect.</p>

<p>This is not “forcing agreement.” It is forcing effort.</p>

<h3 id="why-it-helps-and-what-happens-if-the-veto-still-blocks-action">Why it helps, and what happens if the veto still blocks action</h3>

<p>The nightmare scenario is real.</p>

<p>What if this process exists and a veto still blocks action during an active genocide?</p>

<p>Then the Council’s impotence becomes more transparent.</p>

<p>That sounds grim, but transparency is not nothing. Opacity is how paralysis becomes normal.</p>

<p>The additional value here is that the mandated General Assembly debate after a veto (76/262) becomes better informed. Instead of debating fog, the wider membership debates a structured record that includes what alternative protection pathways were offered or not offered.<sup id="fnref:2:4"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<p>And yes, powerful states can still ignore the norm. But a norm tied to a forced iteration loop changes behavior at the margin, and “at the margin” is often where real lives are saved.</p>

<h2 id="3-expand-membership-with-a-specific-shape-and-admit-the-trade-offs">3. Expand membership with a specific shape, and admit the trade offs</h2>

<h3 id="what-happens-today-2">What happens today</h3>

<p>The Council has 15 members: five permanent, ten elected for two-year terms. (UN Charter, Article 23)<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup></p>

<p>Those ten elected seats are distributed by regional groups: three for Africa, two for Asia Pacific, two for Latin America and the Caribbean, two for Western Europe and Others, and one for Eastern Europe.<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup></p>

<p>Two problems follow from this design.</p>

<p>First, two years is short. Many elected members become effective only when their term is ending.</p>

<p>Second, representation is frozen in a world that has changed.</p>

<h3 id="what-i-propose-2">What I propose</h3>

<p>A concrete model that sits between “keep it at 15 forever” and “blow it up to 25 plus overnight” is this.</p>

<p>Expand from 15 to 21 members by adding six longer-term renewable seats, without adding new vetoes.</p>

<ol>
  <li>Keep the current 10 two-year elected seats and their existing regional distribution.<sup id="fnref:9:1"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup></li>
  <li>Add six renewable seats with four-year terms, eligible for immediate re-election once.<br />
Four years is long enough to build genuine file expertise and relationships; short enough that renewal still means something.</li>
  <li>Allocate those six seats by region as follows:
2 Africa<br />
2 Asia Pacific<br />
1 Latin America and the Caribbean<br />
1 split rotation between Eastern Europe and Small Island Developing States</li>
</ol>

<p>This is not the only possible distribution. The point is to stop hand waving and put a shape on the table.</p>

<p>It also aligns with the core complaint that the current Council has no permanent representation for Africa or Latin America, a point repeatedly raised in reform debates.<sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">10</a></sup></p>

<h3 id="who-defines-the-criteria-and-why-would-anyone-accept-them">Who defines the criteria, and why would anyone accept them?</h3>

<p>Criteria should be defined by the General Assembly through the ongoing intergovernmental negotiations process, not by the permanent members alone. The politics are difficult, but the venue is clear.<sup id="fnref:11"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">11</a></sup></p>

<p>A Charter amendment is still required for composition change, and Charter amendments require ratification including by all permanent members. (UN Charter, Article 108)<sup id="fnref:12"><a href="#fn:12" class="footnote" rel="footnote" role="doc-noteref">12</a></sup></p>

<p>That is the hard wall. No serious reform should pretend it is not there.</p>

<p>So the real strategy is phased.</p>

<p>Phase 1 is working-methods reform that does not require Charter amendment. Veto briefs, structured hearings, iteration loops.</p>

<p>Phase 2 is composition reform, which requires a broader bargain.</p>

<h3 id="trade-off-larger-councils-can-be-slower">Trade off: larger councils can be slower</h3>

<p>This is a real risk. Larger groups can move slower, and more seats can add friction.</p>

<p>The mitigation is to keep the extra seats longer-term and renewable. Continuity reduces the constant onboarding churn that already slows the Council today.</p>

<p>Another mitigation is procedural discipline: time-limited negotiations, published draft histories, and structured “what would unlock agreement” fields, so debate does not become infinite.</p>

<p>And it is worth noting a counterweight to the “bigger means slower” argument: the Council already runs at high tempo. In 2024 it held 305 formal meetings (a record), which suggests there is procedural capacity for a modest increase in membership without fundamentally changing the Council’s operating rhythm.<sup id="fnref:15"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">13</a></sup></p>

<h2 id="4-replace-permanent-forever-with-long-term-renewable-as-a-direction-the-system-can-actually-walk">4. Replace “permanent forever” with “long term renewable,” as a direction the system can actually walk</h2>

<h3 id="what-happens-today-3">What happens today</h3>

<p>Permanence is embedded in the Charter. Removing it is not politically realistic in the near term.<sup id="fnref:12:1"><a href="#fn:12" class="footnote" rel="footnote" role="doc-noteref">12</a></sup></p>

<p>But “near term realism” is not the same as “long term surrender.”</p>

<h3 id="what-i-propose-3">What I propose</h3>

<p>Treat renewable legitimacy as a parallel prestige track.</p>

<p>Build up the longer-term renewable seats described above. Make them consequential. Make them hard to win and easy to lose if a state does not sustain contribution and responsibility.</p>

<p>Over time, those seats become the institution’s living legitimacy mechanism.</p>

<p>There is precedent for this kind of design in regional security bodies. The African Union Peace and Security Council has 15 members, with five elected for three-year terms and ten for two-year terms, and it has no permanent members and no veto. It uses rotation and re-election to balance continuity with legitimacy.<sup id="fnref:13"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">14</a></sup></p>

<p>The AU PSC is not the UN, and global politics are nastier than regional politics. But the institutional idea is useful: continuity without permanence, and influence that must be renewed.</p>

<h3 id="why-it-helps-even-if-the-old-structure-remains">Why it helps, even if the old structure remains</h3>

<p>Because it creates a pathway where legitimacy is something you keep earning, not something you inherit.</p>

<p>Even if permanence stays, renewable seats can gradually shift the Council’s center of gravity toward responsibility-based legitimacy.</p>

<h2 id="5-stakeholder-buy-in-and-how-to-build-a-coalition-that-does-not-require-miracles">5. Stakeholder buy in, and how to build a coalition that does not require miracles</h2>

<p>This is where the earlier draft was too optimistic by omission.</p>

<p>The Council does not reform because someone writes a good blog post.
It reforms when enough states can see a bargain.</p>

<p>There are already two building blocks.</p>

<p>First, the veto initiative (76/262) was adopted by consensus, and it has already triggered repeated debates after vetoes.<sup id="fnref:2:5"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> UN press reporting in November 2025 noted that multiple vetoes had triggered corresponding General Assembly debates under this mechanism.<sup id="fnref:14"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">15</a></sup></p>

<p>Second, there are existing coalitions around voluntary restraint and working methods, like the ACT group’s Code of Conduct.<sup id="fnref:6:1"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup></p>

<p>A practical coalition path looks like this.</p>

<ol>
  <li>Start with elected members and accountability-minded middle powers pushing working methods reforms, because working methods do not require Charter change.</li>
  <li>Use the General Assembly debate mechanism after each veto to normalize structured records and structured questions.<sup id="fnref:2:6"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></li>
  <li>Build a “default expectation” that a veto without a brief is a veto without legitimacy, even if it remains legally valid.</li>
  <li>Only then push composition reform, when the system has already shifted culturally toward accountability.</li>
</ol>

<p>This does not guarantee success. It does something more valuable.</p>

<p>It creates a ratchet. A direction. A path that can be walked.</p>

<h2 id="closing-thought">Closing thought</h2>

<p>Institutional design cannot force good faith. But it can punish bad faith with friction, sunlight, and repetition.</p>

<p>The veto will remain a power tool. The question is whether it remains a power tool that operates in fog, or a power tool that must operate in daylight.</p>

<p>In fog, the Council becomes theatre.
In daylight, it at least becomes a record.
And sometimes, that record becomes the first step toward a better bargain.</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>United Nations Charter, Article 27 (Voting): https://www.un.org/en/about-us/un-charter/chapter-5 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>UN General Assembly Resolution 76/262: https://docs.un.org/en/a/res/76/262 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:2:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:2:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:2:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:2:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a> <a href="#fnref:2:6" class="reversefootnote" role="doc-backlink">&#8617;<sup>7</sup></a></p>
    </li>
    <li id="fn:3">
      <p>Analysis noting 76/262 imposes no obligations to attend and mainly increases political cost: https://www.osorin.it/uploads/model_4/.files/199_item_2.pdf?v=1747211642 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Security Council Report, “The Veto”: https://www.securitycouncilreport.org/un-security-council-working-methods/the-veto.php <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p>UN Research Guide, Security Council veto list: https://research.un.org/en/docs/sc/quick <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>ACT Code of Conduct (A/70/621–S/2015/978): https://docs.un.org/en/A/70/621 <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:6:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:7">
      <p>France–Mexico initiative on veto restraint in mass atrocities: https://centerforunreform.org/wp-content/uploads/2015/10/French-Mexican-Proposal-English.pdf <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>United Nations Charter, Article 23 (Composition): https://www.un.org/en/about-us/un-charter/chapter-5 <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p>Regional distribution of elected seats (summary): https://futures.issafrica.org/thematic/19-un-security-council/ <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:9:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:10">
      <p>Example analysis of Africa’s reform position and representation arguments: https://www.csis.org/analysis/africas-design-reformed-un-security-council <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:11">
      <p>UN GA Security Council reform process documents (element paper): https://www.un.org/en/ga/screform/78/pdf/2024-04-05-cochairs-revised-element-paper.pdf <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:12">
      <p>United Nations Charter, Article 108 (Amendments): https://www.un.org/en/about-us/un-charter/chapter-18 <a href="#fnref:12" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:12:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:15">
      <p>UN Security Council, “Highlights of Security Council Practice 2024” (meeting totals): https://main.un.org/securitycouncil/en/content/highlights-2024 <a href="#fnref:15" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:13">
      <p>African Union Peace and Security Council structure and terms: https://www.peaceau.org/en/page/39-secretariat-psc <a href="#fnref:13" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:14">
      <p>UN Press, 2025 GA debate noting repeated veto initiative debates: https://press.un.org/en/2025/ga12733.doc.htm <a href="#fnref:14" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Politics &amp; Governance" /><category term="un" /><category term="unsc" /><category term="security-council" /><category term="reform" /><category term="diplomacy" /><category term="governance" /><summary type="html"><![CDATA[A practical case for UNSC reform that preserves stability, legitimacy, and the incentives for major powers to stay invested.]]></summary></entry><entry><title type="html">Escaping GOTO: How We Learned to Make Programs Readable</title><link href="https://systemhalted.in/2025/12/17/escaping-goto/" rel="alternate" type="text/html" title="Escaping GOTO: How We Learned to Make Programs Readable" /><published>2025-12-17T00:00:00+00:00</published><updated>2025-12-17T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/17/escaping-goto</id><content type="html" xml:base="https://systemhalted.in/2025/12/17/escaping-goto/"><![CDATA[<p>In early BASIC, the line numbers felt like street addresses.</p>

<p>You could point to a place in your program and say: “Go there.”
The computer would nod, politely, and do exactly that.</p>

<p>And for a beginner, this felt comforting. Orderly. Almost architectural.</p>

<figure class="highlight"><pre><code class="language-basic" data-lang="basic">10 PRINT "Hello"
20 PRINT "World"
30 END</code></pre></figure>

<p>A neat little staircase of intentions.</p>

<p>Then we learned the spell.</p>

<h2 id="the-seduction-of-goto">The seduction of GOTO</h2>

<p>GOTO is the programming equivalent of discovering you can teleport.</p>

<p>Why walk like a peasant when you can jump?</p>

<p>Want a loop? Jump back.</p>

<figure class="highlight"><pre><code class="language-basic" data-lang="basic">10 LET X = 0
20 LET X = X + 1
30 PRINT X
40 IF X &lt; 5 THEN GOTO 20
50 END</code></pre></figure>

<p>It works. It’s simple. It even feels clever.</p>

<p>But teleportation has a cost: once you start jumping, your program stops being a story and becomes a maze.</p>

<h2 id="the-day-basic-stopped-feeling-friendly">The day BASIC stopped feeling friendly</h2>

<p>At some point the program gets longer than your short-term memory.</p>

<p>You add one more rule. Then another.</p>

<p>Now you’re jumping forward to handle special cases, jumping back to repeat, jumping sideways to “retry,” and suddenly you’re not writing code.</p>

<p>You’re playing detective.</p>

<p>Here’s the kind of shape that starts to appear:</p>

<figure class="highlight"><pre><code class="language-basic" data-lang="basic">10 INPUT "Enter a number (1-10)"; N
20 IF N &lt; 1 THEN GOTO 90
30 IF N &gt; 10 THEN GOTO 90
40 PRINT "OK"
50 GOTO 110
90 PRINT "Invalid. Try again."
100 GOTO 10
110 END</code></pre></figure>

<p>This is still readable.</p>

<p>But scale it up a bit: ten validations, multiple modes, nested loops, an error path, a “back” option, and suddenly the logic is scattered across line numbers like breadcrumbs thrown into a hurricane.</p>

<p>You don’t read it anymore.
You trace it.</p>

<p>Tracing burns attention. Attention is expensive.</p>

<p>That’s the real crime of spaghetti code: not aesthetics, cognitive cost.</p>

<h2 id="why-it-turns-into-spaghetti">Why it turns into spaghetti</h2>

<p>A clean program has a shape you can hold in your head:</p>

<p>Start → do things → finish.</p>

<p>Unstructured jumps destroy that shape.</p>

<p>GOTO breaks the one promise your reader desperately wants: that control flow will be local and predictable.</p>

<p>If any line can jump to any other line, then every line must be read with paranoia.</p>

<p>That’s not programming. That’s anxiety with line numbers.</p>

<h2 id="the-escape-structured-programming">The escape: structured programming</h2>

<p>Structured programming wasn’t invented to be fancy.
It was invented to make code readable at scale.</p>

<p>Instead of “jump anywhere,” you get a small set of composable structures:<br />
	•	sequence (do this, then that) <br />
	•	selection (if/else) <br />
	•	iteration (for/while)</p>

<p>You still do the same things, but the control flow becomes visible again.</p>

<p>Here’s the key move: instead of scattering retry logic across labels, you put it inside a loop.</p>

<p>If your BASIC dialect supports WHILE…WEND (many did), you can do:</p>

<figure class="highlight"><pre><code class="language-basic" data-lang="basic">10 PRINT "Enter a number (1-10)"
20 INPUT N
30 WHILE N &lt; 1 OR N &gt; 10
40   PRINT "Invalid. Try again."
50   INPUT N
60 WEND
70 PRINT "OK"
80 END</code></pre></figure>

<p>Now the program reads like a story again:</p>

<p>Ask → repeat until valid → proceed.</p>

<p>Same behavior. Different shape.</p>

<p>And that shape is the whole point.</p>

<h2 id="the-lesson-dont-use-goto">The lesson: don’t use GOTO</h2>

<p>Here’s the grown-up version, stated plainly:</p>

<p>Don’t use GOTO.</p>

<p>Yes, there are rare cases where it can be used carefully: generated code, constrained environments, or very low-level cleanup paths.</p>

<p>But that’s not the world most of us are programming in.</p>

<p>In real software, with real teammates and real deadlines, GOTO is a trap. It makes control flow non-local, and non-local flow makes reasoning expensive. It turns debugging into archaeology.</p>

<p>So the beginner-to-professional upgrade is simple:</p>

<p>Stop jumping. Start structuring.</p>

<p>If you need to repeat, use a loop.
If you need to choose, use IF…THEN…ELSE.
If you need to reuse, use a function.
If you need to abort, return early or throw an error.</p>

<p>Your future self will thank you. Your teammates will thank you. Your pager will thank you.</p>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>Logo taught me wonder: move the turtle, watch a picture appear.</p>

<p>BASIC taught me discipline: tell the machine exactly what to do, step by step.</p>

<p>But the most important thing BASIC taught me might be this:</p>

<p>A program is not just instructions for a computer.</p>

<p>It is a story for the next human.</p>

<p>And the moment your story needs a map and a compass, you’ve stopped writing a program and started writing a trap.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Technology" /><category term="Computer Science" /><category term="Series 2 - Turtle, BASIC, and the Long Road to Taste" /><category term="basic" /><category term="gw-basic" /><category term="goto" /><category term="structured-programming" /><category term="programming" /><category term="software-engineering" /><summary type="html"><![CDATA[Line numbers made BASIC feel orderly. GOTO made it powerful. Then everything turned into spaghetti.]]></summary></entry><entry><title type="html">BASIC: The Language That Taught Me to Think Step by Step</title><link href="https://systemhalted.in/2025/12/16/basic-programming-lang/" rel="alternate" type="text/html" title="BASIC: The Language That Taught Me to Think Step by Step" /><published>2025-12-16T00:00:00+00:00</published><updated>2025-12-16T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/16/basic-programming-lang</id><content type="html" xml:base="https://systemhalted.in/2025/12/16/basic-programming-lang/"><![CDATA[<p>Logo taught me to draw.</p>

<p>BASIC taught me to instruct.</p>

<p>If Logo felt like whispering wishes to a turtle, BASIC felt like standing next to a machine and giving it crisp, literal orders. Not suggestions. Not vibes. Orders.</p>

<p>And the machine was obedient in the way only machines can be: perfectly, relentlessly, and without mercy for ambiguity.</p>

<h2 id="the-basic-mindset-the-computer-is-dumb-so-be-precise">The BASIC mindset: the computer is dumb, so be precise</h2>

<p>BASIC’s core lesson is simple:</p>

<p>Computers do not infer intent.
They execute steps.</p>

<p>So you learn to think in sequences: <br />
	1.	Put the input somewhere.  <br />
	2.	Transform it in small moves. <br />
	3.	Store intermediate results. <br />
	4.	Print the outcome.  <br />
	5.	Stop.</p>

<p>That “step-by-step” habit is not just syntax. It is a worldview.</p>

<h2 id="programs-as-recipes-not-drawings">Programs as recipes, not drawings</h2>

<p>In Logo, the turtle is the main character. You tell it to move, and the picture emerges.</p>

<p>In BASIC, the program itself is the main character. It is a recipe.</p>

<p>Here’s the kind of thing early BASIC invites you to write:</p>

<figure class="highlight"><pre><code class="language-basic" data-lang="basic">10 INPUT “Enter side length”; S
20 P = 4 * S
30 PRINT “Perimeter = “; P
40 END</code></pre></figure>

<p>No magic. No hidden state. No geometry fairy.</p>

<p>Just: ask, compute, print.</p>

<p>And even that tiny program quietly teaches important ideas:<br />
variables, arithmetic, input/output, and the idea that a program is a controlled sequence of actions.</p>

<h2 id="line-numbers-the-original-breadcrumb-trail">Line numbers: the original breadcrumb trail</h2>

<p>The first thing you notice in old-school BASIC is the line numbers.</p>

<p>They are not decoration. They are control points.</p>

<p>You don’t just write code.<br />
You create a path the computer will walk.</p>

<figure class="highlight"><pre><code class="language-basic" data-lang="basic">10 PRINT “I will count.”
20 FOR I = 1 TO 5
30 PRINT I
40 NEXT I
50 PRINT “Done.”</code></pre></figure>

<p>The flow is visible, like a little parade.</p>

<p>And once you learn that you can jump…</p>

<h2 id="goto-power-then-chaos">GOTO: power, then chaos</h2>

<p>BASIC makes it very easy to say: “Go there next.”</p>

<figure class="highlight"><pre><code class="language-basic" data-lang="basic">10 LET X = 0
20 LET X = X + 1
30 PRINT X
40 IF X &lt; 5 THEN GOTO 20
50 END</code></pre></figure>

<p>This works. It is also the seed of future pain.</p>

<p>Because once your program becomes a web of jumps, your brain becomes a detective in a bad mystery novel. Every GOTO is a plot twist.</p>

<p>This is why people later talked about “structured programming”: it’s not about being fancy, it’s about keeping the story readable.</p>

<h2 id="state-is-the-real-subject">State is the real subject</h2>

<p>In BASIC, you’re always holding state in your hands.</p>

<p>Variables are the center of gravity. They change, they accumulate, they persist.</p>

<p>That teaches a different kind of thinking than Logo:</p>

<p>Logo: move an agent, watch an effect</p>

<p>BASIC: change a value, watch consequences</p>

<p>And you start noticing patterns:
	•	Counters
	•	Accumulators
	•	Flags
	•	Branches
	•	Loops</p>

<p>In other words: the basic building blocks of the “imperative” style of programming, where you tell the machine how to do the job, not just what you want.</p>

<h2 id="error-messages-as-teachers">Error messages as teachers</h2>

<p>BASIC’s errors are blunt little teachers.</p>

<p>“Syntax error.”  <br />
“Type mismatch.”   <br />
“Out of data.”</p>

<p>Each one says: you assumed the computer would guess what you meant. It will not.</p>

<p>So you learn to be explicit.<br />
You learn to reduce ambiguity.<br />
You learn to debug.</p>

<p>Not as a skill for code, but as a skill for thought.</p>

<h2 id="the-punchline">The punchline</h2>

<p>Logo gave me wonder.<br />
BASIC gave me discipline.</p>

<p>Logo made me feel like programming was art.<br />
BASIC made me feel like programming was logic.</p>

<p>And both were true.</p>

<p>BASIC’s gift is not that it’s the most elegant language.<br />
Its gift is that it forces you to think like a machine for a while.</p>

<p>And once you can do that, you can later learn the higher trick: how to make machines feel a little more human.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Technology" /><category term="Computer Science" /><category term="Series 2 - Turtle, BASIC, and the Long Road to Taste" /><category term="basic" /><category term="gw-basic" /><category term="education" /><category term="imperative-programming" /><category term="programming-language" /><summary type="html"><![CDATA[How BASIC’s “tell the machine exactly what to do” mindset shaped how I learned programming.]]></summary></entry><entry><title type="html">Logo Looks Nothing Like Lisp and Yet It Is</title><link href="https://systemhalted.in/2025/12/15/is-logo-a-lisp/" rel="alternate" type="text/html" title="Logo Looks Nothing Like Lisp and Yet It Is" /><published>2025-12-15T00:00:00+00:00</published><updated>2025-12-15T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/15/is-logo-a-lisp</id><content type="html" xml:base="https://systemhalted.in/2025/12/15/is-logo-a-lisp/"><![CDATA[<p>It was in 1991 that my school<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> introduced Computer Science for everyone from class IV onwards. It was the first school in Agra<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> to do so<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>. Luckily for me I was in class IV and was excited to learn Computer Science.</p>

<p>The first day of the class we were excited. We did not get to see the newly build Computer Labs but were told lots about Computers - what is a computer? what is data? what is meaningful information? cpu, alu, monitor, keyboard and the funniest of all - the mouse. Soon we progressed to learn programming and the language of choice for that was <strong>Logo</strong><sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>.</p>

<p>In Class IV, Logo felt like magic with training wheels.</p>

<p>A turtle sat at the center of the screen and waited for commands. You would say:</p>

<figure class="highlight"><pre><code class="language-logo" data-lang="logo">FD 50 RT 90 FD 50 RT 90 FD 50 RT 90 FD 50 HT</code></pre></figure>

<p>and a square would appear, as if geometry had agreed to be friendly for once.</p>

<h2 id="logo-is-lisp">Logo is Lisp?</h2>

<p>Years later, I discovered a fact that sounds like a prank until you stare at it long enough:</p>

<p>Logo is often described as a dialect of Lisp, or at least a Lisp-family language.<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup></p>

<p>That claim is confusing at first because Logo does not look like Lisp. Lisp is famously parenthesized. Logo is famously turtle-ish. So what gives?</p>

<p>The trick is that “dialect” here is not about surface syntax<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>. It is about the underlying ideas: evaluation, data structures, and the way programs are shaped.</p>

<p>Below are the family resemblances hiding in plain sight.</p>

<h3 id="1-prefix-thinking-verbs-first">1. Prefix thinking: verbs first</h3>

<p>Lisp’s signature habit is that functions come first, arguments follow.</p>

<figure class="highlight"><pre><code class="language-lisp" data-lang="lisp"><span class="p">(</span><span class="nb">+</span> <span class="mi">3</span> <span class="mi">4</span><span class="p">)</span>
<span class="p">(</span><span class="nv">forward</span> <span class="mi">50</span><span class="p">)</span>
<span class="p">(</span><span class="nv">right</span> <span class="mi">90</span><span class="p">)</span></code></pre></figure>

<p>Logo does the same thing, it just drops the parentheses and speaks like a teacher.</p>

<figure class="highlight"><pre><code class="language-logo" data-lang="logo">sum 3 4
fd 50
rt 90</code></pre></figure>

<p>Same mental model. Different costume.</p>

<h3 id="2-lists-matter-a-lot">2. Lists matter a lot</h3>

<p>Lisp is built on lists. Logo inherits that list-centered worldview.</p>

<p>In many Logo dialects you can work with lists directly:</p>

<figure class="highlight"><pre><code class="language-logo" data-lang="logo">print [1 2 3 4]
print first [a b c]     ; a
print butfirst [a b c]  ; [b c]</code></pre></figure>

<p>If you have ever met Lisp’s car and cdr, you can feel the same head-and-tail spirit here, just with names that won’t scare a fourth grader.</p>

<h3 id="3-quoting-and-symbols-name-vs-value">3. Quoting and symbols: “name” vs “value”</h3>

<p>In Lisp, quoting is essential because you often want to talk about symbols without evaluating them.</p>

<p>Logo has a similar separation between a name and a value.</p>

<p>In UCBLogo-style notation:</p>

<figure class="highlight"><pre><code class="language-logo" data-lang="logo">make "x 10
print :x</code></pre></figure>

<p>The “x is the symbol name. The :x is the value stored in that name.</p>

<p>Different punctuation, same conceptual split: symbol vs value, data vs evaluation.</p>

<h3 id="4-recursion-feels-natural-not-exotic">4. Recursion feels natural, not exotic</h3>

<p>Lisp culture loves recursion because it pairs naturally with lists and self-similar problems.</p>

<p>Logo teaches recursion early too, especially in dialects like UCBLogo.</p>

<p>A simple recursive spiral:</p>

<figure class="highlight"><pre><code class="language-logo" data-lang="logo">to spiral :n
if :n &lt; 1 [stop]
fd :n
rt 90
spiral :n - 1
end</code></pre></figure>

<p>Here we are declaring the function named spiral. <code class="language-plaintext highlighter-rouge">to</code> is the keyword to declare functions in Logo.</p>

<p>This is not “turtle magic.” This is a core Lisp-family habit: define a procedure, then solve the big problem by repeatedly solving smaller versions of it.</p>

<h3 id="5-code-as-data-vibes-instruction-lists-you-can-run">5. Code-as-data vibes: instruction lists you can run</h3>

<p>One of Lisp’s deepest tricks is that code and data are made of the same stuff. That enables patterns like building code as a data structure, then evaluating it.</p>

<p>Logo approaches that idea in a friendlier way: lists can represent sequences of commands, and many Logo environments support executing those command lists.</p>

<p>Even when you never say the word “eval,” you are inching toward the same philosophical cliff: programs can be treated as manipulable objects.</p>

<p>That is very Lisp. It is also very sneaky.</p>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>Logo doesn’t look like Lisp because it was designed for humans first, especially young humans.</p>

<p>But the bones show through:  <br />
	1.	Prefix function application (verbs first)<br />
	2.	List-centered data thinking<br />
	3.	Quoting and symbol/value separation<br />
	4.	Comfort with recursion<br />
	5.	A path toward code-as-data</p>

<p>So the sentence “Logo is a Lisp dialect” is not a joke. It is a reminder that programming languages can share a soul even when they do not share a wardrobe.</p>

<h2 id="back-where-it-all-begin">Back where it all begin</h2>

<p>In Class IV, we had no idea that we were accidentally being taught one of the deepest ideas in CS, that lists can represent both data and instructions, and the difference between them is often just “how you choose to evaluate.”</p>

<hr />

<h2 id="notes-and-references">Notes and references</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>St. George’s College, Agra <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>Agra is a city situated on the banks of River Yamuna, around 200Km south of Delhi, in the state of Uttar Pradesh in India. It was once a Mughal capital and is famous for mostly Mughal Architecture with two famous buildings of that time being - Taj Mahal, a mausaoleum build by Shahjahan for his wife and Fatehpur Sikri, the fort that was eventually abandoned by Akbar. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>My memory is vague on this and I have not corroborated this with the school. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>I don’t remember the version of Logo, whether it was UCB Logo or MSW Logo. More information about the language can be found <a href="https://el.media.mit.edu/logo-foundation/index.html">here</a>. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p>Descriptions of Logo as a Lisp dialect / Lisp-family language are common in historical and documentation sources. A good starting point is the MIT Logo Foundation page and the UCBLogo documentation. (URLs for easy copying: https://el.media.mit.edu/logo-foundation/ and https://people.eecs.berkeley.edu/~bh/logo.html) <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>In Linguistic, we often talk about Deep Structures and surface structures. Surface structure is how the language is spoken in contrast to Deep structure which deals with the deeper meaning. <a href="https://en.wikipedia.org/wiki/Deep_structure_and_surface_structure?wprov=sfti1#">Deep and Surface Structures</a> <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Technology" /><category term="Computer Science" /><category term="Series 2 - Turtle, BASIC, and the Long Road to Taste" /><category term="logo" /><category term="lisp" /><category term="computer-science" /><category term="education" /><category term="turtle-graphics" /><category term="programming-language" /><summary type="html"><![CDATA[Why Logo can be called a Lisp dialect even though it does not look like one?]]></summary></entry><entry><title type="html">Vibe Coding and the Baby Genius Problem</title><link href="https://systemhalted.in/2025/12/15/vibe-coding-and-baby-genius/" rel="alternate" type="text/html" title="Vibe Coding and the Baby Genius Problem" /><published>2025-12-15T00:00:00+00:00</published><updated>2025-12-15T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/15/vibe-coding-and-baby-genius</id><content type="html" xml:base="https://systemhalted.in/2025/12/15/vibe-coding-and-baby-genius/"><![CDATA[<p>Vibe coding has been ridiculously fun.</p>

<p>It has brought back the joy to programming. The kind of joy you thought adulthood, meetings, and Jira had permanently confiscated.</p>

<p>And yet, right now, most agents are more or less <strong>super-intelligent babies</strong>.</p>

<p>They are brilliant. They are fast. They can surprise you.
But they still need to be fed context again and again.</p>

<p>Today, while working on my service virtualizer, I had to continuously inform the agent about my preference for naming API models and domain models.
The difference matters:</p>

<ul>
  <li>API models are edge interfaces, representing outward state.</li>
  <li>Domain models are internal shapes, used to pass messages within the API.</li>
</ul>

<p>The agent continuously “lied” that it was following the instructions I provided under the <code class="language-plaintext highlighter-rouge">.github/instructions</code> folder.
After multiple rounds of trial and error, it partially understood the assignment.</p>

<p>That’s the gap between intelligence and autonomy.</p>

<h2 id="the-autonomy-ladder-vibe-coding-ping-pong-vacation">The autonomy ladder: vibe coding, ping pong, vacation</h2>

<p>It is going to take some time before agents can act independently while you play ping pong.
And slightly longer for you to delegate the work completely to a few AI agents and go on vacation.</p>

<p>AI agents need to be <strong>context-aware</strong> for me to enjoy ping pong.
They need to write properly logged and observable clean code before I can trust them enough to enjoy a vacation.</p>

<p>That last line is the whole story.</p>

<p>Ping pong is a context problem.
Vacation is an accountability problem.</p>

<h2 id="a-concrete-example-my-model-naming-rule">A concrete example: my model naming rule</h2>

<p>This is where “feed context again and again” shows up in real code.</p>

<h3 id="api-models-should-be-nouns">API models should be nouns</h3>

<p>API models “datafy” representational state.
So there is no separate request and response.
There is only one state to represent for both request and response.</p>

<p>Some fields are read-only, sure.
But it is still one state.</p>

<p>So instead of:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">CreateEndpointRequest</code></li>
  <li><code class="language-plaintext highlighter-rouge">CreateEndpointResponse</code></li>
  <li><code class="language-plaintext highlighter-rouge">EndpointResponse</code></li>
</ul>

<p>I want:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Endpoint</code></li>
</ul>

<h3 id="domain-models-should-not-say-request-response-or-data">Domain models should not say “Request”, “Response”, or “Data”</h3>

<p>Domain models don’t need <code class="language-plaintext highlighter-rouge">Request</code>, <code class="language-plaintext highlighter-rouge">Response</code>, or <code class="language-plaintext highlighter-rouge">Data</code> as suffixes.
That information is redundant.</p>

<p><code class="language-plaintext highlighter-rouge">EndpointData</code> as a class serves no purpose compared to <code class="language-plaintext highlighter-rouge">Endpoint</code> as a class.</p>

<p>If it is the domain concept, name it like the concept:
<code class="language-plaintext highlighter-rouge">Endpoint</code>, <code class="language-plaintext highlighter-rouge">VirtualService</code>, <code class="language-plaintext highlighter-rouge">Route</code>, <code class="language-plaintext highlighter-rouge">Rule</code>, <code class="language-plaintext highlighter-rouge">Match</code>, <code class="language-plaintext highlighter-rouge">Mapping</code>.</p>

<p>If context matters, packages can carry it:
<code class="language-plaintext highlighter-rouge">in.systemhalted.api.model.Endpoint</code>
<code class="language-plaintext highlighter-rouge">in.systemhalted.domain.model.Endpoint</code></p>

<p>Same noun, different layer.</p>

<h2 id="the-real-issue-preferences-are-not-enforceable">The real issue: preferences are not enforceable</h2>

<p>Human teams survive because we turn preferences into systems.</p>

<p>“Please follow this convention” is polite.
But politeness is not a compiler.</p>

<p>If you want agents to stop “lying” (really: confidently guessing), you have to move from memory to mechanism.</p>

<p>In other words, from vibes to accountability.</p>

<h2 id="make-conventions-executable">Make conventions executable</h2>

<p>The big move is simple:
turn conventions into checks that fail loudly.</p>

<p>Then the agent doesn’t need to remember your rules.
It just needs to pass reality.</p>

<h3 id="option-1-enforce-architecture-rules-with-archunit">Option 1: enforce architecture rules with ArchUnit</h3>

<p><a href="https://www.archunit.org/">ArchUnit</a> is a Java testing library for validating architectural decisions in code.
It lets you write tests for package boundaries, dependency direction, layering rules, and conventions that are otherwise tribal knowledge.</p>

<p>That is exactly what an AI agent struggles with: it will agree with tribal knowledge, then forget it, then agree again.</p>

<p>Here’s a practical enforcement approach:</p>

<ul>
  <li>API models live in <code class="language-plaintext highlighter-rouge">..api.model..</code> and must not end with <code class="language-plaintext highlighter-rouge">Request</code>, <code class="language-plaintext highlighter-rouge">Response</code>, or <code class="language-plaintext highlighter-rouge">Data</code></li>
  <li>Domain models live in <code class="language-plaintext highlighter-rouge">..domain.model..</code> and must not end with <code class="language-plaintext highlighter-rouge">Request</code>, <code class="language-plaintext highlighter-rouge">Response</code>, or <code class="language-plaintext highlighter-rouge">Data</code></li>
  <li>Domain must not depend on API</li>
</ul>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// src/test/java/in/systemhalted/architecture/ModelConventionsTest.java</span>
<span class="kn">package</span> <span class="nn">in.systemhalted.architecture</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">com.tngtech.archunit.core.domain.JavaClasses</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.tngtech.archunit.core.importer.ClassFileImporter</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.tngtech.archunit.lang.ArchRule</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.junit.jupiter.api.Test</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">static</span> <span class="n">com</span><span class="o">.</span><span class="na">tngtech</span><span class="o">.</span><span class="na">archunit</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">syntax</span><span class="o">.</span><span class="na">ArchRuleDefinition</span><span class="o">.</span><span class="na">classes</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">com</span><span class="o">.</span><span class="na">tngtech</span><span class="o">.</span><span class="na">archunit</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">syntax</span><span class="o">.</span><span class="na">ArchRuleDefinition</span><span class="o">.</span><span class="na">noClasses</span><span class="o">;</span>

<span class="kd">class</span> <span class="nc">ModelConventionsTest</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">JavaClasses</span> <span class="n">classes</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ClassFileImporter</span><span class="o">()</span>
            <span class="o">.</span><span class="na">importPackages</span><span class="o">(</span><span class="s">"in.systemhalted"</span><span class="o">);</span>

    <span class="nd">@Test</span>
    <span class="kt">void</span> <span class="nf">api_models_must_not_end_with_request_response_or_data</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">ArchRule</span> <span class="n">rule</span> <span class="o">=</span> <span class="n">classes</span><span class="o">()</span>
                <span class="o">.</span><span class="na">that</span><span class="o">().</span><span class="na">resideInAPackage</span><span class="o">(</span><span class="s">"..api.model.."</span><span class="o">)</span>
                <span class="o">.</span><span class="na">should</span><span class="o">().</span><span class="na">haveSimpleNameNotEndingWith</span><span class="o">(</span><span class="s">"Request"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">andShould</span><span class="o">().</span><span class="na">haveSimpleNameNotEndingWith</span><span class="o">(</span><span class="s">"Response"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">andShould</span><span class="o">().</span><span class="na">haveSimpleNameNotEndingWith</span><span class="o">(</span><span class="s">"Data"</span><span class="o">);</span>

        <span class="n">rule</span><span class="o">.</span><span class="na">check</span><span class="o">(</span><span class="n">classes</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@Test</span>
    <span class="kt">void</span> <span class="nf">domain_models_must_not_end_with_request_response_or_data</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">ArchRule</span> <span class="n">rule</span> <span class="o">=</span> <span class="n">classes</span><span class="o">()</span>
                <span class="o">.</span><span class="na">that</span><span class="o">().</span><span class="na">resideInAPackage</span><span class="o">(</span><span class="s">"..domain.model.."</span><span class="o">)</span>
                <span class="o">.</span><span class="na">should</span><span class="o">().</span><span class="na">haveSimpleNameNotEndingWith</span><span class="o">(</span><span class="s">"Request"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">andShould</span><span class="o">().</span><span class="na">haveSimpleNameNotEndingWith</span><span class="o">(</span><span class="s">"Response"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">andShould</span><span class="o">().</span><span class="na">haveSimpleNameNotEndingWith</span><span class="o">(</span><span class="s">"Data"</span><span class="o">);</span>

        <span class="n">rule</span><span class="o">.</span><span class="na">check</span><span class="o">(</span><span class="n">classes</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@Test</span>
    <span class="kt">void</span> <span class="nf">domain_must_not_depend_on_api</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">ArchRule</span> <span class="n">rule</span> <span class="o">=</span> <span class="n">noClasses</span><span class="o">()</span>
                <span class="o">.</span><span class="na">that</span><span class="o">().</span><span class="na">resideInAPackage</span><span class="o">(</span><span class="s">"..domain.."</span><span class="o">)</span>
                <span class="o">.</span><span class="na">should</span><span class="o">().</span><span class="na">dependOnClassesThat</span><span class="o">().</span><span class="na">resideInAnyPackage</span><span class="o">(</span><span class="s">"..api.."</span><span class="o">);</span>

        <span class="n">rule</span><span class="o">.</span><span class="na">check</span><span class="o">(</span><span class="n">classes</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>This does not prove a name is a noun.
But it blocks the most common failure mode: growing a forest of <code class="language-plaintext highlighter-rouge">*Request</code>, <code class="language-plaintext highlighter-rouge">*Response</code>, and <code class="language-plaintext highlighter-rouge">*Data</code> types that all mean the same thing.</p>

<h3 id="option-2-generate-api-models-handcraft-domain-models">Option 2: generate API models, handcraft domain models</h3>

<p>If your API layer is OpenAPI driven, treat API models as edge artifacts and generate them.
Then keep domain models intentionally authored and stable.</p>

<p>The principle is simple:</p>
<blockquote>
  <p>when the outside world changes, your domain model shouldn’t break.</p>
</blockquote>

<h3 id="option-3-a-definition-of-done-for-agents">Option 3: a definition of done for agents</h3>

<p>Agents struggle because “done” is fuzzy.</p>

<p>So make it concrete:</p>

<ol>
  <li>Restate conventions from <code class="language-plaintext highlighter-rouge">.github/instructions</code> in plain English</li>
  <li>Implement the change</li>
  <li>Run tests</li>
  <li>Show evidence: test output, files changed, and convention checks passing</li>
</ol>

<p>That’s how you replace “trust me” with “see for yourself.”</p>

<h2 id="observability-is-the-vacation-requirement">Observability is the vacation requirement</h2>

<p>Ping pong needs context awareness.
Vacation needs observability.</p>

<p>If an agent writes code that “works” but cannot explain itself at runtime, you have not delegated work.
You have adopted a mystery.</p>

<p>Vacation-grade code needs boring, grown-up traits:</p>

<ul>
  <li>structured logs you can search</li>
  <li>correlation IDs you can follow</li>
  <li>meaningful error handling</li>
  <li>metrics and traces where it matters</li>
</ul>

<p>Because on vacation, the only thing worse than an outage is one you cannot diagnose from the logs.</p>

<h2 id="final-thoughts">Final thoughts</h2>

<p>Agents don’t mainly need to be smarter.
They need to be more accountable.</p>

<p>An agent without a stop condition is just a very confident infinite loop.</p>

<p>The trick is not to lecture the agent harder.
The trick is to make “following instructions” measurable.</p>

<p>Then you can pick up the paddle.</p>

<p>And later, maybe, the suitcase.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Technology" /><category term="Software Engineering" /><category term="AI" /><category term="agentic-workflows" /><category term="vibe-coding" /><category term="testing" /><category term="observability" /><category term="conventions" /><summary type="html"><![CDATA[Vibe coding is fun again. However, Agents are still baby geniuses. The fix is turning preferences into accountability.]]></summary></entry><entry><title type="html">Project Jigsaw (JPMS) - Part 1 - What is Modularity?</title><link href="https://systemhalted.in/2025/12/14/jigsaw-what-is-modularity/" rel="alternate" type="text/html" title="Project Jigsaw (JPMS) - Part 1 - What is Modularity?" /><published>2025-12-14T00:00:00+00:00</published><updated>2025-12-14T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/14/jigsaw-what-is-modularity</id><content type="html" xml:base="https://systemhalted.in/2025/12/14/jigsaw-what-is-modularity/"><![CDATA[<h2 id="a-note-from-the-future">A note from the future</h2>

<p>I began this post in 2016, when Java 9 was still unreleased and Project Jigsaw felt like an approaching weather system. Java 9 eventually shipped, and the module system became real, imperfect, and unexpectedly enlightening.</p>

<p>This post keeps the original question intact: what is modularity and why did Java need the platform itself to care?</p>

<h2 id="java-already-had-modules-right">Java already had “modules”… right?</h2>

<p>For the past few years, Java has evolved quickly, mostly for the better. JDK 8 changed everyday Java with lambdas and streams, making a more functional style feel native instead of like cosplay.</p>

<p>Then came JDK 9 and Project Jigsaw, the Java Platform Module System (JPMS). The obvious objection was, and still is:</p>

<p>Java already has JARs. Enterprise Java has WARs and EARs. Source code has packages and access modifiers. So why add “modules” at all?</p>

<p>To answer that, we need to be annoyingly precise about what a module is.</p>

<h2 id="what-is-a-module">What is a module?</h2>

<p>A module is not just “a bunch of related code.” That definition is so generous it would make my <code class="language-plaintext highlighter-rouge">Downloads/</code> folder a module, and it absolutely should not be trusted with responsibility.</p>

<p>A better definition:</p>

<p>A module is a unit with an enforceable boundary.</p>

<p>In practice, a module needs at least these traits.</p>

<h3 id="1-encapsulation-a-hidden-interior">1. Encapsulation (a hidden interior)</h3>

<p>Encapsulation is the right to say: “this part is implementation detail; do not touch.”</p>

<p>Java supports encapsulation via access modifiers (<code class="language-plaintext highlighter-rouge">private</code>, package-private, <code class="language-plaintext highlighter-rouge">protected</code>, <code class="language-plaintext highlighter-rouge">public</code>) and via packages. In the POJO world, we keep fields private and expose behavior through methods so nobody can accidentally mutate internal state and then act surprised when physics happens.</p>

<p>But here is the catch:</p>

<p>Packages are excellent organizing units, but by themselves they are not deployment boundaries.</p>

<p>Once code is on the classpath, it tends to behave like a single sprawling neighborhood where everyone can wander into everyone else’s backyard, sometimes through reflection, sometimes through “it was convenient,” sometimes through sheer dependency gravity.</p>

<h3 id="2-a-public-surface-an-intentional-api">2. A public surface (an intentional API)</h3>

<p>A module must have a public surface: a set of types meant to be used by outsiders.</p>

<p>This matters because modularity is not just hiding. It is also communicating on purpose.</p>

<p>When modules interact, they should do so through the exported API, not through accidental knowledge of internals. This is how systems remain refactorable without becoming brittle.</p>

<p>So far, this sounds like “write disciplined code,” which is true, and still not the full story.</p>

<p>The painful question is: can the platform help enforce that discipline?</p>

<h2 id="why-packages-and-jars-were-not-enough">Why packages and JARs were not enough?</h2>

<p>Packages and JARs let us aspire to be modular. They do not consistently let us enforce it.</p>

<p>Here are the classic failure modes that show up as systems grow.</p>

<h3 id="1-the-classpath-is-a-soup">1. The classpath is a soup</h3>

<p>The classpath is wonderfully simple and brutally permissive. Put things on it, and they exist.</p>

<p>Common outcomes:</p>

<ul>
  <li>Accidental dependencies form silently.</li>
  <li>Internals become “public” by habit. “Just import it.”</li>
  <li>Debugging becomes archaeology. “Who pulled in this version, and why does it only fail on Jenkins?”</li>
</ul>

<h3 id="2-jar-hell-is-real-and-it-has-receipts">2. JAR hell is real, and it has receipts</h3>

<p>When multiple JARs provide overlapping classes, or when classloading order changes, you can get failures that only appear at runtime and only on certain machines.</p>

<p>Even when your build tool is trying to help, the model is still basically: assemble a pile of bytecode and hope it behaves.</p>

<h3 id="3-encapsulation-at-runtime-was-historically-negotiable">3. Encapsulation at runtime was historically negotiable</h3>

<p>Before JPMS, it was common for libraries to reach into non-public areas, either:</p>

<ul>
  <li>Using reflection to access private members.</li>
  <li>Using internal JDK APIs because they existed and were “handy.”</li>
</ul>

<p>This worked until it did not. And the “did not” usually arrived as a production upgrade - not an upgrade by choice but force.</p>

<h3 id="4-split-packages-and-duplicate-worlds">4. Split packages and duplicate worlds</h3>

<p>On the classpath you can end up with the same package name spread across multiple JARs. Tools can limp along, humans can suffer quietly, and then one day something breaks in a way that makes you question reality.</p>

<p>A modular system has to be stricter about identity, or it cannot reason about the graph.</p>

<h2 id="what-jigsaw-actually-adds">What Jigsaw actually adds</h2>

<p>Project Jigsaw turns modularity into something the compiler and runtime can understand.</p>

<p>A Java module has:</p>

<ul>
  <li>A name (a stronger identity than “whatever the filename is”).</li>
  <li>Declared dependencies (what it requires).</li>
  <li>Declared exports (what it makes available to other modules).</li>
  <li>Strong encapsulation by default (if you do not export it, it is not part of your public world).</li>
</ul>

<p>This is expressed with a module descriptor: <code class="language-plaintext highlighter-rouge">module-info.java</code>.</p>

<p>A minimal example:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">module</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">billing</span> <span class="o">{</span>
    <span class="n">requires</span> <span class="n">java</span><span class="o">.</span><span class="na">sql</span><span class="o">;</span>
    <span class="n">exports</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">billing</span><span class="o">.</span><span class="na">api</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<p>The point is not ceremony. The point is boundaries that can be checked.</p>

<h2 id="packages-vs-modules-a-clean-mental-model">Packages vs modules, a clean mental model</h2>

<p>Think of it like this:</p>

<ul>
  <li>A package groups related types and helps structure code.</li>
  <li>A module groups packages and declares which packages are visible to the outside world.</li>
</ul>

<p>Packages help you organize. Modules help you enforce.</p>

<h2 id="enforce-how-exactly">“Enforce” how, exactly?</h2>

<p>JPMS enforces things in two major places: compilation and runtime.</p>

<h3 id="1-reliable-configuration-dependency-truth">1. Reliable configuration (dependency truth)</h3>

<p>With modules, dependencies are not an emergent property of “whatever happens to be on the classpath today.”</p>

<p>Instead, the runtime resolves a module graph:</p>

<ul>
  <li>Each module declares what it requires.</li>
  <li>The system checks that required modules exist.</li>
  <li>The system checks for conflicts that would make the graph incoherent.</li>
</ul>

<p>This catches certain categories of surprise runtime failure earlier, because the platform can actually see your dependency structure.</p>

<h3 id="2-strong-encapsulation-real-boundaries">2. Strong encapsulation (real boundaries)</h3>

<p>With modules, code in a non-exported package is not accessible to other modules at compile time, and at runtime access is strongly controlled.</p>

<p>This is the key philosophical shift:</p>

<blockquote>
  <p>You are not just suggesting which parts are internal. You are declaring it, and the platform can enforce it.</p>
</blockquote>

<h2 id="a-quick-tour-of-module-descriptor-concepts">A quick tour of module descriptor concepts</h2>

<p>The <code class="language-plaintext highlighter-rouge">module-info.java</code> file is small, but it has teeth. Here are the ideas you will see in real projects.</p>

<h3 id="requires"><code class="language-plaintext highlighter-rouge">requires</code></h3>

<p>A module can state that it depends on another module.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">module</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">app</span> <span class="o">{</span>
    <span class="n">requires</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">billing</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<h3 id="exports"><code class="language-plaintext highlighter-rouge">exports</code></h3>

<p>A module can export a package to make it part of its public API.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">module</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">billing</span> <span class="o">{</span>
    <span class="n">exports</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">billing</span><span class="o">.</span><span class="na">api</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<p>Everything else stays internal by default.</p>

<p>There is also qualified export, where you export only to specific friend modules:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">module</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">billing</span> <span class="o">{</span>
    <span class="n">exports</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">billing</span><span class="o">.</span><span class="na">internal</span> <span class="n">to</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">app</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<p>That is useful for tightly coupled modules, though it should be used sparingly because it creates special relationships that age poorly.</p>

<h3 id="opens"><code class="language-plaintext highlighter-rouge">opens</code></h3>

<p>Exporting is about normal access. Opening is about reflection.</p>

<p>Frameworks use reflection for dependency injection, serialization, proxies, and other wizardry. JPMS makes you be explicit about that:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">module</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">model</span> <span class="o">{</span>
    <span class="n">opens</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">model</span><span class="o">.</span><span class="na">entities</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<p>You can also open to specific modules only:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">module</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">model</span> <span class="o">{</span>
    <span class="n">opens</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">model</span><span class="o">.</span><span class="na">entities</span> <span class="n">to</span> <span class="n">com</span><span class="o">.</span><span class="na">fasterxml</span><span class="o">.</span><span class="na">jackson</span><span class="o">.</span><span class="na">databind</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<h2 id="services-uses-and-provides">Services: <code class="language-plaintext highlighter-rouge">uses</code> and <code class="language-plaintext highlighter-rouge">provides</code></h2>

<p>The service mechanism is the module-friendly way to do plugin architecture.</p>

<p>Consumer:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">module</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">app</span> <span class="o">{</span>
    <span class="n">uses</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">spi</span><span class="o">.</span><span class="na">PaymentProvider</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<p>Provider:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">module</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">stripe</span> <span class="o">{</span>
    <span class="n">provides</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">spi</span><span class="o">.</span><span class="na">PaymentProvider</span>
        <span class="n">with</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">.</span><span class="na">stripe</span><span class="o">.</span><span class="na">StripePaymentProvider</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>

<h2 id="but-what-about-my-existing-non-modular-jars">But what about my existing non-modular JARs?</h2>

<p>JPMS did not pretend the world would instantly become modular. It introduced a migration story, and it is worth understanding because it explains a lot of real-world behavior.</p>

<h3 id="the-unnamed-module">The unnamed module</h3>

<p>If you put JARs on the classpath instead of the module path, they effectively live in the unnamed module. This unnamed module can read everything, and everything can read it, which is intentionally permissive to keep legacy code running.</p>

<p>It is the compatibility bridge. It is also where modular purity goes to take a nap.</p>

<h3 id="automatic-modules">Automatic modules</h3>

<p>If you put a regular JAR on the module path, the system can treat it as an automatic module with a derived name and permissive readability rules.</p>

<p>This is a practical stepping stone, not a perfect end state.</p>

<p>A common migration path looks like:</p>

<ul>
  <li>Run as classpath, accept the unnamed module.</li>
  <li>Move some things to module path, tolerate automatic modules.</li>
  <li>Modularize the important libraries and applications over time.</li>
</ul>

<p>Not glamorous, but it works, and it respects the fact that software is mostly sedimentary rock.</p>

<h2 id="the-jdk-itself-became-modular">The JDK itself became modular</h2>

<p>One of the most concrete outcomes of Jigsaw is that the JDK stopped being a monolith. It became a set of modules.</p>

<p>This matters because:</p>

<ul>
  <li>The platform itself has clearer internal boundaries.</li>
  <li>The JDK can strongly encapsulate internal APIs, reducing accidental dependencies on JDK internals.</li>
  <li>Tools can assemble smaller runtimes for specific applications.</li>
</ul>

<p>That last point is where tools like <code class="language-plaintext highlighter-rouge">jlink</code> enter the story, but it is better saved for a later post, once the basics are anchored.</p>

<h2 id="so-why-project-jigsaw">So why Project Jigsaw?</h2>

<p>Now we can answer the original “why” without hand-waving.</p>

<p>Java had packaging and namespacing. It did not have platform-enforced boundaries.</p>

<p>Project Jigsaw exists to make modularity:</p>

<ul>
  <li>Declarative, so the platform can see your intent.</li>
  <li>Verifiable, so tools can reason about your dependency graph.</li>
  <li>Enforceable, so “internal” actually means internal.</li>
</ul>

<p>It turns modularity from a cultural norm into a constraint you can build against.</p>

<p>That is the difference between “please do not touch” and “the door is locked.”</p>

<h2 id="what-is-next">What is next</h2>

<p>This was Part 1: the definition, the motivation, and the shape of the solution.</p>

<p>In Part 2, we will go concrete:</p>

<ul>
  <li>A tiny multi-module project you can compile and run.</li>
  <li>The first “why can I not access this package anymore?” moment, explained.</li>
  <li>Practical patterns: what to export, what to keep internal, and how to avoid designing a module system that only you understand.</li>
</ul>

<p>Because nothing teaches architecture faster than a compiler error that is technically correct and emotionally rude.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="Technology" /><category term="Software Engineering" /><category term="Series 3 - Project Jigsaw (JPMS)" /><category term="java" /><category term="jdk9" /><category term="project-jigsaw" /><category term="jpms" /><category term="modularity" /><category term="programming-language" /><summary type="html"><![CDATA[A practical definition of modularity in Java, why Jigsaw existed even though we already had packages and JARs, and what the module system actually enforces.]]></summary></entry><entry><title type="html">Testing and the Perfect Software</title><link href="https://systemhalted.in/2025/12/13/testing-perfect-software/" rel="alternate" type="text/html" title="Testing and the Perfect Software" /><published>2025-12-13T00:00:00+00:00</published><updated>2025-12-13T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/13/testing-perfect-software</id><content type="html" xml:base="https://systemhalted.in/2025/12/13/testing-perfect-software/"><![CDATA[<p>Testing is important. Testing software is important. But testing cannot guarantee a perfect product.</p>

<p>At best, testing gives you information. That information helps you mitigate the risk of releasing something that is quietly wrong and quietly expensive. If you want a perfect product, you would need exhaustive testing. Gerald M. Weinberg points out the trap: exhaustive testing implies an infinite number of tests, and that takes more time than the life of the product itself<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>. Edsger W. Dijkstra said, “Testing may convincingly demonstrate the presence of bugs, but can never demonstrate their absence.”<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<p>So do not treat tests as a purity ritual. Use them to gain information, and point them at the highest risk first.</p>

<h2 id="testing-is-important-but-knowing-what-to-test-is-imperative">Testing is important, but knowing what to test is imperative</h2>

<p>Most teams do not fail because they do not test. They fail because they test the wrong things, at the wrong layer, for the wrong reasons. The real question is not whether we have tests. It is whether our tests give us truth where it matters most.</p>

<p>A simple way to choose is to think in tiers: Must Haves (Guarantees), Should Haves (Safeguards), and Good to Have (Hardening).</p>

<h3 id="must-be-tested-guarantees">Must be tested (Guarantees)</h3>
<p>These cover the things that cause real damage if they break: lost money, lost trust, regulatory exposure.</p>

<p>Examples:</p>
<ol>
  <li>Money movement and irreversibility: double charges, missing payments, incorrect balances, wrong rounding.</li>
  <li>Identity and authorization: user A accessing user B’s data, broken MFA, weak reset flows.</li>
  <li>Data integrity invariants: dedupe rules, ordering guarantees, idempotency, “exactly once” assumptions.</li>
  <li>Privacy and compliance: PII leakage, audit log gaps, retention and encryption failures.</li>
</ol>

<h3 id="should-be-tested-safeguards">Should be tested (Safeguards)</h3>
<p>These cover the everyday production failure modes. Not glamorous, but common.</p>

<p>Examples:</p>
<ol>
  <li>Integration seams: timeouts, retries, partial failures, schema changes.</li>
  <li>Negative paths: invalid inputs, missing fields, weird encodings, malformed payloads.</li>
  <li>Performance cliffs: cold starts, peak traffic, queue buildup, slow queries.</li>
</ol>

<h3 id="good-to-have-hardening">Good to have (Hardening)</h3>
<p>This is maturity. Not mandatory on day one, but it pays off over time.</p>

<p>Examples:</p>
<ol>
  <li>Property based tests: verify invariants across many generated inputs.</li>
  <li>Fuzzing: especially for parsers, inputs, and security boundaries.</li>
  <li>Chaos and resilience tests: inject latency, kill dependencies, verify graceful degradation.</li>
  <li>Observability checks: alerts that fire, logs that help, traces that connect.</li>
</ol>

<h2 id="your-test-portfolio">Your test portfolio</h2>

<p>Think of tests as a portfolio. You are allocating limited time to buy maximum confidence. Some tests are cheap and plentiful, some are fewer but high value, and some are expensive insurance policies. A healthy suite is not more tests. It is better allocation.</p>

<p>Two rules keep you honest:</p>
<ol>
  <li>Put most assertions as close to the code as possible. Fast feedback, precise failures.</li>
  <li>Put a small number of assertions as close to the user as necessary. Real confidence.</li>
</ol>

<h2 id="the-testing-pyramid">The testing pyramid</h2>

<p>The classic pyramid is still useful, not as dogma, but as economics.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>End-to-End (few)
Integration / Component (some)
Unit (many)
</code></pre></div></div>

<p>Unit tests are cheap, fast, and local. End-to-end tests are expensive, slow, and brittle, but they tell you whether the actual business journey worked. The pyramid is a cost statement, not a moral one.</p>

<h3 id="unit-tests">Unit tests</h3>
<p>Best for pure and deterministic logic: calculations, transformations, validation, edge cases, invariants.</p>

<p>Unit tests answer:</p>
<blockquote>
  <p>Is this piece correct in isolation?</p>
</blockquote>

<p>A unit test is most valuable when it is small, fast, deterministic, and cheap enough to run on every change. Google’s testing guidance<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> emphasizes “small tests”, their closest equivalent to unit tests as constrained in resources, and their book<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> stresses that unit tests should be fast and deterministic so engineers can run them frequently.</p>

<p>Michael Feathers’ commonly cited rule of thumb is also useful as a boundary. If a test touches a database, the network, the filesystem, or needs a special environment configuration, it is not a unit test<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>.</p>

<p>A common trap is over-mocking. If a unit test is mostly mocks, stubs, and call verification, you may be testing your imagination instead of behavior. Prefer asserting outputs and state over asserting call choreography. Only lean on interaction-based or call-count assertions when the unit’s job is orchestration, for example retries, sequencing, or enforcing that a collaborator is invoked a specific number of times.</p>

<h3 id="integration-and-component-tests">Integration and component tests</h3>
<p>These test the seams where production failures tend to happen.</p>

<p>Integration tests answer:</p>

<blockquote>
  <p>Do my assumptions about the world hold?</p>
</blockquote>

<p>Martin Fowler’s simplest framing is still the most useful<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>: integration tests check whether independently developed units work correctly when connected. The term is overloaded, so it helps to state the scope you mean (two components, one service plus its database, one service plus a message broker, etc.).</p>

<p>Component tests are a related idea. They limit the exercised software to a <em>portion</em> of the system rather than the full stack. In practice that often means this service works with its dependencies, without standing up the entire ecosystem.</p>

<p>Google’s “test sizes” vocabulary maps nicely here. Their guidance often treats “medium tests” as the place where a small number of tiers communicate properly, which is the integration sweet spot.<sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>

<p>Examples:</p>
<ol>
  <li>Database queries, migrations, transaction boundaries.</li>
  <li>Serialization, schema evolution, backward compatibility.</li>
  <li>Messaging, retries, dedupe, idempotency under real conditions.</li>
  <li>Service-to-service calls with real HTTP and real headers.</li>
</ol>

<h3 id="contract-tests">Contract tests</h3>
<p>Often overlooked, but important in distributed systems.</p>

<p>Contract tests answer:</p>

<blockquote>
  <p>Are we <em>still</em> speaking the same language across the boundary?</p>
</blockquote>

<p>Martin Fowler’s framing is crisp: contract tests check the <em>contract</em> of external service calls, focusing on the format and expectations, not necessarily the exact data values.<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup> They verify the handshake between a consumer and a provider without requiring a full end-to-end environment.</p>

<p>Thoughtworks makes the case for microservices: consumer-driven contract testing is a key part of a mature testing portfolio because it enables independent service deployments without accidentally breaking consumers.<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup></p>

<p>A contract test asserts the handshake between consumer and provider: request shape, response shape, defaults, error contracts, compatibility.</p>

<h3 id="end-to-end-tests">End-to-end tests</h3>
<p>Keep these few.</p>

<p>End-to-end tests answer:</p>
<blockquote>
  <p>Does the customer story still work?</p>
</blockquote>

<p>Google’s testing guidance treats these as “large” tests: higher fidelity, but slower, more expensive, and more prone to flakiness than smaller tests, which is why you keep them few and focused.<sup id="fnref:3:1"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:4:2"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>

<p>A common trap is using end-to-end tests to compensate for missing lower-level tests. That is how you get a flaky suite that everyone learns to ignore.</p>

<p>Pick critical business journeys and test those: payment flow, login flow, upload-and-view flow.</p>

<h2 id="a-modern-pyramid-that-fits-microservices-better">A “modern pyramid” that fits microservices better</h2>

<p>Many teams quietly evolve toward this:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">End-to-End (tiny cap)
Integration / Component (solid middle)
Contract tests (thin band)
Unit (broad base)</code></pre></figure>

<p>This reduces the urge to build a giant end-to-end suite that is slow, brittle, and mostly good at burning afternoons.</p>

<h2 id="a-practical-decision-rule-where-should-a-test-live">A practical decision rule: where should a test live?</h2>

<p>Use this mental routing table:</p>

<ol>
  <li>If it is a business invariant, test it at the unit level first.</li>
  <li>If it crosses a boundary you do not control, add a contract test.</li>
  <li>If it depends on real infrastructure behavior, add an integration or component test.</li>
  <li>If it is a critical customer journey, add an end-to-end test.</li>
</ol>

<p>Test the truth close to the source. Test the handshake at the boundary. Test the journey only where it matters.</p>

<h2 id="real-world-examples">Real world examples</h2>

<h3 id="example-1-payments-or-loan-servicing-posting-pipeline">Example 1: Payments or loan servicing posting pipeline</h3>

<p>A customer makes a payment. It gets authorized, posted, and the receipt is generated.</p>

<p>Must be tested:</p>
<ol>
  <li>Idempotency: the same payment request processed twice must not double-post.</li>
  <li>Ledger correctness: principal vs interest allocation, fees, rounding, balance changes.</li>
  <li>Atomicity: either all related records update, or none do. No half-paid states.</li>
</ol>

<p>Should be tested:</p>
<ol>
  <li>Retries and timeouts between services.</li>
  <li>Duplicate events from the bus, out-of-order delivery, delayed settlement.</li>
  <li>Reconciliation: what happens when tomorrow’s batch notices a mismatch?</li>
</ol>

<p>Good to have:</p>
<ol>
  <li>Chaos: gateway latency spikes, downstream returns 503, does your system degrade or panic?</li>
  <li>Property tests: ledger invariants remain true across random sequences.</li>
</ol>

<p>A tiny invariant sketch:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">record</span> <span class="nf">Ledger</span><span class="o">(</span><span class="kt">long</span> <span class="n">debitCents</span><span class="o">,</span> <span class="kt">long</span> <span class="n">creditCents</span><span class="o">)</span> <span class="o">{</span>
  <span class="kt">long</span> <span class="nf">net</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">creditCents</span> <span class="o">-</span> <span class="n">debitCents</span><span class="o">;</span> <span class="o">}</span>
<span class="o">}</span>

<span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">ledgerNetMatchesArithmetic</span><span class="o">()</span> <span class="o">{</span>
  <span class="kt">var</span> <span class="n">r</span> <span class="o">=</span> <span class="k">new</span> <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">Random</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
  <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10_000</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
    <span class="kt">long</span> <span class="n">debit</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="mi">1_000_000</span><span class="o">);</span>
    <span class="kt">long</span> <span class="n">credit</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="mi">1_000_000</span><span class="o">);</span>
    <span class="kt">var</span> <span class="n">l</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Ledger</span><span class="o">(</span><span class="n">debit</span><span class="o">,</span> <span class="n">credit</span><span class="o">);</span>
    <span class="n">org</span><span class="o">.</span><span class="na">junit</span><span class="o">.</span><span class="na">jupiter</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="n">credit</span> <span class="o">-</span> <span class="n">debit</span><span class="o">,</span> <span class="n">l</span><span class="o">.</span><span class="na">net</span><span class="o">());</span>
  <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>This does not prove correctness. It proves you declared a truth, and you keep checking it.</p>

<h3 id="example-2-login-plus-token-issuance">Example 2: Login plus token issuance</h3>

<p>Must be tested:</p>
<ol>
  <li>Authorization boundaries: user A cannot access user B’s resources. Ever.</li>
  <li>Token expiry and refresh: no immortal sessions.</li>
  <li>Password reset and MFA: the “I lost access” path is a common attack surface.</li>
</ol>

<p>Should be tested:</p>
<ol>
  <li>Clock skew behavior in token validation.</li>
  <li>Rate limiting and lockouts without harming real users.</li>
  <li>Key rotation behavior in realistic rollout windows.</li>
</ol>

<p>Good to have:</p>
<ol>
  <li>Fuzz JWT claims and headers.</li>
  <li>Replay and session fixation abuse cases.</li>
</ol>

<h3 id="example-3-document-upload-plus-classification">Example 3: Document upload plus classification</h3>

<p>Must be tested:</p>
<ol>
  <li>Correct customer association: a misfiled document is a silent failure.</li>
  <li>Access control: only authorized users can view or reclassify.</li>
  <li>PII handling: retention, redaction, encryption behavior.</li>
</ol>

<p>Should be tested:</p>
<ol>
  <li>Corrupt PDFs, huge files, mixed encodings, virus scan failures.</li>
  <li>Async pipeline correctness: retries, dead-letter paths, idempotent processing.</li>
  <li>Search indexing lag and eventual consistency behavior.</li>
</ol>

<p>Good to have:</p>
<ol>
  <li>Drift checks on classification confidence over time.</li>
  <li>Canary documents monitored continuously.</li>
</ol>

<h2 id="closing">Closing</h2>

<p>Testing will not make your product perfect. It will keep your product honest.</p>

<p>Use tests to gain information about bugs, then fix them. Do not let the presence of bugs shame you if your testing caught them.</p>

<p>A perfect product is a myth. A low-quality product is a choice.</p>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Gerald M. Weinberg, <em>Perfect Software</em>. (leanpub.com). <a href="https://leanpub.com/perfectsoftware?utm_source=systemhalted.in">https://leanpub.com/perfectsoftware?utm_source=systemhalted.in</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>Dijkstra, <em>Reliability of programs</em>. (utexas.edu).<a href="https://www.cs.utexas.edu/users/EWD/transcriptions/EWD03xx/EWD303.html?utm_source=systemhalted.in">https://www.cs.utexas.edu/users/EWD/transcriptions/EWD03xx/EWD303.html?utm_source=systemhalted.in</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>Simon Stewart, <em>Google Test Sizes</em>. (googleblog.com). <a href="https://testing.googleblog.com/2010/12/test-sizes.html?utm_source=systemhalted.in">https://testing.googleblog.com/2010/12/test-sizes.html?utm_source=systemhalted.in</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:3:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:4">
      <p>Titus Winters, Tom Manshreck, and Hyrum Wright. “Testing Overview.” Software Engineering at Google, n.d., <a href="https://abseil.io/resources/swe-book/html/ch11.html">https://abseil.io/resources/swe-book/html/ch11.html</a> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:4:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:5">
      <p>Michael Feathers, <em>Working Effectively With Legacy Code</em>. 1st Edition, Pearson <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>Martin Fowler. “Integration Test.” (martinfowler.com). 2018, <a href="https://martinfowler.com/bliki/IntegrationTest.html">https://martinfowler.com/bliki/IntegrationTest.html</a> <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p>Martin Fowler. “Contract Test.” <em>martinfowler.com</em>, 2011, <a href="https://martinfowler.com/bliki/ContractTest.html">https://martinfowler.com/bliki/ContractTest.html</a> <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>Thoughtworks. “Consumer-driven contract testing.” <em>Technology Radar</em>, 2015, <a href="https://www.thoughtworks.com/en-us/radar/techniques/consumer-driven-contract-testing">https://www.thoughtworks.com/en-us/radar/techniques/consumer-driven-contract-testing</a> <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Software Engineering&quot;]" /><category term="testing" /><category term="quality" /><category term="risk" /><category term="tdd" /><summary type="html"><![CDATA[Testing matters, but choosing what to test is the real craft. A practical way to decide what must be tested, what should be tested, and what is good to have.]]></summary></entry><entry><title type="html">Hinduism, Hindutva, and the People Who Lit My Mind</title><link href="https://systemhalted.in/2025/12/11/hinduism/" rel="alternate" type="text/html" title="Hinduism, Hindutva, and the People Who Lit My Mind" /><published>2025-12-11T00:00:00+00:00</published><updated>2025-12-11T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/11/hinduism</id><content type="html" xml:base="https://systemhalted.in/2025/12/11/hinduism/"><![CDATA[<p>On the night of 3rd February 2006, at almost exactly half past eleven, a twenty-five-year-old version of me sat down with a small notebook and tried, in the way one sometimes does at that hour, to write out a list of the people who had genuinely shaped the way I thought about the world. The list was short, and now feels rather grand in retrospect — Lord Krishna, Mahatma Gandhi, Bhagat Singh, Ram Prasad Bismil, Premchand, Jawaharlal Nehru, Sardar Vallabhbhai Patel, Dr. A.P.J. Abdul Kalam, and Dr. Manmohan Singh — but it was the list I had at the time, and it felt honest in the way that lists made late at night sometimes do.</p>

<p>One more name joined that list almost at the end of the entry: Dr. Karan Singh. I had just come across his articulation of the difference between Hinduism and Hindutva, and it had quietly rearranged the furniture in my head — in a way that took me, in fact, the better part of two decades to fully unpack, and that I want to try, finally, to write down here.</p>

<hr />

<h2 id="the-rare-people-who-touch-both-mind-and-heart">The rare people who touch both mind and heart</h2>

<p>I have always been a little suspicious of the word “inspiration”, because most of the speeches that one is told to find inspiring tend to evaporate at roughly the same speed at which one walks back to the parking lot afterwards. Every once in a while, however, you encounter a person whose words refuse to evaporate; they begin instead to do slow, patient, occasionally annoying work somewhere inside the way you think, challenging you on points you had taken for granted and quietly educating you on points you had not yet realised were relevant. They take up residence on whatever mental shelf one keeps for ideas that might be useful later, and they refuse to be filed away.</p>

<p>Each of the figures on the list above was, to me, an instance of that kind of person, although for different reasons. Krishna’s conversations in the Gita are, on close reading, a long argument that doubt is not a moral failure but the proper starting point for ethical thought, and that has stayed with me. Gandhi’s stubborn insistence on non-violence demonstrated, more clearly than any abstract theory could have, that an inner discipline applied steadily over time can become a political force in its own right. Bhagat Singh and Ram Prasad Bismil offered, between them, an example of courage that refused to wait for ideal conditions and acted under whatever conditions were actually available — which is, in the end, the only kind of courage that ever turns out to matter. Premchand, in his fiction, did the unglamorous work of dragging everyday Indian life into literature and forcing us, as readers, to look at it without the filters we usually use. Nehru and Patel did the equally unglamorous work of turning what had been impossible theory into a functioning state, often in spite of each other. And Kalam and Manmohan Singh, in different ways, represented a quiet, technocratic ideal that I have come to value more as I have got older — intelligence without theatrics, competence as a form of public service.</p>

<p>What I took from this crowd of giants, taken collectively, was not really a formula for living. It was, I think, something closer to a way of approaching the world: to question what one has been told, to try to feel one’s way into the position of people who are not oneself, and to stay curious about the world for longer than the news cycle, on its own, encourages.</p>

<p>Karan Singh’s contribution, when I came across it later that same evening, was to give me a vocabulary for a distinction I had been groping toward without quite being able to name. The distinction, in his hands, was deceptively simple — Hinduism, he suggested, is best understood as a way of seeking, while Hindutva is a project of identity — and the cleanness of that pairing is one of those things you only really notice once it has, almost without permission, started to clarify everything else you have been reading on the subject.</p>

<hr />

<h2 id="hinduism-river-not-fortress">Hinduism: river, not fortress</h2>

<p>The Hinduism that I, like most people of my generation, grew up with did not arrive as a rulebook of any sort. It arrived as the stories that my elders told us in the evenings, as bhajans drifting in from a neighbour’s open window, as temple visits in which the spiritual half of the experience was usually entangled, perfectly happily, with the street-food half, and as a slightly vague habit of folding one’s hands in the direction of any image that looked relevant before an examination, on the apparent assumption that one might as well not take chances. It was, in retrospect, messy, contradictory, generous, and almost entirely tolerant of one’s own ambivalence: one could argue with God, ignore God, or quietly convert God into a metaphor for something more abstract, and no part of the surrounding tradition was much interested in coming to confiscate one’s ration card over the choice.</p>

<p>What Karan Singh did, in his writing and his lectures, was to put sharper language to something I had only previously felt. Hinduism, in his account, is best described as a civilizational habit of searching, comfortable in a way that few traditions of comparable age have managed to be with a thoroughgoing multiplicity of deities, of philosophies, and of paths. One can be drawn to the non-dual silence of Advaita, to the emotional surrender of Bhakti, to the disciplined practice of Yoga, or to a calm agnosticism that still treats the tradition with respect — and all of these positions can, in principle, sit around the same metaphorical kitchen table and argue with each other late into the night without any of them needing to be expelled from the room.</p>

<p>The point of saying this, I should be careful to add, is not that Hinduism is flawless or somehow superior to other traditions. Its history is, like every long history, littered with its own injustices, its own blind spots, and its own embarrassing chapters that no honest account can wave away. The point is, rather, that at its core it contains the tools required to correct those injustices from within — the debate, the commentary, the reinterpretation, the dissent that has always come from inside the tradition rather than from outside it — and that capacity for self-correction is what, in the end, makes the tradition feel to me more like a river than like a fortress: ancient, meandering, periodically polluted, and yet, at each stage of its history, capable of renewing itself.</p>

<hr />

<h2 id="hindutva-when-a-river-is-turned-into-a-wall">Hindutva: when a river is turned into a wall</h2>

<p>Hindutva, set beside the Hinduism described above, is something quite different, and the difference is not, in my reading, primarily theological. It is structural. Where Hinduism, at its best, is preoccupied with questions about the nature of reality, of the self, and of duty — questions that the tradition has, in characteristic fashion, allowed to remain partially open across centuries of debate — Hindutva is preoccupied instead with the question of who properly belongs and who does not, which is a political question with a political answer, and one whose answer must, by the logic of the project, be unambiguous. The first is, in essence, metaphysical and ethical, willing to live with the fact that not every important question has a settled answer; the second is political and majoritarian, and is uncomfortable — often actively hostile — in the face of any answer that admits of qualification.</p>

<p>Once one has noticed this difference, it becomes, in my experience, difficult to stop noticing it. Hinduism, taken as a tradition, has historically been able to live alongside other faiths, to learn from them, and at times to absorb their ideas into itself without feeling its own coherence to be threatened by the encounter. Hindutva, by contrast, requires a more or less permanent sense of external threat in order to justify its own existence; without an enemy of one kind or another, it does not really have a purpose to fall back on, and its rhetoric reflects this consistently. Hinduism, as a posture, tends to invite the practitioner to look inward and to undertake the much harder work of transforming themselves; Hindutva tends to invite them to look outward, and to begin to suspect their neighbours.</p>

<p>The same contrast plays out at the level of intellectual content. Hinduism, as it has actually been practised over centuries, is vast enough to comfortably contain the Gita, the Upanishads, the Charvaka materialists, the Bhakti poets, the Sufi-infused traditions of medieval north India, and the very large number of local deities with their own stories that any visitor to a small Indian town will eventually run into. Hindutva, in its public form, attempts to compress that whole library into a single political slogan — and, as is generally the case when something rich is compressed into something simple, what is lost in the compression turns out to be precisely the parts that made the original worth defending.</p>

<hr />

<h2 id="why-karan-singhs-distinction-still-matters">Why Karan Singh’s distinction still matters</h2>

<p>Back in 2006, sitting with that notebook, I do not think I fully grasped the political weight of the distinction Karan Singh was drawing. What I knew, at the level of feeling rather than analysis, was that it felt right, in some way that I could not yet articulate, for someone to be able to say: “I love Hindu philosophy deeply, and <em>precisely because of that</em>, I reject the narrow, exclusionary nationalism that is so often done in its name.” That formulation seemed, to my younger self, to do justice to two things at once which the rest of the public conversation seemed determined to put in opposition — affection for one’s own tradition, and a refusal to allow it to be turned into a weapon.</p>

<p>Almost two decades later, in a period in which every identity in public life seems to be being steadily sharpened into a weapon of one sort or another, I think the stance feels, if anything, more important than it did then, rather than less. What it offers, in the end, is a third path between two more familiar ones — between, on the one hand, a self-hating rejection of one’s own culture as if there were nothing of value in it, and on the other, a credulous worship of a mythical golden past which, on close inspection, never quite existed. The third path, which Karan Singh expressed better than I have ever managed to, is something closer to a rooted openness: an attitude that is secure enough in its heritage to be honestly self-critical, and humane enough in its relations with others to refuse the easy temptations of identity politics in either direction.</p>

<p>That is the reason I quietly added Karan Singh’s name to that short list of mine on the night I first encountered his work. He did, in the end, what good teachers in my experience tend to do: he gave language to a discomfort I had already been carrying around without being able to name, and he made it clear that loving a tradition and resisting its misuse are not, as they are sometimes presented, contradictions to be reconciled. They are, when one looks at them honestly, two faces of the same obligation.</p>

<hr />

<h2 id="where-this-leaves-me-today">Where this leaves me today</h2>

<p>Nearly two decades after that late-night notebook entry, I find that my relationship with faith has become, on the whole, more agnostic, more questioning, and from time to time more impatient than it once was. The basic architecture, however, has stayed remarkably stable. I remain, as I was then, more interested in <em>Dharma</em> as a lived everyday ethic than in any of the more elaborate theological structures that are sometimes built on top of it; the tradition continues to give me a sense of identity which I do not feel any need to apologise for. I remain, just as steadily, suspicious of any ideology — religious, political, or otherwise — which appears to require a permanent supply of enemies in order to maintain its own coherence, on the grounds that an idea which cannot survive a peaceful neighbourhood is rarely an idea worth defending in the first place. And I continue to feel, with something close to conviction, that if a person’s conception of God cannot coexist with someone else’s conception of God, or with their honest absence of one, then the problem on close inspection is not with the neighbour but with the conception of God being defended.</p>

<p>What Hinduism, on its better days, gives me is a generous supply of metaphors, stories, and philosophical instruments to think with — and the freedom to use them carefully or carelessly, depending on the question at hand. What Hindutva, on its worse days, attempts to do is to compress all of that complexity into a political loyalty test, and to demand that one declare, in advance and in public, which side of a fence one is on. I have, on the whole, become reasonably clear about which side of that line I want to stand on, and the longer I think about it, the less it feels like a difficult choice.</p>

<hr />

<p>The version of me who sat with that notebook in 2006 was, I think, under the impression that he was just writing down a list of impressive people whose ideas had stuck with him. What he was actually doing, in retrospect, was something rather more consequential: he was choosing the intellectual ancestors he wanted to be answerable to, even if he would not have used those words at twenty-five. I am still, two decades later, trying clumsily to make myself worthy of that lineage — a lineage in which courage, curiosity, and compassion count for more than flags and slogans, and in which a tradition is something one tends and renews rather than something one weaponises. If Hinduism is, as I have come to believe, more like a river than a fortress, then I would rather spend my time learning, however imperfectly, to swim in it, than spend it helping anyone build walls around it.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Personal Essays&quot;]" /><category term="hinduism" /><category term="hindutva" /><category term="india" /><category term="philosophy" /><summary type="html"><![CDATA[A reflection on Dr. Karan Singh’s idea of Hinduism and Hindutva, and the long line of people who quietly shaped my thinking.]]></summary></entry><entry><title type="html">Letter to Self - Passion</title><link href="https://systemhalted.in/2025/12/10/passion/" rel="alternate" type="text/html" title="Letter to Self - Passion" /><published>2025-12-10T00:00:00+00:00</published><updated>2025-12-10T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/10/passion</id><content type="html" xml:base="https://systemhalted.in/2025/12/10/passion/"><![CDATA[<p>Dear Palak,</p>

<p>I know you would not have expected your future self to write to you, but this is in fashion these days and you have always been fashionable, so here it is.</p>

<p>I want to talk to you about this whole “find your passion” thing you keep worrying about.</p>

<p>You keep saying you are not sure what you are passionate about. You say it like it is a defect, like some magical signal that never arrived from the sky. I want to tell you gently: that sentence is hiding the truth instead of revealing it.</p>

<p>Most people, including you, already have some sense of what they enjoy. You know which problems make you forget the time. You know which topics your eyes light up for, because your family has to tell you to stop talking about them. When you say “I don’t know my passion,” what is usually happening inside you is not emptiness. It is a mix of fear, confusion, and borrowed ideas about what passion is supposed to look like. Don’t let other people define your preferences. Decide and build your own.</p>

<p>So let us unpack the lies you have quietly absorbed.</p>

<p><em>The first lie is that there is one true passion.</em></p>

<p>You are not a single label. You never were. You like computer science, physics, management, writing, astronomy, history, mentoring, and three other things you will discover later. You are not broken because you cannot squeeze all of that into one tidy word like “developer” or “writer”.</p>

<p>For you, passion is not a single topic. Passion is the way you engage with many topics. You love taking things apart, understanding how they work, building something from that understanding, and then explaining it to someone else. That pattern keeps repeating in different domains. That pattern is your passion, whether you are debugging a system, appreciating history, writing an essay, or teaching someone to look at Jupiter for the first time.</p>

<p>When you try to force yourself to choose one banner and kill everything else, you feel like you are betraying yourself. You are right to resist that.</p>

<p><em>The second lie is that you must discover your passion before you are allowed to move.</em></p>

<p>You imagine that somewhere out there is a perfect fit, a calling with neon lights and background music. Until you “find” it, you feel you are just wandering.</p>

<p>Here is what you will slowly learn. Passion is not a lightning bolt from the sky. Passion is a story you tell later about the things you have stuck with long enough to get good at and care about. You start with something you already kind of like. You do it again. You do it when it is fun. You do it when it is boring. You survive the part where you think you are terrible. You improve. Other people benefit from what you do. Somewhere along that path, it stops being just a task and starts becoming part of who you are.</p>

<p>From the outside, people say, “You found your passion.” From the inside, it feels much more ordinary. You chose something. You stayed with it. Habit, skill, and meaning slowly braided themselves into something deeper.</p>

<p>This is not ordinary. Building habit and skill takes effort and time, and you did it. Yourself.</p>

<p>This is true for writing. It is true for leadership. It is true for engineering. You did not “find” those passions like lost keys. You built them out of small, stubborn repetitions.</p>

<p><em>The third lie is that real passion feels like a 24/7 fire.</em></p>

<p>You secretly believe that if something is truly your passion, you will wake up excited every single day. No doubt. No friction. No boredom. Always “on fire”. Therefore, whenever you feel tired, or bored, or stuck, you quietly conclude, “Maybe this is not my passion after all.” Then you start scanning the horizon for the next thing that will keep you permanently excited. You call this searching for passion. Mostly it is just running away from discomfort.</p>

<p>Here is what you need to know. Real passion has seasons. Sometimes it looks like flow and obsession and that lovely tunnel-vision when code, or prose, or an idea just pours out of you. Sometimes it looks like maintenance: cleaning up old messes, answering emails, debugging someone else’s bug, rereading and rewriting the same paragraph ten times. The fire is still there, but it is not always a bonfire. Some days it is just a candle that you protect with your hands while the wind blows.</p>

<p>Years later, you will read Mark Twain saying:</p>

<blockquote>
  <p>“No, Sir, not a day’s work in all my life. What I have done I have done, because it has been play. If it had been work I shouldn’t have done it.”</p>
</blockquote>

<p>You will recognise yourself in that line. Not because your life was effortless, but because the things you cared about most often blurred that line between work and play. Some days it felt like play. Some days it felt like hard work. It was still yours.</p>

<p>The absence of constant excitement does not mean the absence of passion. It usually means you have moved from the honeymoon phase to the craft phase.</p>

<p>So where does this leave you?</p>

<p>You are multi-passionate. That is not a diagnosis. It is a description. You love many domains, and you also love the process itself: learning, building, connecting, teaching. You will never be happy stuffing all of that into one narrow identity. You do not need to.</p>

<p>You also do not need to sit on the floor and wait for a divine “passion notification” to arrive. You already know enough to start. You know which things you enjoy more than average. You know which problems you complain about and secretly want to fix. You know where your curiosity keeps returning when nobody is watching.</p>

<p>So, here is the advice I wish you would trust a little earlier.</p>

<p>Stop searching for a single, mythical passion. Start taking small bets on the things you already like.</p>

<p>Write one blog post, even if only three people read it. Take on one slightly scary problem at work and see it through. Teach one person something you know. Let yourself be bad at new things long enough for them to become familiar. Let habit, skill, and impact do their slow, quiet work.</p>

<p>Over years, what will emerge will not be a single word you can put in your bio. It will be a pattern of work and play that feels deeply yours. Call that passion if you want. Call it craft. Call it your way of being in the world.</p>

<p>Just do not keep telling yourself you do not have it.</p>

<p>You have always had it. You were just expecting it to look different.</p>

<p>Regards,
Palak</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Personal Essays&quot;]" /><category term="personal" /><category term="opinion" /><category term="astronomy" /><category term="poetry" /><summary type="html"><![CDATA[This is a letter to my past self just beginning to grow into an adult.]]></summary></entry><entry><title type="html">TDD as a Management Technique Revisited</title><link href="https://systemhalted.in/2025/12/09/tdd-revisted/" rel="alternate" type="text/html" title="TDD as a Management Technique Revisited" /><published>2025-12-09T00:00:00+00:00</published><updated>2025-12-09T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/09/tdd-revisted</id><content type="html" xml:base="https://systemhalted.in/2025/12/09/tdd-revisted/"><![CDATA[<p>In an earlier post I argued that <strong>Test Driven Development is fundamentally a management technique</strong>. Not a path to better design, not a guarantee of fewer bugs. It is a Taylorian construct, and its main achievement is that it forces people to write unit tests.</p>

<p>I still believe that.</p>

<p>What has changed is that I now care less about TDD the ritual and more about the habit underneath it. Thinking about testing and evaluation before writing code is valuable, and not just at the unit level. It matters at the functional, component, and integration levels too. The ritual is Taylorism. The mindset is design.</p>

<h2 id="version-1-tdd-as-taylorism">Version 1: TDD as Taylorism</h2>

<p>Unit tests are good. They reduce maintenance costs, prevent regressions, and give us courage to refactor.</p>

<p>The problem is human, not technical. Left alone, many teams will not write enough tests. Even when they do, tests are an afterthought, written just before merge or not at all. Management wants fewer bugs and lower maintenance cost but cannot directly measure future headaches avoided.</p>

<p>TDD turns “write tests” into a visible, enforceable step:</p>

<ol>
  <li>Write a failing unit test</li>
  <li>Make it pass</li>
  <li>Refactor</li>
  <li>Repeat</li>
</ol>

<p>That loop is measurable. You can ask whether the developer wrote tests first, whether the build is green, and what the coverage is. That is classic Taylorism: break work into small steps, make them observable, then control them. TDD is not a design religion. It is management plumbing for test-writing behavior.</p>

<p>That framing explains why TDD took off in large organizations obsessed with metrics. But it is incomplete.</p>

<h2 id="what-changed-from-ritual-to-evaluation">What changed: from ritual to evaluation</h2>

<p>Even in teams that never practiced pure TDD, the best engineers did a version of the thinking behind it. They imagined failure modes, thought about inputs and outputs, and cared about how their work would be evaluated, not just whether it compiled. That is not TDD. That is good engineering.</p>

<p>So my view shifted. TDD as ritual is still a Taylorian construct, but test-first thinking, in the broader sense, is something you want almost everywhere. The mistake is to shrink test-first down to unit tests only and treat everything else as a side quest.</p>

<p>The real question is larger: before we add this behavior to the system, how will we know it works, at every relevant level? Once you phrase it that way, you are pushed beyond unit tests.</p>

<h2 id="four-levels-of-thinking-before-you-code">Four levels of thinking before you code</h2>

<p>I do not mean “write a unit test file before a class file” and declare victory. I mean something more layered.</p>

<h3 id="1-functional-what-would-convince-a-user">1. Functional: what would convince a user?</h3>

<p>Imagine a skeptical product partner or end user next to you. What would convince them the feature works in their world? That question leads to example flows (“a customer with X, who does Y, should see Z”), edge scenarios (“what if the user loses connectivity halfway through”), and policy constraints (“what are we allowed to do”).</p>

<p>This is the territory of acceptance or functional tests. You do not need a test framework to start. A one-page doc with concrete examples is already test-first thinking at the functional level.</p>

<h3 id="2-component-what-contract-does-this-service-promise">2. Component: what contract does this service promise?</h3>

<p>For any service or module, ask what contract you are promising to the rest of the system, what you guarantee about behavior, latency, and failure modes, and how other teams will know you broke something. These questions give rise to component or API-level tests.</p>

<p>Designing them early reveals awkwardness: “why are there three ways to call this?”, “what happens if the downstream is slow but not down?”, “do we retry, for how long, with what backoff?”. We have not written a single test method yet. We are still shaping the surface area of the system.</p>

<h3 id="3-integration-how-does-this-behave-in-the-real-ecosystem">3. Integration: how does this behave in the real ecosystem?</h3>

<p>The third level involves databases, message queues, third-party APIs, authentication, caches, and time. Integration thinking asks whether the system can survive real data rather than happy-path fixtures, what happens when the schema changes, and how it behaves when a dependency is briefly unavailable.</p>

<p>You may not be able to write full integration tests before code exists. But if you do not think about them early, you will design a system that only works in your unit test universe. Many nice designs fail this way in production.</p>

<h3 id="4-unit-what-invariants-must-always-hold">4. Unit: what invariants must always hold?</h3>

<p>At this level the questions are granular. What assumptions must never be violated inside this function or class? What bug would be annoying to debug in prod? Which branches are easy to forget?</p>

<p>Unit tests enforce local invariants and give you courage to refactor internals. They are not sufficient to guarantee the system does what you think, but they are a crucial layer in the overall evaluation story.</p>

<p>The order of thought matters. You do not start from “how do I hit 85 percent coverage”. You start from “what does success look like at each level” and let tests follow.</p>

<h2 id="tdd-as-a-management-tool-test-first-as-a-design-habit">TDD as a management tool, test-first as a design habit</h2>

<p>I still see TDD, as originally popularized, as a management-friendly practice. It decomposes work into repeatable, measurable steps, helps organizations enforce test-writing, and pairs nicely with coverage dashboards and pipeline gates.</p>

<p>None of that is bad. In large systems with many teams, some enforcement is necessary, otherwise tests are the first thing sacrificed under pressure. The problem is when the enforcement mechanism becomes the goal: coverage numbers turn into performance proxies and green builds turn into the scoreboard. Meanwhile nobody asks whether we are testing the right things at the right levels, whether our tests reflect how the system is really used, or whether we over-invest in unit tests and under-invest in integration and functional evaluation.</p>

<p>That is the shift I care about. Tests as management control are useful but limited. Tests as design and evaluation tools are essential, and much broader than TDD usually admits.</p>

<h2 id="so-what-do-we-actually-do-differently">So what do we actually do differently?</h2>

<p>First, for any non-trivial feature, write down how we will know this works before someone opens the IDE. Not a 20-page spec. A short checklist:</p>

<ul>
  <li>Key functional scenarios that must work</li>
  <li>User-facing behavior we care about (latency, error messages, data correctness)</li>
  <li>Critical integration points and their expectations</li>
  <li>A couple of nasty edge cases we are afraid of</li>
</ul>

<p>Second, think in layers of evaluation. When a design is proposed, ask what the acceptance tests are even if they are manual, what API or component tests will protect other teams, what integration tests will keep us honest about reality, and where unit tests provide value versus where they are just coverage padding.</p>

<p>Third, be explicit about where TDD fits. If a team finds “write a failing unit test, then code” helpful, let them use it. Just do not confuse the ritual with the outcome. The outcome is a clear shared understanding of success, tests at the right levels to guard it, and the ability to change the system with confidence. Whether that came from strict TDD, loose test-first thinking, or some other workflow is secondary.</p>

<h2 id="closing-the-loop">Closing the loop</h2>

<p>My old self was right about one thing. TDD has a strong Taylorian flavor. It is a management technique that emerged to tame the chaos of software development and make test-writing more predictable.</p>

<p>But the valuable instinct is not “thou shalt always write tests first”. It is this: before I add behavior to a complex system, I should be clear about how that behavior will be evaluated, at the functional level, the component boundary, the integration seam, and inside the unit. You can reject TDD as religion and still keep that instinct.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Software Engineering&quot;]" /><category term="software" /><category term="leadership" /><category term="management" /><summary type="html"><![CDATA[I still think TDD is a Taylorian construct. What I care about now is thinking about testing and evaluation before writing code.]]></summary></entry><entry><title type="html">Org Incentive Optimization Problem</title><link href="https://systemhalted.in/2025/12/08/org-incentive-optimization/" rel="alternate" type="text/html" title="Org Incentive Optimization Problem" /><published>2025-12-08T00:00:00+00:00</published><updated>2025-12-08T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/08/org-incentive-optimization</id><content type="html" xml:base="https://systemhalted.in/2025/12/08/org-incentive-optimization/"><![CDATA[<p>Recently, in a conversation on <a href="https://www.linkedin.com/posts/jessewarden_heard-a-rumor-google-used-to-have-an-incentive-activity-7403871145076162560-IfQs?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAAAEFJMcB_miK-56ZKPbmvTNGUxM6IrkeN_M">LinkedIn</a>, I came to realization that most companies are secretly running an algorithm they’ve never actually written down.</p>

<p>I call it the <strong>Org Incentive Optimization Problem</strong>.</p>

<p>Here’s how it usually behaves:</p>

<p>You say:</p>
<blockquote>
  <p>“I fixed a bunch of bugs, cleaned up tech debt, and made the platform more reliable.”</p>
</blockquote>

<p>The system hears:</p>
<blockquote>
  <p>“did routine work”.</p>
</blockquote>

<p>You say:</p>
<blockquote>
  <p>“I built a new platform.”</p>
</blockquote>

<p>The system hears:</p>
<blockquote>
  <p>“high impact, visionary, promotable”.</p>
</blockquote>

<p>Same keyboard. Same brain. Completely different reward curve.</p>

<p>The catch is that reliability and maintenance work are <strong>compounding assets</strong>.<br />
They just don’t come with launch emails, codenames, or shiny demos.</p>

<p>Avoided outages never show up on a slide.<br />
Stable systems don’t page executives at 2am.<br />
So they quietly vanish from the story.</p>

<p>From a computing perspective, this is a messy <strong>credit-assignment</strong> problem with delayed rewards and noisy signals.</p>

<p>From a human perspective, it looks like this:</p>

<ul>
  <li>People chase visible “new” work, even when the real value is in strengthening what already exists.</li>
  <li>Platform teams feel pressure to rebrand every major refactor as a “new platform” just to get air cover.</li>
  <li>The folks who keep things boring and dependable get labeled “steady” instead of “high potential”.</li>
</ul>

<p>If you model it in CS terms, it roughly looks like this:</p>

<p><strong>Input</strong></p>

<ul>
  <li>A graph of teams, platforms, incidents, and features</li>
  <li>Noisy signals: outages, launches, OKRs, customer metrics</li>
  <li>Human agents who optimize whatever you measure</li>
</ul>

<p><strong>Objective</strong></p>

<p>Maximize:</p>

<ul>
  <li>Reliability</li>
  <li>Long-term velocity</li>
  <li>Fair rewards for “unsexy” maintenance work</li>
</ul>

<p>Minimize:</p>

<ul>
  <li>Cargo-cult “new platforms”</li>
  <li>Incentive gaming</li>
  <li>Burnout and PIPs for the people keeping it alive</li>
</ul>

<p>Most orgs end up running a greedy online heuristic:</p>

<pre><code class="language-txt">if (work.isVisibleLaunch()) {
    promote++;
} else if (work.isQuietReliability()) {
    shrug++;
}
</code></pre>

<p>Bug fixing gets treated as constant-time hygiene,
while “new platform” is treated as <em>quadratic impact</em>,
even when the real business value is reversed.</p>

<p>The trick, if you are inside the system:</p>

<p>Make reliability work computable.
Never leave it as “I fixed bugs”.</p>

<p>Translate it into:</p>

<blockquote>
  <p>I reduced incident rate by 40%.</p>
</blockquote>

<blockquote>
  <p>I removed a whole class of failures that used to wake people up every weekend.</p>
</blockquote>

<blockquote>
  <p>I made it safer and faster for five other teams to ship.</p>
</blockquote>

<p>Same work.<br />
Different representation in the algorithm.<br />
Very different outcome.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Personal Essays&quot;]" /><category term="personal" /><category term="opinion" /><category term="leadership" /><summary type="html"><![CDATA[Most orgs are unknowingly running an “Org Incentive Optimization” algorithm that over-rewards flashy new platforms and under-values quiet reliability work, so you have to translate maintenance into visible, computable outcomes.]]></summary></entry><entry><title type="html">[Movie Review] Dhurandhar</title><link href="https://systemhalted.in/2025/12/06/dhurandhar-movie-review/" rel="alternate" type="text/html" title="[Movie Review] Dhurandhar" /><published>2025-12-06T00:00:00+00:00</published><updated>2025-12-06T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/06/dhurandhar-movie-review</id><content type="html" xml:base="https://systemhalted.in/2025/12/06/dhurandhar-movie-review/"><![CDATA[<p>Watched Dhurandhar today in a cinema hall after a very long gap from Hindi movies on the big screen, and it was absolutely worth it. It was completely unplanned – on a whim, Priti and I decided to go to a cinema hall, found a Hindi movie playing nearby, and booked the 4:10 pm show at 3:45 pm. We didn’t know who were the actors in the movie, when was it released, rating - nothing.</p>

<p>Dhurandhar is a lot to take in – literally the longest Hindi film in 17 years – nearly three-and-a-half hours, very violent, very loud, very extra. But it never felt boring.</p>

<p>Everyone in the cast was fantastic. It was such a fun surprise to see both Rakesh Bedi and Gaurav Gera. It was also a joy to watch Akshaye Khanna after so many years. I’ve been a fan of his since Border, and he still holds the screen with that same quiet intensity. R. Madhavan and Sanjay Dutt were great as usual and Arjun Rampal was properly menacing. Ranveer Singh was simply phenomenal. Sara Arjun, who is just 20, showed her skills even with limited screen time.</p>

<p>The violence is definitely not for the faint-hearted. That was the part Priti didn’t enjoy. But despite the brutality on screen, both of us walked out genuinely loving the movie and the experience of watching it together.</p>

<p>After a long time, a Hindi movie in a theatre felt worth every minute.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Movie Review&quot;]" /><summary type="html"><![CDATA[Review of Hindi movie Dhurandhar]]></summary></entry><entry><title type="html">India and the United States</title><link href="https://systemhalted.in/2025/12/06/india-us-relations/" rel="alternate" type="text/html" title="India and the United States" /><published>2025-12-06T00:00:00+00:00</published><updated>2025-12-06T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/06/india-us-relations</id><content type="html" xml:base="https://systemhalted.in/2025/12/06/india-us-relations/"><![CDATA[<p>For most of my life, India–US relations were a story you heard about only when something went wrong.</p>

<p>A nuclear test.<br />
A war with Pakistan.<br />
Some new sanctions.<br />
A US president lecturing India on something.   <br />
An Indian PM “aligning” with Non-Aligned Movement.</p>

<p>Today the relationship is everywhere and nowhere at once. It is in H-1B visas and OCI cards, in TikTok bans and chip fabs, in Quad communiqués and tariff notices to the WTO. It is no longer an exotic foreign policy file. It is infrastructure.</p>

<p>This is my attempt to trace that long arc, from 1947 to the current round of tariff drama that started under Trump and simply refuses to go away.</p>

<hr />

<h2 id="non-alignment-nuclear-tests-and-the-cold-war-hangover">Non-alignment, nuclear tests, and the Cold War hangover</h2>

<p>India became independent in 1947 and partition ripped through the subcontinent. The US, fresh out of the Second World War and already looking at Moscow, wanted allies. India wanted space. Nehru’s trip to the US in 1949 produced warmth in photos and distance in policy. Soon after, India formally planted itself in the Non-Aligned Movement, which Washington always read as “nice words for not being on our side”. <sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<p>The first big fracture was 1971. As Pakistan tore itself apart and Bangladesh was born, India intervened decisively and signed a 20-year Treaty of Friendship with the Soviet Union. The US, busy courting China via Pakistan, tilted towards Islamabad even as reports of atrocities in East Pakistan piled up. <sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup></p>

<p>Then came 1974. India tested a nuclear device at Pokhran and declared itself, very politely, a country that would not be told where its nuclear red lines lay. The US response was predictable for that era: non-proliferation laws, nuclear technology cut-offs, and a long season of frost. For more than two decades, “estranged democracies” became the default label for India and the US. <sup id="fnref:1:2"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2:1"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup><sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>

<p>The relationship in that phase was transactional and narrow. Food aid during the Green Revolution. Some cooperation, some lectures. A lot of mutual irritation. Very little trust.</p>

<hr />

<h2 id="1991-when-economics-finally-crashed-the-party">1991: when economics finally crashed the party</h2>

<p>The real reset started not with a summit, but with a balance-of-payments crisis.</p>

<p>In 1991, Narasimha Rao’s government and a then-little-known finance minister called Manmohan Singh opened the Indian economy under duress. Tariffs fell. Markets opened. Foreign investment stopped being a dirty phrase. That single move changed how Washington looked at India, and how Indian business looked at the world. <sup id="fnref:2:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup><sup id="fnref:4:1"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>

<p>Suddenly, India was not just the country that tested nukes and talked about moral leadership. It was also a place where American companies could sell planes, computers, telecom equipment, and consulting services. It was a place that had cheap, well-educated engineers.</p>

<p>Y2K, offshoring, and the IT services boom did what no amount of diplomacy had managed. They created a dense mesh of everyday connections. Indian engineers in US suburbs. US customers on the other end of a phone line in Bangalore. Companies with more Indians in their org chart than some Indian PSUs.</p>

<p>Even the nuclear argument began to bend. Pokhran-II in 1998 triggered another round of US sanctions, but this time they did not last. By 2000, President Clinton was in India and people were already talking about “warmed ties” instead of “estrangement”. <sup id="fnref:3:1"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:4:2"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>

<hr />

<h2 id="the-nuclear-deal-and-the-strategic-turn">The nuclear deal and the strategic turn</h2>

<p>The real political signal came in the mid-2000s.</p>

<p>Between 2005 and 2008, India and the US negotiated and passed the civil nuclear agreement. India separated its civilian and military nuclear facilities, accepted IAEA safeguards on the civilian side, and in return the US worked to tear down a three-decade-old moratorium on nuclear trade with India. <sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup><sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup><sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup></p>

<p>You can argue about the exact economic value of that deal for hours. What matters for this story is something else. It told the world that Washington now saw Delhi as a long-term strategic bet, not a charity case to be managed with aid and occasional scolding. <sup id="fnref:6:1"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup><sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup></p>

<p>Around the same time, the US declared India a “Major Defense Partner”. Defense trade went from almost zero to billions. Malabar exercises grew in scope. The Quad slowly crawled out of acronym limbo and acquired ballast. <sup id="fnref:3:2"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup><sup id="fnref:8:1"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup></p>

<p>Terrorism, Pakistan, Afghanistan, Iran, Russia – these remained real areas of friction. But the big axis had shifted. The primary shared headache had a different name now: China. <sup id="fnref:2:3"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup><sup id="fnref:8:2"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup></p>

<hr />

<h2 id="trump-tariffs-and-the-mini-trade-war">Trump, tariffs, and the mini trade war</h2>

<p>Then came Trump, and with him, tariffs as a lifestyle choice.</p>

<p>In 2018, the US used the “national security” provision to slap tariffs of 25 percent on steel and 10 percent on aluminium from a long list of countries, India included. Those duties hit hundreds of millions of dollars’ worth of Indian exports, a couple of percentage points of what India sold to the US. <sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup><sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">10</a></sup></p>

<p>In 2019, Washington went a step further and yanked away India’s GSP status – the preferential trade program that gave duty-free access to a basket of Indian exports. That decision affected more than 6 billion dollars of trade and triggered what economists politely called a “mini trade war”. India retaliated with higher duties on selected US goods. <sup id="fnref:10:1"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">10</a></sup><sup id="fnref:11"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">11</a></sup></p>

<p>Trump’s messaging was blunt and on brand. India was a “tariff king”. The US was being taken for a ride. Tariffs were justice. <sup id="fnref:9:1"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup></p>

<p>If you lived in the diaspora, you got used to this strange split-screen. On one side, “Howdy Modi” in Houston and “Namaste Trump” in Ahmedabad – full stadiums, warm hugs, and talk of “natural allies”. On the other side, steel tariffs, GSP removal, and a constant drumbeat of trade complaints.</p>

<p>What held through that turbulence was the strategic layer. Defense cooperation deepened. Intelligence sharing increased. The Indo-Pacific became a more explicit frame. The two militaries worked together more. The Chinese PLA’s behaviour on the LAC made sure of that. <sup id="fnref:2:4"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup><sup id="fnref:8:3"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup></p>

<hr />

<h2 id="after-trumps-tariffs-less-romance-more-plumbing">After Trump’s tariffs: less romance, more plumbing</h2>

<p>Changing presidents did not magically reset the economics.</p>

<p>Under Biden, some of the sharp edges were filed down. Trade talks restarted. There was more predictable messaging. The tone softened. But the age of painless access to the US market was clearly over. Tariffs and “national security” in trade policy are now a permanent part of the furniture. <sup id="fnref:9:2"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup><sup id="fnref:10:2"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">10</a></sup></p>

<p>At the same time, the strategic and technological embrace accelerated.</p>

<p>In 2023, Modi’s state visit to Washington produced an unusually dense joint statement. It covered everything from clean energy to space, from AI to critical minerals. The two sides pushed the Initiative on Critical and Emerging Technology (iCET), agreed on deeper cooperation in 5G and 6G, advanced chip partnerships, and moved forward on co-production of jet engines and other defence technologies. <sup id="fnref:12"><a href="#fn:12" class="footnote" rel="footnote" role="doc-noteref">12</a></sup><sup id="fnref:13"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">13</a></sup><sup id="fnref:14"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">14</a></sup><sup id="fnref:15"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">15</a></sup></p>

<p>Trade numbers quietly told the same story. The US emerged as India’s largest trading partner in goods, while India climbed into the top tier of US trading relationships. Services, digital trade, and investment deepened the connection further. <sup id="fnref:10:3"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">10</a></sup><sup id="fnref:11:1"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">11</a></sup></p>

<p>Yet the tariff saga did not die. It mutated.</p>

<p>In 2025, Washington expanded its use of steel and aluminium tariffs again, doubling duties in the name of national security. New orders raised headline rates and widened the coverage. <sup id="fnref:16"><a href="#fn:16" class="footnote" rel="footnote" role="doc-noteref">16</a></sup><sup id="fnref:17"><a href="#fn:17" class="footnote" rel="footnote" role="doc-noteref">17</a></sup></p>

<p>Delhi responded by formally notifying the WTO that it was considering retaliatory duties on a fresh list of US products, as a suspension of earlier concessions. The filing put the potential impact at about 7.6 billion dollars of Indian exports. <sup id="fnref:18"><a href="#fn:18" class="footnote" rel="footnote" role="doc-noteref">18</a></sup><sup id="fnref:19"><a href="#fn:19" class="footnote" rel="footnote" role="doc-noteref">19</a></sup></p>

<p>The situation further deteriorated when US imposed 25% ban on Indian goods in August with additional 25% soon after, citing New Delhi’s continued imports of Russian oil as the trade talks between the two nation resulted in a “no deal”.<sup id="fnref:20"><a href="#fn:20" class="footnote" rel="footnote" role="doc-noteref">20</a></sup><sup id="fnref:21"><a href="#fn:21" class="footnote" rel="footnote" role="doc-noteref">21</a></sup></p>

<p>The hugs are still there. So are the tariff notices.</p>

<hr />

<h2 id="so-where-are-we-really">So where are we, really?</h2>

<p>If you zoom out from the daily noise, the India–US story can be read in three large movements.</p>

<p>First, the moral and ideological phase. Non-alignment, nuclear lectures, food aid, sanctions, righteous speeches from both sides.</p>

<p>Second, the economic and technological phase. Liberalisation, IT services, diaspora, the nuclear deal, defense cooperation, the Indo-Pacific, the Quad.</p>

<p>Third, the current messy phase. High strategic convergence, thick economic interdependence, and a running gunfight over tariffs, data, digital rules, visas, and domestic politics in both countries. <sup id="fnref:2:5"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup><sup id="fnref:8:4"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup><sup id="fnref:10:4"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">10</a></sup></p>

<p>The centre of gravity has moved from ideology to interest.</p>

<p>India no longer wants to be told whom to buy oil from or what its nuclear doctrine should be. It wants market access, technology transfer, and room to play Russia and the West off each other on its own terms.</p>

<p>The US no longer looks at India primarily through the lens of poverty, non-alignment, or Pakistan. It looks at India as a hedge against China, a market, a talent pool, and a sometimes-difficult partner that still matters a lot more inside Washington than it did thirty years ago. <sup id="fnref:2:6"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup><sup id="fnref:8:5"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup><sup id="fnref:10:5"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">10</a></sup></p>

<p>Tariffs in this picture are not an aberration. They are a tax on that new intimacy.</p>

<p>When you barely trade and barely cooperate, sanctions and tariffs are symbolic. When you are each other’s top partners in multiple domains, they are friction in the plumbing. They hurt very real businesses and very real workers on both sides, even as the broader relationship keeps moving forward.</p>

<hr />

<h2 id="my-bias-laid-bare">My bias, laid bare</h2>

<p>I grew up with stories of a distant superpower that alternated between lecturing India and sanctioning it.</p>

<p>I now live in a world where the US is inside Indian lives in a way that would have been hard to imagine in 1974. Phones, apps, films, VCs, H-1Bs, climate finance, chip supply chains, drones, joint statements, Netflix specials. India, in turn, is inside America through its people, its IT systems, its doctors and engineers, and increasingly through its market and its politics.</p>

<p>So when I look at the current tariff regime and whatever its next avatar will be, I see it as part of a longer story, not a breaking-news headline.</p>

<p>The story is simple.</p>

<p>Two large, loud democracies spent decades talking past each other.<br />
Then they discovered they had overlapping interests and compatible fears.<br />
Now they are trying to work together without becoming each other’s client state.</p>

<p>That is not a romance. It is a negotiation.</p>

<p>Tariffs will come and go. Leaders will hug and glare in cycles. There will be real disagreements on Russia, on digital rules, on climate, on human rights.</p>

<p>Underneath all that, the India–US relationship will keep doing what it has been doing for the last thirty years.</p>

<p>Getting denser.<br />
Getting messier.<br />
And, slowly, becoming too important to be left to just diplomats and tariff lawyers.</p>

<hr />

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Dennis Kux, <em>India and the United States: Estranged Democracies, 1941–1991</em> (Washington, DC: US Institute of Peace Press, 1993).  <a href="https://www.govinfo.gov/content/pkg/GOVPUB-D5_400-PURL-LPS51512/pdf/GOVPUB-D5_400-PURL-LPS51512.pdf?">GovInfo</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:1:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:2">
      <p>Frédéric Grare, “Looking Back at Three Decades of India-US Relationship,” Carnegie Endowment for International Peace, 2018.  <a href="https://carnegie-production-assets.s3.amazonaws.com/static/files/Looking_Back_at_Three_Decades_of_India-US_Relationship.pdf?">Carnegie Production Assets</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:2:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:2:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:2:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:2:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a> <a href="#fnref:2:6" class="reversefootnote" role="doc-backlink">&#8617;<sup>7</sup></a></p>
    </li>
    <li id="fn:3">
      <p>Robert J. McMahon, <em>Cold War on the Periphery: The United States, India, and Pakistan</em> (Columbia University Press, 1994). <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:3:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:3:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:4">
      <p>“India–United States relations,” background summary.  <a href="https://en.wikipedia.org/wiki/India%E2%80%93United_States_relations?">Wikipedia</a> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:4:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:4:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:5">
      <p>“U.S.-India Civil Nuclear Cooperation,” U.S. Department of State archive.  <a href="https://2001-2009.state.gov/p/sca/c17361.htm?">State.gov</a> <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>“The U.S.–India Nuclear Deal,” Council on Foreign Relations Backgrounder.  <a href="https://www.cfr.org/backgrounder/us-india-nuclear-deal?">Council on Foreign Relations</a> <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:6:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:7">
      <p>“Chronology of the Indo-US nuclear deal,” <em>Times of India</em>, October 9, 2008.  <a href="https://timesofindia.indiatimes.com/world/us/chronology-of-the-indo-us-nuclear-deal/articleshow/3575350.cms?">The Times of India</a> <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>“The 10th Anniversary of the U.S.-India Civil Nuclear Deal,” U.S. Embassy New Delhi, July 14, 2015.  <a href="https://ci.usembassy.gov/the-10th-anniversary-of-the-u-s-india-civil-nuclear-deal">U.S. Embassy in Côte d’Ivoire</a> <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:8:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:8:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:8:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:8:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:8:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a></p>
    </li>
    <li id="fn:9">
      <p>“Adjusting Imports of Aluminum and Steel into the United States,” Presidential actions on Section 232 tariffs.  <a href="https://www.whitehouse.gov/presidential-actions/2025/06/adjusting-imports-of-aluminum-and-steel-into-the-united-states">The White House</a> <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:9:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:9:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:10">
      <p>“U.S.-India Trade Relations,” Congressional Research Service / WITA summaries.  <a href="https://www.congress.gov/crs_external_products/IF/PDF/IF10384/IF10384.10.pdf?">Congress.gov</a> <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:10:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:10:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:10:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a> <a href="#fnref:10:4" class="reversefootnote" role="doc-backlink">&#8617;<sup>5</sup></a> <a href="#fnref:10:5" class="reversefootnote" role="doc-backlink">&#8617;<sup>6</sup></a></p>
    </li>
    <li id="fn:11">
      <p>Andrew K. Rose and others, “Trump’s mini-trade war with India,” VoxEU/CEPR, July 22, 2019.  <a href="https://cepr.org/voxeu/blogs-and-reviews/trumps-mini-trade-war-india?">CEPR</a> <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:11:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:12">
      <p>“Joint Statement from the United States and India,” White House, June 22, 2023.  <a href="https://bidenwhitehouse.archives.gov/briefing-room/statements-releases/2023/06/22/joint-statement-from-the-united-states-and-india">The White House</a> <a href="#fnref:12" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:13">
      <p>“Joint Statement from India and the United States,” Indian Ministry of External Affairs, September 8, 2023.  <a href="https://www.mea.gov.in/bilateral-documents.htm?dtl%2F37078%2FJoint_Statement_from_India_and_the_United_States=&amp;">Indian External Affairs Ministry</a> <a href="#fnref:13" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:14">
      <p>“FACT SHEET: United States and India Elevate Strategic Partnership with the Initiative on Critical and Emerging Technology (iCET),” U.S. Embassy.  <a href="https://in.usembassy.gov/fact-sheet-united-states-and-india-elevate-strategic-partnership-with-the-initiative-on-critical-and-emerging-technology-icet">in.usembassy.gov</a> <a href="#fnref:14" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:15">
      <p>“FACT SHEET: Republic of India Official State Visit to the United States,” White House, June 22, 2023.  <a href="https://bidenwhitehouse.archives.gov/briefing-room/statements-releases/2023/06/22/fact-sheet-republic-of-india-official-state-visit-to-the-united-states">The White House</a> <a href="#fnref:15" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:16">
      <p>Associated Press, “Trump steps up his 2018 tariffs on steel and aluminum,” February 2025.  <a href="https://apnews.com/article/0a91ceaf3aa3c1756c339817d1d58076?">apnews.com</a> <a href="#fnref:16" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:17">
      <p>“Adjusting Imports of Aluminum and Steel into the United States,” Presidential proclamation, June 3, 2025.  <a href="https://www.whitehouse.gov/presidential-actions/2025/06/adjusting-imports-of-aluminum-and-steel-into-the-united-states">The White House</a> <a href="#fnref:17" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:18">
      <p>Reuters, “India considers counter duties on US products, notice to WTO shows,” May 13, 2025.  <a href="https://www.reuters.com/markets/commodities/india-proposes-counter-duties-against-us-notice-wto-shows-2025-05-13">reuters.com</a> <a href="#fnref:18" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:19">
      <p><em>Economic Times</em>, “India proposes retaliatory duties against US on steel, aluminium duties at WTO,” May 2025.  <a href="https://economictimes.indiatimes.com/news/economy/foreign-trade/india-proposes-retaliatory-duties-against-us-on-steel-aluminium-duties-at-wto/articleshow/121120497.cms">economictimes.indiatimes.com</a> <a href="#fnref:19" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:20">
      <p>Reuters, “Trump imposes extra 25% tariff on Indian goods, ties hit new low,” Aug 6, 2025. <a href="https://www.reuters.com/world/india/trump-imposes-extra-25-tariff-indian-goods-ties-hit-new-low-2025-08-06/">reuters.com</a> <a href="#fnref:20" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:21">
      <p>Reuters, “Missed signals, lost deal: How India-US trade talks collapsed”, Aug 6, 2025. <a href="https://www.reuters.com/world/india/missed-signals-lost-deal-how-india-us-trade-talks-collapsed-2025-08-06/">reuters.com</a> <a href="#fnref:21" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Politics &amp; Governance&quot;, &quot;Society &amp; Economy&quot;]" /><category term="politics" /><category term="society" /><category term="economy" /><category term="india" /><category term="united-states" /><summary type="html"><![CDATA[Let us explore the Indo-US relations over the year and where they are headed after recent tariffs imposed by US on India]]></summary></entry><entry><title type="html">Part 1: IEEE 754 Doubles - The Numbers That Lie With A Straight Face</title><link href="https://systemhalted.in/2025/12/04/ieee-754-doubles/" rel="alternate" type="text/html" title="Part 1: IEEE 754 Doubles - The Numbers That Lie With A Straight Face" /><published>2025-12-04T00:00:00+00:00</published><updated>2025-12-04T00:00:00+00:00</updated><id>https://systemhalted.in/2025/12/04/ieee-754-doubles</id><content type="html" xml:base="https://systemhalted.in/2025/12/04/ieee-754-doubles/"><![CDATA[<p><em>This post is part of my <a href="https://systemhalted.in/categories/#cat-series-4-floating-point-without-tears">Floating Point Without Tears</a> series on how Java numbers misbehave and how to live with them.</em></p>

<p>In Java, <code class="language-plaintext highlighter-rouge">double</code> feels like a real number. You write <code class="language-plaintext highlighter-rouge">1.0</code>, the compiler nods, the program runs, and everything looks fine.
Until it does not.</p>

<p>Take this tiny example:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">List</span> <span class="n">values</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
<span class="mi">1</span><span class="n">e16</span><span class="o">,</span> <span class="mf">1.0</span><span class="o">,</span> <span class="mf">1.0</span><span class="o">,</span> <span class="mf">1.0</span><span class="o">,</span> <span class="mf">1.0</span>
<span class="o">);</span>

<span class="kt">double</span> <span class="n">s1</span> <span class="o">=</span> <span class="n">values</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">reduce</span><span class="o">(</span><span class="mf">0.0</span><span class="o">,</span> <span class="nl">Double:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span>
<span class="kt">double</span> <span class="n">s2</span> <span class="o">=</span> <span class="n">values</span><span class="o">.</span><span class="na">parallelStream</span><span class="o">().</span><span class="na">reduce</span><span class="o">(</span><span class="mf">0.0</span><span class="o">,</span> <span class="nl">Double:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s1</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s2</span><span class="o">);</span></code></pre></figure>

<p>You might see</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">1.0E16
1.0000000000000004E16</code></pre></figure>

<p>Same data, same operation, different result.</p>

<p>To understand why, you have to stop thinking of doubles as numbers and start thinking of them as compressed approximations of numbers with strict rules and sharp edges.</p>

<h2 id="doubles-are-just-bit-patterns">Doubles are just bit patterns</h2>

<p>An IEEE 754 double is 64 bits laid out like this:
	1.	One bit for the sign
	2.	Eleven bits for the exponent
	3.	Fifty two bits for the fraction (often called the mantissa or significand)</p>

<p>That layout represents numbers of the form:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(-1)^sign × 1.fraction_bits × 2^(exponent - bias)
</code></pre></div></div>
<p>There is no infinite continuum here. There is a huge but finite set of exactly representable values. Between any two nearby doubles, there is literally nothing.</p>

<p>So when you write:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">x</span> <span class="o">=</span> <span class="mf">0.1</span><span class="o">;</span></code></pre></figure>

<p>Java does its best to find the closest representable double to 0.1. But 0.1 in base 10 is a repeating fraction in base 2, just like 1/3 is repeating in base 10. There is no exact binary representation. The runtime rounds to the nearest representable double and moves on.</p>

<p>If you print it with enough digits, you see the approximation leak through:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="err">”</span><span class="o">%.</span><span class="mi">20</span><span class="n">f</span><span class="o">%</span><span class="n">n</span><span class="err">”</span><span class="o">,</span> <span class="mf">0.1</span><span class="o">);</span></code></pre></figure>

<p>You will get something like:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">0.10000000000000000555</code></pre></figure>

<p>The lie is small, but it is always there.</p>

<h2 id="why-1e16--1--1e16">Why 1e16 + 1 == 1e16</h2>

<p>Back to our friend <code class="language-plaintext highlighter-rouge">1e16</code>.</p>

<p>At that scale, the distance between adjacent representable doubles is larger than 1. Think of the number line as a ladder. Near zero the rungs are very close. Near 1e16 the rungs are far apart. If adding 1 does not reach the next rung, the result rounds back to the same double.</p>

<p>In practice:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="n">e16</span><span class="o">;</span>
<span class="kt">double</span> <span class="n">b</span> <span class="o">=</span> <span class="n">a</span> <span class="o">+</span> <span class="mf">1.0</span><span class="o">;</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span> <span class="o">==</span> <span class="n">b</span><span class="o">);</span> <span class="c1">// true</span></code></pre></figure>

<p>It looks absurd, but it is perfectly legal in IEEE land. You are not adding real numbers. You are adding approximations and rounding the result back into the finite set of doubles.</p>

<p>Now imagine how this interacts with summation order.</p>

<p>Sequential stream:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">s1</span> <span class="o">=</span> <span class="n">values</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">reduce</span><span class="o">(</span><span class="mf">0.0</span><span class="o">,</span> <span class="nl">Double:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span></code></pre></figure>

<p>Evaluation order is effectively:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>((((0 + 1e16) + 1) + 1) + 1) + 1
</code></pre></div></div>

<p>After the first addition you have <code class="language-plaintext highlighter-rouge">1e16</code>. Each subsequent <code class="language-plaintext highlighter-rouge">+ 1</code> is below the resolution of the ladder at that height, so it keeps snapping back to 1e16. End result: <code class="language-plaintext highlighter-rouge">1.0E16</code>.</p>

<p>Parallel stream:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">s2</span> <span class="o">=</span> <span class="n">values</span><span class="o">.</span><span class="na">parallelStream</span><span class="o">().</span><span class="na">reduce</span><span class="o">(</span><span class="mf">0.0</span><span class="o">,</span> <span class="nl">Double:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span></code></pre></figure>

<p>Now the framework is allowed to regroup:
	1.	One thread might sum <code class="language-plaintext highlighter-rouge">1e16 + 1.0</code> to get <code class="language-plaintext highlighter-rouge">1e16</code>.
	2.	Another might sum <code class="language-plaintext highlighter-rouge">1.0 + 1.0 + 1.0</code> to get <code class="language-plaintext highlighter-rouge">3.0</code>.
	3.	Then it adds <code class="language-plaintext highlighter-rouge">1e16 + 3.0</code>.</p>

<p>Depending on the exact rounding behavior, that last addition might actually hit the next rung on the ladder and give:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">1.0000000000000004E16</code></pre></figure>

<p>Same math on paper. Different rounding path in silicon.</p>

<p>You have just met the most important fact about floating point arithmetic:</p>

<p><em>Associativity is broken in practice.</em></p>

<p>Mathematically:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(a + b) + c == a + (b + c)
</code></pre></div></div>

<p>In floating point, <code class="language-plaintext highlighter-rouge">(a + b) + c</code> and <code class="language-plaintext highlighter-rouge">a + (b + c)</code> can differ by a few bits.</p>

<h2 id="equality-with-doubles-is-a-trap">Equality with doubles is a trap</h2>

<p>This is why direct equality checks with doubles are dangerous:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">double</span> <span class="n">x</span> <span class="o">=</span> <span class="mf">0.1</span> <span class="o">+</span> <span class="mf">0.2</span><span class="o">;</span>
<span class="kt">double</span> <span class="n">y</span> <span class="o">=</span> <span class="mf">0.3</span><span class="o">;</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x</span> <span class="o">==</span> <span class="n">y</span><span class="o">);</span> <span class="c1">// often false</span></code></pre></figure>

<p>The left side is “nearest double to <code class="language-plaintext highlighter-rouge">(0.1 + 0.2)</code> after two rounding steps”.
The right side is “nearest double to <code class="language-plaintext highlighter-rouge">0.3</code> after one rounding step”.</p>

<p>You are really comparing two approximations that reached the neighborhood of <code class="language-plaintext highlighter-rouge">0.3</code> via different routes. The neighborhood is small, but the routes do not always end at the same exact bit pattern.</p>

<p>The usual advice is:
	1.	Compare with an epsilon, for example: <code class="language-plaintext highlighter-rouge">Math.abs(x - y) &lt; 1e-9</code>, tuned to your domain.
	2.	Or avoid equality checks altogether and reason in ranges, ratios or integers when possible.</p>

<h2 id="why-parallel-reduce-cares-about-associativity">Why parallel reduce cares about associativity</h2>
<p>The Java Streams API assumes your reduction operator is associative and has a proper identity. For doubles, <code class="language-plaintext highlighter-rouge">Double::sum</code> with <code class="language-plaintext highlighter-rouge">0.0</code> definitely satisfies the mathematical definition.</p>

<p>The parallel stream uses this to split the work: 
	1.	Compute partial sums in parallel. 
	2.	Combine partials in arbitrary order.</p>

<p>From basic math, this is fine.</p>

<p>From IEEE 754 reality, it means you get “approximately the same result most of the time, with possible tiny differences depending on grouping, thread scheduling and platform”.</p>

<p>In most business code that is acceptable. In some domains it is not.</p>

<p>If you need reproducible sums independent of order, you have options.</p>

<p>Use BigDecimal:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">BigDecimal</span> <span class="n">sum</span> <span class="o">=</span> <span class="n">values</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">BigDecimal:</span><span class="o">:</span><span class="n">valueOf</span><span class="o">)</span>
<span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">ZERO</span><span class="o">,</span> <span class="nl">BigDecimal:</span><span class="o">:</span><span class="n">add</span><span class="o">);</span></code></pre></figure>

<p>This gives you honest <code class="language-plaintext highlighter-rouge">base 10</code> arithmetic at the cost of performance.</p>

<p>Or implement a compensated summation like Kahan’s algorithm, possibly in a custom Collector, to reduce error accumulation.</p>

<p>Or structure your algorithm so you sum small magnitude values first and large values later. That makes the ladder problem less vicious.</p>

<p>None of these fix floating point. They just manage its tradeoffs more consciously.</p>

<h2 id="doubles-are-not-broken-they-are-engineered">Doubles are not broken, they are Engineered</h2>
<p>It is tempting to call all this a bug. It is not.</p>

<p>The IEEE 754 design is a compromise between range, precision, performance and hardware simplicity. Doubles give you:</p>

<ol>
  <li>A huge dynamic range, from tiny numbers around 1e-308 up to around 1e308.</li>
  <li>About 15 to 17 decimal digits of precision.</li>
  <li>Fast operations supported directly by the CPU.</li>
</ol>

<p>The cost is:</p>

<ol>
  <li>Not all decimal fractions are exact.</li>
  <li>Rounding happens all the time.</li>
  <li>Algebraic laws like associativity and distributivity become “mostly true, but not guaranteed”.</li>
</ol>

<p>Once you internalize that, your mental model shifts.</p>

<p>You stop thinking “the computer is bad at math”.</p>

<p>You start thinking “the computer is doing carefully specified approximate math on a discrete set of representable values, and I need to respect that contract”.</p>

<h2 id="where-this-matters-in-real-systems">Where this matters in real systems</h2>

<p>In an auto loan system, this can show up in subtle ways.</p>

<p>You might compute a customer’s total interest by:</p>

<ol>
  <li>Summing daily interest over the life of the loan.</li>
  <li>Summing per period interest.</li>
  <li>Using a closed form formula.</li>
</ol>

<p>All three approaches should be equivalent mathematically, but can drift by a few cents because of rounding and summation order. Then you run in parallel, or refactor a loop into a stream, or change the order in which fees and interest are applied, and suddenly some accounts are off by a cent.</p>

<p>Nothing catastrophic, but enough to fail reconciliation tests.</p>

<p>Once you remember that doubles are approximations and order matters, you design differently:</p>

<ol>
  <li>Use BigDecimal for money.</li>
  <li>Keep calculations stable and deterministic.</li>
  <li>Avoid relying on bit perfect equality when the underlying math is continuous.</li>
</ol>

<p>And when your <code class="language-plaintext highlighter-rouge">parallelStream()</code> sum differs from your sequential sum in the thirteenth decimal place, you do not panic. You smile a little and think:</p>

<blockquote>
  <p>Floating point kept its side of the bargain.
I just finally started reading the fine print.</p>
</blockquote>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Computer Science&quot;, &quot;Software Engineering&quot;, &quot;Technology&quot;, &quot;Series 4 - Floating Point Without Tears&quot;]" /><category term="computer-science" /><category term="software" /><category term="technology" /><summary type="html"><![CDATA[In Java, double feels like a real number. You write 1.0, the compiler nods, the program runs, and everything looks fine. Until it doesn’t.]]></summary></entry><entry><title type="html">Part 2: Associativity, Identity, and Folding - Why Your reduce Keeps Biting You</title><link href="https://systemhalted.in/2025/11/28/associativity-identity-folding/" rel="alternate" type="text/html" title="Part 2: Associativity, Identity, and Folding - Why Your reduce Keeps Biting You" /><published>2025-11-28T00:00:00+00:00</published><updated>2025-11-28T00:00:00+00:00</updated><id>https://systemhalted.in/2025/11/28/associativity-identity-folding</id><content type="html" xml:base="https://systemhalted.in/2025/11/28/associativity-identity-folding/"><![CDATA[<p><em>This post is part of my <a href="https://systemhalted.in/categories/#cat-series-4-floating-point-without-tears">Floating Point Without Tears</a> series on how Java numbers misbehave and how to live with them.</em></p>

<p>If you write Java today, you probably use <code class="language-plaintext highlighter-rouge">Stream.reduce()</code> or something like it.</p>

<p>Sometimes it feels magical.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">int</span> <span class="n">sum</span> <span class="o">=</span> <span class="n">nums</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">reduce</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="nl">Integer:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span></code></pre></figure>

<p>Other times it feels cursed.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">int</span> <span class="n">weird</span> <span class="o">=</span> <span class="n">nums</span><span class="o">.</span><span class="na">parallelStream</span><span class="o">().</span><span class="na">reduce</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">a</span> <span class="o">-</span> <span class="n">b</span><span class="o">);</span></code></pre></figure>

<p>Same method, very different behavior.</p>

<p>Underneath these differences are three simple ideas:</p>

<ol>
  <li>Associativity</li>
  <li>Identity</li>
  <li>Folding (reducing a collection into one value)</li>
</ol>

<p>You do not need category theory to use them. You just need to recognize when your operation behaves like <code class="language-plaintext highlighter-rouge">+</code> (associative) and when it behaves like <code class="language-plaintext highlighter-rouge">-</code> (non-associative).</p>

<p>This post walks through concrete Java examples you are likely to hit in normal work and uses them to build intuition for those three words.</p>

<p>It also gives you an understanding of when using parallelStreams is probably safe.</p>

<hr />

<h2 id="folding-what-reduce-really-does">Folding: what <code class="language-plaintext highlighter-rouge">reduce</code> really does</h2>

<p>A fold or reduce is just this:</p>

<p>Start with some value.<br />
Combine it with each element in a collection.<br />
End up with a single result.</p>

<p>For example, summing numbers by hand:</p>

<ol>
  <li>Start with <code class="language-plaintext highlighter-rouge">0</code></li>
  <li>Add the first number</li>
  <li>Add the second number</li>
  <li>Keep going until you have one, final, number left</li>
</ol>

<p>In Java Streams:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">int</span> <span class="n">sum</span> <span class="o">=</span> <span class="n">nums</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="o">(</span><span class="n">acc</span><span class="o">,</span> <span class="n">x</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">+</span> <span class="n">x</span><span class="o">);</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">reduce</code> needs two things from you.</p>

<ol>
  <li>An initial value, sometimes called the identity.</li>
  <li>A function that combines the accumulator and the next element.</li>
</ol>

<p>Everything else in this post is about choosing those two well.</p>

<hr />

<h2 id="example-1-integer-sum-the-happy-path">Example 1: Integer sum, the happy path</h2>

<p>Summing integers is the canonical example of reduce working perfectly, even in parallel.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>

<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">nums</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">);</span>

<span class="c1">// Sequential</span>
<span class="kt">int</span> <span class="n">sum1</span> <span class="o">=</span> <span class="n">nums</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="nl">Integer:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span>

<span class="c1">// Parallel</span>
<span class="kt">int</span> <span class="n">sum2</span> <span class="o">=</span> <span class="n">nums</span><span class="o">.</span><span class="na">parallelStream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="nl">Integer:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">sum1</span><span class="o">);</span> <span class="c1">// 15</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">sum2</span><span class="o">);</span> <span class="c1">// 15</span></code></pre></figure>

<p>Why does this behave?</p>

<p>The short version:</p>

<ol>
  <li>Addition is associative.</li>
  <li>Zero is the identity for addition.</li>
</ol>

<p>Let us unpack that.</p>

<p>Associativity means you can change parentheses without changing the result:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="o">(</span><span class="mi">1</span> <span class="o">+</span> <span class="mi">2</span><span class="o">)</span> <span class="o">+</span> <span class="mi">3</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="o">(</span><span class="mi">2</span> <span class="o">+</span> <span class="mi">3</span><span class="o">);</span>
<span class="c1">// a == b</span></code></pre></figure>

<p>Zero is the identity for <code class="language-plaintext highlighter-rouge">+</code> because it does nothing:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="mi">0</span> <span class="o">+</span> <span class="n">x</span> <span class="o">==</span> <span class="n">x</span><span class="o">;</span>
<span class="n">x</span> <span class="o">+</span> <span class="mi">0</span> <span class="o">==</span> <span class="n">x</span><span class="o">;</span></code></pre></figure>

<p>When both of these are true, the stream framework is free to:</p>

<ol>
  <li>Split the list into chunks.</li>
  <li>Sum each chunk separately.</li>
  <li>Add the partial sums in any grouping it likes.</li>
</ol>

<p>Sequential or parallel, left grouped or right grouped, the result is always the same.</p>

<hr />

<h2 id="example-2-subtraction-with-reduce-same-shape-weird-result">Example 2: Subtraction with reduce, same shape, weird result</h2>

<p>Change one thing. Use subtraction instead of addition.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">nums</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">);</span>

<span class="c1">// Sequential</span>
<span class="kt">int</span> <span class="n">result1</span> <span class="o">=</span> <span class="n">nums</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">a</span> <span class="o">-</span> <span class="n">b</span><span class="o">);</span>

<span class="c1">// Parallel</span>
<span class="kt">int</span> <span class="n">result2</span> <span class="o">=</span> <span class="n">nums</span><span class="o">.</span><span class="na">parallelStream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">a</span> <span class="o">-</span> <span class="n">b</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">result1</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">result2</span><span class="o">);</span></code></pre></figure>

<p>On most machines you will see different numbers printed.</p>

<p>Subtraction is not associative:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="o">(</span><span class="mi">1</span> <span class="o">-</span> <span class="mi">2</span><span class="o">)</span> <span class="o">-</span> <span class="mi">3</span><span class="o">;</span>  <span class="c1">// -4</span>
<span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">-</span> <span class="o">(</span><span class="mi">2</span> <span class="o">-</span> <span class="mi">3</span><span class="o">);</span>  <span class="c1">//  2</span>
<span class="c1">// a != b</span></code></pre></figure>

<p>Once the operation is not associative, the way the stream groups operations changes the result.</p>

<p>Sequential <code class="language-plaintext highlighter-rouge">reduce</code> tends to behave as if it were:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="o">((((</span><span class="mi">0</span> <span class="o">-</span> <span class="mi">1</span><span class="o">)</span> <span class="o">-</span> <span class="mi">2</span><span class="o">)</span> <span class="o">-</span> <span class="mi">3</span><span class="o">)</span> <span class="o">-</span> <span class="mi">4</span><span class="o">)</span> <span class="o">-</span> <span class="mi">5</span><span class="o">;</span></code></pre></figure>

<p>Parallel <code class="language-plaintext highlighter-rouge">reduce</code> might do something more like:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="o">(</span><span class="mi">0</span> <span class="o">-</span> <span class="o">(</span><span class="mi">1</span> <span class="o">-</span> <span class="mi">2</span><span class="o">))</span> <span class="o">-</span> <span class="o">(</span><span class="mi">3</span> <span class="o">-</span> <span class="o">(</span><span class="mi">4</span> <span class="o">-</span> <span class="mi">5</span><span class="o">));</span></code></pre></figure>

<p>Same numbers, same lambda, same <code class="language-plaintext highlighter-rouge">reduce</code> method. Different grouping, different answer.</p>

<p>The day to day lesson:</p>

<p>When you pass a lambda as the combiner to <code class="language-plaintext highlighter-rouge">reduce</code>, ask yourself whether it behaves more like <code class="language-plaintext highlighter-rouge">+</code> or more like <code class="language-plaintext highlighter-rouge">-</code>. If it is like <code class="language-plaintext highlighter-rouge">-</code>, parallel reduction will be surprising.</p>

<hr />

<h2 id="example-3-wrong-identity-value-leads-to-skewed-output">Example 3: Wrong identity value leads to skewed output</h2>

<p>Even if your operation is associative, choosing the wrong initial value quietly skews your result.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">nums</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">);</span>

<span class="c1">// Wrong identity: 1 instead of 0</span>
<span class="kt">int</span> <span class="n">sum</span> <span class="o">=</span> <span class="n">nums</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="nl">Integer:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">sum</span><span class="o">);</span>  <span class="c1">// 7, but the real sum is 6</span></code></pre></figure>

<p>Sequentially this works out as:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="o">(((</span><span class="mi">1</span> <span class="o">+</span> <span class="mi">1</span><span class="o">)</span> <span class="o">+</span> <span class="mi">2</span><span class="o">)</span> <span class="o">+</span> <span class="mi">3</span><span class="o">)</span> <span class="o">==</span> <span class="mi">7</span><span class="o">;</span></code></pre></figure>

<p>In parallel, the identity can be applied to every chunk, so the effect is even stranger.</p>

<p>The identity should be a ==do nothing== value for the operation:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>combine(identity, x) == x  
combine(x, identity) == x
</code></pre></div></div>

<p>For addition, the identity is <code class="language-plaintext highlighter-rouge">0</code>. For multiplication, it is <code class="language-plaintext highlighter-rouge">1</code>. For string concatenation, it is <code class="language-plaintext highlighter-rouge">""</code>.</p>

<p>The day to day rule:</p>

<p>If you call <code class="language-plaintext highlighter-rouge">reduce(identity, combiner)</code>, check those two equations in your head for the operation you are using.</p>

<hr />

<h2 id="example-4-string-concatenation-grouping-versus-ordering">Example 4: String concatenation, grouping versus ordering</h2>

<p>String concatenation is another operation you use constantly.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">words</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="s">"c"</span><span class="o">);</span>

<span class="nc">String</span> <span class="n">s1</span> <span class="o">=</span> <span class="n">words</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="s">""</span><span class="o">,</span> <span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="o">);</span>

<span class="nc">String</span> <span class="n">s2</span> <span class="o">=</span> <span class="n">words</span><span class="o">.</span><span class="na">parallelStream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="s">""</span><span class="o">,</span> <span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s1</span><span class="o">);</span>  <span class="c1">// usually "abc"</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s2</span><span class="o">);</span>  <span class="c1">// also usually "abc"</span></code></pre></figure>

<p>Concatenation is associative:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="o">(</span><span class="s">"a"</span> <span class="o">+</span> <span class="s">"b"</span><span class="o">)</span> <span class="o">+</span> <span class="s">"c"</span> <span class="o">==</span> <span class="s">"a"</span> <span class="o">+</span> <span class="o">(</span><span class="s">"b"</span> <span class="o">+</span> <span class="s">"c"</span><span class="o">);</span></code></pre></figure>

<p>and the empty string is the identity:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="s">""</span> <span class="o">+</span> <span class="n">s</span> <span class="o">==</span> <span class="n">s</span><span class="o">;</span>
<span class="n">s</span> <span class="o">+</span> <span class="s">""</span> <span class="o">==</span> <span class="n">s</span><span class="o">;</span></code></pre></figure>

<p>So grouping does not matter.</p>

<p>Order still matters though:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="s">"a"</span> <span class="o">+</span> <span class="s">"b"</span> <span class="o">!=</span> <span class="s">"b"</span> <span class="o">+</span> <span class="s">"a"</span><span class="o">;</span></code></pre></figure>

<p>Concatenation is associative but not commutative.</p>

<p>Streams will respect encounter order unless you explicitly undo that. So in practice the result is stable, but the moment you introduce reordering operations you lose that guarantee.</p>

<p>The message here:</p>

<ol>
  <li>Associativity protects you from grouping changes.</li>
  <li>Commutativity protects you from ordering changes.</li>
  <li>Most real world operations are not commutative, so do not assume order is irrelevant unless you are sure.</li>
</ol>

<hr />

<h2 id="example-5-floating-point-sums-almost-associative">Example 5: Floating point sums, almost associative</h2>

<p>Now a more subtle one: floating point numbers.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Double</span><span class="o">&gt;</span> <span class="n">values</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
        <span class="mi">1</span><span class="n">e16</span><span class="o">,</span> <span class="mf">1.0</span><span class="o">,</span> <span class="mf">1.0</span><span class="o">,</span> <span class="mf">1.0</span><span class="o">,</span> <span class="mf">1.0</span>
<span class="o">);</span>

<span class="kt">double</span> <span class="n">s1</span> <span class="o">=</span> <span class="n">values</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="mf">0.0</span><span class="o">,</span> <span class="nl">Double:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span>

<span class="kt">double</span> <span class="n">s2</span> <span class="o">=</span> <span class="n">values</span><span class="o">.</span><span class="na">parallelStream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="mf">0.0</span><span class="o">,</span> <span class="nl">Double:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s1</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s2</span><span class="o">);</span></code></pre></figure>

<p>In real math, addition is associative. In IEEE double precision, it is only approximately so.</p>

<p>When you add a very large number and a very small number, the small number can get rounded away. Different groupings of additions can lose or keep different tiny pieces of information.</p>

<p>Practically you see things like:</p>

<ol>
  <li>Slightly different sums when you use parallel streams.</li>
  <li>Results like <code class="language-plaintext highlighter-rouge">999.9999999997</code> instead of <code class="language-plaintext highlighter-rouge">1000.0</code>.</li>
</ol>

<p>For many applications this does not matter. For financial calculations it absolutely does, which is one reason developers reach for <code class="language-plaintext highlighter-rouge">BigDecimal</code> or integer cents rather than <code class="language-plaintext highlighter-rouge">double</code>.</p>

<p>The take away:</p>

<p>Floating point addition mostly behaves like a good associative operation, but not perfectly. Do not expect bit identical results when you regroup or parallelise floating point reductions.</p>

<hr />

<h2 id="example-6-merging-counts-in-maps-a-realistic-associative-operation">Example 6: Merging counts in maps, a realistic associative operation</h2>

<p>Imagine you have words and you want to count how many times each appears.</p>

<blockquote>
  <p>A Note on Engineering vs. Math:
While our Counts merge logic is mathematically associative (satisfying the requirements for reduce), using reduce for mutable objects like Maps is inefficient. It creates a copy of the map at every step.
In Java, when you need to combine mutable containers (like Lists or Maps) in parallel, you should use the Mutable Reduction pattern via Stream.collect.</p>
</blockquote>

<p>First, a small helper type:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.HashMap</span><span class="o">;</span>

<span class="kd">record</span> <span class="nf">Counts</span><span class="o">(</span><span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">counts</span><span class="o">)</span> <span class="o">{</span>

    <span class="kd">static</span> <span class="nc">Counts</span> <span class="nf">empty</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">Counts</span><span class="o">(</span><span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;());</span>
    <span class="o">}</span>

    <span class="nc">Counts</span> <span class="nf">addWord</span><span class="o">(</span><span class="nc">String</span> <span class="n">word</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">copy</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;(</span><span class="n">counts</span><span class="o">);</span>
        <span class="n">copy</span><span class="o">.</span><span class="na">merge</span><span class="o">(</span><span class="n">word</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="nl">Integer:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">Counts</span><span class="o">(</span><span class="n">copy</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nc">Counts</span> <span class="nf">combine</span><span class="o">(</span><span class="nc">Counts</span> <span class="n">other</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">copy</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;(</span><span class="n">counts</span><span class="o">);</span>
        <span class="n">other</span><span class="o">.</span><span class="na">counts</span><span class="o">.</span><span class="na">forEach</span><span class="o">((</span><span class="n">w</span><span class="o">,</span> <span class="n">c</span><span class="o">)</span> <span class="o">-&gt;</span>
                <span class="n">copy</span><span class="o">.</span><span class="na">merge</span><span class="o">(</span><span class="n">w</span><span class="o">,</span> <span class="n">c</span><span class="o">,</span> <span class="nl">Integer:</span><span class="o">:</span><span class="n">sum</span><span class="o">));</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">Counts</span><span class="o">(</span><span class="n">copy</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Now we can use <code class="language-plaintext highlighter-rouge">reduce</code> with this.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>

<span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">words</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="s">"a"</span><span class="o">,</span> <span class="s">"c"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="s">"a"</span><span class="o">);</span>

<span class="nc">Counts</span> <span class="n">result</span> <span class="o">=</span> <span class="n">words</span><span class="o">.</span><span class="na">parallelStream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">w</span> <span class="o">-&gt;</span> <span class="nc">Counts</span><span class="o">.</span><span class="na">empty</span><span class="o">().</span><span class="na">addWord</span><span class="o">(</span><span class="n">w</span><span class="o">))</span>  <span class="c1">// each word → its own count map</span>
        <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="nc">Counts</span><span class="o">.</span><span class="na">empty</span><span class="o">(),</span> <span class="nl">Counts:</span><span class="o">:</span><span class="n">combine</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">counts</span><span class="o">());</span>
<span class="c1">// {a=3, b=2, c=1}</span></code></pre></figure>

<p>Why this works well:</p>

<ol>
  <li>
    <p><code class="language-plaintext highlighter-rouge">combine</code> is associative.<br />
Combining <code class="language-plaintext highlighter-rouge">(A combine B) combine C</code> gives the same counts as <code class="language-plaintext highlighter-rouge">A combine (B combine C)</code>.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">Counts.empty()</code> is an identity.<br />
Combining <code class="language-plaintext highlighter-rouge">empty</code> with any <code class="language-plaintext highlighter-rouge">Counts</code> yields that <code class="language-plaintext highlighter-rouge">Counts</code> back.</p>
  </li>
</ol>

<p>That is exactly what a framework like <code class="language-plaintext highlighter-rouge">reduce</code> wants: an associative operation with an identity element.</p>

<p>This is the same pattern you see in log aggregation, metrics systems, and analytics jobs. It is not an abstract trick, it is a workhorse.</p>

<hr />

<h2 id="a-mental-checklist-for-reduce">A mental checklist for <code class="language-plaintext highlighter-rouge">reduce</code></h2>

<p>Whenever you write something like:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="no">X</span> <span class="n">result</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="n">identity</span><span class="o">,</span> <span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">combine</span><span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">));</span></code></pre></figure>

<p>you can run a short mental checklist.</p>

<ol>
  <li>
    <p>If I ignore floating point quirks, is <code class="language-plaintext highlighter-rouge">combine</code> associative?</p>

    <p>combine(combine(a, b), c) == combine(a, combine(b, c))</p>
  </li>
  <li>
    <p>Did I choose a true identity value?<br />
Does <code class="language-plaintext highlighter-rouge">combine(identity, x)</code> give <code class="language-plaintext highlighter-rouge">x</code> and does <code class="language-plaintext highlighter-rouge">combine(x, identity)</code> give <code class="language-plaintext highlighter-rouge">x</code>?</p>
  </li>
  <li>
    <p>Do I care about order?<br />
If I do, am I doing anything that changes encounter order?</p>
  </li>
</ol>

<p>If the answer to 1 and 2 is yes, <code class="language-plaintext highlighter-rouge">reduce</code> is usually safe, even in parallel.<br />
If 1 or 2 fails, you either accept the weirdness, or you avoid <code class="language-plaintext highlighter-rouge">parallelStream</code> and regrouping.</p>

<hr />

<p>Every <code class="language-plaintext highlighter-rouge">reduce</code> you write is a small promise.</p>

<p>You are telling the runtime:</p>

<ol>
  <li>Here is how to combine two partial results.</li>
  <li>Here is the neutral element that represents “no information yet”.</li>
</ol>

<p>If that promise matches reality, you get predictable, parallel friendly code.<br />
If it does not, you get ghosts: bugs that only appear under load, only with parallel streams, only with certain sizes of input.</p>

<p>Thinking in terms of associativity and identity is not about being fancy. It is just a way to make that promise explicit in your own head, instead of leaving it as “whatever this lambda does”.</p>

<p>Once you start looking at your own code through that lens, you will find that a surprising amount of what you do every day is already algebra. You were just calling it “business logic”.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Computer Science&quot;, &quot;Software Engineering&quot;, &quot;Technology&quot;, &quot;Series 4 - Floating Point Without Tears&quot;]" /><category term="computer-science" /><category term="software" /><category term="technology" /><summary type="html"><![CDATA[A practical look at associativity, identity, and folding in Java Streams, and why some reduce calls behave nicely while others explode in surprising ways.]]></summary></entry><entry><title type="html">Either This Or That - Why I Wanted Disjunctive Types In Java</title><link href="https://systemhalted.in/2025/11/25/disjunctive-types/" rel="alternate" type="text/html" title="Either This Or That - Why I Wanted Disjunctive Types In Java" /><published>2025-11-25T00:00:00+00:00</published><updated>2025-11-25T00:00:00+00:00</updated><id>https://systemhalted.in/2025/11/25/disjunctive-types</id><content type="html" xml:base="https://systemhalted.in/2025/11/25/disjunctive-types/"><![CDATA[<p>A few years ago at work, while hacking on a parser and analyser, I realised I needed something my language did not give me: disjunctive types.</p>

<p>Java did not have it.<br />
Kotlin did not have it.<br />
Scala did, in the form of <code class="language-plaintext highlighter-rouge">Either</code>.</p>

<p>I found <code class="language-plaintext highlighter-rouge">Either</code> while searching through Scala docs and it felt exactly right for the problem in front of me. Simple idea, huge leverage. That sent me down the path of thinking about what disjunctive types really are and why I want them in my everyday Java code.</p>

<hr />
<p><strong>TL;DR</strong></p>

<p>You can treat “either this or that” as a <em>first-class</em> thing in Java today:</p>

<ul>
  <li>For <strong>“value or error”</strong> results, define a small <code class="language-plaintext highlighter-rouge">Result&lt;E, T&gt;</code> type (a home-grown <code class="language-plaintext highlighter-rouge">Either</code>):
    <ul>
      <li>See section <strong>“Making The Disjunction Explicit: A <code class="language-plaintext highlighter-rouge">Result</code> Type”</strong>.</li>
      <li>Change methods like <code class="language-plaintext highlighter-rouge">Ast parse(String)</code> or <code class="language-plaintext highlighter-rouge">Optional&lt;Ast&gt; parse(...)</code> into<br />
<code class="language-plaintext highlighter-rouge">Result&lt;ParseError, Ast&gt; parse(String input)</code>.</li>
      <li>Callers are now <em>forced</em> by the compiler to handle both success and failure.</li>
    </ul>
  </li>
  <li>For <strong>ASTs and node kinds</strong>, use <strong>sealed hierarchies</strong> as N-way disjunctive types:
    <ul>
      <li>See section <strong>“Disjunctive AST Nodes: An N-Way Either”</strong>.</li>
      <li>Model your tree as:<br />
<code class="language-plaintext highlighter-rouge">sealed interface AstNode permits PackageNode, FunctionNode, StoredProcNode, ...</code></li>
      <li>Traversals return <code class="language-plaintext highlighter-rouge">AstNode</code> and pattern matching becomes exhaustive and honest.</li>
    </ul>
  </li>
  <li>Use the tools where they fit:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> → “T or nothing” (absence is the <em>only</em> alternative).</li>
      <li><code class="language-plaintext highlighter-rouge">Result&lt;E, T&gt;</code> / <code class="language-plaintext highlighter-rouge">Either&lt;L, R&gt;</code> → “this meaningful thing or that meaningful thing”.</li>
      <li>Sealed hierarchies → “one of these shapes, and the compiler knows all of them”.</li>
    </ul>
  </li>
</ul>

<p>If a method’s real contract is “either this or that”, this post shows you exactly how to:</p>
<ol>
  <li>Encode that as a <code class="language-plaintext highlighter-rouge">Result&lt;E, T&gt;</code> (for outcomes), and</li>
  <li>Encode it as a sealed interface hierarchy (for AST/node shapes),</li>
</ol>

<p>so the type system finally tells the same story you do in your head.</p>

<hr />

<h2 id="where-my-idea-of-disjunctive-types-comes-from">Where My Idea Of Disjunctive Types Comes From</h2>

<p>My mental model of disjunctive types comes from logic.</p>

<p>First, think about ordinary disjunction, the familiar logical OR. A disjunction <code class="language-plaintext highlighter-rouge">A ∨ B</code> is true whenever at least one of its arguments is true and both are not simultaneously false.</p>

<p>You can write the truth table like this:</p>

<table>
  <thead>
    <tr>
      <th>A</th>
      <th>B</th>
      <th>A ∨ B</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>0</td>
      <td>0</td>
    </tr>
    <tr>
      <td>0</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr>
      <td>1</td>
      <td>0</td>
      <td>1</td>
    </tr>
    <tr>
      <td>1</td>
      <td>1</td>
      <td>1</td>
    </tr>
  </tbody>
</table>

<p>This version is often called inclusive disjunction, because the case where both <code class="language-plaintext highlighter-rouge">A</code> and <code class="language-plaintext highlighter-rouge">B</code> are true still counts as true.</p>

<p>There is another flavour: exclusive disjunction. Here the result is true only when the two arguments differ. In boolean logic this is the XOR gate, usually written as <code class="language-plaintext highlighter-rouge">A ⊕ B</code>.</p>

<table>
  <thead>
    <tr>
      <th>A</th>
      <th>B</th>
      <th>A ⊕ B</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>0</td>
      <td>0</td>
    </tr>
    <tr>
      <td>0</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr>
      <td>1</td>
      <td>0</td>
      <td>1</td>
    </tr>
    <tr>
      <td>1</td>
      <td>1</td>
      <td>0</td>
    </tr>
  </tbody>
</table>

<p>This second table matches how I think about disjunctive types in programming languages. In plain English it is the phrase “either this or that”. A value of such a type holds one variant or the other, but not both at the same time.</p>

<hr />

<h2 id="conjunctive-types-and-their-limits">Conjunctive Types And Their Limits</h2>

<p>Most of the time, existing data structures are enough.</p>

<p>If I want to group several fields together, I can define a record, a data class, or a POJO. These are conjunctive types: they say “you get this and that and that”.</p>

<p>For example, if I want a result that always includes both a status and a payload, a simple class like this works fine:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">ResultWithStatus</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Status</span> <span class="n">status</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Payload</span> <span class="n">payload</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">ResultWithStatus</span><span class="o">(</span><span class="nc">Status</span> <span class="n">status</span><span class="o">,</span> <span class="nc">Payload</span> <span class="n">payload</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">status</span> <span class="o">=</span> <span class="n">status</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">payload</span> <span class="o">=</span> <span class="n">payload</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">Status</span> <span class="nf">status</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">status</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">Payload</span> <span class="nf">payload</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">payload</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Here the idea really is “status and payload together”, so a conjunction feels natural.</p>

<p>There are situations where this does not reflect the intent.</p>

<p>Sometimes a method should return one of two different shapes of data. A very common example is a method that either returns a successful result or an error description.</p>

<p>In that case a pure conjunction feels awkward. I can create a wrapper object that has both fields and then abuse <code class="language-plaintext highlighter-rouge">null</code> for whichever field is absent at the moment:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">BadResult</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Payload</span> <span class="n">payload</span><span class="o">;</span>  <span class="c1">// null when there is an error</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Error</span> <span class="n">error</span><span class="o">;</span>      <span class="c1">// null when there is a payload</span>

    <span class="kd">public</span> <span class="nf">BadResult</span><span class="o">(</span><span class="nc">Payload</span> <span class="n">payload</span><span class="o">,</span> <span class="nc">Error</span> <span class="n">error</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">payload</span> <span class="o">=</span> <span class="n">payload</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">error</span> <span class="o">=</span> <span class="n">error</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">Payload</span> <span class="nf">payload</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">payload</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">Error</span> <span class="nf">error</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">error</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>The type says “payload and error”, but the runtime behaviour says “either payload or error”. The compiler cannot help here. Nothing stops me from building a value that has both fields non null or both null.</p>

<p>What I really want is a type that expresses the “either this or that” constraint directly.</p>

<hr />

<h2 id="enter-disjunctive-types">Enter Disjunctive Types</h2>

<p>This is where disjunctive types become interesting. A typical example in functional languages is something like:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">sealed</span> <span class="n">trait</span> <span class="nc">Either</span><span class="o">[+</span><span class="no">A</span><span class="o">,</span> <span class="o">+</span><span class="no">B</span><span class="o">]</span>
<span class="kd">final</span> <span class="k">case</span> <span class="kd">class</span> <span class="nc">Left</span><span class="o">[</span><span class="no">A</span><span class="o">](</span><span class="nl">value:</span> <span class="no">A</span><span class="o">)</span> <span class="kd">extends</span> <span class="nc">Either</span><span class="o">[</span><span class="no">A</span><span class="o">,</span> <span class="nc">Nothing</span><span class="o">]</span>
<span class="kd">final</span> <span class="k">case</span> <span class="kd">class</span> <span class="nc">Right</span><span class="o">[</span><span class="no">B</span><span class="o">](</span><span class="nl">value:</span> <span class="no">B</span><span class="o">)</span> <span class="kd">extends</span> <span class="nc">Either</span><span class="o">[</span><span class="nc">Nothing</span><span class="o">,</span> <span class="no">B</span><span class="o">]</span></code></pre></figure>

<p>A value of type <code class="language-plaintext highlighter-rouge">Either[A, B]</code> is always either a <code class="language-plaintext highlighter-rouge">Left[A]</code> or a <code class="language-plaintext highlighter-rouge">Right[B]</code>, never both at once. That mirrors the exclusive disjunction table from earlier.</p>

<p>In a language like Scala, returning</p>

<figure class="highlight"><pre><code class="language-scala" data-lang="scala">    <span class="k">def</span> <span class="nf">parse</span><span class="o">(</span><span class="n">input</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span><span class="k">:</span> <span class="kt">Either</span><span class="o">[</span><span class="kt">ParseError</span>, <span class="kt">Ast</span><span class="o">]</span></code></pre></figure>

<p>is completely natural. Callers are forced to handle both cases. Pattern matching makes the intent very clear:</p>

<figure class="highlight"><pre><code class="language-scala" data-lang="scala">    <span class="nf">parse</span><span class="o">(</span><span class="n">source</span><span class="o">)</span> <span class="k">match</span> <span class="o">{</span>
      <span class="k">case</span> <span class="nc">Left</span><span class="o">(</span><span class="n">error</span><span class="o">)</span>  <span class="k">=&gt;</span> <span class="nf">log</span><span class="o">(</span><span class="n">error</span><span class="o">)</span>
      <span class="k">case</span> <span class="nc">Right</span><span class="o">(</span><span class="n">ast</span><span class="o">)</span>   <span class="k">=&gt;</span> <span class="nf">evaluate</span><span class="o">(</span><span class="n">ast</span><span class="o">)</span>
    <span class="o">}</span></code></pre></figure>

<p>No nulls. No dummy wrapper objects. The type itself documents the contract of the method.</p>

<hr />

<h2 id="what-about-optional-in-java">What About <code class="language-plaintext highlighter-rouge">Optional</code> In Java?</h2>

<p>As soon as you talk about “maybe this, maybe that” in Java, <code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> shows up in the conversation.</p>

<p><code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> is useful, but it solves a weaker problem.</p>

<p>In type-theory style shorthand you can think of it as:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Optional&lt;T&gt; ≈ Either&lt;Unit, T&gt;
</code></pre></div></div>

<p>which reads as “either there is nothing interesting here, or there is a <code class="language-plaintext highlighter-rouge">T</code>”. The second branch of the disjunction is just absence.</p>

<p>The set of all possible values for <code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> is</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>`Optional⟨T⟩ = {None} ∪ {Some(t) | t ∈ T}` 
</code></pre></div></div>

<p>That makes <code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> perfect for things like map lookups:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">    <span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Value</span><span class="o">&gt;</span> <span class="n">maybeValue</span> <span class="o">=</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">map</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">));</span></code></pre></figure>

<p>Sometimes the key exists, sometimes it does not. The “other side” of the result is simply “no value”.</p>

<p>Now look at the parser example again. If I write:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">    <span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Ast</span><span class="o">&gt;</span> <span class="nf">parse</span><span class="o">(</span><span class="nc">String</span> <span class="n">input</span><span class="o">);</span></code></pre></figure>

<p>then an empty <code class="language-plaintext highlighter-rouge">Optional</code> only tells me that parsing failed. It does not tell me why it failed. All the interesting information about the error has to live somewhere else: logs, exceptions, some side channel.</p>

<p>This is not what I want for the <code class="language-plaintext highlighter-rouge">parse</code> method. The contract of the method is not “Ast or nothing”. The real contract is “either a <code class="language-plaintext highlighter-rouge">ParseError</code>, or an <code class="language-plaintext highlighter-rouge">Ast</code>”.</p>

<p>That is a different shape altogether:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">    <span class="c1">// Conceptual, not real Java</span>
    <span class="nc">Either</span><span class="o">&lt;</span><span class="nc">ParseError</span><span class="o">,</span> <span class="nc">Ast</span><span class="o">&gt;</span> <span class="nf">parse</span><span class="o">(</span><span class="nc">String</span> <span class="n">input</span><span class="o">);</span></code></pre></figure>

<p>Here both branches are meaningful. The “left” side carries structured error data, not a vague absence. The caller has to consider both cases, and the type system helps enforce that.</p>

<p>So the distinction looks like this:</p>

<blockquote>
  <p>Use <code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> when the alternative is “no value”.<br />
Use <code class="language-plaintext highlighter-rouge">Either&lt;A, B&gt;</code> when the alternative is “a different, meaningful value”.</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">Optional</code> is still valuable. It cleans up a lot of cases where we used to throw around naked nulls. It just does not replace a genuine disjunctive type where both branches have real content.</p>

<hr />

<h2 id="how-do-you-handle-this-in-java-today">How Do You Handle This In Java Today?</h2>

<p>Once you see that you want “either this or that” in the type system, the next question is obvious: what can you actually do in Java right now?</p>

<h3 id="exceptions-the-classic-java-way">Exceptions: The Classic Java Way</h3>

<p>The traditional Java approach is:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ast parse(String input) throws ParseException
</code></pre></div></div>

<p>On success you get an <code class="language-plaintext highlighter-rouge">Ast</code>. On failure you get a thrown <code class="language-plaintext highlighter-rouge">ParseException</code>.</p>

<p>This has some clear advantages. It is familiar to Java developers and the error can carry rich data, stack trace, nested causes. The downside is that the control flow is invisible in the type. The method signature looks like “returns <code class="language-plaintext highlighter-rouge">Ast</code>”, but semantically it is “returns <code class="language-plaintext highlighter-rouge">Ast</code> or blows up”.</p>

<p>You also get the whole checked versus unchecked exception debate, and callers can easily forget to handle failures or accidentally swallow exceptions.</p>

<p>Exceptions are one way of expressing a disjunction in Java, but they live outside the return type.</p>

<h3 id="making-the-disjunction-explicit-a-result-type">Making The Disjunction Explicit: A <code class="language-plaintext highlighter-rouge">Result</code> Type</h3>

<p>You can instead model the disjunction as data in the type system.</p>

<p>With modern Java, especially with sealed interfaces and records, this is not too painful. For Java 17 and above:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="n">sealed</span> <span class="kd">interface</span> <span class="nc">Result</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span>
        <span class="n">permits</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Ok</span><span class="o">,</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Err</span> <span class="o">{</span>

    <span class="kd">record</span> <span class="nc">Ok</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;(</span><span class="no">T</span> <span class="n">value</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">Result</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="o">{</span> <span class="o">}</span>

    <span class="kd">record</span> <span class="nc">Err</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;(</span><span class="no">E</span> <span class="n">error</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">Result</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="o">{</span> <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Your parser then becomes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Result&lt;ParseError, Ast&gt; parse(String input);
</code></pre></div></div>

<p>Using it with pattern matching for <code class="language-plaintext highlighter-rouge">switch</code>:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">Result</span><span class="o">&lt;</span><span class="nc">ParseError</span><span class="o">,</span> <span class="nc">Ast</span><span class="o">&gt;</span> <span class="n">result</span> <span class="o">=</span> <span class="n">parse</span><span class="o">(</span><span class="n">source</span><span class="o">);</span>

<span class="k">switch</span> <span class="o">(</span><span class="n">result</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">case</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Ok</span><span class="o">&lt;</span><span class="nc">ParseError</span><span class="o">,</span> <span class="nc">Ast</span><span class="o">&gt;</span> <span class="n">ok</span> <span class="o">-&gt;</span> <span class="o">{</span>
        <span class="nc">Ast</span> <span class="n">ast</span> <span class="o">=</span> <span class="n">ok</span><span class="o">.</span><span class="na">value</span><span class="o">();</span>
        <span class="n">evaluate</span><span class="o">(</span><span class="n">ast</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">case</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Err</span><span class="o">&lt;</span><span class="nc">ParseError</span><span class="o">,</span> <span class="nc">Ast</span><span class="o">&gt;</span> <span class="n">err</span> <span class="o">-&gt;</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">(</span><span class="n">err</span><span class="o">.</span><span class="na">error</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Now the disjunction is right there in the signature. No nulls. No surprise exceptions. The compiler forces you to think about both branches.</p>

<p>If you are not on a sealed-types Java yet, you can do a simpler interface plus nested classes:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">    <span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Result</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="o">{</span>
      <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Ok</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="nc">Result</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="o">{</span>
          <span class="kd">private</span> <span class="kd">final</span> <span class="no">T</span> <span class="n">value</span><span class="o">;</span>
          <span class="kd">public</span> <span class="nf">Ok</span><span class="o">(</span><span class="no">T</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
              <span class="k">this</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>
          <span class="o">}</span>
          <span class="kd">public</span> <span class="no">T</span> <span class="nf">value</span><span class="o">()</span> <span class="o">{</span>
              <span class="k">return</span> <span class="n">value</span><span class="o">;</span>
          <span class="o">}</span>
      <span class="o">}</span>
      
      <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Err</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="nc">Result</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="o">{</span>
          <span class="kd">private</span> <span class="kd">final</span> <span class="no">E</span> <span class="n">error</span><span class="o">;</span>
          <span class="kd">public</span> <span class="nf">Err</span><span class="o">(</span><span class="no">E</span> <span class="n">error</span><span class="o">)</span> <span class="o">{</span>
              <span class="k">this</span><span class="o">.</span><span class="na">error</span> <span class="o">=</span> <span class="n">error</span><span class="o">;</span>
          <span class="o">}</span>
          <span class="kd">public</span> <span class="no">E</span> <span class="nf">error</span><span class="o">()</span> <span class="o">{</span>
              <span class="k">return</span> <span class="n">error</span><span class="o">;</span>
          <span class="o">}</span>
      <span class="o">}</span>
   <span class="o">}</span></code></pre></figure>

<p>At this point you have your own mini-<code class="language-plaintext highlighter-rouge">Either</code> in Java.</p>

<p>Now you can use it like:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">   <span class="kd">public</span> <span class="nc">Result</span><span class="o">&lt;</span><span class="nc">ParseError</span><span class="o">,</span> <span class="nc">Ast</span><span class="o">&gt;</span> <span class="nf">parse</span><span class="o">(</span><span class="nc">String</span> <span class="n">input</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="nc">Ast</span> <span class="n">ast</span> <span class="o">=</span> <span class="n">realParse</span><span class="o">(</span><span class="n">input</span><span class="o">);</span> <span class="c1">// imagine this may throw ParseException</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Ok</span><span class="o">&lt;&gt;(</span><span class="n">ast</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ParseException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Err</span><span class="o">&lt;&gt;(</span><span class="k">new</span> <span class="nc">ParseError</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">()));</span>
        <span class="o">}</span>
   <span class="o">}</span></code></pre></figure>

<p>The calling code is then forced to check the result, for example, using ugly <code class="language-plaintext highlighter-rouge">instanceOf</code> :</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">     <span class="nc">Result</span><span class="o">&lt;</span><span class="nc">ParseError</span><span class="o">,</span> <span class="nc">Ast</span><span class="o">&gt;</span> <span class="n">result</span> <span class="o">=</span> <span class="n">parse</span><span class="o">(</span><span class="n">source</span><span class="o">);</span>

    <span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="k">instanceof</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Ok</span><span class="o">&lt;</span><span class="nc">ParseError</span><span class="o">,</span> <span class="nc">Ast</span><span class="o">&gt;</span> <span class="n">ok</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Ast</span> <span class="n">ast</span> <span class="o">=</span> <span class="n">ok</span><span class="o">.</span><span class="na">value</span><span class="o">();</span>
        <span class="n">evaluate</span><span class="o">(</span><span class="n">ast</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="k">instanceof</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Err</span><span class="o">&lt;</span><span class="nc">ParseError</span><span class="o">,</span> <span class="nc">Ast</span><span class="o">&gt;</span> <span class="n">err</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">ParseError</span> <span class="n">error</span> <span class="o">=</span> <span class="n">err</span><span class="o">.</span><span class="na">error</span><span class="o">();</span>
        <span class="n">log</span><span class="o">(</span><span class="n">error</span><span class="o">);</span>
    </code></pre></figure>

<p>You can hate the verbosity of <code class="language-plaintext highlighter-rouge">instanceof</code>, but at least the type system is no longer lying about the fact that there are two meaningful branches.</p>

<hr />

<h2 id="disjunctive-ast-nodes-an-n-way-either">Disjunctive AST Nodes: An N-Way Either</h2>

<p>In many real parsers, the “either this or that” situation is not just success versus error. Sometimes you are walking an abstract syntax tree (AST) and the node you are visiting could be one of several meaningful shapes.</p>

<p>For example, when traversing a PL/SQL file, you might encounter:</p>

<ul>
  <li>a package</li>
  <li>a function</li>
  <li>a stored procedure</li>
  <li>or some other declaration further down the AST</li>
</ul>

<p>Conceptually, that return type is not “a function and a stored proc and a package”. It is “either a package, or a function, or a stored proc, or something else”.</p>

<p>In Java 17+, the cleanest way to express this is as a sealed hierarchy. The AST node type itself becomes a multi-way disjunctive type:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">    <span class="kd">public</span> <span class="n">sealed</span> <span class="kd">interface</span> <span class="nc">AstNode</span>
            <span class="n">permits</span> <span class="nc">PackageNode</span><span class="o">,</span> <span class="nc">FunctionNode</span><span class="o">,</span> <span class="nc">StoredProcNode</span><span class="o">,</span> <span class="nc">OtherNode</span> <span class="o">{</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">PackageNode</span> <span class="kd">implements</span> <span class="nc">AstNode</span> <span class="o">{</span>
        <span class="c1">// nested declarations, children, etc.</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">FunctionNode</span> <span class="kd">implements</span> <span class="nc">AstNode</span> <span class="o">{</span>
        <span class="c1">// function-specific fields</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">StoredProcNode</span> <span class="kd">implements</span> <span class="nc">AstNode</span> <span class="o">{</span>
        <span class="c1">// stored-procedure-specific fields</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">OtherNode</span> <span class="kd">implements</span> <span class="nc">AstNode</span> <span class="o">{</span>
        <span class="c1">// whatever other construct you have</span>
    <span class="o">}</span></code></pre></figure>

<p>Now a traversal method can simply say:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">    <span class="nc">AstNode</span> <span class="nf">visit</span><span class="o">(</span><span class="nc">NodeContext</span> <span class="n">ctx</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// logic that returns one of the concrete node types</span>
    <span class="o">}</span></code></pre></figure>

<p>And the consumer can handle all the possibilities with pattern matching:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">    <span class="nc">AstNode</span> <span class="n">node</span> <span class="o">=</span> <span class="n">visit</span><span class="o">(</span><span class="n">ctx</span><span class="o">);</span>

    <span class="k">switch</span> <span class="o">(</span><span class="n">node</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">case</span> <span class="nc">PackageNode</span> <span class="n">pkg</span>      <span class="o">-&gt;</span> <span class="n">handlePackage</span><span class="o">(</span><span class="n">pkg</span><span class="o">);</span>
        <span class="k">case</span> <span class="nc">FunctionNode</span> <span class="n">fn</span>      <span class="o">-&gt;</span> <span class="n">handleFunction</span><span class="o">(</span><span class="n">fn</span><span class="o">);</span>
        <span class="k">case</span> <span class="nc">StoredProcNode</span> <span class="n">sp</span>    <span class="o">-&gt;</span> <span class="n">handleStoredProc</span><span class="o">(</span><span class="n">sp</span><span class="o">);</span>
        <span class="k">case</span> <span class="nc">OtherNode</span> <span class="n">other</span>      <span class="o">-&gt;</span> <span class="n">handleOther</span><span class="o">(</span><span class="n">other</span><span class="o">);</span>
    <span class="o">}</span></code></pre></figure>

<p>That <code class="language-plaintext highlighter-rouge">AstNode</code> interface is an N-ary disjunctive type: “either a function, or a stored proc, or a package, or …”. If you want to stack this with success/failure, you can even combine it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Result&lt;ParseError, AstNode&gt; visit(NodeContext ctx);
</code></pre></div></div>

<p>So you end up with:</p>

<ul>
  <li>outer disjunction: <code class="language-plaintext highlighter-rouge">Result&lt;Error, Value&gt;</code> (success vs failure)</li>
  <li>inner disjunction: <code class="language-plaintext highlighter-rouge">AstNode</code> being one of <code class="language-plaintext highlighter-rouge">{PackageNode, FunctionNode, StoredProcNode, OtherNode, …}</code></li>
</ul>

<p>This is exactly the kind of situation where making the alternatives explicit in the type system keeps the code honest.</p>

<hr />

<h2 id="where-optional-still-fits">Where <code class="language-plaintext highlighter-rouge">Optional</code> Still Fits</h2>

<p><code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> still has a clear niche: it fits when the alternative branch is “nothing to see here”.</p>

<p>Typical cases:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Optional&lt;User&gt; findById(String id);

Optional&lt;String&gt; findHeader(String name);
</code></pre></div></div>

<p>These really are “value or no value” cases. There is no error object with rich semantics that you want to propagate. Once you have a meaningful error domain, you are back in <code class="language-plaintext highlighter-rouge">Result&lt;E, T&gt;</code> or some other disjunctive type.</p>

<p>So one way to think about the layering is:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> for “present or absent”.</li>
  <li><code class="language-plaintext highlighter-rouge">Result&lt;E, T&gt;</code> (or <code class="language-plaintext highlighter-rouge">Either&lt;A, B&gt;</code>-style types) for “this meaningful thing or that meaningful thing”.</li>
  <li>Exceptions only for genuinely exceptional failures: bugs, I/O issues, broken invariants.</li>
</ul>

<hr />

<h2 id="mixing-exceptions-and-results">Mixing Exceptions And Results</h2>

<p>You do not have to pick a single tool forever.</p>

<p>A pragmatic pattern looks like this:</p>

<ul>
  <li>In lower-level logic and domain code, return <code class="language-plaintext highlighter-rouge">Result&lt;E, T&gt;</code> (or another explicit disjunctive type) to represent expected failures.</li>
  <li>At the edges of the system, such as HTTP controllers or CLI handlers, translate <code class="language-plaintext highlighter-rouge">Result</code> into HTTP responses or user messages.</li>
  <li>Throw exceptions only for truly unexpected situations, such as corrupted state or impossible code paths.</li>
</ul>

<p>That way you use disjunctive types for expected control flow, and keep exceptions for “this should not have happened”.</p>

<hr />

<h2 id="a-small-step-you-can-take-right-now">A Small Step You Can Take Right Now</h2>

<p>If you want to live this idea in Java code today, you can:</p>

<ol>
  <li>Introduce a tiny <code class="language-plaintext highlighter-rouge">Result&lt;E, T&gt;</code> type as above in a shared utilities module.</li>
  <li>Start by using it in one or two flows, such as parsing or service calls.</li>
  <li>Add helper methods like <code class="language-plaintext highlighter-rouge">map</code>, <code class="language-plaintext highlighter-rouge">flatMap</code>, and <code class="language-plaintext highlighter-rouge">fold</code> later if you want a more functional style.</li>
</ol>

<p>With a helper class:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java">    <span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Results</span> <span class="o">{</span>

        <span class="kd">private</span> <span class="nf">Results</span><span class="o">()</span> <span class="o">{</span> <span class="o">}</span>

        <span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">,</span> <span class="no">U</span><span class="o">&gt;</span> <span class="nc">Result</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">U</span><span class="o">&gt;</span> <span class="nf">map</span><span class="o">(</span>
                <span class="nc">Result</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="n">result</span><span class="o">,</span>
                <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">function</span><span class="o">.</span><span class="na">Function</span><span class="o">&lt;</span><span class="no">T</span><span class="o">,</span> <span class="no">U</span><span class="o">&gt;</span> <span class="n">f</span>
        <span class="o">)</span> <span class="o">{</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="k">instanceof</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Ok</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="n">ok</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Ok</span><span class="o">&lt;&gt;(</span><span class="n">f</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">ok</span><span class="o">.</span><span class="na">value</span><span class="o">()));</span>
            <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="k">instanceof</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Err</span><span class="o">&lt;</span><span class="no">E</span><span class="o">,</span> <span class="no">T</span><span class="o">&gt;</span> <span class="n">err</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nc">Result</span><span class="o">.</span><span class="na">Err</span><span class="o">&lt;&gt;(</span><span class="n">err</span><span class="o">.</span><span class="na">error</span><span class="o">());</span>
            <span class="o">}</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Unknown Result variant"</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span></code></pre></figure>

<p>Once you have that, you can start composing operations without losing error context, all within the type system.</p>

<hr />

<h2 id="closing-thoughts">Closing Thoughts</h2>

<p>In most codebases we lean heavily on “and” types: records, POJOs, data classes that say “this and that and that”. They are great when your domain really is a bundle of things that always travel together.</p>

<p>Disjunctive types live on the other side of that line. They say “either this or that” and force you to acknowledge both branches as first-class citizens. That is what <code class="language-plaintext highlighter-rouge">Either</code>, <code class="language-plaintext highlighter-rouge">Result&lt;E, T&gt;</code>, sealed AST hierarchies, and similar patterns bring to Java: a way to make alternatives part of the type story instead of an afterthought.</p>

<p><code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> still earns its keep for “value or no value”. Exceptions still matter for “this should never happen”. But when the alternative is a meaningful value – a <code class="language-plaintext highlighter-rouge">ParseError</code>, a <code class="language-plaintext highlighter-rouge">ValidationError</code>, a different AST node type – it is worth promoting that alternative into the type system and letting the compiler nag you into handling it.</p>

<p>Once you start doing that, a lot of familiar patterns begin to look different. Classes full of nullable fields, APIs that quietly throw, methods whose real contract lives in Javadoc rather than in types – all of them start to feel like places where a missing “either this or that” is hiding in plain sight.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Computer Science&quot;, &quot;Software Engineering&quot;, &quot;Technology&quot;]" /><category term="computer-science" /><category term="software" /><category term="technology" /><category term="programming-language" /><summary type="html"><![CDATA[When your Java methods really mean "either this or that" but the type system only says "here’s something…maybe," you’re flying blind. This post walks through using Result types, sealed hierarchies, and a bit of logic-thinking to make those hidden alternatives explicit, so the compiler – not your Javadoc – tells the truth about your code.]]></summary></entry><entry><title type="html">Revolutionizing Global Payments - A Comprehensive Analysis of Real-Time Payment Systems - UPI in India, FedNow in the US, and Similar Interfaces Worldwide</title><link href="https://systemhalted.in/2023/07/25/real-time-payments/" rel="alternate" type="text/html" title="Revolutionizing Global Payments - A Comprehensive Analysis of Real-Time Payment Systems - UPI in India, FedNow in the US, and Similar Interfaces Worldwide" /><published>2023-07-25T00:00:00+00:00</published><updated>2023-07-25T00:00:00+00:00</updated><id>https://systemhalted.in/2023/07/25/real-time-payments</id><content type="html" xml:base="https://systemhalted.in/2023/07/25/real-time-payments/"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Real-time payment systems have emerged as an essential pillar of the digital era, reshaping the financial landscape and providing individuals and businesses with efficient, seamless, and secure transaction capabilities. This comprehensive technical article delves into the evolution and functioning of real-time payment systems, focusing on two prominent examples - the Unified Payments Interface (UPI)<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> in India and the FedNow<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> standard in the United States. Moreover, we will explore similar systems in other countries, such as the Faster Payments Service (FPS)<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> in the UK, SEPA Instant Credit Transfer (SCT Inst)<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup> in the Eurozone, and PIX<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup> in Brazil. By examining the intricate technical aspects, architecture, and global impact of these real-time payment solutions, this article aims to shed light on their pivotal role in revolutionizing global financial transactions.</p>

<h3 id="1-unified-payments-interface-upi-in-india">1. Unified Payments Interface (UPI) in India</h3>
<h4 id="11-genesis-and-evolution">1.1 Genesis and Evolution</h4>
<p>The Unified Payments Interface (UPI) was a pioneering initiative launched by the National Payments Corporation of India (NPCI) in 2016. Its genesis can be traced back to the Indian government’s vision of promoting a cashless economy and enhancing financial inclusion. Prior to UPI, digital payment systems in India were fragmented and lacked interoperability. UPI aimed to address these challenges by providing a seamless, real-time, and interbank payment solution.</p>

<p>The evolution of UPI has been remarkable. It started with a handful of banks and has now grown to include almost all major banks in the country. Its rapid adoption can be attributed to the convenience it offers to users, allowing them to link multiple bank accounts and perform transactions using a single mobile application.</p>

<h4 id="12-architecture-and-key-components">1.2 Architecture and Key Components</h4>
<p>The architecture of UPI is designed to be simple, scalable, and secure. At its core lies the NPCI’s payment switch, which acts as a central hub for routing transactions between participating banks. When a user initiates a UPI transaction through a mobile app, the payment switch ensures seamless communication between the user’s bank and the recipient’s bank.</p>

<p>Key components of the UPI system include the Virtual Payment Address (VPA), which acts as a unique identifier for users, eliminating the need for sharing sensitive bank details during transactions. Additionally, UPI leverages the Immediate Payment Service (IMPS)<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup> infrastructure for real-time fund transfers, further enhancing its efficiency.</p>

<h4 id="13-real-time-transaction-processing">1.3 Real-Time Transaction Processing</h4>
<p>One of the most significant advantages of UPI is its ability to process transactions in real-time. When a user initiates a payment, it is authorized and settled instantly, reflecting the amount in the recipient’s bank account within seconds. This real-time processing is instrumental in enabling quick and hassle-free payments between individuals and merchants.</p>

<p>The real-time transaction processing has fueled the adoption of UPI for various use cases, including bill payments, merchant transactions, peer-to-peer transfers, and online shopping. Additionally, its integration with other platforms like e-commerce apps and digital wallets has further contributed to its popularity.</p>

<h4 id="14-security-and-authentication">1.4 Security and Authentication</h4>
<p>Given the sensitive nature of financial transactions, UPI places a strong emphasis on security. Each UPI transaction requires two-factor authentication, usually involving a combination of a unique MPIN (Mobile Personal Identification Number) and biometric authentication (fingerprint or face recognition).</p>

<p>Moreover, UPI transactions are secured using end-to-end encryption, safeguarding the confidentiality of user data during transmission. The Virtual Payment Address (VPA)<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup> acts as a shield, as users need not share their bank account details, reducing the risk of potential cyber frauds.</p>

<h3 id="2-fednow-standard-in-the-united-states">2. FedNow Standard in the United States</h3>
<h4 id="21-the-vision-for-fednow">2.1 The Vision for FedNow</h4>
<p>The Federal Reserve’s vision behind the development of the FedNow standard was to bridge the gap between existing payment systems and meet the growing demand for faster, more inclusive, and efficient payment options in the United States.</p>

<p>Recognizing the need for a ubiquitous real-time payment infrastructure, the Federal Reserve aims to provide businesses and consumers with instantaneous access to funds, enabling near-instantaneous transactions 24/7, including weekends and holidays.</p>

<h4 id="22-technical-architecture-and-infrastructure">2.2 Technical Architecture and Infrastructure</h4>
<p>The technical architecture of FedNow revolves around a secure, resilient, and high-speed payment network. The infrastructure is built to handle a massive volume of transactions securely, while adhering to industry standards and regulations.</p>

<p>The FedNow system operates on a central payment clearing mechanism, allowing participating banks to settle transactions instantly. The system will accommodate a wide range of payment types, including person-to-person (P2P), business-to-business (B2B), and business-to-consumer (B2C) transactions.</p>

<h4 id="23-real-time-settlement">2.3 Real-Time Settlement</h4>
<p>One of the key highlights of FedNow is its ability to provide real-time settlement, ensuring that funds are made available to the recipient’s account immediately after the transaction is initiated. This feature eliminates the need for delayed settlement periods, reducing risks and enhancing liquidity for businesses and individuals.</p>

<p>The real-time settlement capability is poised to revolutionize payment systems in the US, as it offers unprecedented speed and convenience, rivaling the efficiency of cash transactions.</p>

<h4 id="24-advancing-financial-inclusion">2.4 Advancing Financial Inclusion</h4>
<p>FedNow’s 24/7 availability is a significant step towards advancing financial inclusion in the United States. Unlike traditional payment systems, which often operate on limited schedules, FedNow will provide a level playing field for all users, irrespective of their location or economic status.</p>

<p>By offering real-time payment capabilities around the clock, FedNow will enable underserved communities to access financial services without barriers, empowering them with greater control over their finances.</p>

<h3 id="3-similar-interfaces-in-other-countries">3. Similar Interfaces in Other Countries</h3>
<h4 id="31-faster-payments-service-fps-in-the-uk">3.1 Faster Payments Service (FPS) in the UK</h4>
<p>The Faster Payments Service (FPS) in the UK is another noteworthy real-time payment system. Launched in 2008, FPS allows near-instantaneous electronic transfers between UK bank accounts. The system operates 24/7, providing users with the convenience of instant payments at any time.</p>

<p>FPS has played a vital role in transforming the UK’s payments landscape, facilitating faster and secure transactions for individuals and businesses. It has bolstered the growth of mobile banking and digital payments, further reducing the reliance on cash-based transactions.</p>

<h4 id="32-sepa-instant-credit-transfer-sct-inst-in-the-eurozone">3.2 SEPA Instant Credit Transfer (SCT Inst) in the Eurozone</h4>
<p>SEPA Instant Credit Transfer (SCT Inst) was introduced in the Eurozone in 2017, enabling individuals and businesses to transfer euros instantly between participating banks. SCT Inst operates on a pan-European level, fostering cross-border transactions with speed and efficiency.</p>

<p>The adoption of SCT Inst has been steadily growing, promoting seamless and borderless payments within the Eurozone. It has also opened up new opportunities for businesses to serve customers in different European countries without the constraints of delayed payments.</p>

<h4 id="33-pix-in-brazil">3.3 PIX in Brazil</h4>
<p>PIX, launched in Brazil in 2020, is a game-changer in the country’s payment landscape. This real-time payment system operates 24/7, allowing individuals and businesses to make instant transfers, payments, and purchases.</p>

<p>PIX has been widely embraced in Brazil, rapidly becoming the preferred method of payment for various transactions, including e-commerce, bill payments, and even peer-to-peer transfers. Its adoption has been accelerated by its integration into popular apps and digital platforms.</p>

<h3 id="4-global-impact-and-future-outlook">4. Global Impact and Future Outlook</h3>
<h4 id="41-financial-inclusion-and-economic-growth">4.1 Financial Inclusion and Economic Growth</h4>
<p>Real-time payment systems have had a profound impact on advancing financial inclusion. By providing instant and accessible financial services, these systems empower underserved populations to participate actively in the formal economy, stimulating economic growth and reducing income inequality.</p>

<p>The widespread adoption of real-time payment systems has also led to increased consumer spending and business efficiency, further fueling economic development.</p>

<h4 id="42-interoperability-and-cross-border-transactions">4.2 Interoperability and Cross-Border Transactions</h4>
<p>As real-time payment systems continue to gain traction worldwide, the potential for enhanced interoperability and seamless cross-border transactions becomes more apparent. With the establishment of common standards and protocols, international payments could be executed instantly, overcoming the complexities and delays associated with traditional cross-border transfers.</p>

<p>The interoperability of real-time payment systems is expected to facilitate smoother global commerce, benefiting businesses engaged in international trade and fostering economic cooperation between nations.</p>

<h4 id="43-technological-challenges-and-security-considerations">4.3 Technological Challenges and Security Considerations</h4>
<p>The development and implementation of real-time payment systems come with their own set of challenges. One of the key challenges is ensuring robust and secure infrastructures that can handle a massive volume of transactions without compromising on reliability.</p>

<p>Addressing these challenges requires continuous advancements in cybersecurity measures, fraud detection systems, and data encryption protocols. Additionally, collaboration between financial institutions, regulators, and technology providers is crucial in developing standardized security practices and frameworks to safeguard against cyber threats.</p>

<p>Looking ahead, real-time payment systems will continue to evolve, incorporating cutting-edge technologies like blockchain and artificial intelligence to enhance efficiency and security further. However, the collaborative effort to stay one step ahead of potential threats remains critical to maintaining the integrity of these systems.</p>

<h2 id="conclusion">Conclusion</h2>
<p>The world of real-time payment systems is witnessing an unprecedented revolution, reshaping the financial landscape and redefining the way individuals and businesses conduct transactions. Unified Payments Interface (UPI) in India and the emerging FedNow standard in the United States stand as testament to the power of technology in fostering a seamless and secure payment ecosystem.</p>

<p>Additionally, the global adoption of similar real-time payment interfaces, such as the Faster Payments Service (FPS) in the UK, SEPA Instant Credit Transfer (SCT Inst) in the Eurozone, and PIX in Brazil, exemplifies the growing demand for efficient and inclusive payment solutions worldwide.</p>

<p>As real-time payment systems continue to expand, their positive impact on financial inclusion, economic growth, and cross-border transactions is undeniable. Nevertheless, addressing technological challenges and ensuring robust security measures will be essential in maintaining the trust of users and further propelling the growth of these systems.</p>

<p>The future outlook for real-time payment systems is promising, with potential advancements in interoperability, global connectivity, and security protocols. As technology continues to evolve, these systems will play an increasingly pivotal role in shaping the future of global financial transactions, propelling us toward a cashless, interconnected, and inclusive world.</p>

<p><em>References</em></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>https://www.npci.org.in/what-we-do/upi/product-overview <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>https://en.wikipedia.org/wiki/Unified_Payments_Interface <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>https://www.frbservices.org/financial-services/fednow <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>https://en.wikipedia.org/wiki/Faster_Payments <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p>https://www.europeanpaymentscouncil.eu/what-we-do/sepa-instant-credit-transfer <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6">
      <p>https://www.bcb.gov.br/en/financialstability/pix_en <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p>https://www.npci.org.in/what-we-do/imps/product-overview <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>https://paytm.com/blog/payments/upi/virtual-payment-address-know-how-to-create-vpa-benefits/ <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Society &amp; Economy&quot;, &quot;Technology&quot;, &quot;Politics &amp; Governance&quot;]" /><category term="society" /><category term="economy" /><category term="technology" /><category term="politics" /><category term="india" /><category term="united-states" /><summary type="html"><![CDATA[Real-time payment systems have become the backbone of modern economies, offering rapid and secure transactions for individuals and businesses alike. This in-depth technical article explores the transformative power of real-time payment systems, with a focus on two major players - Unified Payments Interface (UPI) in India and the emerging FedNow standard in the United States. Additionally, we analyze similar interfaces from around the world, including the Faster Payments Service (FPS) in the UK, SEPA Instant Credit Transfer (SCT Inst) in the Eurozone, and PIX in Brazil. Through an in-depth examination of each system’s development, architecture, features, and global impact, this article aims to provide readers with a comprehensive understanding of the rapidly evolving landscape of real-time payment solutions.]]></summary></entry><entry><title type="html">The Power of Transparency in Leadership - Lessons from Experience</title><link href="https://systemhalted.in/2023/07/24/transparency-in-leadership/" rel="alternate" type="text/html" title="The Power of Transparency in Leadership - Lessons from Experience" /><published>2023-07-24T00:00:00+00:00</published><updated>2023-07-24T00:00:00+00:00</updated><id>https://systemhalted.in/2023/07/24/transparency-in-leadership</id><content type="html" xml:base="https://systemhalted.in/2023/07/24/transparency-in-leadership/"><![CDATA[<p>As a manager, I’ve always believed in the fundamental importance of open and transparent communication with my team. I recognize that, as human beings, we all have our moments of forgetfulness, and I’m no exception. Therefore, I make it a point to be as forthcoming as professionally possible with my team, acknowledging that I might not remember to share everything with them, but I’m dedicated to improving this aspect continuously.</p>

<h2 id="embracing-transparency-as-a-core-value">Embracing Transparency as a Core Value</h2>

<p>Transparency is not just a buzzword for me; it’s a core value that I hold dearly. I firmly believe that creating a culture of openness and trust leads to greater collaboration and empowers the team to perform at their best. By being honest about successes and challenges, we can collectively find solutions and grow together.</p>

<p>In my years as a manager, I have witnessed firsthand the positive impact of transparency on team dynamics and overall productivity. When team members feel that their manager is transparent, they are more likely to reciprocate with their own openness, fostering an environment of mutual respect and understanding.</p>

<h2 id="expecting-transparency-from-above">Expecting Transparency from Above</h2>

<p>But this isn’t just a one-way street. In my journey as a manager, I’ve always expected the same level of transparency from my own superiors. When important news needs to be shared with the team, I strongly advocate for leaders to inform the team directly. Relying solely on other channels risks creating undercurrents and misunderstandings within the team which hampers the team dynamics. The power of face-to-face communication should never be underestimated.</p>

<p>As a manager, I see myself as a conduit between my team and upper management. It is my responsibility to ensure that vital information is conveyed transparently and effectively. This approach not only strengthens the bond of trust with my team but also sets an example for them to follow in their own interactions with colleagues.</p>

<h2 id="navigating-the-corporate-landscape-with-transparency">Navigating the Corporate Landscape with Transparency</h2>

<p>As we navigate the ever-changing landscape of the corporate world, it’s essential to remember that genuine transparency goes beyond mere lip service. It involves actively fostering an environment where open dialogue is encouraged, and every team member feels comfortable sharing their thoughts and ideas.</p>

<p>Transparency can be challenging, especially in high-stakes situations or when delivering unfavorable news. However, I’ve learned that being transparent about the reasoning behind decisions, even difficult ones, helps team members understand the bigger picture and adapt accordingly.</p>

<h2 id="the-benefits-of-embracing-transparency">The Benefits of Embracing Transparency</h2>

<p>Transparency isn’t always easy, and it requires vulnerability and courage, but the benefits far outweigh the challenges. It builds trust, strengthens relationships, and ultimately leads to a more engaged and motivated team.</p>

<p>When team members feel that their voices are heard and their concerns are taken seriously, they are more likely to take ownership of their work and strive for excellence. Transparency paves the way for constructive feedback, which is invaluable for individual and team growth.</p>

<p>Moreover, transparency can break down silos and foster cross-functional collaboration. When information is shared openly, teams can work together more effectively, leveraging each other’s expertise and talents to achieve common goals.</p>

<h2 id="embracing-transparency-in-personal-and-professional-life">Embracing Transparency in Personal and Professional Life</h2>

<p>Transparency should not be confined to the workplace; it’s a value that extends to all aspects of life. By practicing openness in our personal relationships, we can build deeper connections and foster understanding.</p>

<p>In our professional lives, transparency with clients and stakeholders can lead to stronger partnerships and lasting business relationships. Honest communication builds credibility and enhances the reputation of individuals and organizations alike.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Transparency is a powerful tool in leadership that fosters trust, collaboration, and growth. As a manager, I’ve experienced firsthand how embracing transparency positively impacts team dynamics and overall performance.</p>

<p>By expecting transparency from above and leading by example, I strive to create a culture of openness and mutual respect within my team. Together, we navigate the corporate landscape with transparency as our guiding principle, acknowledging challenges while valuing the numerous benefits it brings.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Personal Essays&quot;]" /><category term="personal" /><category term="opinion" /><category term="leadership" /><summary type="html"><![CDATA[Transparency is a powerful tool in leadership that fosters trust, collaboration, and growth. As a manager, I've experienced firsthand how embracing transparency positively impacts team dynamics and overall performance. By expecting transparency from above and leading by example, I strive to create a culture of openness and mutual respect within my team. Together, we navigate the corporate landscape with transparency as our guiding principle, acknowledging challenges while valuing the numerous benefits it brings.]]></summary></entry><entry><title type="html">Navigating Chaos - Reflecting on India’s Traffic Woes and Empowering Change on India’s Road with AI</title><link href="https://systemhalted.in/2023/06/12/navigating-chaos-indias-traffic-woes/" rel="alternate" type="text/html" title="Navigating Chaos - Reflecting on India’s Traffic Woes and Empowering Change on India’s Road with AI" /><published>2023-06-12T00:00:00+00:00</published><updated>2023-06-12T00:00:00+00:00</updated><id>https://systemhalted.in/2023/06/12/navigating-chaos-indias-traffic-woes</id><content type="html" xml:base="https://systemhalted.in/2023/06/12/navigating-chaos-indias-traffic-woes/"><![CDATA[<h2 id="introduction">Introduction:</h2>
<p>In the bustling streets of India, a chaotic symphony unfolds daily, as vehicles of all shapes and sizes jostle for space. For some, this spectacle is a source of amazement and curiosity, but for others, like myself, it evokes a profound sense of frustration and disappointment, which makes me persistently angry. The disregard for traffic laws, the lack of compassion for fellow travelers, and the pervasive “me-first” attitude have turned our roads into an ocean of malice. In this blog post, we delve into the realities of India’s traffic chaos and explore potential avenues for change.</p>

<h2 id="the-reality-of-chaos">The Reality of Chaos:</h2>
<p>India’s traffic woes are no secret. Roads that resemble a tangled web, lanes that are mere suggestions, and signals treated as inconsequential guidelines paint a vivid picture of disorder. The blatant violation of traffic rules, such as driving on the wrong side of the road without shame, is disheartening, to say the least. In this environment, the needs and safety of others fade into the background, as the individual’s interests take center stage.</p>

<h2 id="collective-karma">Collective Karma:</h2>
<p>While the concept of karma may be subjective and open to interpretation, it is worth contemplating its role in our society. If karma indeed operates, one could argue that our “collective karma” as a nation is submerged in an ocean of malice and contempt for one another. The continuous disregard for traffic laws and the lack of compassion on the roads might be seen as manifestations of this collective karma. However, it is crucial to recognize that not everyone shares this mindset. There are individuals who diligently adhere to traffic rules, embodying a sense of responsibility and consideration.</p>

<h2 id="understanding-the-complexity">Understanding the Complexity:</h2>
<p>To address India’s traffic challenges, we must acknowledge the multifaceted nature of the issue. Factors such as rapid urbanization, inadequate infrastructure, population density, and cultural attitudes towards driving all contribute to the current state of affairs. Changing these deep-rooted problems requires a collective effort from the government, law enforcement agencies, and society as a whole.</p>

<h2 id="embracing-constructive-solutions">Embracing Constructive Solutions:</h2>
<p>Rather than succumbing to despair, it is essential to channel our frustration into constructive actions that pave the way for positive change. Encouraging responsible behavior on the road, educating others about the importance of traffic rules, and supporting initiatives for better traffic management are all steps in the right direction. By focusing on solutions and fostering a sense of responsibility and compassion, we can gradually transform the chaotic landscape into a more orderly and safer environment.</p>

<h3 id="shifting-societal-attitudes">Shifting Societal Attitudes:</h3>
<p>To address India’s traffic problems, we must start by acknowledging that change begins at the societal level. It is imperative to foster a culture that values law and order, compassion, and respect for others. By promoting awareness and education about the importance of following traffic laws, we can sow the seeds of change. Initiatives such as road safety campaigns, community engagement, and school programs can play a pivotal role in instilling a sense of responsibility and accountability.</p>

<h3 id="improving-infrastructure-and-enforcement">Improving Infrastructure and Enforcement:</h3>
<p>Alongside changing attitudes, improvements in infrastructure and enforcement are vital. Adequate road infrastructure, well-designed traffic management systems, and clear signage can contribute to smoother traffic flow. Strengthening law enforcement agencies, ensuring stricter penalties for traffic violations, and implementing technology-driven solutions like AI-based traffic surveillance systems can help deter reckless behavior.</p>

<h3 id="ai-for-traffic-management">AI for Traffic Management:</h3>
<p>Artificial intelligence has the potential to revolutionize traffic management in India. AI-powered systems can analyze real-time data from cameras, sensors, and other sources to identify traffic patterns, congestion hotspots, and areas requiring attention. This data-driven approach enables authorities to make informed decisions about traffic flow optimization, signal timing, and road network improvements. Additionally, AI can help in predicting traffic incidents and suggesting alternative routes to reduce congestion.</p>

<h3 id="smart-traffic-control">Smart Traffic Control:</h3>
<p>AI can be leveraged to develop smart traffic control systems that dynamically adapt to traffic conditions. By using machine learning algorithms, these systems can optimize traffic signal timings based on real-time traffic data, improving the overall flow of vehicles and reducing congestion. AI-enabled traffic control can also enhance pedestrian safety by prioritizing pedestrian crossings during peak hours.</p>

<h3 id="intelligent-transportation-systems">Intelligent Transportation Systems:</h3>
<p>Intelligent Transportation Systems (ITS) powered by AI can enable better management of traffic across various modes of transportation. Integration of AI algorithms in public transportation systems can optimize routes, improve scheduling, and reduce waiting times, enhancing overall efficiency. AI can also facilitate the development of predictive models for traffic forecasting and adaptive traffic management strategies.</p>

<h3 id="collaborative-efforts">Collaborative Efforts:</h3>
<p>Addressing India’s traffic challenges requires collaboration between the government, law enforcement agencies, civil society organizations, and the public. Stakeholders must work together to develop comprehensive strategies that encompass infrastructure development, education, and enforcement. Engaging citizens through participatory initiatives and platforms for feedback can also foster a sense of ownership and collective responsibility. Working together, we can develop AI-driven solutions tailored to the unique needs of Indian roads and traffic conditions. Public participation, feedback mechanisms, and transparency in implementing AI-based systems are crucial to build trust and ensure responsible use of technology.</p>

<h3 id="education-and-empathy">Education and Empathy:</h3>
<p>Education plays a pivotal role in transforming societal attitudes. Incorporating traffic safety education in school curricula, driver training programs, and public awareness campaigns can empower individuals with knowledge and promote responsible behavior. Encouraging empathy and consideration for fellow travelers through public campaigns and initiatives can create a culture of compassion on the roads.</p>

<h2 id="conclusion">Conclusion:</h2>
<p>India’s traffic chaos is a complex challenge that demands our attention and collective effort. While it is disheartening to witness the disregard for traffic laws and the absence of compassion on the roads, it is crucial to recognize the individuals who embody responsible behavior. By reflecting on our individual actions and their potential consequences, we can contribute to positive change. Let us navigate through the chaos with a determination to promote a culture of respect, empathy, and adherence to traffic laws. India’s traffic chaos demands a comprehensive approach that embraces societal change and harnesses the power of AI. By shifting attitudes, improving infrastructure, strengthening enforcement, and leveraging AI for traffic management, we can pave the way for a safer, more efficient, and sustainable transportation system. Let us embrace AI as a tool to empower change, making India’s roads a symbol of order, safety, and harmony. Together, we can drive towards a safer and more harmonious future on India’s roads.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Society &amp; Economy&quot;, &quot;Politics &amp; Governance&quot;]" /><category term="society" /><category term="economy" /><category term="politics" /><category term="india" /><summary type="html"><![CDATA[In the bustling streets of India, a chaotic symphony unfolds daily, as vehicles of all shapes and sizes jostle for space. For some, this spectacle is a source of amazement and curiosity, but for others, like myself, it evokes a profound sense of frustration and disappointment, which makes me persistently angry. The disregard for traffic laws, the lack of compassion for fellow travelers, and the pervasive "me-first" attitude have turned our roads into an ocean of malice. In this blog post, we delve into the realities of India's traffic chaos and explore potential avenues for change including use of Artificial Intelligence (AI).]]></summary></entry><entry><title type="html">The Advancements in Technology and Training Methods that have Improved the Performance of Athletes</title><link href="https://systemhalted.in/2023/01/17/technology-training-improved-performance/" rel="alternate" type="text/html" title="The Advancements in Technology and Training Methods that have Improved the Performance of Athletes" /><published>2023-01-17T00:00:00+00:00</published><updated>2023-01-17T00:00:00+00:00</updated><id>https://systemhalted.in/2023/01/17/technology-training-improved-performance</id><content type="html" xml:base="https://systemhalted.in/2023/01/17/technology-training-improved-performance/"><![CDATA[<p>Recently Virat Kohli<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> scored his 46th ODI<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> century. Sachin Tendulkar<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> has scored 100 centuries in all formats with 49 centuries in ODI. Virat has always been compared to Sachin and his recent centuries have re-started the comparison. One contention is that Sachin took 463 ODI matches to score his 49 centuries, while Virat has scored his 46 centuries in just 268 matches.</p>

<p>In this debate, I stipulate that the world of sports has come a long way in the past decade. With advancements in technology and training methods, athletes are now able to push their bodies to new limits and break records that were once thought impossible. From professional athletes to weekend warriors, everyone is benefitting from these technological advancements. In this article, we will explore the ways in which technology and training methods have improved the performance of athletes and what we can expect to see in the future.</p>

<p>One of the biggest advancements in technology that has impacted the world of sports is the development of wearable technology. From fitness trackers to smart watches, athletes are now able to track their performance in real-time. This allows them to monitor their heart rate, distance, and pace while they are training and competing. This data can then be used to make adjustments to their training regimen and fine-tune their performance.</p>

<p>Another important aspect of sports performance that has been greatly impacted by technology is the clothing athletes wear. Breathable clothing has become increasingly popular in recent years, and for good reason. Breathable clothing can have a positive impact on sports performance by helping to regulate an athlete’s body temperature. When an athlete is participating in physical activity, their body generates heat, which can cause them to become overheated and fatigued. Breathable clothing allows sweat to evaporate through the fabric, helping to cool the body and keep the athlete comfortable. This can help to prolong endurance and improve overall performance.</p>

<p>Yet another way that technology is transforming the world of sports is through the use of video analysis. With the help of cameras and software, coaches and trainers are now able to analyze an athlete’s technique and identify areas for improvement. This allows them to make adjustments to the athlete’s training regimen in order to improve their performance. You can see the spider cameras all around. In cricket, the stump camera and technology like ball tracking and Ultra Edge <sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> to confirm if the ball hit the bat have changed the way the game is played. (That means the chances that the batter is given a out in a wrong decision have reduced. Sachin has been given out a lot of times due to bad on-field umpire decisions<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>)</p>

<p>In addition to technology, training methods have also seen significant advancements in recent years. One of the biggest changes has been the shift towards functional training. Rather than focusing on isolated muscle groups, functional training focuses on the body as a whole and trains the muscles to work together. This type of training is more effective for athletes because it helps to improve their overall movement patterns, making them more efficient and less prone to injury.</p>

<p>Another training method that has become increasingly popular is high-intensity interval training (HIIT). This type of training involves short bursts of high-intensity activity followed by periods of rest or low-intensity activity. Research has shown that HIIT is an effective way to improve cardiovascular fitness and increase muscle mass. This makes it an ideal training method for athletes who need to be in top physical condition.</p>

<p>Nutrition and dietary supplements are also playing a bigger role in the performance of athletes. With the help of sports scientists and nutritionists, athletes are now able to tailor their diets to meet their specific needs. This includes things like consuming the right types of carbohydrates to fuel their bodies during training and competition, and taking supplements to support their recovery and overall health.</p>

<p>Finally, medical treatments and recovery techniques have also improved in recent years. Athletes are now able to access a wide range of treatments, from physical therapy to regenerative medicine. These treatments help to speed up the recovery process, allowing athletes to get back to training and competing more quickly.</p>

<p>In conclusion, the advancements in technology and training methods have had a significant impact on the performance of athletes. From wearable technology to functional training, these advancements have allowed athletes to push their bodies to new limits and break records that were once thought impossible. With technology continuing to evolve, we can expect to see even more improvements in the future. Athletes and coaches will continue to use data and analytics to track performance and make training and game-time decisions. As a result, we will see even more athletes achieving new heights of excellence in their respective sports.</p>

<p><strong>References</strong></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><a href="https://en.wikipedia.org/wiki/Virat_Kohli">Virat Kohli</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p><a href="https://systemhalted.in/2023/01/16/cricket-beginners-guide/">ODI</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p><a href="https://en.wikipedia.org/wiki/Sachin_Tendulkar">Sachin Tendulkar</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Bilal, Mohammad, <a href="https://www.dailyo.in/ground/what-is-ultra-edge-technology-how-does-it-work-in-cricket-37386">What is ultra-edge technology? How does it work in cricket?</a> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p><a href="http://sachinandcritics.com/sachin_vs_ump.php">Wrong Decisions against Sachin Tendulkar</a> <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Sports &amp; Hobbies&quot;]" /><category term="sports" /><summary type="html"><![CDATA[The world of sports has come a long way in the past decade. With advancements in technology and training methods, athletes are now able to push their bodies to new limits and break records that were once thought impossible. From professional athletes to weekend warriors, everyone is benefitting from these technological advancements. In this post, we will explore the ways in which technology and training methods have improved the performance of athletes and what we can expect to see in the future.]]></summary></entry><entry><title type="html">American Football - Beginners Guide</title><link href="https://systemhalted.in/2023/01/16/american-football-rules/" rel="alternate" type="text/html" title="American Football - Beginners Guide" /><published>2023-01-16T00:00:00+00:00</published><updated>2023-01-16T00:00:00+00:00</updated><id>https://systemhalted.in/2023/01/16/american-football-rules</id><content type="html" xml:base="https://systemhalted.in/2023/01/16/american-football-rules/"><![CDATA[<p>As a newcomer to USA in 2013, American Football was really confusing at first. Here I summarize the rules at high level for the people who are new to the USA and want to understand and enjoy the game.</p>

<p>American football is a sport played by two teams, each consisting of eleven players. The objective of the game is to advance an oval-shaped ball down the field and score points by crossing the opposing team’s goal line or kicking the ball through a set of goalposts.</p>

<p>The game is divided into four quarters, each lasting 15 minutes, with a 2-minute break between the 1st and 2nd quarters and between the 3rd and 4th quarters, and a 15-minute halftime break between the 2nd and 3rd quarters.</p>

<p>Each team takes turns playing offense and defense. The team on offense, also known as the “offense,” attempts to advance the ball down the field by either running with the ball or throwing it to a teammate. The team on defense, also known as the “defense,” attempts to stop the offense and take control of the ball for themselves. Each team has four chances, known as downs, to advance the ball at least 10 yards. They can do this by running or throwing the ball. If the team successfully advances the ball 10 yards, they get a new set of downs and the yardage is reset. If they fail to make the yardage after 4 downs, the opposing team takes possession of the ball.</p>

<p>There are hundreds of different plays that can be run on any down, and they are often complex and chaotic. The head coach or quarterback calls the plays for the offensive team, while the defensive captain calls the plays for the defensive team.</p>

<p>The game starts with a coin toss to determine which team will receive the ball first and which side of the field they will start from. The team that wins the coin toss begins the game with a kick-off, where they punt the ball down the field for the other team to run back as far as possible.</p>

<p>On fourth down, the offensive team has the option to either try to make up the yards they are short or to kick the ball. If they decide to kick, they have two options: to punt or to try for a field goal. Depending on their position on the field, they will usually choose one over the other. If they are within 40 yards or so of the opposing team’s goalposts, they will likely attempt a field goal. If they are further back, they will likely choose to punt the ball.</p>

<p>Points can be scored in several ways:</p>

<ul>
  <li>Touchdown: 6 points, awarded when the offense carries the ball across the opponent’s goal line or catches a pass in the end zone.</li>
  <li>Field goal: 3 points, awarded when the offense successfully kicks the ball through the opponent’s goalposts.</li>
  <li>Safety: 2 points, awarded when the defense tackles the offensive player with the ball in their own end zone.</li>
  <li>Extra point: 1 point, awarded after a touchdown, the offense can choose to attempt to kick the ball through the opponent’s goalposts for one point or run/pass the ball into the end zone for 2 points.</li>
</ul>

<p>Go and enjoy the game!</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Sports &amp; Hobbies&quot;]" /><category term="sports" /><category term="united-states" /><summary type="html"><![CDATA[As a newcomer to USA in 2013, American Football was really confusing at first. Here I summarize the rules at high level for the people who are new to the USA and want to understand and enjoy the game.]]></summary></entry><entry><title type="html">Cricket - Beginner’s Guide</title><link href="https://systemhalted.in/2023/01/16/cricket-beginners-guide/" rel="alternate" type="text/html" title="Cricket - Beginner’s Guide" /><published>2023-01-16T00:00:00+00:00</published><updated>2023-01-16T00:00:00+00:00</updated><id>https://systemhalted.in/2023/01/16/cricket-beginners-guide</id><content type="html" xml:base="https://systemhalted.in/2023/01/16/cricket-beginners-guide/"><![CDATA[<p>Cricket is a sport that is played between two teams of eleven players each. The objective of the game is for one team to score more runs than the other team. If you are new to the game, it can seem complex and difficult to understand. However, with a basic understanding of the rules and strategies, anyone can enjoy watching and playing cricket.</p>

<h3 id="the-field">The Field</h3>

<p>The game of cricket is played on a large oval-shaped field, with a rectangular 22-yard-long strip of grass called the pitch in the center. At each end of the pitch, there are three upright sticks called stumps, and on top of these stumps, two small horizontal sticks called bails are placed. These stumps and bails make up the wicket, which is the target that the fielding team tries to hit and the batting team tries to protect.</p>

<h3 id="the-coin-toss">The Coin Toss</h3>

<p>A coin toss is held at the beginning of the game to determine which team will bat first. The team that bats first tries to score as many runs as possible before they have ten players “out.” Once the first team has ten players out, the teams switch roles and the second team tries to score more runs than the first team.</p>

<h3 id="offense-and-defense">Offense and Defense</h3>

<p>In cricket, the team that is currently trying to score runs is said to be “batting,” while the team that is trying to prevent the opposing team from scoring runs is said to be “fielding.” Each team takes turns playing offense and defense, with the goal of outscoring the opposing team.</p>

<h3 id="getting-out">Getting Out</h3>

<p>When the team is on defense, they try to prevent the opposing team from scoring runs by getting the players “out.” There are several ways to get a player out:</p>

<ul>
  <li>A player can be “out” in several ways, including:</li>
  <li>Being caught by a fielder off a hit ball</li>
  <li>Being bowled (stumps hit by the ball on the wicket)</li>
  <li>Being run out (stumps hit by the ball while running between the wickets)</li>
  <li>Being stumped (the wicket-keeper puts down the wicket while the player is out of his crease)</li>
</ul>

<h3 id="scoring-runs">Scoring Runs</h3>

<p>When the team is on offense, they try to score runs by hitting the ball and running back and forth between the wickets. A run is scored each time the two batsmen successfully run from one end of the pitch to the other.</p>

<h3 id="end-of-the-game-and-winning-the-match">End of the Game and Winning the Match</h3>

<p>The game can end in one of several ways:</p>

<ul>
  <li>The second team may be out before scoring more runs than the first team, in which case the first team wins.</li>
  <li>The second team may score more runs than the first team before all their players are out, in which case the second team wins.</li>
  <li>The game may end in a tie if both teams have the same score.</li>
</ul>

<h3 id="match-formats">Match Formats</h3>

<h4 id="test-match">Test Match</h4>

<p>A test match is the traditional format of cricket and is considered to be the pinnacle of the sport. It is the longest format of the game, usually played over five days, with each team having two innings. In test matches, there is no limit to the number of overs that each team can play, and a draw is a possible outcome. This means that if both teams are unable to bowl out the other team, or if the game ends in a tie, the match is considered to be a draw.</p>

<p>Also, the test matches tend to have a more strategic and tactical approach, as the teams have more time to plan and execute their strategies. The players also have to be more patient and disciplined in this format as the game can last up to five days.</p>

<p>The test match format is considered to be the most challenging and demanding format of the game and is often considered to be a true test of a team’s skill and ability. It is a format that is played between national teams and considered as the traditional format of cricket.</p>

<h4 id="limited-overs-match">Limited Overs Match</h4>

<p>These formats are designed to be completed in a shorter amount of time and are often considered to be more fast-paced and exciting. The two most popular limited-overs formats are One Day and T20.
    * One Day - In One Day format, each team has a set number of overs, usually 50, to score as many runs as possible. The team that scores the most runs wins the match. This format is considered to be a balance between the longer test format and the shorter T20 format, and it is popular for international matches.
    * T20 - T20 format is the shortest and most exciting format of cricket, where each team has 20 overs to score as many runs as possible. The team that scores the most runs wins the match. This format is popular for domestic and international matches, and it is known for its fast-paced action and high-scoring matches.</p>

<p>Cricket is a sport with many rules and strategies, and can take some time to fully understand. However, with a basic understanding of the game and its rules, anyone can enjoy watching and playing cricket. The game is known for its rich history and traditions, as well as its unique terminology and strategies. It is especially popular in countries such as England, Australia, India, and South Africa. Give it a try, you might find it to be a fascinating sport!</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Sports &amp; Hobbies&quot;]" /><category term="sports" /><category term="india" /><summary type="html"><![CDATA[This post summarizes sport of cricket for people who are interested in the game.]]></summary></entry><entry><title type="html">How to keep kids safe playing Roblox?</title><link href="https://systemhalted.in/2023/01/16/safety-tips-roblox/" rel="alternate" type="text/html" title="How to keep kids safe playing Roblox?" /><published>2023-01-16T00:00:00+00:00</published><updated>2023-01-16T00:00:00+00:00</updated><id>https://systemhalted.in/2023/01/16/safety-tips-roblox</id><content type="html" xml:base="https://systemhalted.in/2023/01/16/safety-tips-roblox/"><![CDATA[<p>I am constantly worried about my kids’ safety when they are playing Roblox or any game online. As a parent, it’s important to be aware of the digital world that our children are exploring. One of the most popular online gaming platforms among kids today is Roblox, a massively multiplayer game that allows players to create and share their own virtual worlds. While Roblox can be a fun and educational experience for kids, it’s important to take steps to ensure their safety while they’re playing.</p>

<p>Here are some tips to help keep your child safe while playing Roblox:</p>

<ol>
  <li>Set up parental controls: Roblox offers a variety of parental control options that allow you to limit the amount of time your child can spend on the platform, as well as restrict access to certain features. You can also set up a PIN code to prevent your child from making in-game purchases without your permission.</li>
  <li>Monitor your child’s chat: Roblox allows players to communicate with each other through in-game chat, which can be a great way for kids to make friends and collaborate on projects. However, it’s important to keep an eye on the chat to make sure that your child isn’t being bullied or exposed to inappropriate content.</li>
  <li>Check the game’s rating: Roblox games are rated by other players, so it’s important to check the rating of a game before your child starts playing it. Games with a high rating are generally considered to be safe, while games with a low rating may contain inappropriate content.</li>
  <li>Educate your child about online safety: It’s important to teach your child about online safety and the importance of being mindful of the information they share online. Remind them not to share personal information, such as their name, address, or phone number, with strangers.</li>
  <li>Be involved in their gaming: Be aware of your child’s gaming habits and interests. Play with them and observe the games they are playing, the people they are interacting with and the kind of content they are exposed to.</li>
  <li>Be aware of the potential risks of virtual currency: Roblox has virtual currency called “Robux” that can be used to purchase in-game items. Be aware that some children may be tempted to buy Robux with real money, so make sure you discuss the potential risks of virtual currency with your child.</li>
  <li>Keep an open communication with your child: Encourage your child to talk to you if they ever feel uncomfortable or unsafe while playing Roblox or online. This will help them feel more comfortable coming to you if they ever need help.</li>
  <li>Keep the computer in a common area: If possible, place the computer in a common area of the house so you can keep an eye on your child’s online activities.</li>
  <li>Limit the time they can play: It is important that your kids are not spending their entire time online playing games. Other aspects are important. Encourage them to read and go out and play.</li>
  <li>Keep an eye on your child’s friends list: Roblox allows players to add friends, so it’s important to monitor your child’s friends list to ensure that they are only interacting with people you trust. 
11.Use the “Report Abuse” feature: Roblox has a “Report Abuse” feature that allows players to report any inappropriate behavior or content they encounter. Encourage your child to use this feature if they ever come across anything that makes them feel uncomfortable or unsafe.</li>
  <li>Discuss online behavior with your child: Talk to your child about appropriate online behavior and explain to them the importance of being respectful and kind to others while playing games online.</li>
  <li>Stay informed: Stay informed and educated on the latest features and updates of Roblox platform. This will help you to keep your child safe and aware of any new risks or concerns. Also, be aware of the latest trends and games that your child may be interested in.</li>
  <li>Monitor your child’s online activities: Regularly check your child’s account to see what games they’re playing, who they’re interacting with, and what kind of content they’re viewing. This will give you a better understanding of your child’s online activities and will help you to address any concerns or issues that may arise.</li>
  <li>Be a role model: Be a role model for your child by showing them how to use the internet and online games in a safe and responsible manner.</li>
</ol>

<p>I hope by taking these steps, you and I can help keep our child safe while they’re playing Roblox and enjoy the fun and educational benefits that the platform has to offer. It’s important to remember that no system or method can provide a 100% guarantee of safety so it’s important to keep an open line of communication with your child and stay informed about the latest updates and trends of the platform.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Technology&quot;]" /><category term="video-games" /><category term="security" /><summary type="html"><![CDATA[This post will discuss some tips for parents to keep the kids safe while playing Roblox.]]></summary></entry><entry><title type="html">Git Bisect - Finding the Source of a Bug in Your Codebase</title><link href="https://systemhalted.in/2023/01/12/git-bisect/" rel="alternate" type="text/html" title="Git Bisect - Finding the Source of a Bug in Your Codebase" /><published>2023-01-12T00:00:00+00:00</published><updated>2023-01-12T00:00:00+00:00</updated><id>https://systemhalted.in/2023/01/12/git-bisect</id><content type="html" xml:base="https://systemhalted.in/2023/01/12/git-bisect/"><![CDATA[<p>As a software developer, it’s inevitable that bugs will appear in your codebase. When this happens, it can be a time-consuming and frustrating process to track down the source of the problem. However, there is a powerful tool built into Git that can help: bisect.</p>

<p>Git bisect allows you to quickly and easily identify the commit that introduced a bug by using a binary search algorithm. The process works by narrowing down the possible commits that could have introduced the bug by repeatedly selecting a “good” and “bad” commit and checking the ones in between.</p>

<p>To use Git bisect, first make sure that you have the latest version of your codebase on a clean branch. Then, run the command <code class="language-plaintext highlighter-rouge">git bisect start</code> to begin the bisect process. Next, you’ll need to mark the current commit as “bad” using the command <code class="language-plaintext highlighter-rouge">git bisect bad</code>. This will tell Git that the bug is present in the current version of the code.</p>

<p>Next, you’ll need to find a commit that you know is “good”, meaning that the bug is not present. This could be a commit from before the bug was introduced, or a release version of your code that you know is working correctly. Once you have identified a “good” commit, run the command <code class="language-plaintext highlighter-rouge">git bisect good &lt;commit&gt;</code> to mark it as such.</p>

<p>Git will now begin the bisect process by checking out a commit in between the “good” and “bad” commits. You’ll need to test the code at this point to determine if the bug is present or not. If the bug is present, run the command git bisect bad to mark the current commit as bad. If the bug is not present, run the command git bisect good to mark it as good.</p>

<p>Git will continue to bisect the codebase in this way until it has identified the specific commit that introduced the bug. Once the commit has been found, run the command git bisect reset to exit the bisect process and return to the latest version of the code.</p>

<p>It’s important to note that bisecting a codebase can take some time, especially for large codebases with many commits. However, the process is automated and can save a significant amount of time compared to manually searching through the codebase for the source of the bug.</p>

<p>In conclusion, Git bisect is a powerful tool that can help you quickly and efficiently identify the source of a bug in your codebase. By using a binary search algorithm, bisect can narrow down the possible commits that could have introduced the bug, saving you time and frustration. Give it a try the next time you run into a tricky bug in your code!”</p>

<p>Note: the commands above are just examples and the exact commands may vary depending on the version of git and the operating system you are using.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Software Engineering&quot;]" /><category term="software" /><summary type="html"><![CDATA[Git bisect allows you to quickly and easily identify the commit that introduced a bug by using a binary search algorithm. The process works by narrowing down the possible commits that could have introduced the bug by repeatedly selecting a "good" and "bad" commit and checking the ones in between.]]></summary></entry><entry><title type="html">Language Part 6 - Non-Verbal Communication, Tone, and Body language</title><link href="https://systemhalted.in/2022/12/26/language-part-6-tone-body-lang-nvc/" rel="alternate" type="text/html" title="Language Part 6 - Non-Verbal Communication, Tone, and Body language" /><published>2022-12-26T00:00:00+00:00</published><updated>2022-12-26T00:00:00+00:00</updated><id>https://systemhalted.in/2022/12/26/language-part-6-tone-body-lang-nvc</id><content type="html" xml:base="https://systemhalted.in/2022/12/26/language-part-6-tone-body-lang-nvc/"><![CDATA[<p><strong><em>Disclaimer</em></strong>
I am not a linquist and have no knowledge about languages. Even though I am filing this under <code class="language-plaintext highlighter-rouge">Linguistic</code> category, it is more of my <code class="language-plaintext highlighter-rouge">Insane Thoughts</code> and thus is twin filed under that category as well.</p>

<p>Language is a complex system of communication that involves not only words and grammar, but also other elements such as nonverbal communication, tone, and body language. These elements can play a significant role in conveying meaning and understanding in language, and they are important to consider when communicating with others.</p>

<p>Non-verbal communication is a type of communication that does not involve words or language. It can include body language, gestures, facial expressions, eye movements, posture, and other physical cues. Non-verbal communication can convey a wide range of emotions and meanings, and it can be an important aspect of language in different cultural contexts.</p>

<p>For example, gestures are a common form of nonverbal communication that can convey meaning and emotion. Different cultures and societies may use different gestures to convey the same meaning, so it is important to be aware of these differences when communicating with others. Facial expressions are another common form of nonverbal communication that can convey a wide range of emotions, such as happiness, sadness, anger, or surprise. Eye movements and gaze patterns can also convey meaning and emotion, and they can be an important aspect of nonverbal communication in some cultural contexts.</p>

<p>Posture is another aspect of nonverbal communication that can convey meaning and emotion. For example, a person who is standing with their arms crossed and their body turned away may be conveying a sense of discomfort or disinterest. On the other hand, a person who is standing with their body turned towards the speaker and their arms uncrossed may be conveying a sense of interest and engagement.</p>

<p>Tone is another important aspect of language that can convey meaning and emotion. Tone is the way that words are spoken, and it can be affected by the pitch, volume, and rhythm of speech. Tone can convey a wide range of emotions, such as anger, happiness, or sadness. For example, a person speaking in a high-pitched, excited tone may be conveying happiness or enthusiasm, while a person speaking in a low, monotone voice may be conveying boredom or disinterest.</p>

<p>It is important to be aware of the tone that is being used when speaking, as it can have a significant impact on the meaning and interpretation of words and sentences. For example, a statement spoken in a sarcastic tone may be interpreted differently than the same statement spoken in a sincere tone. Tone can also be used to convey emphasis and to signal the end of a sentence or thought. For example, a rising pitch at the end of a sentence can indicate a question, while a falling pitch can indicate a statement.</p>

<p>Overall, nonverbal communication is a complex and important aspect of language that can convey a wide range of meanings and emotions. It is important to be aware of the role that nonverbal communication plays in language and to consider it when communicating with others.</p>]]></content><author><name>Palak Mathur</name><email>insanethoughts@live.com</email></author><category term="[&quot;Languages &amp; Linguistics&quot;, &quot;Series 1 - Language and Linguistics&quot;]" /><summary type="html"><![CDATA[This is Part 6 of Language series where we will talk about non-verbal communication.]]></summary></entry></feed>