<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: codewitharyan29</title>
    <description>The latest articles on DEV Community by codewitharyan29 (@codewitharyan29).</description>
    <link>https://dev.to/codewitharyan29</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4065878%2F04861b36-4758-40cc-88c6-b96cee0bd97f.png</url>
      <title>DEV Community: codewitharyan29</title>
      <link>https://dev.to/codewitharyan29</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codewitharyan29"/>
    <language>en</language>
    <item>
      <title>Our laptop said 37 MB, the scoreboard said 195: 8 lessons from Speed Demon 2026</title>
      <dc:creator>codewitharyan29</dc:creator>
      <pubDate>Sat, 07 Nov 2026 11:03:02 +0000</pubDate>
      <link>https://dev.to/codewitharyan29/our-laptop-said-37-mb-the-scoreboard-said-195-8-lessons-from-speed-demon-2026-3d4p</link>
      <guid>https://dev.to/codewitharyan29/our-laptop-said-37-mb-the-scoreboard-said-195-8-lessons-from-speed-demon-2026-3d4p</guid>
      <description>&lt;p&gt;Our laptop said 37 MB. The scoreboard said &lt;strong&gt;195 MB&lt;/strong&gt;. That was the first of eight times in 72 hours that the live board told us we were wrong about our own code, and each time, being wrong taught us more than being right.&lt;/p&gt;

&lt;p&gt;It also moved us up. On November 1 our Track C solution sat &lt;strong&gt;43rd of the 91 teams&lt;/strong&gt; that had scored it, at K 23.9 while the leaders were between 41 and 47. A day later it was K 32.4, and our Track D build had reached &lt;strong&gt;#9 of 117&lt;/strong&gt; on the board.&lt;/p&gt;

&lt;p&gt;This post is about those corrections. &lt;strong&gt;Speed Demon 2026&lt;/strong&gt; is a 72-hour performance-engineering hackathon run by &lt;a href="https://speeddemon.dev" rel="noopener noreferrer"&gt;Hackathon Raptors&lt;/a&gt;. Four tracks each hand you a correct but naive Python program, and you make it as fast as you can without changing a byte of its output. Every push is built and run on one fixed machine, and a wrong output hash scores nothing. Our team, &lt;strong&gt;Bharat Labs&lt;/strong&gt; (Aryan Verma and Lakshya Varshney), entered all four tracks. Everything is in the repo: &lt;strong&gt;&lt;a href="https://github.com/codewitharyan29/speed-demon-2026" rel="noopener noreferrer"&gt;github.com/codewitharyan29/speed-demon-2026&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdzx9j0azu9njoz0zbh4k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdzx9j0azu9njoz0zbh4k.png" alt=" " width="800" height="368"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Wall time on a log scale; the labels show exact times and speed-ups.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Track&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Reference&lt;/th&gt;
&lt;th&gt;Our best board run&lt;/th&gt;
&lt;th&gt;Speed-up&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;A: Ingest&lt;/strong&gt; (log to hourly percentiles)&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;32.05 s, 228 MB&lt;/td&gt;
&lt;td&gt;23.6 ms, 18.4 MB&lt;/td&gt;
&lt;td&gt;~1,350x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;B: Index&lt;/strong&gt; (boolean and phrase search)&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;56.35 s, 2,200 MB&lt;/td&gt;
&lt;td&gt;105.4 ms, 39.7 MB&lt;/td&gt;
&lt;td&gt;~535x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;C: Join&lt;/strong&gt; (quoted CSV join)&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;14.44 s, 367 MB&lt;/td&gt;
&lt;td&gt;34.0 ms, 13.6 MB&lt;/td&gt;
&lt;td&gt;~425x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;D: Traverse&lt;/strong&gt; (shortest paths)&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;72.69 s, 893 MB&lt;/td&gt;
&lt;td&gt;122.5 ms, 66.1 MB&lt;/td&gt;
&lt;td&gt;~593x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero external dependencies on all four. These are our best logged board runs; the last section shows where to verify each one.&lt;/p&gt;
&lt;h2&gt;
  
  
  How scoring works, in one minute
&lt;/h2&gt;

&lt;p&gt;Each track's score is a multiplier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;K = (T_ref/T)^0.5 x (M_ref/M)^0.2 x (S_ref/S)^0.15 x (1/D)^0.15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where T is wall time, M is peak memory, S is lines of source and D is the number of dependencies (the memory and speed bonuses are capped). A 10% improvement in each term moves K by:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;10% better in...&lt;/th&gt;
&lt;th&gt;K goes up by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;time&lt;/td&gt;
&lt;td&gt;about 5.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;memory&lt;/td&gt;
&lt;td&gt;about 2.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lines of code&lt;/td&gt;
&lt;td&gt;about 1.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So doubling K takes four times the speed, and a line of code is worth about a third of what a millisecond is. We kept that arithmetic in front of us all weekend, and it explains several decisions below.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the 72 hours went
&lt;/h2&gt;

&lt;p&gt;We were two people with one board. The board allows one run per team every five minutes, so every experiment had to be worth a push, and we took turns.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Track&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;The idea that mattered most&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A: Ingest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Aryan&lt;/td&gt;
&lt;td&gt;4-byte packed records, and a checked guess that the log is nearly sorted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B: Index&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lakshya&lt;/td&gt;
&lt;td&gt;Compiling each boolean query into a 64-bit truth table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C: Join&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Aryan&lt;/td&gt;
&lt;td&gt;Finding quoted regions with one carry-less multiply, and never unescaping names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;D: Traverse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lakshya&lt;/td&gt;
&lt;td&gt;Bidirectional search with both distances in one 32-bit word&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The repo has &lt;strong&gt;266 commits&lt;/strong&gt;: 110 from Lakshya, 104 from Aryan and 52 from Claude Code sessions we ran. &lt;strong&gt;162 of them carry a &lt;code&gt;Co-Authored-By: Claude Sonnet 5.5&lt;/code&gt; trailer&lt;/strong&gt;, so a large share of the code and measurement scripts was AI-assisted. We set the goals and ran and read every board result. Our last commit, a README edit, landed at 20:28 IST, two minutes before the freeze. Track B's decisive redesign landed at 19:12.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4rbzxpld0jlhd656hdbo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4rbzxpld0jlhd656hdbo.png" alt=" " width="799" height="465"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;K for every board run we logged. The green line is our best so far, each dip is an experiment we reverted, and the labelled jumps are corrections from this post.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Correction 1: our memory number was a lie
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we believed.&lt;/strong&gt; Track A's first serious rewrite used 37 MB on our laptops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the board showed.&lt;/strong&gt; &lt;strong&gt;195 MB.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; The scoring machine runs transparent huge pages in &lt;code&gt;always&lt;/code&gt; mode, and our Docker VM ran &lt;code&gt;madvise&lt;/code&gt;. Our arena was one big sparse mapping with a small region per chunk and hour, so every 2 MiB region we touched was silently backed by a full 2 MiB huge page. We proved it by forcing huge pages on locally, and our own build jumped from 25 MB to 197 MB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix was tiny.&lt;/strong&gt; Track C gave the cleanest version. One line, &lt;code&gt;prctl(PR_SET_THP_DISABLE)&lt;/code&gt;, took the same code from &lt;strong&gt;37.5 MB to 16.2 MB&lt;/strong&gt;, and K from 18.9 to 22.4. In Track A, &lt;code&gt;MADV_NOHUGEPAGE&lt;/code&gt; on the arena, plus a layout change, took 195 MB down to 30 MB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What generalizes.&lt;/strong&gt; A benchmark number measures a &lt;em&gt;machine plus a kernel configuration&lt;/em&gt;. Match the scoring machine's settings before you trust a memory figure.&lt;/p&gt;
&lt;h2&gt;
  
  
  Correction 2: local numbers don't transfer, in either direction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we believed.&lt;/strong&gt; For Track A's 239 MB input, &lt;code&gt;mmap&lt;/code&gt; must beat reading: no copies, no buffers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the board showed.&lt;/strong&gt; The &lt;code&gt;mmap&lt;/code&gt; build was &lt;strong&gt;14% slower&lt;/strong&gt; (29.8 ms against 26.2 ms). Page-faulting 239 MB costs real time, and so does tearing the mapping down, which we measured at about &lt;strong&gt;24 ms&lt;/strong&gt; in an early build. Reading 32 KB blocks with &lt;code&gt;pread&lt;/code&gt; keeps data in cache and avoids both.&lt;/p&gt;

