<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Octallium</title>
<link>https://octallium.com/</link>
<atom:link href="https://octallium.com/rss.xml" rel="self" type="application/rss+xml"/>
<description>Code, mathematics, systems, and the industry around them.</description>
<language>en-gb</language>
<lastBuildDate>Tue, 18 Aug 2026 13:54:40 GMT</lastBuildDate>
<copyright>Octallium Inc</copyright>
<item><title>SpaceX buys Cursor, and the developer-tool endgame arrives early</title><link>https://octallium.com/articles/spacex-cursor-acquisition/</link><guid isPermaLink="false">https://octallium.com/articles/spacex-cursor-acquisition/</guid><pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Industry</category><category>acquisitions</category><category>developer tools</category><category>ai</category><category>aerospace</category><description>A launch company acquiring an AI code editor sounds like a category error. Read it as vertical integration of the thing that actually gates aerospace software, and it stops being strange.</description><content:encoded><![CDATA[<p>The first reaction to SpaceX acquiring Cursor was that somebody had misread a headline. Launch vehicles and text editors do not obviously belong on the same balance sheet. The second reaction, which took about a day to arrive, was that this is the most legible acquisition in developer tooling since Microsoft bought GitHub — and that the logic was visible for anyone who had been watching how much of a modern aerospace programme is, in practice, a software programme wearing a rocket costume.</p><p>What follows is an argument about structure rather than a report on terms. Deal specifics were not public at the time of writing and are not guessed at here. The interesting question was never the number; it is what happens to a tool used by a very large number of engineers when it stops being a company and becomes a department.</p><h2>Why a launch company wants an editor</h2><p>Flight software is not written the way web software is written. It is specified, reviewed, simulated, formally argued about, and then written — and the writing is the cheapest step. The expensive steps are the ones that establish that the written thing matches the specified thing. That is exactly the seam where the current generation of AI coding tools has been quietly useful: not in generating novel code, but in maintaining a live correspondence between a large body of source and a large body of intent.</p><p>An organisation with tens of thousands of engineers and an unusually severe correctness requirement has two options. It can buy that capability as a subscription from a vendor whose roadmap it does not control and whose model providers it cannot audit. Or it can own the surface where every engineer meets the codebase. The second option looks expensive right up until you price the first one over a decade.</p><h2>The context window is the moat</h2><p>Editors have historically been a terrible business. They are sticky, beloved, and nearly impossible to charge for, which is why the good ones have mostly been loss leaders for something else — a cloud, a compiler, a language. What changed is that the editor became the place where organisational context is assembled. Every retrieval decision, every file the assistant chooses to read, every past incident it surfaces at review time: that is a company&apos;s institutional memory being indexed and served back at the moment of a decision.</p><p>Whoever owns that assembly step owns something considerably more durable than a text buffer [2]. It is the same insight that has made merge-time tooling interesting — the observation that the valuable artefact is not the diff but everything the organisation already knows about code that looks like this diff.</p><ul><li>Retrieval policy — which parts of a monorepo the assistant is allowed to see, and which it must not.</li><li>Provenance — whether a suggestion came from your codebase, a public corpus, or a model&apos;s parameters.</li><li>Residency — where the index lives, which for a defence-adjacent contractor is not a preference but a requirement.</li><li>Continuity — whether the tool still exists, unchanged, in seven years.</li></ul><p>Read that list again as a procurement document rather than a feature list [1]. Every item is a reason a large regulated engineering organisation cannot comfortably rent this capability, and every item resolves the moment it owns the vendor.</p><h2>What happens to everyone else</h2><p>The immediate question for the several million engineers who use the tool is whether it continues to exist for them. The optimistic precedent is GitHub, which grew after acquisition and kept most of its character. The pessimistic precedents are numerous and shorter to write about, because the products in them no longer have names.</p><p>The distinction that matters is between an acquirer that wants the product and one that wants the team. In the first case the external user base is an asset and it survives. In the second it is an operating cost with no strategic return, and it is wound down politely over eighteen months. Nothing announced so far settles which of those this is, and anyone telling you otherwise is reading tea leaves.</p><h2>The real signal</h2><p>Strip out the novelty of the acquirer and a plainer story remains: AI-assisted development has stopped being a productivity add-on and started being infrastructure, and infrastructure gets bought by the people who cannot afford for it to be someone else&apos;s. Expect the next three of these to be less surprising and no less consequential — a defence prime, a bank, a chipmaker. The pattern is not aerospace. The pattern is that the tools sitting closest to a company&apos;s source code are now too load-bearing to rent.</p><p><em>Tables and pull quotes are omitted from the feed. <a href="https://octallium.com/articles/spacex-cursor-acquisition/">Read the full article</a>.</em></p>]]></content:encoded></item><item><title>Type systems you can argue with</title><link>https://octallium.com/articles/type-systems-you-can-argue-with/</link><guid isPermaLink="false">https://octallium.com/articles/type-systems-you-can-argue-with/</guid><pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Programming</category><category>type theory</category><category>verification</category><category>rust</category><category>liquid types</category><description>Refinement types have spent twenty years as a research curiosity. The tooling finally caught up, and the argument for them changed from elegance to economics.</description><content:encoded><![CDATA[<p>Every type system draws a line between the properties it will check and the properties it will let you assert. Move the line and you trade compile time and cognitive load for a class of bugs that stops existing. The interesting development of the last few years is not that the line moved — it moves constantly — but that the cost of moving it fell far enough that the trade is now worth making in ordinary code, rather than only in avionics and cryptography.</p><h2>The shape of a refinement</h2><p>A refinement type is a base type plus a predicate. Where an ordinary signature says a function takes an integer, a refined one says it takes an integer greater than zero, and the compiler discharges that obligation at every call site by handing it to a solver. The syntax is unremarkable; the consequence is not.</p><pre><code>#[requires(idx &lt; xs.len())]
fn get_unchecked&lt;T&gt;(xs: &amp;[T], idx: usize) -&gt; &amp;T {
    // No bounds check is emitted: the caller proved this.
    unsafe { xs.get_unchecked(idx) }
}