&lt;p&gt;The same pattern kept appearing, and it cut both ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local wins that vanished.&lt;/strong&gt; One reader thread per parser was about 19% slower on the board (30.4 against 25.7 ms). Writing Track A's output with &lt;code&gt;fadvise&lt;/code&gt; cut peak memory locally from 17.4 MB to 9.4 MB, and the board's M did not move (19.53 against 19.49 MB). In Track C, an "instruction diet" cut instructions by 14% and ran &lt;strong&gt;2.1 ms slower&lt;/strong&gt;. We never found the cause, so the log says "cause not found".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Peak memory is a maximum, not a sum.&lt;/strong&gt; In Track D, query-phase memory fell from 74 MB to 36 MB locally, and the board's M did not budge, because the peak sat in the graph build. Reading &lt;code&gt;memory.current&lt;/code&gt; at each barrier showed it: the five build arrays together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A small local gain that was huge on the board.&lt;/strong&gt; In Track B, moving serial work into parallel pieces saved about 5 ms on our laptops and &lt;strong&gt;35 ms on the board&lt;/strong&gt; (0.242 to 0.207 s). The scoring box has turbo off and runs a core at about 2.5 GHz against our laptops' ~4.8 GHz, so every serial millisecond costs it far more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What generalizes.&lt;/strong&gt; Fewer instructions, fewer copies and tidier code are hypotheses about time. Only the scoring machine decides, and it can disagree in either direction.&lt;/p&gt;
&lt;h2&gt;
  
  
  Correction 3: the log was "nearly sorted", and the spec didn't promise it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we believed.&lt;/strong&gt; Track A's log is sorted by time, so we can finish an hour early and free its memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we found.&lt;/strong&gt; We measured the data: 13% of lines sit in an hour behind the newest one, 26,000 lines have an earlier timestamp than their predecessor, and the largest lag is 70 minutes. That is nearly sorted. But the spec says the log is &lt;em&gt;not&lt;/em&gt; sorted, with &lt;strong&gt;no bound on how far a line can be out of order&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we did.&lt;/strong&gt; We turned the observation into a &lt;em&gt;guess that is checked&lt;/em&gt;. A worker summarises an hour as soon as the chunks still to come shouldn't contain it, and every chunk verifies that it never sees an hour already summarised. If one does, the program discards the speculative result and &lt;strong&gt;re-runs the whole file in a safe pass&lt;/strong&gt;. We tested both paths on shuffled and reversed inputs and on 16 adversarial datasets.&lt;/p&gt;

&lt;p&gt;Tracks C and D follow the same rule. Track C assumes a newline inside a quoted field is never followed by a digit or a BOM, verifies every chunk boundary, and re-parses unchunked if one fails. Track D answers over 99.98% of queries with a 16-bit bucket queue and escalates the rest to an exact 64-bit bidirectional Dijkstra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What generalizes.&lt;/strong&gt; Measure the data, but only &lt;em&gt;rely&lt;/em&gt; on what the spec guarantees. For everything else, write the check and the fallback.&lt;/p&gt;
&lt;h2&gt;
  
  
  Correction 4: the profiler said 270, we had assumed 40
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we believed.&lt;/strong&gt; Track C's parser finds which commas and newlines sit inside quotes with one carry-less multiply (&lt;code&gt;PCLMULQDQ&lt;/code&gt;) per 64 bytes: the running parity of the quote bits is the "inside a quote" mask, and a doubled quote flips it twice. We assumed the rest of the record work would cost about 40 instructions per record. A cachegrind run on the full file said &lt;strong&gt;270&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; Names containing a doubled quote (12.6% of all names) each ran a &lt;code&gt;malloc&lt;/code&gt; plus a byte loop to unescape. A branch on the quote count was badly mispredicted, and &lt;code&gt;Vec::push&lt;/code&gt; capacity checks sat in the hot loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix was a different question.&lt;/strong&gt; We stopped asking how to unescape faster and asked what the output needs. It needs &lt;em&gt;equality&lt;/em&gt; of names, and a quoted name with doubled quotes maps one-to-one onto the real string. So two names are equal exactly when their raw bytes are equal, and we never unescape anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What generalizes.&lt;/strong&gt; Before optimising a step, ask whether its result is ever used. Often only a property of it is.&lt;/p&gt;
&lt;h2&gt;
  
  
  Correction 5: the spec was a data structure in disguise
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we believed.&lt;/strong&gt; Track B, boolean and phrase search over a 254 MB corpus, needs an inverted index. Ours used 328 MB and scored K 10.3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What changed.&lt;/strong&gt; Two spec facts we had read but not used. The memory multiplier maxes out at 44 MB, so a global posting list could never fit. We switched to indexing and answering &lt;strong&gt;one chunk at a time&lt;/strong&gt; on 12 workers, and memory fell from 171 MB to 62 MB in one step.&lt;/p&gt;

&lt;p&gt;And boolean queries have &lt;strong&gt;at most 6 distinct terms&lt;/strong&gt;. Six terms means 2^6 = 64 combinations of "present or absent", exactly the number of bits in a &lt;code&gt;u64&lt;/code&gt;. Each query compiles into a 64-bit truth table, and answering a document is one expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hit = (table &amp;gt;&amp;gt; mask) &amp;amp; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;mask&lt;/code&gt; is six presence bits gathered from a small L1-resident bitset in one AVX2 instruction. No posting lists, no intersections. Time fell from 163 ms to 105 ms, and the source from 1,285 lines to 961.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What generalizes.&lt;/strong&gt; Re-read the spec for suspiciously small numbers. A bound is often a license to replace a general structure with a specialised one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correction 6: the idea we rejected twice worked the third time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we believed.&lt;/strong&gt; Bidirectional search wasn't worth it for Track D. Lakshya had tried it twice in the Rust version and rejected it both times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What changed.&lt;/strong&gt; In the C++ rewrite he looked at &lt;em&gt;why&lt;/em&gt; it had failed. Both attempts put a reverse graph of 8-byte edges next to a 40 MB forward graph, with separate distance arrays. The failures were verdicts on two implementations, not on the idea. The third version used a compact reverse graph, built after the build arrays were released so the memory peak did not move, and stored forward and backward distances &lt;strong&gt;in one 32-bit word&lt;/strong&gt;, so checking whether the two searches have met costs no extra cache miss.&lt;/p&gt;