fn sum_prefix(xs: &amp;[i32], n: usize) -&gt; i32 {
    let mut total = 0;
    let mut i = 0;
    // The loop invariant i &lt; n &lt;= xs.len() discharges the
    // precondition above on every iteration.
    while i &lt; n &amp;&amp; n &lt;= xs.len() {
        total += get_unchecked(xs, i);
        i += 1;
    }
    total
}</code></pre><p>The thing to notice is what did not happen. No proof was written. No tactic language was invoked. The obligation was generated by the compiler, shipped to an SMT solver, and discharged in single-digit milliseconds — and if it had failed, the error would have named the call site rather than the theorem.</p><h2>Why now</h2><p>Three things converged. Solvers got substantially faster at the fragment that actually appears in systems code — linear arithmetic over bounded integers, with arrays. Compilers grew the intermediate representations needed to generate obligations without a separate front end. And, least discussed but probably most important, error reporting got good enough that a failed obligation reads like a type error instead of a proof-assistant transcript.</p><ol><li>Solver throughput on the relevant fragment improved by roughly two orders of magnitude over the decade, mostly through better preprocessing rather than better core search.</li><li>Borrow checking normalised the idea that the compiler may reject a correct program because it cannot see why it is correct.</li><li>Incremental checking made the feedback loop interactive, which is the difference between a tool engineers use and a tool engineers schedule.</li></ol><p>That third point deserves more weight than it usually gets. A verification tool with a ninety-second turnaround is a batch job, and batch jobs get run before releases. A verification tool with a two-second turnaround is a type checker, and type checkers get run on every keystroke. Nothing about the underlying mathematics changed between those two products.</p><blockquote><p>A proof obligation that arrives while you are still holding the context in your head costs almost nothing. The same obligation delivered an hour later costs the whole context reload.</p></blockquote><h2>Where it still hurts</h2><p>Refinements are excellent at properties that are local and arithmetic, and poor at properties that are global and temporal. Anything phrased as &quot;eventually&quot; or &quot;for every interleaving&quot; remains firmly in model-checking territory, and pretending otherwise leads to the failure mode where a codebase accumulates enormous decorative predicates that prove very little at considerable cost.</p><p>The pragmatic position is to refine the boundaries — indices, capacities, lifetimes, units — and leave the interior alone. That captures most of the bug classes worth eliminating and almost none of the annotation burden that historically killed these systems.</p><p><em>Tables and pull quotes are omitted from the feed. <a href="https://octallium.com/articles/type-systems-you-can-argue-with/">Read the full article</a>.</em></p>]]></content:encoded></item><item><title>What makes a proof explanatory</title><link>https://octallium.com/articles/what-makes-a-proof-explanatory/</link><guid isPermaLink="false">https://octallium.com/articles/what-makes-a-proof-explanatory/</guid><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Mathematics</category><category>proof theory</category><category>philosophy of mathematics</category><category>combinatorics</category><description>Two proofs of the same theorem can differ enormously in how much they tell you. Mathematicians have argued about why for a century, and the answer turns out to matter for how we teach machines.</description><content:encoded><![CDATA[<p>There is a proof that the sum of the first n odd numbers is n squared which proceeds by induction, and there is a proof which consists of drawing a square and noticing that it decomposes into L-shaped shells of sizes one, three, five, and so on. Both are complete. Both are rigorous. Only one of them tells you why the theorem is true, and almost everyone agrees on which.</p><p>This is awkward, because rigour is supposed to be the whole game. If two arguments both establish the same fact with the same certainty, on what grounds can one be better? And yet the intuition is nearly universal, survives translation between fields, and shows up in how working mathematicians choose what to publish.</p><h2>The standard accounts</h2><p>The literature offers roughly three answers, none of them fully satisfying, and the disagreements between them are more interesting than any of them individually.</p><ul><li>Unification — an explanatory proof is one that derives the result from a principle which also derives many other results. Explanation is economy of assumption.</li><li>Generality — an explanatory proof is one that shows the theorem is an instance of something larger, so that the specific hypotheses can be seen as inessential.</li><li>Visualisability — an explanatory proof is one you can hold in your head as a single object rather than a sequence of steps. This is the shell decomposition.</li></ul><p>The third is the least respectable and the most predictive. Ask a room of mathematicians which proof of the Pythagorean theorem is explanatory and the rearrangement argument wins over the coordinate computation nearly unanimously, despite the coordinate computation being shorter, more general, and more mechanically checkable.</p><h2>Why this suddenly matters</h2><p>For most of the twentieth century this was a question for philosophers of mathematics and nobody else. It became an engineering question the moment automated systems started producing proofs at scale, because an automated prover optimises for whatever you tell it to optimise for, and &quot;shortest&quot; is much easier to specify than &quot;most explanatory&quot;.</p><p>The result is a growing corpus of machine-found proofs that are certainly correct and almost entirely opaque — case splits over thousands of configurations, each individually trivial, collectively meaningless. They settle the question and teach nothing. For a formal verification pipeline that is perfectly acceptable. For mathematics it is a strange kind of loss: the theorem is now known, and no one understands it.</p><p>Which suggests the useful reframing. Explanation is not a property of the proof; it is a relation between the proof and a reader who wants to do something next. The shell decomposition is explanatory because it hands you a method — decompose the object, count the shells — that transfers. Induction hands you nothing but the theorem, which is exactly what you asked for and less than you wanted.</p><p><em>Tables and pull quotes are omitted from the feed. <a href="https://octallium.com/articles/what-makes-a-proof-explanatory/">Read the full article</a>.</em></p>]]></content:encoded></item><item><title>The latency was in the serialiser (it usually is)</title><link>https://octallium.com/series/systems-at-scale/the-latency-was-in-the-serialiser/</link><guid isPermaLink="false">https://octallium.com/series/systems-at-scale/the-latency-was-in-the-serialiser/</guid><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Systems</category><category>performance</category><category>profiling</category><category>distributed systems</category><category>postmortem</category><description>A tail-latency investigation that went through the network stack, the scheduler, and the garbage collector before finding the answer in the least interesting layer in the system.</description><content:encoded><![CDATA[<p>The symptom was a p99.9 of 340 milliseconds against a p50 of four [1]. That ratio is the signature of something categorical rather than gradual — not a system under load, but a system doing an entirely different thing on a small fraction of requests. The investigation took nine days, and eight of them were spent in the wrong layer.</p><h2>The wrong answers, in order</h2><p>Tail latency investigations have a standard suspect list, and there is a strong temptation to work through it in order of how interesting each suspect would be. This is precisely backwards [2], and it is worth writing down why each candidate looked right and was not.</p><ol><li>Garbage collection. The pause histogram had a tail in roughly the right place. It was not correlated with the slow requests — collection was frequent enough that the overlap was coincidental, which took two days and a joint histogram to establish.</li><li>Scheduler preemption. Plausible on a busy host, and the involuntary context-switch counter was high. It was high on every request, including fast ones.</li><li>Head-of-line blocking. The connection pool was small enough to be suspicious. Enlarging it changed nothing except memory use.</li><li>DNS. It is never DNS, except when it is. It was not.</li></ol><p>What eventually broke the investigation open [3] was giving up on hypotheses and taking a wall-clock profile of the slow requests specifically, rather than of the process as a whole. Aggregate profiles are actively misleading for tail problems: the slow path is by construction 0.1% of samples, and it disappears into the noise floor of the fast path.</p><pre><code># Filtered flame profile, slow requests only (n=1,284)
  87.2%  encode_response
    84.9%  serialise_field
      81.1%  reflect.TypeOf            &lt;-- here
       2.4%  appendString
    1.8%   growSlice
   6.1%  write_socket
   4.3%  handler</code></pre><h2>What it actually was</h2><p>The serialiser cached type descriptors in a map keyed by reflected type. The cache was correct, fast, and had a lock. Under nearly all conditions the lock was uncontended and the whole thing cost nothing. But one response type was constructed dynamically, so it produced a fresh type identity on every request, so it never hit the cache, so every one of those requests took the slow path through reflection while holding the lock — and blocked every other serialisation on the host for the duration.</p><p>The fix was four lines: give the dynamic type a stable identity so it caches like everything else. The p99.9 fell to eleven milliseconds. The p50 did not move, which is the tell — a genuine tail fix should be invisible in the median, and any change that improves both was probably measuring something else.</p><h2>The transferable part</h2><p>Three things generalise. Profile the slow population, not the population. Suspect the boring layer before the interesting one, because the boring layer is where nobody has looked recently. And treat any lock held across a variable-cost operation as a latency amplifier by default — it converts one slow request into every concurrent request being slow, which is exactly the shape of a tail that appears from nowhere.</p><p><em>Tables and pull quotes are omitted from the feed. <a href="https://octallium.com/series/systems-at-scale/the-latency-was-in-the-serialiser/">Read the full article</a>.</em></p>]]></content:encoded></item><item><title>Small models, large context, and the return of retrieval</title><link>https://octallium.com/articles/small-models-large-context/</link><guid isPermaLink="false">https://octallium.com/articles/small-models-large-context/</guid><pubDate>Sun, 02 Aug 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Frontier</category><category>machine learning</category><category>retrieval</category><category>inference</category><category>cost</category><description>The scaling story quietly forked. One branch kept getting bigger; the other got better at knowing what to read, and it is the second one showing up in production.</description><content:encoded><![CDATA[<p>For several years the answer to almost every capability question was more parameters, and it was a good answer because it kept being right. What has changed is not that scaling stopped working — it did not — but that a second axis became cheap enough to trade against it, and on a large class of tasks the trade is now lopsided.</p><h2>The economics of knowing where to look</h2><p>A model that has memorised a fact answers instantly and cannot tell you when it learned it, whether it is still true, or where it came from. A smaller model with good retrieval answers slightly slower, cites its source, and updates the moment the source does. For consumer chat the first is usually preferable. For anything with an audit requirement the second is not merely preferable, it is the only admissible option.</p><p>That last row is where practitioners have moved. Both approaches fail; the retrieval failure leaves a trail. When a retrieval system is wrong you can look at what it fetched and see immediately why the answer went where it went, and that debuggability compounds over the life of a system in a way that raw capability does not.</p><h2>What actually got better</h2><ul><li>Chunking stopped being naive. Boundaries follow document structure rather than token counts, which removed a large fraction of the retrieval failures that used to be blamed on embeddings.</li><li>Rerankers got cheap enough to run on every query rather than as an optimisation.</li><li>Long contexts stopped degrading in the middle, which made the retrieve-a-lot-and-let-the-model-sort-it strategy viable.</li><li>Evaluation improved — the single largest practical gain, because most retrieval systems were previously tuned against intuition.</li></ul><p>None of these is a breakthrough. All of them are engineering, and the aggregate effect is that a small model with a well-built index now beats a much larger model without one on most tasks that involve a corpus you own. Which is nearly every task inside a company.</p><p><em>Tables and pull quotes are omitted from the feed. <a href="https://octallium.com/articles/small-models-large-context/">Read the full article</a>.</em></p>]]></content:encoded></item><item><title>Cache invalidation is a naming problem</title><link>https://octallium.com/series/systems-at-scale/cache-invalidation-is-a-naming-problem/</link><guid isPermaLink="false">https://octallium.com/series/systems-at-scale/cache-invalidation-is-a-naming-problem/</guid><pubDate>Wed, 29 Jul 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Systems</category><category>caching</category><category>architecture</category><category>distributed systems</category><description>The joke says there are two hard problems. There is really only one, and the second is the first wearing a different hat.</description><content:encoded><![CDATA[<p>Invalidation is hard because it requires you to know, at write time, every identity under which a piece of data might later be requested. That is not a cache problem. That is the problem of having given the data a name that does not fully determine what it depends on — which is to say, a naming problem.</p><p>Systems that make invalidation easy all do the same thing: they make the key a function of the inputs, so that changing an input changes the key and the old entry becomes unreachable rather than wrong. Content addressing is the pure form of this. Most practical systems approximate it, and the quality of the approximation predicts how much invalidation logic they end up carrying.</p><blockquote><p>You never invalidate a content-addressed cache. You simply stop asking for the old thing.</p></blockquote><p>The corollary is that every explicit invalidation call in a codebase marks a place where a key failed to capture a dependency. Counting them is a surprisingly good architectural health metric, and unlike most such metrics it points directly at the fix.</p>]]></content:encoded></item><item><title>The monorepo question is settled, and both sides won</title><link>https://octallium.com/articles/the-monorepo-question-is-settled/</link><guid isPermaLink="false">https://octallium.com/articles/the-monorepo-question-is-settled/</guid><pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Programming</category><category>build systems</category><category>tooling</category><category>organisation</category><description>Ten years of argument resolved into an unglamorous consensus that nobody claimed as a victory.</description><content:encoded><![CDATA[<p>The monorepo debate was never really about repository count. It was about whether a change should be able to cross a module boundary atomically, and whether the cost of making that possible is worth paying. Framed that way, the answer that emerged is obvious in hindsight: it depends entirely on how often changes need to cross those boundaries, and organisations differ wildly on that.</p><p>What actually settled the argument was tooling convergence. Build graphs, remote caching, and code ownership at path granularity are now available in both topologies, which removed most of the concrete advantages each side used to claim. The remaining difference is genuinely organisational rather than technical, and organisational questions do not have universal answers.</p><ul><li>If a typical feature touches three services owned by three teams, the coordination cost of many repositories will dominate. Use one.</li><li>If a typical feature touches one service and the teams ship independently, the ceremony of a shared build graph is pure overhead. Use many.</li><li>If you cannot tell which of those describes you, instrument it. The answer is in your merge history and it takes an afternoon to extract.</li></ul>]]></content:encoded></item><item><title>Funding rounds are a lagging indicator of everything</title><link>https://octallium.com/articles/funding-rounds-are-a-lagging-indicator/</link><guid isPermaLink="false">https://octallium.com/articles/funding-rounds-are-a-lagging-indicator/</guid><pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Industry</category><category>venture</category><category>analysis</category><category>media</category><description>By the time a round is announced the interesting decisions are eighteen months old. Reading the announcement as news is reading the wrong document.</description><content:encoded><![CDATA[<p>A funding announcement records a negotiation that concluded weeks earlier, about a business plan drafted months before that, in response to a market read that is older still. Treating it as a signal about the present is a category error, and it is the single most common one in technology coverage.</p><p>The documents that do carry present-tense information are duller and mostly public: job postings, which reveal what a company has decided to build before it will say so; pricing pages, which reveal who it has decided to sell to; and changelogs, which reveal whether any of it is actually shipping.</p><p>This is not an argument against covering funding. It is an argument for covering it as history — useful for establishing what a company can afford to attempt, useless for establishing what it is attempting.</p><p><em>Tables and pull quotes are omitted from the feed. <a href="https://octallium.com/articles/funding-rounds-are-a-lagging-indicator/">Read the full article</a>.</em></p>]]></content:encoded></item><item><title>Counting without listing</title><link>https://octallium.com/series/discrete-mathematics/counting-without-listing/</link><guid isPermaLink="false">https://octallium.com/series/discrete-mathematics/counting-without-listing/</guid><pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Mathematics</category><category>combinatorics</category><category>counting</category><category>discrete mathematics</category><description>Combinatorics is the art of knowing how many things there are without producing them. Four rules do almost all of the work, and the fourth is the one people get wrong.</description><content:encoded><![CDATA[<p>Every complexity analysis you have ever read rests on a counting argument, usually an unstated one. How many subsets, how many orderings, how many paths — the algorithm&apos;s cost is a count, and the analysis is the act of finding that count without enumerating the things being counted. This is the whole subject in one sentence, and everything that follows is technique.</p><h2>The four rules</h2><p>Almost every elementary count is one of four moves, or a composition of them. They are worth stating precisely, because the errors people make are almost always a misapplication of the fourth.</p><ol><li>SUM. If a thing is either an A or a B and never both, the count is |A| + |B|. The failure mode is forgetting the &apos;never both&apos;.</li><li>PRODUCT. If a thing is an A followed independently by a B, the count is |A| × |B|. The failure mode is that the second choice depends on the first.</li><li>BIJECTION. If you can pair the things you want with things you can already count, the counts are equal. This is the most powerful of the four and the least used.</li><li>DIVISION. If your count sees every object exactly k times, divide by k. This is where the errors live.</li></ol><p>The division rule is treacherous because it silently assumes the overcount is uniform — that every object is seen the same number of times. When it is not, the division is meaningless and the answer is confidently wrong. Necklaces are the standard counterexample: rotating a necklace of beads usually gives you a different arrangement, but a necklace of all one colour is fixed by every rotation, so it is overcounted once rather than n times.</p><h2>Bijections earn their keep</h2><p>The bijection rule deserves special attention because it converts hard counts into easy ones without any arithmetic at all. The classic: how many ways can you choose k items from n? Rather than reason about choices, pair each selection with a binary string of length n containing exactly k ones. Now the question is how many such strings there are, which is a question about arrangements, which you can already answer.</p><pre><code>choose 3 from {a,b,c,d,e}          binary string of length 5
  {a, c, d}              &lt;--&gt;      1 0 1 1 0
  {b, d, e}              &lt;--&gt;      0 1 0 1 1
  {a, b, c}              &lt;--&gt;      1 1 1 0 0</code></pre><p>The discipline is to check both directions. A map that is onto but not one-to-one is an overcount, not a bijection, and it puts you back in rule four with all its hazards.</p><h2>Why this generalises</h2><p>These four rules reappear, barely disguised, throughout the rest of this series. Inclusion-exclusion is the sum rule repaired for overlapping sets. Generating functions are the product rule made algebraic so that it composes. Graph colouring counts are bijections in disguise. Learn the four properly and the later material stops being a sequence of tricks.</p><p><em>Tables and pull quotes are omitted from the feed. <a href="https://octallium.com/series/discrete-mathematics/counting-without-listing/">Read the full article</a>.</em></p>]]></content:encoded></item><item><title>Induction is recursion, read backwards</title><link>https://octallium.com/series/discrete-mathematics/induction-is-recursion/</link><guid isPermaLink="false">https://octallium.com/series/discrete-mathematics/induction-is-recursion/</guid><pubDate>Sun, 12 Jul 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Mathematics</category><category>induction</category><category>recursion</category><category>proof</category><category>discrete mathematics</category><description>Every recursive function you have written is a proof by induction you did not know you were writing. Seeing the correspondence makes both halves easier.</description><content:encoded><![CDATA[<p>A recursive function has a base case and a recursive case, and it terminates because each call moves strictly closer to the base. A proof by induction has a base case and an inductive step, and it is valid because every instance is reachable from the base in finitely many steps. These are not analogous. They are the same structure, written by two communities who mostly do not read each other.</p><h2>The correspondence</h2><p>The practical consequence is that anyone who can write a correct recursive function can write a proof by induction, and the step they find hard — &apos;am I allowed to just assume it holds for n?&apos; — is a step they already take every time they trust a recursive call.</p><blockquote><p>You do not verify the recursive call. You assume it works and check that the surrounding case is right. That assumption is the inductive hypothesis.</p></blockquote><h2>Strong induction, and why it is not stronger</h2><p>Strong induction assumes the statement for all values below n rather than just for n − 1. This looks like a more powerful principle and is not: each form can be derived from the other. What it is, is a more convenient one — and it corresponds exactly to a recursive function that may call itself on any smaller input rather than only on its immediate predecessor.</p><p>Merge sort is the canonical example on both sides. It recurses on halves, not on n − 1, so its correctness proof needs the hypothesis for all smaller sizes. Nobody finds the code surprising; the proof surprises people only because it is presented as a separate principle rather than as the same code with the types erased.</p><h2>Where the correspondence breaks</h2><p>It breaks at infinity. A recursive function must terminate, so its measure must be well-founded and finite. Transfinite induction has no computational counterpart — there is no function you can run that recurses through the ordinals. For everything a program can actually do, though, the two are interchangeable, and treating them as one idea is worth more than treating them as two.</p><p><em>Tables and pull quotes are omitted from the feed. <a href="https://octallium.com/series/discrete-mathematics/induction-is-recursion/">Read the full article</a>.</em></p>]]></content:encoded></item><item><title>Graphs are the default shape of everything</title><link>https://octallium.com/series/discrete-mathematics/graphs-are-the-default-shape/</link><guid isPermaLink="false">https://octallium.com/series/discrete-mathematics/graphs-are-the-default-shape/</guid><pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate><dc:creator>Editorial</dc:creator><category>Mathematics</category><category>graph theory</category><category>discrete mathematics</category><category>algorithms</category><description>Trees, orders, dependencies, state machines and schedules are all graphs wearing different vocabularies. Learning the general object once beats learning five special cases.</description><content:encoded><![CDATA[<p>A graph is a set of things and a set of pairs of those things. That is the entire definition, and its poverty is the point: almost any structure you meet in computing turns out to be a graph with extra adjectives, which means a result proved about graphs in general applies to all of them at once.</p><h2>The adjectives</h2><ul><li>Acyclic and connected — a tree. Every hierarchy, every parse, every filesystem.</li><li>Acyclic and directed — a DAG. Every build system, every dependency resolver, every migration order.</li><li>Directed with labelled edges — a state machine. Every protocol and every parser.</li><li>Bipartite — a matching problem. Every assignment of workers to shifts or shards to nodes.</li><li>Weighted — a shortest-path problem. Every routing table and every scheduler.</li></ul><p>Notice how much of a working engineer&apos;s week is on that list. The value of naming the general object is that a technique learned in one row transfers to the others for free — topological order is the same idea whether you are ordering migrations or resolving imports.</p><h2>The two traversals</h2><p>Almost every graph algorithm is a traversal with bookkeeping attached, and there are only two traversals. The difference between them is one data structure.</p><pre><code>def traverse(graph, start, frontier):
    seen = {start}
    frontier.add(start)
    while frontier:
        node = frontier.take()        # pop() -&gt; DFS
        yield node                    # popleft() -&gt; BFS
        for nxt in graph[node]:
            if nxt not in seen:
                seen.add(nxt)
                frontier.add(nxt)</code></pre><p>Record the depth and breadth-first search gives you shortest paths on an unweighted graph. Record finish times and depth-first search gives you a topological order and the strongly connected components. Record the predecessor and you get the path itself. The algorithms are not separate inventions; they are annotations on one loop.</p><h2>What to take forward</h2><p>Two things. First, when a problem resists, ask what the vertices are and what the edges are — the question is frequently the whole solution. Second, resist the urge to write a custom traversal. The eleven lines above, plus whatever you record on the way, cover more ground than any special-purpose routine you are likely to write under time pressure.</p><p><em>Tables and pull quotes are omitted from the feed. <a href="https://octallium.com/series/discrete-mathematics/graphs-are-the-default-shape/">Read the full article</a>.</em></p>]]></content:encoded></item>
</channel>
</rss>