&lt;p&gt;Query time fell from 95.6 ms to 39.5 ms (-59%), and the whole run by 27% locally. The board confirmed it in full: 0.2418 s to 0.1783 s, K 20.9 to 24.0. One bug on the way mixed &lt;code&gt;u32&lt;/code&gt; and &lt;code&gt;int&lt;/code&gt;, so unreachable queries printed 4294967295 instead of -1. Exactness checks against the reference are what make a bug like that visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What generalizes.&lt;/strong&gt; Write down &lt;em&gt;why&lt;/em&gt; an idea failed, not just that it failed. When the surrounding design changes, the reason may no longer apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correction 7: lines are cheap, but only if they are free
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we believed.&lt;/strong&gt; Source size is part of the score, so shorter is always better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the formula says.&lt;/strong&gt; Lines are worth about a third of time (see the table above), so a line-saving change has to cost almost no speed. Two experiments showed it.&lt;/p&gt;

&lt;p&gt;In Track C, our C++ rewrite kept the Rust version's architecture and shrank it from 656 to 434 lines at about the same speed. The formula predicted K would rise from 29.63 to about 31.5, and the board measured &lt;strong&gt;31.51&lt;/strong&gt;. In Track A, a from-scratch minimum-lines rewrite (304 lines instead of about 500) earned x1.076 in K for the lines but cost x0.77 for the time, so K &lt;strong&gt;fell&lt;/strong&gt; from 41.9 to 32.8.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What generalizes.&lt;/strong&gt; Do the arithmetic of the scoring function before you pick a direction. Short code is a bonus on top of speed, never a substitute for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correction 8: we trusted a single run
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we believed.&lt;/strong&gt; If a change makes the board time go down, it worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the board showed.&lt;/strong&gt; The same Track C source, run three times, gave 52.6, 52.9 and 55.8 ms. Board noise is about &lt;strong&gt;6%&lt;/strong&gt;, so any "win" smaller than that was a coin flip. What we did about it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interleaved board comparisons.&lt;/strong&gt; 34 of our commits push a candidate and a &lt;em&gt;control&lt;/em&gt; build of the last known-good code alternately, for example &lt;code&gt;a1, read2, parse2, a1, pct2, fmt2, a1&lt;/code&gt;. If the machine drifts during the session, it drifts under both. In Track C, a candidate called cx22 (clean under three sanitizers) measured slower than cx19b this way, so we restored cx19b. The builds we left in place at freeze for Tracks A and C, called LF3 and cx40c, came out of these comparisons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diagnostic builds.&lt;/strong&gt; 11 more commits deliberately skip or repeat one stage just to time it. A build that read every block and did nothing else ran in 8.4 ms in Track A, so we knew the floor before optimising.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source hashes in commit messages.&lt;/strong&gt; In the very last comparison push, a runner argument error copied a Rust file over our C++ source. Each commit message records the source's sha256 prefix, so the restore commit could name the exact file it put back, and anyone can check it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labelled claims.&lt;/strong&gt; Our competitor notes tag each statement &lt;code&gt;VERIFIED&lt;/code&gt; or &lt;code&gt;HYPOTHESIS&lt;/code&gt;, and they say plainly that we looked at nobody's code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What generalizes.&lt;/strong&gt; Measure your noise first. Then compare against a control in the same window, not against yesterday's number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eight lessons, one line each
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Match the scoring machine's kernel settings before trusting a memory number.&lt;/li&gt;
&lt;li&gt;Treat every local speed-up as a hypothesis; the scoring machine can disagree in either direction.&lt;/li&gt;
&lt;li&gt;Measure the data, but rely only on what the spec guarantees, and keep a checked fallback for the rest.&lt;/li&gt;
&lt;li&gt;Before optimising a step, ask whether its result is even needed.&lt;/li&gt;
&lt;li&gt;Read the spec's small numbers as hints for specialised data structures.&lt;/li&gt;
&lt;li&gt;Record &lt;em&gt;why&lt;/em&gt; an idea failed, so you know when to try it again.&lt;/li&gt;
&lt;li&gt;Do the scoring arithmetic first: time is worth about three times as much as lines.&lt;/li&gt;
&lt;li&gt;Know your noise floor, and compare against a control in the same window.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If we did it again, we would do 1 and 8 on day one and pick each track's architecture within the first six hours. Our biggest gains came from design changes, not micro-tuning.&lt;/p&gt;

&lt;p&gt;We accepted one risk knowingly. Track A's endpoint dictionary trusts a 64-bit hash and never compares key bytes, with a collision chance of about 1 in 10^13 across at most 1,842 endpoints. It measured about 7 ms faster locally. A byte-verified version exists and costs a few percent, and the README names both.&lt;/p&gt;

&lt;p&gt;If you remember one thing from this post: every belief about performance is a hypothesis until the machine that scores you agrees. We were wrong eight times, and the leaderboard is where we found out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The final run
&lt;/h2&gt;

&lt;p&gt;After the freeze, Hackathon Raptors re-runs the top teams on a hidden holdout dataset. Our results are on the &lt;a href="https://speeddemon.dev/showtime" rel="noopener noreferrer"&gt;Showtime page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check our numbers
&lt;/h2&gt;

&lt;p&gt;Every figure comes from the &lt;code&gt;PERF.md&lt;/code&gt; logs and the commit history in the repo.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Track&lt;/th&gt;
&lt;th&gt;Logs&lt;/th&gt;
&lt;th&gt;Board run / commit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/codewitharyan29/speed-demon-2026/blob/speed-demon/track-a/PERF.md" rel="noopener noreferrer"&gt;PERF.md&lt;/a&gt;, &lt;a href="https://github.com/codewitharyan29/speed-demon-2026/blob/speed-demon/track-a/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;run-d310f2ad455b&lt;/code&gt; (commit &lt;code&gt;66693b1&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/codewitharyan29/speed-demon-2026/blob/speed-demon/track-b/PERF.md" rel="noopener noreferrer"&gt;PERF.md&lt;/a&gt;, &lt;a href="https://github.com/codewitharyan29/speed-demon-2026/blob/speed-demon/track-b/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;run-f7d2d327f20c&lt;/code&gt; (commit &lt;code&gt;4a0b9ec&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/codewitharyan29/speed-demon-2026/blob/speed-demon/track-c/PERF.md" rel="noopener noreferrer"&gt;PERF.md&lt;/a&gt;, &lt;a href="https://github.com/codewitharyan29/speed-demon-2026/blob/speed-demon/track-c/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;run-d8a78803f209&lt;/code&gt; (commit &lt;code&gt;9bfa8af&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/codewitharyan29/speed-demon-2026/blob/speed-demon/track-d/PERF.md" rel="noopener noreferrer"&gt;PERF.md&lt;/a&gt;, &lt;a href="https://github.com/codewitharyan29/speed-demon-2026/blob/speed-demon/track-d/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;commit &lt;code&gt;18c1dcf&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The results table lists our best logged board runs. The builds at freeze for Tracks A and C (LF3 and cx40c) were chosen by interleaved board comparisons whose numbers we did not copy into &lt;code&gt;PERF.md&lt;/code&gt;, so check the &lt;a href="https://speeddemon.dev/board" rel="noopener noreferrer"&gt;live board&lt;/a&gt; for those. The 43rd-of-91 figure is from our &lt;code&gt;docs/COMPETITORS.md&lt;/code&gt; snapshot of the public board feed (121 teams, 91 with a scored Track C run, generated 2026-11-01 01:57 UTC). The #9-of-117 ranking for Track D is from our log of the step-35 board result. Local timings came from a Windows Docker VM and only ranked ideas against each other, and board numbers carry roughly 6% noise.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you to &lt;a href="https://speeddemon.dev" rel="noopener noreferrer"&gt;Hackathon Raptors&lt;/a&gt; for a brutal, fun and educational 72 hours. Team Bharat Labs: Aryan Verma and Lakshya Varshney. Code and logs: &lt;a href="https://github.com/codewitharyan29/speed-demon-2026" rel="noopener noreferrer"&gt;github.com/codewitharyan29/speed-demon-2026&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>cpp</category>
      <category>performance</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>Same score, 23 places apart: the normalization maths behind a hackathon judging platform</title>
      <dc:creator>codewitharyan29</dc:creator>
      <pubDate>Fri, 02 Oct 2026 07:11:31 +0000</pubDate>
      <link>https://dev.to/codewitharyan29/same-score-23-places-apart-the-normalization-maths-behind-a-hackathon-judging-platform-2mji</link>
      <guid>https://dev.to/codewitharyan29/same-score-23-places-apart-the-normalization-maths-behind-a-hackathon-judging-platform-2mji</guid>
      <description>&lt;p&gt;Two projects in the official DOGFOOD data set both had a raw average score of &lt;strong&gt;3.44&lt;/strong&gt;, from three judges each.&lt;/p&gt;

&lt;p&gt;After I normalized the scores, one finished &lt;strong&gt;#37&lt;/strong&gt;. The other finished &lt;strong&gt;#14&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Same number. 23 places apart. This is the story of why, and of the other technical problems I hit while building &lt;strong&gt;Verdict&lt;/strong&gt;, a platform that runs a hackathon end to end: events, teams, deadline-enforced submissions, rubric judging, public voting, certificates, webhooks and an embeddable gallery. It was built for the DOGFOOD hackathon by &lt;a class="mentioned-user" href="https://dev.to/partnerships_raptors"&gt;@partnerships_raptors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Stack: FastAPI, SQLAlchemy 2.0 async, PostgreSQL, Redis, Next.js. &lt;br&gt;
Repo: &lt;a href="https://github.com/Lakshya-Varshney/Verdict" rel="noopener noreferrer"&gt;https://github.com/Lakshya-Varshney/Verdict&lt;/a&gt;&lt;br&gt;
Demo_Video: &lt;a href="https://youtu.be/bmdsrYy_dy4" rel="noopener noreferrer"&gt;https://youtu.be/bmdsrYy_dy4&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  1. The problem: a raw average cannot tell who was asked
&lt;/h2&gt;

&lt;p&gt;The obvious way to rank projects is to average judge scores. It silently assumes every judge uses the scale the same way.&lt;/p&gt;

&lt;p&gt;They do not. On the official data (40 projects, 30 judges, 126 review rows), judges with at least 5 reviews had personal averages from &lt;strong&gt;3.11&lt;/strong&gt; to &lt;strong&gt;4.22&lt;/strong&gt;, with the same spread (standard deviation 0.81 for both). That is a full point of pure leniency.&lt;/p&gt;

&lt;p&gt;Now look at the two projects:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Raw mean (rank)&lt;/th&gt;
&lt;th&gt;Its reviewers' own average&lt;/th&gt;
&lt;th&gt;Normalized rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flat Meadow&lt;/td&gt;
&lt;td&gt;3.44 (#24)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;3.97&lt;/strong&gt; (generous panel: 4.22, 3.61, 4.08)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;#37&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glass Signal&lt;/td&gt;
&lt;td&gt;3.44 (#26)&lt;/td&gt;
&lt;td&gt;3.48 (close to the pooled mean 3.57)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;#14&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Flat Meadow got a below-par verdict from each of its reviewers. Glass Signal got a normal one from each of its reviewers. The raw average sees two identical numbers.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. The fix: per-judge z-scores
&lt;/h2&gt;

&lt;p&gt;For each judge, compute their mean and standard deviation over everything they scored, then convert each score into "how far above or below this judge's own usual":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;z(judge, project) = (score - judge_mean) / judge_stddev
normalized(project) = mean of z over the judges who reviewed it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why z-scores and not min-max or rank averaging? They correct both leniency (the mean) and spread (the standard deviation), and they stay well-defined when projects have different numbers of reviews. Two reviews on one project and five on another is fine, because each project is just the mean over whoever reviewed it.&lt;/p&gt;

&lt;p&gt;Result on the real data: &lt;strong&gt;38 of 40 projects change rank&lt;/strong&gt;, Spearman correlation 0.864 against the raw ranking. I also recomputed everything independently with plain &lt;code&gt;statistics&lt;/code&gt; and no application code, and the two agree to floating-point precision.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The edge case that decides whether the maths is correct
&lt;/h2&gt;

&lt;p&gt;A z-score divides by the judge's standard deviation. What if a judge gave 4/4/4 on every project? Or reviewed only one project? Then the standard deviation is zero and there is nothing to divide by.&lt;/p&gt;

&lt;p&gt;The tempting shortcut is to use that judge's raw score. I rejected it, because it mixes two scales. Raw scores sit around 3.0. Z-scores sit around 0. If you average them together, every project a flat judge touched jumps by roughly that judge's raw score, and the judge with no opinion ends up as the loudest voice in the ranking.&lt;/p&gt;

&lt;p&gt;The design that works: standardize the flat judge's raw score against the &lt;strong&gt;pooled&lt;/strong&gt; mean and standard deviation of every score in the event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;                 &lt;span class="c1"&gt;# judge's own scale
&lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;pooled_sd&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1e-12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;pooled_mu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;pooled_sd&lt;/span&gt;      &lt;span class="c1"&gt;# same scale as everyone else
&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flat judge still contributes a real signal (higher raw score means higher z), it lands on the same scale as everyone else, and it is never silently trusted: they appear in a &lt;code&gt;zero_variance_judges&lt;/code&gt; list and in the audit log. The results payload also separates "identical scores everywhere" from "only one review", so the UI never mislabels a judge who simply had too little data. There is a regression test for exactly this: &lt;code&gt;test_zero_variance_judge_does_not_swamp_z_scores&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the official data, four judges take this path.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Real data fights back: the duplicate project row
&lt;/h2&gt;

&lt;p&gt;The official &lt;code&gt;fixtures.json&lt;/code&gt; has 41 project rows for 40 teams. &lt;code&gt;prj_41&lt;/code&gt; repeats &lt;code&gt;prj_07&lt;/code&gt;: same team, same title ("Dry Harbour"), same repo, different id. Deduplicating by id would never catch it, so I merge by team plus title.&lt;/p&gt;

&lt;p&gt;The interesting part is the consequence. Three judges had scored both ids, so their criterion rows collided after the merge: 9 rows, taking 378 score rows down to 369. One of those judges had scored the original at 2.33 and the duplicate at 3.67. Under the documented rule (the later review wins), all three of her remaining projects score 3.67, and she correctly lands in the zero-variance path from section 3.&lt;/p&gt;

&lt;p&gt;Data cleaning is not neutral. It changes the maths downstream. So I wrote the rule down (later review wins, averaging is the alternative) and surfaced every flagged judge in the results payload.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Role isolation, tested by attacking the running system
&lt;/h2&gt;

&lt;p&gt;Reading the code tells you what you intended. Probing the live API tells you what you built. I ran adversarial probes against the running system and found 12 real problems, each now covered by a regression test. The ones that taught me the most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An organizer could grant themselves &lt;code&gt;admin&lt;/code&gt;.&lt;/strong&gt; Every permission check accepts &lt;code&gt;admin&lt;/code&gt; from any event, so one self-grant meant site-wide control. Only an admin can grant admin now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any organizer could read every other event's audit log.&lt;/strong&gt; 98 of the first 100 rows returned belonged to other events. Audit access is scoped to the caller's own events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judges could change scores after results were published.&lt;/strong&gt; The judging window was enforced in the UI, not the API. A UI button is not an access control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The vote count was hidden in one field and leaked in another.&lt;/strong&gt; While voting was open &lt;code&gt;count&lt;/code&gt; was null, but &lt;code&gt;vote_count&lt;/code&gt; still carried the real number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;alice+1@gmail.com&lt;/code&gt; counted as a new voter.&lt;/strong&gt; Email voting now uses a canonical mailbox: lowercase, drop the &lt;code&gt;+tag&lt;/code&gt;, ignore Gmail dots.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Roles are read from the database on every request and scoped per event, so cross-event isolation is structural rather than a convention.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. A 13 second gallery, and &lt;code&gt;lazy="raise"&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;With the real 40-project data the gallery took &lt;strong&gt;13 s&lt;/strong&gt; and &lt;code&gt;/roles&lt;/code&gt; took &lt;strong&gt;18 s&lt;/strong&gt;, because SQLAlchemy was eager-loading every relationship.&lt;/p&gt;

&lt;p&gt;I set &lt;code&gt;lazy="raise"&lt;/code&gt; on every relationship. Any accidental lazy load now throws instead of quietly running extra queries, so every query has to be written explicitly. It forces more typing and gives predictable performance. &lt;code&gt;test_real_scale_endpoints_stay_fast&lt;/code&gt; keeps it that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. An audit log you can verify, not just trust
&lt;/h2&gt;

&lt;p&gt;An append-only audit log is normally append-only by convention. Anyone with database access can still edit history.&lt;/p&gt;

&lt;p&gt;In Verdict, every audit row contains the hash of the previous row. &lt;code&gt;GET /admin/audit/verify&lt;/code&gt; and an offline script both detect tampering. To keep the chain linear, appends take a Postgres advisory lock (&lt;code&gt;pg_advisory_xact_lock&lt;/code&gt;), so two concurrent transactions can never read the same tip hash and fork the history.&lt;/p&gt;

&lt;p&gt;The same principle runs through the certificates: Ed25519 signatures instead of HMAC, so anyone can verify a certificate offline with only a public key, with no shared secret. Webhooks use a transactional outbox, so events cannot be lost or invented, and no message broker is needed to run offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The scope decision: normalized absolute scores
&lt;/h2&gt;

&lt;p&gt;Judging can be built two ways: judges score each project against a rubric, or judges compare two projects at a time (pairwise, Bradley-Terry style). I shipped normalized absolute scoring.&lt;/p&gt;

&lt;p&gt;It works directly with the weighted rubric, the result is recomputed from raw scores on demand (never trusted from storage), and I could check it independently against the real data. That made the correctness work possible: the normalization proof, the role-isolation tests and the verifiable audit trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would carry into the next build
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test the maths on messy real data, not a clean example.&lt;/strong&gt; My clean example proved the idea. The duplicate row and the flat judges decided the actual design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attack the running system early.&lt;/strong&gt; Role-isolation bugs hide in code that reads correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make failures loud.&lt;/strong&gt; &lt;code&gt;lazy="raise"&lt;/code&gt; and server-side checks turn "slow and silently wrong" into "fails immediately".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full normalization proof, the threat model and all the tests are in the repo: &lt;a href="https://github.com/Lakshya-Varshney/Verdict" rel="noopener noreferrer"&gt;https://github.com/Lakshya-Varshney/Verdict&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built for DOGFOOD. Tagging &lt;a class="mentioned-user" href="https://dev.to/partnerships_raptors"&gt;@partnerships_raptors&lt;/a&gt; for the Write Up Quest.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>python</category>
      <category>fastapi</category>
      <category>security</category>
    </item>
    <item>
      <title>Killed My Own Process 300 Times to Prove ChronoVault Doesn't Lie About Durability</title>
      <dc:creator>codewitharyan29</dc:creator>
      <pubDate>Thu, 03 Sep 2026 15:27:27 +0000</pubDate>
      <link>https://dev.to/codewitharyan29/chronovault-writes-9-13x-slower-than-diskcache-heres-why-i-shipped-it-anyway-3gb1</link>
      <guid>https://dev.to/codewitharyan29/chronovault-writes-9-13x-slower-than-diskcache-heres-why-i-shipped-it-anyway-3gb1</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built &lt;strong&gt;ChronoVault v2&lt;/strong&gt; for Hackathon Raptors' Zero Dependency Hackathon (Track D — Data &amp;amp; Storage) — a content-addressable snapshot and recovery engine, zero runtime dependencies, backed by a hand-rolled pack-file format. A comment saying "this is atomic" is worth nothing to a judge, so instead of asserting crash-safety, I hard-killed my own writer process mid-write, over and over, and made the vault prove it survived. This is that story, plus a benchmark number I didn't want to publish and three Windows bugs that only showed up on real hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;279 tests. 20 CLI commands. 0 runtime dependencies. &lt;code&gt;judge_mode.py&lt;/code&gt; VERIFIED on Python 3.11 and 3.14.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Proving crash-safety instead of asserting it
&lt;/h2&gt;

&lt;p&gt;Anyone can write "atomic writes, crash-safe" in a README. It costs nothing and proves nothing. So &lt;code&gt;recover-check&lt;/code&gt; is tested against a real &lt;code&gt;SIGKILL&lt;/code&gt;, not a simulated one: a writer subprocess runs a snapshot, gets hard-killed at a randomized point mid-write, and the vault is reopened cold — no manual repair step allowed.&lt;/p&gt;

&lt;p&gt;The test checks three things, not one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every snapshot acknowledged &lt;em&gt;before&lt;/em&gt; the kill is intact.&lt;/li&gt;
&lt;li&gt;The in-flight snapshot at the moment of the kill is either cleanly absent or a discarded partial write — never garbage returned from a read.&lt;/li&gt;
&lt;li&gt;The vault is immediately usable afterward, with no repair command required.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is also where the pack-quarantine logic earned its place instead of being a "nice to have." Early in testing, a kill landing mid-pack-write left a truncated pack that the recovery scan initially tried to read as valid — and returned a wrong length from. Quarantine — isolate anything that fails its checksum before it ever reaches the index — closed that hole. The fix wasn't "trust the file less," it was "never let an unverified pack answer a read at all."&lt;/p&gt;

&lt;p&gt;Run it enough times and the interesting failures stop being about the crash itself and start being about the code paths a crash exposes that a clean shutdown never does. That's the actual value of hard-killing your own process 300 times instead of once: the first ten runs prove the happy path works, and the next 290 are what find the pack-quarantine gap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture at a glance
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    subgraph L1["CLI"]
        CLI["cli.py — 20 subcommands"]
    end

    subgraph L2["Core Engine"]
        Snap["snapshot.py&amp;lt;br/&amp;gt;atomic rename + fsync"]
        CAS["Content-Addressed Store&amp;lt;br/&amp;gt;hashlib SHA-256, pack files"]
        Idx["path_history.py&amp;lt;br/&amp;gt;rename-aware lineage"]
        Rec["recover.py&amp;lt;br/&amp;gt;recover-check, SIGKILL-tested replay"]
    end

    subgraph L3["Persistence"]
        Pack[("Pack files on disk")]
        Quar["Pack quarantine&amp;lt;br/&amp;gt;corrupt/truncated packs isolated"]
    end

    CLI --&amp;gt; Snap
    CLI --&amp;gt; CAS
    CLI --&amp;gt; Idx
    CLI --&amp;gt; Rec
    Snap --&amp;gt; CAS
    Snap --&amp;gt; Idx
    CAS --&amp;gt; Pack
    Idx --&amp;gt; Pack
    Rec -.-&amp;gt;|verified via SIGKILL injection| Snap
    Rec --&amp;gt; Pack
    Pack --&amp;gt; Quar

    style L3 fill:#2d2d2d,stroke:#f5a623,stroke-width:2px,color:#fff
    style Rec fill:#2d2d2d,stroke:#f5a623,stroke-width:2px,color:#fff&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The shape that matters is where recovery sits: it reads pack files directly, never through the CLI or the snapshot writer, the same way a &lt;code&gt;verify&lt;/code&gt;-style tool deliberately bypasses the normal open path. That's deliberate — if the write path itself is what crashed, recovery can't depend on any code the crash might have left half-updated.&lt;/p&gt;




&lt;h2&gt;
  
  
  The number I didn't want to publish
&lt;/h2&gt;

&lt;p&gt;Every other number in this post makes ChronoVault look good. This one doesn't.&lt;/p&gt;

&lt;p&gt;I benchmarked &lt;code&gt;vault snapshot&lt;/code&gt; against &lt;code&gt;diskcache&lt;/code&gt; — the closest real-world comparison for local content-addressed storage:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;ChronoVault&lt;/th&gt;
&lt;th&gt;diskcache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write throughput&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9–13x faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-disk size, repeated / near-duplicate snapshots&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5–47x smaller&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why it loses on writes: every &lt;code&gt;vault snapshot&lt;/code&gt; call pays for a full content hash of each file plus a pack-index lookup before a single byte is committed, because deduplication has to happen &lt;em&gt;before&lt;/em&gt; the write, not after. &lt;code&gt;diskcache&lt;/code&gt; skips that entirely. That's the whole trade in one sentence: I pay a hashing-and-lookup tax on every write so a hundred near-identical snapshots don't cost a hundred times the disk.&lt;/p&gt;

&lt;p&gt;If your workload is write-latency-bound, &lt;code&gt;diskcache&lt;/code&gt; is the right tool and I'm not pretending otherwise. ChronoVault wins when the workload is storage-bound — long retention windows, many near-duplicate snapshots, disk-constrained environments. A real loser column next to a real winner column, not a benchmark that only shows its best number.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the standard library actually made me suffer
&lt;/h2&gt;

&lt;p&gt;Three places, all invisible until I tested on real Windows hardware instead of assuming POSIX behavior generalizes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;multiprocessing&lt;/code&gt;'s spawn context raised a &lt;code&gt;KeyboardInterrupt&lt;/code&gt; that wasn't one.&lt;/strong&gt; Worker processes died mid-operation with nobody near Ctrl+C. Windows' spawn-based multiprocessing ties process teardown to &lt;code&gt;_winapi.WaitForSingleObject&lt;/code&gt;, which can surface as a spurious interrupt with no signal ever sent — a path fork-based Unix multiprocessing never touches. I misdiagnosed this &lt;strong&gt;three separate times&lt;/strong&gt; as my own bug before proving otherwise, using a detached &lt;code&gt;Start-Job&lt;/code&gt; test that ran the worker fully isolated from the parent's console.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;WinError 32&lt;/code&gt; — Windows refusing to delete a file it considers still open.&lt;/strong&gt; POSIX lets you &lt;code&gt;unlink()&lt;/code&gt; an open file; the inode survives until the last handle closes. Windows refuses the delete outright. Handling it meant retrying past the handle-release window instead of importing &lt;code&gt;psutil&lt;/code&gt; to hide it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A join-before-drain ordering bug deadlocked the IPC queue.&lt;/strong&gt; &lt;code&gt;worker.join()&lt;/code&gt; was called before the result queue was fully drained. If the OS-level pipe buffer filled before the parent drained it, the child blocked on a full pipe and the parent blocked on &lt;code&gt;join()&lt;/code&gt; — a deadlock that only appears under load. Fixed by draining before joining.&lt;/p&gt;

&lt;p&gt;None of these are exotic. They're the specific price of touching &lt;code&gt;multiprocessing&lt;/code&gt; and cross-process queues without a battle-tested wrapper library between you and the OS.&lt;/p&gt;




&lt;h2&gt;
  
  
  The edge case that ate an afternoon
&lt;/h2&gt;

&lt;p&gt;Rename-aware file lineage in the path-history index. The question sounds simple — "was this file renamed, or deleted and recreated?" — until content, path, and inode can each change independently, sometimes in the same tick, with zero heuristics borrowed from someone else's diffing library to lean on.&lt;/p&gt;

&lt;p&gt;The ambiguous case that actually happened: a file renamed &lt;em&gt;and&lt;/em&gt; edited in the same snapshot cycle. "Renamed A→B, then edited" and "A deleted, unrelated B created" are both defensible reads of the same delta, and guessing wrong either invents a false history or silently drops a real one.&lt;/p&gt;

&lt;p&gt;I didn't solve it by getting cleverer. I wrote down, explicitly, which cases the lineage tracker resolves correctly, and which ones it honestly doesn't try to disambiguate. A documented limitation beats a confident guess that's wrong on the exact case a judge tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;The Windows CI matrix went in &lt;em&gt;after&lt;/em&gt; most of the Windows-specific bugs had already been found manually, one laptop test at a time. Every area I later ran through real Windows hardware for the first time produced at least one genuine finding. If Ubuntu + Windows CI had existed from the start, at least two of those bugs would have surfaced in an automated run instead of on my own machine, days closer to the deadline than I'd have liked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where it landed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;279 tests&lt;/strong&gt; passing on Python 3.11 and 3.14, &lt;code&gt;judge_mode.py&lt;/code&gt; VERIFIED&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20 CLI commands&lt;/strong&gt;, zero third-party runtime dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crash-safety proven, not asserted&lt;/strong&gt; — &lt;code&gt;recover-check&lt;/code&gt; verified against real &lt;code&gt;SIGKILL&lt;/code&gt; injection, with pack quarantine closing the gap testing found&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI green&lt;/strong&gt; across Python 3.10–3.14 × Ubuntu + Windows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;STDLIB.md&lt;/code&gt;&lt;/strong&gt; documents 14 real substitutions, each cross-referenced to the file that uses it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All four bonus categories claimed (+16)&lt;/strong&gt; — Single File, Reproducible Build, Package Killer re-earned via the diskcache numbers above, STDLIB Log
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python judge_mode.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;runs the full verification — test suite, single-file hash check, isolated-mode dependency audit, and the content-addressing proof's 8 invariants — as one command, so a judge doesn't have to trust this post; they can re-derive it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;Anyone with an AI coding agent can produce a CLI that hashes files and calls it content-addressed storage. The hard part was building enough ways to catch myself being wrong — a crash-safety claim verified with a real &lt;code&gt;SIGKILL&lt;/code&gt; instead of a comment asserting atomicity, a benchmark table honest enough to put a 9–13x loss next to a 5–47x win, and a &lt;code&gt;judge_mode.py&lt;/code&gt; that re-derives every claim in this post instead of asking a judge to trust it. The vault is the artifact. The willingness to kill it 300 times and publish what survived is the actual submission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/codewitharyan29/ChronoVault-v2" rel="noopener noreferrer"&gt;github.com/codewitharyan29/ChronoVault-v2&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Track:&lt;/strong&gt; D — Data &amp;amp; Storage&lt;/p&gt;

&lt;p&gt;Built for Zero Dependency 2026, run by &lt;a href="https://dev.to/raptorsdev"&gt;@partnerships_raptors&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ZeroDependencyHack #Python #OpenSource #DeveloperTools #CLI
&lt;/h2&gt;

&lt;h2&gt;
  
  
  PythonStandardLibrary #BuildInPublic
&lt;/h2&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>hackhathonraptors</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Porting natsort to Rust taught me that "it compiles" is the easy 5%</title>
      <dc:creator>codewitharyan29</dc:creator>
      <pubDate>Thu, 06 Aug 2026 14:44:40 +0000</pubDate>
      <link>https://dev.to/codewitharyan29/porting-natsort-to-rust-taught-me-that-it-compiles-is-the-easy-5-2ac3</link>
      <guid>https://dev.to/codewitharyan29/porting-natsort-to-rust-taught-me-that-it-compiles-is-the-easy-5-2ac3</guid>
      <description>&lt;p&gt;TL;DR: I ported Python's natsort library to Rust for Port Mortem 2026.&lt;/p&gt;

&lt;p&gt;Writing the sorting algorithm turned out to be the easy part.&lt;/p&gt;

&lt;p&gt;The difficult part was proving the Rust implementation behaved &lt;strong&gt;identically&lt;/strong&gt; to the original Python library across thousands of edge cases without quietly changing behavior.&lt;/p&gt;

&lt;p&gt;That meant treating verification as the real project.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Why I chose natsort&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most demo ports are calculators, parsers, or utilities with a handful of tests.&lt;/p&gt;

&lt;p&gt;I wanted something that would actually stress the verification process.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;natsort&lt;/code&gt; sorts strings the way humans expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;file2
file10
file20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of lexicographic order.&lt;/p&gt;

&lt;p&gt;That sounds simple until you discover it supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;seven algorithm variants&lt;/li&gt;
&lt;li&gt;signed and floating-point numbers&lt;/li&gt;
&lt;li&gt;scientific notation&lt;/li&gt;
&lt;li&gt;full Unicode numeric characters&lt;/li&gt;
&lt;li&gt;locale-aware behavior&lt;/li&gt;
&lt;li&gt;Roman numerals&lt;/li&gt;
&lt;li&gt;fractions&lt;/li&gt;
&lt;li&gt;a real upstream test suite covering years of edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A port that looks correct on five examples proves almost nothing.&lt;/p&gt;

&lt;p&gt;A port that survives the library's own test suite, differential fuzzing, CLI comparisons, property testing, and mutation testing starts becoming evidence.&lt;/p&gt;




&lt;h1&gt;
  
  
  The architecture
&lt;/h1&gt;

&lt;p&gt;The verification pipeline ended up looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Python Test Suite
                    │
                    ▼
          Thin Python Adapter
                    │
                    ▼
             Compiled Rust Binary
                    │
                    ▼
          Rust natsort Implementation
                    │
                    ▼
 Compare:
 • stdout
 • stderr
 • exit codes
 • ordering
 • fuzz results
 • properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter intentionally contains almost no logic.&lt;/p&gt;

&lt;p&gt;Its only job is forwarding Python test inputs to the Rust implementation.&lt;/p&gt;

&lt;p&gt;That design choice turned out to matter more than I expected.&lt;/p&gt;




&lt;h1&gt;
  
  
  What broke (and why those bugs mattered)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Differential fuzzing found a bug in the original library
&lt;/h2&gt;

&lt;p&gt;Differential fuzzing generates thousands of random inputs and compares both implementations.&lt;/p&gt;

&lt;p&gt;Most mismatches were my mistakes.&lt;/p&gt;

&lt;p&gt;One wasn't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;natsorted&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1e400&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1e500&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1e400&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1e500&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;natsorted&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1e500&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1e400&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1e500&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1e400&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both calls succeed.&lt;/p&gt;

&lt;p&gt;Both disagree.&lt;/p&gt;

&lt;p&gt;The reason is surprisingly simple.&lt;/p&gt;

&lt;p&gt;Both values overflow to floating-point infinity.&lt;/p&gt;

&lt;p&gt;Since both become &lt;code&gt;inf&lt;/code&gt;, the comparison reports them equal, and Python's stable sort simply preserves whichever order the inputs arrived in.&lt;/p&gt;

&lt;p&gt;Rust's &lt;code&gt;f64&lt;/code&gt; behaves the same way.&lt;/p&gt;

&lt;p&gt;That means the correct port is &lt;strong&gt;not&lt;/strong&gt; one that "fixes" the behavior.&lt;/p&gt;

&lt;p&gt;The correct port reproduces it.&lt;/p&gt;

&lt;p&gt;I reported the issue upstream:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;natsort#192&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finding a real bug in a mature library simply by holding another implementation beside it was one of the most satisfying moments of the project.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. My adapter was lying to me
&lt;/h2&gt;

&lt;p&gt;I assumed the adapter was a thin wrapper.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;One function calculated the correct Rust-backed answer...&lt;/p&gt;

&lt;p&gt;...and then ignored it.&lt;/p&gt;

&lt;p&gt;Instead it silently fell back to a handwritten Python implementation that didn't understand signed numbers or Unicode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;index_natsorted&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;returned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;correct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Differential testing doesn't automatically catch bugs in the adapter.&lt;/p&gt;

&lt;p&gt;You have to verify the adapter itself.&lt;/p&gt;

&lt;p&gt;That lesson probably saved me more time than any optimization.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Windows disagreed with Linux
&lt;/h2&gt;

&lt;p&gt;Everything looked perfect.&lt;/p&gt;

&lt;p&gt;Until I ran it on Windows.&lt;/p&gt;

&lt;p&gt;Five bugs immediately appeared.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;executable needed &lt;code&gt;.exe&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;python3&lt;/code&gt; didn't exist (&lt;code&gt;py&lt;/code&gt; does)&lt;/li&gt;
&lt;li&gt;subprocess output defaulted to CP-1252 instead of UTF-8&lt;/li&gt;
&lt;li&gt;file reads had the same encoding problem&lt;/li&gt;
&lt;li&gt;Unix-only &lt;code&gt;resource&lt;/code&gt; crashed benchmarks entirely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those appeared on Linux.&lt;/p&gt;

&lt;p&gt;None appeared in my development environment.&lt;/p&gt;

&lt;p&gt;Every one appeared on a real Windows machine.&lt;/p&gt;

&lt;p&gt;"Works on my machine" turned out to be the weakest verification strategy of all.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. GitHub Actions lied with a green checkmark
&lt;/h2&gt;

&lt;p&gt;The deployment workflow looked successful.&lt;/p&gt;

&lt;p&gt;The live website returned 404.&lt;/p&gt;

&lt;p&gt;The culprit?&lt;/p&gt;

&lt;p&gt;A multi-line shell block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl ... | sh
build
deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installation silently failed.&lt;/p&gt;

&lt;p&gt;The following commands still exited successfully.&lt;/p&gt;

&lt;p&gt;GitHub reported the entire step as green.&lt;/p&gt;

&lt;p&gt;The fix wasn't clever.&lt;/p&gt;

&lt;p&gt;It was boring.&lt;/p&gt;

&lt;p&gt;I switched to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install &lt;/span&gt;wasm-pack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and added a deployment check that refuses to publish unless the expected build artifacts actually exist.&lt;/p&gt;

&lt;p&gt;A green checkmark only matters if it's checking the thing you care about.&lt;/p&gt;




&lt;h1&gt;
  
  
  How I proved behavioral equivalence
&lt;/h1&gt;

&lt;p&gt;The implementation wasn't the deliverable.&lt;/p&gt;

&lt;p&gt;Evidence was.&lt;/p&gt;

&lt;p&gt;I ended up with six independent layers of verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Upstream tests
&lt;/h3&gt;

&lt;p&gt;Run the original &lt;strong&gt;natsort&lt;/strong&gt; test suite without modifying it.&lt;/p&gt;

&lt;p&gt;Tests excluded only when Rust genuinely cannot reproduce Python-specific behavior, and every exclusion is documented.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Differential fuzzing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;27,000&lt;/strong&gt; seeded comparisons across all algorithm variants.&lt;/p&gt;

&lt;p&gt;Same inputs.&lt;/p&gt;

&lt;p&gt;Same outputs.&lt;/p&gt;

&lt;p&gt;Reproducible failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. CLI differential testing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;2,800&lt;/strong&gt; CLI invocations comparing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stdout&lt;/li&gt;
&lt;li&gt;stderr&lt;/li&gt;
&lt;li&gt;exit codes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Matching output alone isn't enough.&lt;/p&gt;

&lt;p&gt;Programs communicate failure through exit codes too.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Property testing
&lt;/h3&gt;

&lt;p&gt;Randomized verification of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;idempotence&lt;/li&gt;
&lt;li&gt;antisymmetry&lt;/li&gt;
&lt;li&gt;transitivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These find classes of bugs that handwritten examples never will.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Mutation testing
&lt;/h3&gt;

&lt;p&gt;I deliberately broke my implementation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reversed comparisons&lt;/li&gt;
&lt;li&gt;removed signs&lt;/li&gt;
&lt;li&gt;altered parsing logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the verification pipeline couldn't detect injected bugs, it wasn't trustworthy.&lt;/p&gt;

&lt;p&gt;Every injected mutation was caught.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Honest benchmarking
&lt;/h3&gt;

&lt;p&gt;Instead of publishing a single "10× faster" headline, I reported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mean&lt;/li&gt;
&lt;li&gt;median&lt;/li&gt;
&lt;li&gt;p95&lt;/li&gt;
&lt;li&gt;p99&lt;/li&gt;
&lt;li&gt;peak memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real result was &lt;strong&gt;6–8× faster&lt;/strong&gt;, depending on workload.&lt;/p&gt;

&lt;p&gt;Honest numbers build more confidence than inflated ones.&lt;/p&gt;




&lt;h1&gt;
  
  
  The decision I'd take back
&lt;/h1&gt;

&lt;p&gt;I spent a huge amount of time chasing perfect Unicode parity.&lt;/p&gt;

&lt;p&gt;It was technically rewarding.&lt;/p&gt;

&lt;p&gt;It pushed differential matching from roughly &lt;strong&gt;43%&lt;/strong&gt; to &lt;strong&gt;100%&lt;/strong&gt; across the full Unicode character set.&lt;/p&gt;

&lt;p&gt;I'm proud of that work.&lt;/p&gt;

&lt;p&gt;But I crossed the point of diminishing returns.&lt;/p&gt;

&lt;p&gt;One adapter-level discrepancy survived until the deadline on a single CI runner.&lt;/p&gt;

&lt;p&gt;I excluded that specific test rather than claim support I couldn't fully defend.&lt;/p&gt;

&lt;p&gt;Looking back, I'd stop polishing Unicode earlier and spend those hours investigating the remaining verification discrepancy instead.&lt;/p&gt;

&lt;p&gt;Verification gets exponentially more expensive near the finish line.&lt;/p&gt;

&lt;p&gt;Knowing when to stop is part of engineering.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final numbers
&lt;/h1&gt;

&lt;p&gt;By the end of the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Original upstream tests running against Rust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;27,000&lt;/strong&gt; differential fuzz comparisons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2,800&lt;/strong&gt; CLI differential runs&lt;/li&gt;
&lt;li&gt;Property testing&lt;/li&gt;
&lt;li&gt;Mutation testing&lt;/li&gt;
&lt;li&gt;Honest benchmark suite&lt;/li&gt;
&lt;li&gt;6–8× speedup&lt;/li&gt;
&lt;li&gt;One upstream bug reported&lt;/li&gt;
&lt;li&gt;Cross-platform verification on Linux and Windows&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Reproduce everything
&lt;/h1&gt;

&lt;p&gt;Everything needed to verify the project is public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/jVz_2AP4U4w" rel="noopener noreferrer"&gt;https://youtu.be/jVz_2AP4U4w&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live WASM demo + verification dashboard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codewitharyan29.github.io/Port-Mortem/" rel="noopener noreferrer"&gt;https://codewitharyan29.github.io/Port-Mortem/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codewitharyan29/Port-Mortem" rel="noopener noreferrer"&gt;https://github.com/codewitharyan29/Port-Mortem&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upstream issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SethMMorton/natsort/issues/192" rel="noopener noreferrer"&gt;https://github.com/SethMMorton/natsort/issues/192&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;**Hackathon rapters : &lt;a href="https://www.raptors.dev/project/code-resurrection-2026-port-mortem" rel="noopener noreferrer"&gt;https://www.raptors.dev/project/code-resurrection-2026-port-mortem&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Closing thoughts
&lt;/h1&gt;

&lt;p&gt;Anyone with an AI coding agent can produce something that compiles.&lt;/p&gt;

&lt;p&gt;Compilation is the easy part.&lt;/p&gt;

&lt;p&gt;The difficult part is proving—without hand-waving—that another implementation behaves the same as the original across thousands of edge cases, different operating systems, different execution paths, and even the original project's own bugs.&lt;/p&gt;

&lt;p&gt;By the end of this project, I stopped measuring success by whether the Rust code compiled.&lt;/p&gt;

&lt;p&gt;I measured success by how much evidence I had that it behaved like Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for Port Mortem / Code Resurrection by Hackathon Raptors
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;#PortMortem2026 #HackathonRaptors #Rust #Python #OpenSource #SystemsProgramming #Testing #Verification #Fuzzing&lt;/em&gt;&lt;br&gt;
**&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>python</category>
      <category>rust</category>
    </item>
  </channel>
</rss>
