<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>The Engineering Journal — Boluwaji Joshua Adedigba</title><description>Field notes on building backend systems that remain reliable, observable, and adaptable as their responsibilities grow.</description><link>https://josh.naijora.com/</link><language>en-gb</language><atom:link href="https://josh.naijora.com/rss.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Tue, 01 Sep 2026 00:00:00 GMT</lastBuildDate><item><title>Move semantics, and why copies are expensive</title><link>https://josh.naijora.com/blog/move-semantics-and-the-cost-of-a-copy/</link><guid isPermaLink="true">https://josh.naijora.com/blog/move-semantics-and-the-cost-of-a-copy/</guid><description>A pointer copied twice will free the same memory twice. Following that bug to its fix explains deep copies, r-value references, and what moving an object really is.</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here is a class small enough to fit in your head and broken in a way that takes a while to see.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;private:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;data;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; d&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    ~Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; d) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    data &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;    // grab some heap&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    *&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;data &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; d;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    delete&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; data;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;       // give it back&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Constructor allocates, destructor frees. That is the deal, and it holds right up until someone copies the object.&lt;/p&gt;
&lt;h2 id=&quot;the-default-copy-is-a-member-wise-copy&quot;&gt;The default copy is a member-wise copy&lt;/h2&gt;
&lt;p&gt;Write no copy constructor and C++ writes one for you. It copies each member across, one at a time. &lt;code&gt;data&lt;/code&gt; is a member, so &lt;code&gt;data&lt;/code&gt; gets copied.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;data&lt;/code&gt; is a pointer. Copying a pointer copies the address, not the thing at the address.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  before                          after  Shallow b {a};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  a.data ──▶ [ 42 ]               a.data ──┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                           ├──▶ [ 42 ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                  b.data ──┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two objects. One buffer. Both destructors will run, and both will call &lt;code&gt;delete&lt;/code&gt; on the same address. The first free is fine. The second is undefined behaviour, which in practice means a crash somewhere unrelated, minutes later, in code that did nothing wrong.&lt;/p&gt;
&lt;p&gt;Worse: while both are alive, writing through &lt;code&gt;a&lt;/code&gt; changes what &lt;code&gt;b&lt;/code&gt; sees. They are not two accounts. They are one account with two names.&lt;/p&gt;
&lt;h2 id=&quot;the-fix-is-to-copy-what-is-pointed-at&quot;&gt;The fix is to copy what is pointed at&lt;/h2&gt;
&lt;p&gt;A deep copy allocates its own buffer and copies the value into it:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; Shallow &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;source) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    data &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;            // my own storage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    *&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;data &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;source.data;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;      // my own copy of the value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  a.data ──▶ [ 42 ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  b.data ──▶ [ 42 ]      two buffers, two owners, two clean frees&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rule this produces: if your class holds a raw pointer to something it owns, you owe it a copy constructor that deep copies. The compiler’s default is correct for &lt;code&gt;int&lt;/code&gt; and &lt;code&gt;std::string&lt;/code&gt; and wrong for anything you allocated yourself.&lt;/p&gt;
&lt;p&gt;And now the class is correct and slow.&lt;/p&gt;
&lt;h2 id=&quot;the-copy-you-did-not-ask-for&quot;&gt;The copy you did not ask for&lt;/h2&gt;
&lt;p&gt;C++ copies a lot, and most of it is invisible. Return an object from a function, pass one by value, push one into a vector that needs to grow, and you have paid for a deep copy. Do that inside a loop over a large buffer and the copies dominate the profile.&lt;/p&gt;
&lt;p&gt;Some of those copies are unavoidable. Many are not, because the source was about to be destroyed anyway.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;total &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; combine&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(a, b);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;combine&lt;/code&gt; builds a result, copies it out, and the original is thrown away. You allocated a buffer, copied every byte into a second buffer, then freed the first. The second buffer is byte for byte what the first one was.&lt;/p&gt;
&lt;p&gt;Nobody would write that by hand. The language wrote it for you, because the only tool it had was “copy”.&lt;/p&gt;
&lt;h2 id=&quot;l-values-r-values-and-the-second-ampersand&quot;&gt;L-values, r-values, and the second ampersand&lt;/h2&gt;
&lt;p&gt;To avoid that copy, the language needs to know something it previously could not express: &lt;em&gt;is this thing about to disappear?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That is the distinction between l-values and r-values. An l-value has a name and a place you can point at. An r-value is a temporary, the unnamed thing an expression produces before it is used.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; x {&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;lref &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; x;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;      // l-value reference, binds to something named&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;rref &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // r-value reference, binds to a temporary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;// int &amp;#x26;&amp;#x26;bad = x;   // error: x has a name, it is not going anywhere&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;#x26;&amp;#x26;&lt;/code&gt; is not “reference to a reference”. It is a separate thing, and it means “this binds only to a temporary”.&lt;/p&gt;
&lt;p&gt;That distinction lets you overload on it:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;num&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;    // A: takes something with a name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;num&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // B: takes a temporary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(x);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;     // calls A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // calls B&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is how the compiler picks between copying and moving without you writing a single &lt;code&gt;if&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;moving-is-stealing&quot;&gt;Moving is stealing&lt;/h2&gt;
&lt;p&gt;A move constructor takes an r-value reference, so it only ever runs on an object that is about to die. That permission changes everything, because it no longer has to leave the source intact.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Shallow&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(Shallow &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;source) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;noexcept&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    : data{source.data}&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;        // take the pointer, not the buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    source.data &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nullptr&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;     // and make sure they cannot free it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  before move                     after move&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  src.data ──▶ [ 42 ]             src.data ──▶ nullptr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                  dst.data ──▶ [ 42 ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No allocation. No byte copying. Two pointer assignments, and the buffer has a new owner. Whether it held four bytes or four megabytes, the move costs the same.&lt;/p&gt;
&lt;p&gt;Two details in that signature earn their place. There is no &lt;code&gt;const&lt;/code&gt;, because the whole point is modifying the source. And &lt;code&gt;noexcept&lt;/code&gt; matters more than it looks: &lt;code&gt;std::vector&lt;/code&gt; will only move your elements while reallocating if the move cannot throw. Leave it off and the vector quietly deep copies instead, which is the exact cost you wrote the move constructor to avoid.&lt;/p&gt;
&lt;h2 id=&quot;the-compiler-was-already-ahead-of-you&quot;&gt;The compiler was already ahead of you&lt;/h2&gt;
&lt;p&gt;Before you conclude that every copy in your codebase is a bug, know that the compiler elides many of them outright. Return value optimization builds the returned object directly in the caller’s storage, so neither a copy nor a move happens. C++17 made this mandatory in common cases.&lt;/p&gt;
&lt;p&gt;This is worth knowing mainly so you are not surprised in a debugger, watching a copy constructor you carefully wrote never get called.&lt;/p&gt;
&lt;h2 id=&quot;what-this-explains-elsewhere&quot;&gt;What this explains elsewhere&lt;/h2&gt;
&lt;p&gt;Once you have seen a double free caused by a copied pointer, other languages read differently.&lt;/p&gt;
&lt;p&gt;A Go slice is a small struct holding a pointer, a length, and a capacity. Assigning it copies those three fields, not the backing array. That is a shallow copy, exactly the one above. Go does not crash, because the garbage collector will not free an array that something still points at, but the aliasing is real, and it is the reason &lt;code&gt;append&lt;/code&gt; sometimes mutates a slice you thought you had copied and sometimes does not.&lt;/p&gt;
&lt;p&gt;JavaScript objects behave the same way. &lt;code&gt;const b = a&lt;/code&gt; gives you two names for one object, and this is why the ecosystem keeps reinventing structured cloning.&lt;/p&gt;
&lt;p&gt;Neither language will hand you a double free. Both will hand you two names for one buffer, and the debugging session that follows is the same one.&lt;/p&gt;</content:encoded><category>C++</category><category>c++</category><category>memory</category><category>performance</category><category>fundamentals</category></item><item><title>A skill is a loading strategy with some knowledge attached</title><link>https://josh.naijora.com/blog/skills-and-progressive-disclosure/</link><guid isPermaLink="true">https://josh.naijora.com/blog/skills-and-progressive-disclosure/</guid><description>A skill that is always in context is a memory file with extra steps. The engineering is in the description field and in everything you keep out of SKILL.md.</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For about a week I opened every session by pasting the same set of prose rules. No em dashes. Straight quotes only, sentence case headings, and never bold the label at the front of a bullet. Different task each time, same paste, because the writing that ships out of this project has a house style and nothing in the model knows it until I say so.&lt;/p&gt;
&lt;p&gt;The obvious fix is to put them in &lt;code&gt;CLAUDE.md&lt;/code&gt;, and it is the wrong fix. Those rules have nothing to say about a failing Terraform plan or a Lighthouse budget, and that is most of what I do in this repo. Memory files load into every conversation whether or not the conversation is about writing. Paying for a style guide while debugging a build is exactly the tax that &lt;a href=&quot;https://josh.naijora.com/blog/context-is-the-budget/&quot;&gt;context is the budget&lt;/a&gt; is about.&lt;/p&gt;
&lt;p&gt;A skill is the same knowledge with a load condition attached. That condition is the entire product.&lt;/p&gt;
&lt;h2 id=&quot;the-two-stage-load&quot;&gt;The two-stage load&lt;/h2&gt;
&lt;p&gt;A skill is a directory with a &lt;code&gt;SKILL.md&lt;/code&gt; inside it. Two places to put one:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/.claude/skills/&amp;#x3C;skill-name&gt;/SKILL.md    every project on this machine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;.claude/skills/&amp;#x3C;skill-name&gt;/SKILL.md      this project only&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The directory name is what you type to invoke it. &lt;code&gt;~/.claude/skills/humanize-writing/&lt;/code&gt; gives you &lt;code&gt;/humanize-writing&lt;/code&gt;, and if the same name exists at both levels the personal one wins.&lt;/p&gt;
&lt;p&gt;Then the part that matters:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  at startup       ┌───────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                   │  every skill&apos;s name + description     │ always&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                   └──────────────────┬────────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      │ description matches&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      ▼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  on invoke        ┌───────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                   │  the SKILL.md body                    │ stays for&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                   └──────────────────┬────────────────────┘ the session&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      │ SKILL.md points at it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      ▼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  on need          ┌───────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                   │  references/*.md                      │ once&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                   └───────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  never            scripts/*  ──▶  executed, only the output is read&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only the top row is unconditional. A listing of every skill’s name and description sits in context from the first token of the session so the model knows what it has; the body arrives when something actually triggers it. That is the whole difference from a memory file, and it is why a twenty-file reference library costs you almost nothing until the day you need it.&lt;/p&gt;
&lt;p&gt;Once a skill does load, its rendered content enters the conversation as one message and stays there across later turns. Claude Code does not re-read the file each turn, so every line in the body is a recurring cost for the rest of the session, not a one-off. Write standing instructions, not step-by-step narration you expect to be forgotten.&lt;/p&gt;
&lt;h2 id=&quot;the-description-is-the-product&quot;&gt;The description is the product&lt;/h2&gt;
&lt;p&gt;Everything else in a skill is downstream of one string. The description is what the model matches your request against to decide whether to load the skill at all, so a description like “help me with docs” either never fires or fires on everything, and in both cases the knowledge inside is irrelevant.&lt;/p&gt;
&lt;p&gt;A good one answers two questions in the same breath: what does this do, and when should it be used. Third person, because it gets injected into the system prompt and “I can help you with X” reads as the model talking to itself. Real keywords, the ones a person would actually say. The description on the skill I ended up writing for those prose rules runs to 921 characters and spends most of them on the second question. Abridged, because it does not fit here:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#22863A&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;Strips machine-written tells out of prose before a human&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;  reads it. Apply automatically to any prose drafted or edited for the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;  user that they might publish, submit, or send, including emails, chat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;  and Slack messages, blog posts, social captions, articles, docs and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;  README prose, bios, cover letters, PR and commit descriptions, and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;  academic work such as papers, theses, and literature reviews.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;  Does not apply to code, tests, config, or data files.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is not padding. Every noun in the second half is a trigger phrase, and the last sentence is a negative trigger that keeps it from firing on a &lt;code&gt;.tf&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;The Agent Skills spec caps &lt;code&gt;description&lt;/code&gt; at 1,024 characters and &lt;code&gt;name&lt;/code&gt; at 64, lowercase letters, numbers and hyphens only. Claude Code adds a budget on top: the listing of names and descriptions gets a share of the context window (1% by default) and when it overflows, descriptions are dropped starting with the skills you invoke least. Names always survive; descriptions do not. So the failure mode of installing thirty skills is not that they stop existing, it is that the ones you rarely use silently lose the text that would have made them fire. &lt;code&gt;/doctor&lt;/code&gt; estimates what the listing is costing you.&lt;/p&gt;
&lt;p&gt;Two spelling traps while you are in the frontmatter, because the fields are not consistent with each other. Tool restrictions are hyphenated, &lt;code&gt;allowed-tools&lt;/code&gt; and &lt;code&gt;disallowed-tools&lt;/code&gt;. Extra trigger phrases go in &lt;code&gt;when_to_use&lt;/code&gt;, with underscores. Everything is optional, including &lt;code&gt;name&lt;/code&gt;, which for a personal or project skill is only a display label; get it wrong and the skill still loads under its directory name.&lt;/p&gt;
&lt;h2 id=&quot;keep-the-body-small-and-the-rest-on-disk&quot;&gt;Keep the body small and the rest on disk&lt;/h2&gt;
&lt;p&gt;The rule of thumb is under 500 lines for &lt;code&gt;SKILL.md&lt;/code&gt;. Past that, split, and the split is the interesting part.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/.claude/skills/humanize-writing/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── SKILL.md                 260 lines, the rules I always want&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── references/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── INDEX.md             routes to everything below&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── academic-writing.md  loaded for a thesis, never for a tweet&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── word-lists.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── ...21 more&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── scripts/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    └── mechanical-scan.sh   greps for em dashes and curly quotes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SKILL.md&lt;/code&gt; holds the instructions that apply every single time. Everything conditional goes into &lt;code&gt;references/&lt;/code&gt;, and the trick that makes the structure work is one line of prose in the main file that names the condition:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Academic work is in scope with overrides. For a paper, thesis chapter,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;or literature review, read &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`references/academic-writing.md`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; first.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without that line the reference file is dead weight on disk. With it, the model has a rule for when to spend the tokens. Keep those pointers one level deep: a reference that points at another reference tends to get skimmed with &lt;code&gt;head&lt;/code&gt; rather than read, and you get half a file’s worth of instructions with no error to tell you so.&lt;/p&gt;
&lt;p&gt;Scripts are the part I did not expect. A script in a skill is executed, not read. Claude runs it through bash and consumes the output, and the source never enters context at all, so a 400-line Python converter and a two-line shell alias cost exactly the same until one of them runs. That inverts the usual advice. If a piece of your skill is deterministic, pushing it into &lt;code&gt;scripts/&lt;/code&gt; is not just more reliable than prose instructions, it is cheaper than prose instructions, because prose you have to keep paying for and a script you pay for once per run.&lt;/p&gt;
&lt;h2 id=&quot;a-skill-is-not-a-sub-agent&quot;&gt;A skill is not a sub-agent&lt;/h2&gt;
&lt;p&gt;Both are ways to stop repeating yourself and they are not interchangeable. A skill joins the conversation you are already in: its instructions sit alongside your context and shape the work you are doing right now. A sub-agent gets its own window, does the work out of sight, and hands back a summary, which is a &lt;a href=&quot;https://josh.naijora.com/blog/sub-agents-are-a-context-decision/&quot;&gt;context decision&lt;/a&gt; rather than a knowledge one.&lt;/p&gt;
&lt;p&gt;They compose in both directions. Put &lt;code&gt;context: fork&lt;/code&gt; in a skill’s frontmatter and the skill body becomes the prompt for a forked sub-agent. Going the other way, a custom sub-agent takes a &lt;code&gt;skills&lt;/code&gt; list in its frontmatter, and the full content of each named skill is injected at startup. That field controls preloading, not permission: a sub-agent with no &lt;code&gt;skills&lt;/code&gt; list can still discover and invoke your skills itself. If you want it to have none, take the &lt;code&gt;Skill&lt;/code&gt; tool away.&lt;/p&gt;
&lt;h2 id=&quot;plugins-are-how-anyone-else-gets-it&quot;&gt;Plugins are how anyone else gets it&lt;/h2&gt;
&lt;p&gt;A skill in &lt;code&gt;.claude/skills/&lt;/code&gt; is committed with the repo and everyone who clones it has it. A skill in &lt;code&gt;~/.claude/skills/&lt;/code&gt; is yours alone, and telling a teammate to copy a directory is not distribution.&lt;/p&gt;
&lt;p&gt;A plugin is a directory with a manifest and the components beside it:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;my-plugin/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── .claude-plugin/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── plugin.json        name, description, version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── skills/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    └── pr-review/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        └── SKILL.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only &lt;code&gt;plugin.json&lt;/code&gt; goes inside &lt;code&gt;.claude-plugin/&lt;/code&gt;. Put &lt;code&gt;skills/&lt;/code&gt; in there too and nothing loads, with no complaint. Test with &lt;code&gt;claude --plugin-dir ./my-plugin&lt;/code&gt; before you ship anything, and run &lt;code&gt;claude plugin validate ./my-plugin&lt;/code&gt; before you publish. Publishing means a marketplace: a git repository that lists plugins, added with &lt;code&gt;/plugin marketplace add &amp;#x3C;owner&gt;/&amp;#x3C;repo&gt;&lt;/code&gt; and installed from with &lt;code&gt;/plugin install &amp;#x3C;name&gt;@&amp;#x3C;marketplace&gt;&lt;/code&gt;. A private repo works fine for that, which is the usual answer for a team.&lt;/p&gt;
&lt;h2 id=&quot;when-it-will-not-fire&quot;&gt;When it will not fire&lt;/h2&gt;
&lt;p&gt;The first thing to check is the thing you wrote last, which is the description. Read it as a stranger and ask whether the words in it are the words you actually typed at the prompt. Nine times out of ten they are not: the skill says “quality assurance procedures” and you said “check my work”.&lt;/p&gt;
&lt;p&gt;The rest of the list is short. Ask “what skills are available” and confirm yours is listed at all. Invoke it by name with &lt;code&gt;/skill-name&lt;/code&gt; to prove the body works, which separates a matching problem from a content problem. If the skill is somehow present but has no description attached to it, the frontmatter did not parse, and &lt;code&gt;claude --debug&lt;/code&gt; prints the error; &lt;code&gt;claude plugin validate .claude/skills&lt;/code&gt; will find that across a whole directory.&lt;/p&gt;
&lt;p&gt;Then there is the case where the skill fires reliably and the model does something else anyway. No amount of description tuning fixes that one. You do not have a knowledge problem, you have an enforcement problem, and that is what &lt;a href=&quot;https://josh.naijora.com/blog/hooks-run-whether-the-model-agrees-or-not/&quot;&gt;hooks&lt;/a&gt; are for.&lt;/p&gt;
&lt;p&gt;The trap in all of this is writing a skill that is really a memory file: a body so general it applies to everything, a description so broad it matches everything, loaded permanently for no reason. If the answer to “when should this load” is “always”, you did not need a skill. Write the load condition first and the knowledge second, and the file mostly writes itself.&lt;/p&gt;</content:encoded><category>AI Engineering</category><category>claude-code</category><category>skills</category><category>plugins</category><category>context</category></item><item><title>Sub-agents are a context decision</title><link>https://josh.naijora.com/blog/sub-agents-are-a-context-decision/</link><guid isPermaLink="true">https://josh.naijora.com/blog/sub-agents-are-a-context-decision/</guid><description>Delegating does not make the model smarter. It buys a clean context window and charges you visibility, and every good use of a sub-agent follows from that.</description><pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I asked a question I did not think was expensive. Which service handles refunds? The codebase was about three weeks old to me. Claude grepped, opened a file, followed an import, opened four more, found an interface, went hunting for implementations, and answered correctly a minute later.&lt;/p&gt;
&lt;p&gt;Then I looked at what the answer had cost. Fifteen files were sitting in my context window, and they were going to sit there for the rest of the session, because a context window only ever grows. I wanted one sentence. I got one sentence and a research trail I had no intention of reading.&lt;/p&gt;
&lt;p&gt;That is the whole case for sub-agents, and it has nothing to do with expertise. The model does not get better at Kubernetes because you told it that it is a Kubernetes specialist. It already knows what it knows. What a sub-agent actually changes is the size and cleanliness of the working set the answer gets produced in, and whether the wreckage of producing it lands in your window or somebody else’s.&lt;/p&gt;
&lt;h2 id=&quot;two-windows-one-paragraph-between-them&quot;&gt;Two windows, one paragraph between them&lt;/h2&gt;
&lt;p&gt;A sub-agent runs in its own context window with its own system prompt and its own tool permissions. It works on its own, and when it finishes, only its report crosses back. Everything else, the searches, the file contents, the reasoning it did to get there, is discarded with the context it lived in.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  main thread                         sub-agent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ┌───────────────────────┐   task    ┌───────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ system prompt         ├──────────▶│ system prompt (agent) │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ CLAUDE.md             │           │ task description      │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ the conversation      │           │ 4 searches            │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │                       │◀──────────┤ 15 file reads         │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ + one paragraph       │  report   │ the reasoning         │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └───────────────────────┘           └───────────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                       discarded on return&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same refunds question, run through a sub-agent: the left box grows by a paragraph instead of by fifteen files. If you have read &lt;a href=&quot;https://josh.naijora.com/blog/context-is-the-budget/&quot;&gt;part one&lt;/a&gt;, this is the same budget argument with a second window attached.&lt;/p&gt;
&lt;p&gt;Now the honest half. You lose the journey. When the answer is right this is exactly what you wanted, and when it’s wrong you have no idea where it went wrong, because the evidence has been thrown away. You can’t scroll up and find the file it misread. All you can do is ask again and hope the second run is better, which is a worse debugging position than reading fifteen files would have been.&lt;/p&gt;
&lt;p&gt;There’s a second cost that surprised me more. A sub-agent starts cold. It does not inherit your conversation history, the files Claude already read, or the skills already loaded in your session. For a self-contained question that’s free. For a task that only makes sense given the last forty minutes of discussion, you pay for it to rediscover all of that, and the isolation you bought is worth less than the context you burned re-establishing. When that’s the shape of the work, &lt;code&gt;/subtask&lt;/code&gt; is the escape hatch: it forks the current conversation, so the delegate inherits your history along with the system prompt, tools, and model.&lt;/p&gt;
&lt;p&gt;You are already relying on this before you configure anything. Three sub-agents ship with Claude Code and launch on their own judgement. Explore holds read-only tools with Write and Edit denied, and handles file discovery and code search, which is my refunds question answered the way it should have been. Plan does the same read-only research behind plan mode. General-purpose gets every tool available to sub-agents and takes multi-step work that has to act, not just look.&lt;/p&gt;
&lt;p&gt;If you would rather watch the searches happen, you can turn the read-only two off:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;  &quot;permissions&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;    &quot;deny&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;Agent(Explore)&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;Agent(Plan)&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I leave them on. Exploration is the single best thing to keep out of a window, and the built-ins are doing it for free.&lt;/p&gt;
&lt;h2 id=&quot;the-parent-writes-half-the-prompt&quot;&gt;The parent writes half the prompt&lt;/h2&gt;
&lt;p&gt;A sub-agent receives exactly two things. Its system prompt, from its configuration file, which you wrote and can read. And a task description, written by the parent agent, based on what it thinks you asked for.&lt;/p&gt;
&lt;p&gt;The second one is where delegation quietly goes wrong. You typed a request with three constraints in it. The parent compressed that into a brief. The sub-agent sees only the compression, has no access to what you actually typed, and cannot ask you a clarifying question. If the constraint you cared about did not survive the compression, you find out when the report comes back confidently wrong about something you thought you had specified.&lt;/p&gt;
&lt;p&gt;I ran into this at scale on this site. It was built by dispatching parallel agents across ten git worktrees, one per slice, and the thing that made it survive was a preamble every dispatched agent had to receive verbatim. Two of the five points exist entirely because of the compression problem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Write &lt;strong&gt;only&lt;/strong&gt; within your owned paths. If you need something outside them, report it, do not create it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Report: files created, contracts published, and anything you needed but could not own.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neither is about capability. Both are about the fact that a delegated agent is working from a summary of my intent, and if I don’t nail the boundaries into the brief, the summary loses them. When you write your own task description, be specific in a way that feels excessive. You are not talking to the model, you are talking through a paraphrase of yourself.&lt;/p&gt;
&lt;h2 id=&quot;configuring-one&quot;&gt;Configuring one&lt;/h2&gt;
&lt;p&gt;Sub-agents are markdown files with YAML frontmatter. &lt;code&gt;.claude/agents/&lt;/code&gt; for a project, &lt;code&gt;~/.claude/agents/&lt;/code&gt; for every project on your machine. Both directories are scanned recursively, so subfolders are free organisation. Identity comes from the &lt;code&gt;name&lt;/code&gt; field, not the filename.&lt;/p&gt;
&lt;p&gt;Only &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; are required. Here is one I’d actually get value from here, where hardcoded colours are the convention that breaks most often:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-dark-font-weight:bold;--shiki-light:#005CC5;--shiki-light-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;name: token-auditor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;description: Finds colour literals outside tokens.css. Run after CSS work.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;tools: Read, Grep, Glob&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;model: haiku&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-dark-font-weight:bold;--shiki-light:#005CC5;--shiki-light-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;You audit styling changes against src/styles/tokens.css. A hex literal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;or an rgb() call in any .astro or .css file outside that one is a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;finding.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Report each finding as one line: file path, line number, the literal,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;and the token that should have been used. If there are none, say so in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;a sentence and stop.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tools&lt;/code&gt; is a comma-separated list, and leaving it out means the sub-agent inherits every tool available to sub-agents in the main conversation. That default is worth overriding more often than not. The agent above physically cannot modify a file, which is a stronger guarantee than instructing it not to, and it does a second job too: three tools describe a role more precisely than a paragraph of system prompt does. There’s a &lt;code&gt;disallowedTools&lt;/code&gt; field if a denylist fits better, applied before &lt;code&gt;tools&lt;/code&gt; is resolved.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;model&lt;/code&gt; pins a model per agent. It takes &lt;code&gt;sonnet&lt;/code&gt;, &lt;code&gt;opus&lt;/code&gt;, &lt;code&gt;haiku&lt;/code&gt;, &lt;code&gt;fable&lt;/code&gt;, a full model ID, or &lt;code&gt;inherit&lt;/code&gt;, and it defaults to the main conversation’s model. A grep-and-report agent on Haiku while the main thread stays on Opus is one of the few genuinely free wins in this whole system. There’s more in the frontmatter than most people need, including &lt;code&gt;maxTurns&lt;/code&gt;, &lt;code&gt;permissionMode&lt;/code&gt;, a &lt;code&gt;skills&lt;/code&gt; list for preloading &lt;a href=&quot;https://josh.naijora.com/blog/skills-and-progressive-disclosure/&quot;&gt;skills&lt;/a&gt; a custom agent would not otherwise get, and an &lt;code&gt;isolation: worktree&lt;/code&gt; setting that does in one line what the eighty-five lines of my &lt;code&gt;spawn-worktrees.sh&lt;/code&gt; do by hand.&lt;/p&gt;
&lt;p&gt;Now the field that decides whether any of this fires. &lt;code&gt;description&lt;/code&gt; is the trigger: the main agent reads it and decides, on its own, whether the task in front of it belongs to this agent. Vague descriptions fail in both directions. Write “helps with code quality” and it either never launches or it launches on everything, and the second failure is worse, because you’ll be paying for a delegation you didn’t want on tasks that were fine in the main thread. Say what it does and when to use it, in that order. When you want it regardless, &lt;code&gt;@agent-token-auditor&lt;/code&gt; guarantees the run.&lt;/p&gt;
&lt;h2 id=&quot;an-output-format-is-a-stopping-condition&quot;&gt;An output format is a stopping condition&lt;/h2&gt;
&lt;p&gt;This is the least obvious property of the whole feature, and the one that fixed the most for me once I understood it.&lt;/p&gt;
&lt;p&gt;A sub-agent with no defined output has no way to know when it has done enough research. It cannot ask you. It has no sense of your patience. So it keeps going, because one more file might improve the answer, and there is always one more file. The runs that go on forever are almost always this, and it reads like a model problem when it’s a specification problem.&lt;/p&gt;
&lt;p&gt;Define the shape of the report and the problem mostly evaporates. “Return each finding as file path, line number, and the token that should have been used” tells the agent what finished looks like. It can check its own work against the shape and see that the shape is full. That’s a stopping condition, and it’s the reason my token-auditor prompt spends more words on the output than on the audit. &lt;code&gt;maxTurns&lt;/code&gt; exists as the blunt version, and it stops a runaway rather than fixing one: you get a partial result instead of a bounded one.&lt;/p&gt;
&lt;p&gt;While you’re defining the output, add a section asking for obstacles. Something like this, near the end of the system prompt:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-dark-font-weight:bold;--shiki-light:#005CC5;--shiki-light-font-weight:bold&quot;&gt;## Obstacles encountered&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Report anything that got in your way: setup problems, workarounds you&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;found, commands that needed unusual flags, dependencies or imports that&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;caused trouble.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reasoning is better than it first looks. A sub-agent hits a broken import, works out that the build needs a flag, solves it, finishes the actual task, reports on the actual task, and the fix dies with its context. Twenty minutes later the main thread hits the same broken import and solves it again from scratch. You paid twice for one piece of knowledge, and the only reason is that nobody asked for it. Ask.&lt;/p&gt;
&lt;h2 id=&quot;the-failures-worth-knowing-about&quot;&gt;The failures worth knowing about&lt;/h2&gt;
&lt;p&gt;Expert personas buy nothing. “You are a senior Python engineer” adds tokens and no capability. Launching a sub-agent has real costs, the lost visibility and the cold start, and a persona pays none of them back. The overhead is only worth it when the sub-agent does something the main thread cannot: keeping exploratory work out of your window, or running under tools and permissions you deliberately narrowed.&lt;/p&gt;
&lt;p&gt;Multi-step pipelines are more fragile than they look. The tempting one is reproduce the bug, then debug it, then fix it, three agents in a chain. It fails because each step depends on what the previous step discovered, and what crosses between them is a summary. The debugger gets a paragraph about a reproduction it never watched. The fixer gets a paragraph about reasoning it never followed. Pipelines work when the steps are genuinely independent, which a debugging chain is the exact opposite of.&lt;/p&gt;
&lt;p&gt;And do not delegate test runs. When a test fails, the thing you need is the failure output: the assertion, the diff, the stack, the line. A sub-agent hands back “the auth tests failed” and a tidy summary of what it believes went wrong, and now you’re writing a debug script to recover output you would have seen for free. This one is worth stating as a rule, because the temptation is real. Test output is verbose, verbose output looks exactly like the thing sub-agents are for, and it’s the one kind of verbose output you actually wanted to read.&lt;/p&gt;
&lt;p&gt;That’s the test, in the end. Delegate the work whose intermediate state you don’t want, and keep the work whose intermediate state is the answer.&lt;/p&gt;</content:encoded><category>AI Engineering</category><category>claude-code</category><category>sub-agents</category><category>context-management</category></item><item><title>Hooks run whether the model agrees or not</title><link>https://josh.naijora.com/blog/hooks-run-whether-the-model-agrees-or-not/</link><guid isPermaLink="true">https://josh.naijora.com/blog/hooks-run-whether-the-model-agrees-or-not/</guid><description>A CLAUDE.md rule is a request. A hook is shell the harness runs regardless, which makes it the only place to put a rule you cannot afford to have ignored.</description><pubDate>Sat, 29 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There is a file in this site’s content directory called &lt;code&gt;draft-should-not-appear.md&lt;/code&gt;. Its frontmatter sets &lt;code&gt;draft: true&lt;/code&gt;, its body opens with “do not delete it”, and its only job is to be the thing that goes wrong first. If it ever renders on the live site, the draft filter is broken and I want to find out from a fixture rather than from someone reading a half-written post.&lt;/p&gt;
&lt;p&gt;The filter itself is one function. &lt;code&gt;getPublishedPosts()&lt;/code&gt; in &lt;code&gt;src/lib&lt;/code&gt; is the only place in the codebase allowed to call &lt;code&gt;getCollection(&apos;blog&apos;)&lt;/code&gt;. Every page imports the helper. The rule exists because a second entry point to the collection is exactly how an unpublished draft reaches production, and there is no version of that bug that gets caught in review, because the diff looks fine.&lt;/p&gt;
&lt;p&gt;I wrote that rule into &lt;code&gt;AGENTS.md&lt;/code&gt;. I wrote it into &lt;code&gt;CLAUDE.md&lt;/code&gt; too. Both of those are text the model reads and mostly follows.&lt;/p&gt;
&lt;p&gt;Mostly is doing a lot of work in that sentence.&lt;/p&gt;
&lt;h2 id=&quot;the-rule-that-could-not-be-a-request&quot;&gt;The rule that could not be a request&lt;/h2&gt;
&lt;p&gt;Everything else in Claude Code persuades the model. &lt;code&gt;CLAUDE.md&lt;/code&gt; is a document loaded into the prompt. A skill is a document loaded on demand. A slash command is a document you triggered by hand. They all work by being convincing, and they all compete for the same finite window, which means their influence decays exactly when you need it most: forty tool calls in, near the compaction boundary, on the run where the model is holding six other constraints at once. See &lt;a href=&quot;https://josh.naijora.com/blog/context-is-the-budget/&quot;&gt;part one&lt;/a&gt; for why that decay is structural rather than a bug.&lt;/p&gt;
&lt;p&gt;A hook is not a document. It is a shell command the harness runs at a fixed point in its own lifecycle, with the model nowhere in the decision. The model does not read the hook, cannot see the hook, and has no ability to weigh it against anything else it was told. It just finds out afterwards.&lt;/p&gt;
&lt;p&gt;That is the whole value proposition. You are trading expressiveness for certainty, and for a small number of rules that trade is obviously correct.&lt;/p&gt;
&lt;p&gt;Here is the one wired into this repo, in &lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;  &quot;hooks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;    &quot;PostToolUse&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;        &quot;matcher&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;Edit|Write&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;        &quot;hooks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;          {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;            &quot;type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;            &quot;command&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;bash &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#005CC5&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;$CLAUDE_PROJECT_DIR/.claude/hooks/check-draft-filter.sh&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#005CC5&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;            &quot;timeout&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;            &quot;statusMessage&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;Checking draft filter&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;        ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;matcher&lt;/code&gt; matches tool names. If it contains only letters, digits, spaces, underscores, hyphens, commas and pipes, it is read as an exact string or a pipe-separated list of exact strings, so &lt;code&gt;Edit|Write&lt;/code&gt; is two exact matches rather than a regex. Put any other character in it and the whole thing is treated as an unanchored JavaScript regular expression instead, which is how you match a whole MCP server at once: &lt;code&gt;mcp__playwright__.*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$CLAUDE_PROJECT_DIR&lt;/code&gt; resolves to the directory the session started in and stays put even when Claude moves into a git worktree. That matters here, because this project spawns ten of them.&lt;/p&gt;
&lt;p&gt;Hook config can live in &lt;code&gt;~/.claude/settings.json&lt;/code&gt; for every project, &lt;code&gt;.claude/settings.json&lt;/code&gt; for one project, &lt;code&gt;.claude/settings.local.json&lt;/code&gt; for one checkout, or in managed policy settings your admin controls. It can also ride along in a plugin, a skill’s frontmatter, or a subagent’s. The levels merge rather than override, so a project hook does not replace your personal one.&lt;/p&gt;
&lt;h2 id=&quot;where-the-hook-fires&quot;&gt;Where the hook fires&lt;/h2&gt;
&lt;p&gt;The event list is long now, past thirty entries, covering compaction, model switches, worktree creation, config changes and subagent lifecycle. Most of them you will never touch. Four carry almost all the weight.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PreToolUse&lt;/code&gt; runs before a tool call and can stop it. &lt;code&gt;PostToolUse&lt;/code&gt; runs after a tool call succeeded and can only talk. &lt;code&gt;UserPromptSubmit&lt;/code&gt; runs on your prompt before the model sees it. &lt;code&gt;SessionStart&lt;/code&gt; runs once at the top.&lt;/p&gt;
&lt;p&gt;The split between the first two is the thing to internalise:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   Claude proposes a tool call&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             ▼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ┌─────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      │ PreToolUse  │──── deny ────┐   the tool never runs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      └──────┬──────┘              │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             │ allowed             │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             ▼                     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       permission flow             │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             │                     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             ▼                     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        the tool runs              │   files are already written&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             │                     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             ▼                     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ┌─────────────┐              │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      │ PostToolUse │─── block ────┤   reason rides along with&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      └──────┬──────┘              │   the tool result&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             │                     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             ▼                     ▼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        tool result ──────▶ Claude&apos;s next turn&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PostToolUse&lt;/code&gt; cannot undo anything. By the time it fires, the file is on disk, the command has run, the request has left the machine. Its &lt;code&gt;decision: &quot;block&quot;&lt;/code&gt; does not roll the edit back and the name is misleading: it attaches a &lt;code&gt;reason&lt;/code&gt; to the tool result, and Claude still sees the original output alongside it. So it is feedback, not enforcement.&lt;/p&gt;
&lt;p&gt;Which is fine, because feedback is what I actually want for the draft filter. Blocking a &lt;code&gt;Write&lt;/code&gt; after the write has happened would leave the file there and the model confused. Handing back a sentence explaining what is wrong lets it fix the file on the next turn, which is the correct repair for a bad import.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UserPromptSubmit&lt;/code&gt; and &lt;code&gt;SessionStart&lt;/code&gt; have a property the others do not: their plain stdout is added to context as something Claude can read. Everywhere else, a successful hook’s stdout goes to the debug log and nobody sees it. This catches people. You write &lt;code&gt;echo &quot;remember to run the tests&quot;&lt;/code&gt; in a &lt;code&gt;PostToolUse&lt;/code&gt; hook, watch nothing happen, and conclude hooks are broken.&lt;/p&gt;
&lt;h2 id=&quot;blocking-and-feedback-are-different-tools&quot;&gt;Blocking and feedback are different tools&lt;/h2&gt;
&lt;p&gt;A hook communicates two ways and you should pick one per script rather than mixing them.&lt;/p&gt;
&lt;p&gt;The blunt one is the exit code. Exit 0 is success. Exit 2 is a blocking error on the events that can block, and the message Claude sees is whatever you wrote to stderr. Every other exit code, including 1, is a non-blocking error: the transcript shows a notice, and the action proceeds anyway.&lt;/p&gt;
&lt;p&gt;That last part deserves a moment. &lt;code&gt;exit 1&lt;/code&gt; is the conventional Unix failure and it does not block. Neither does a crashed script, and neither does a hook whose path you mistyped, which exits 127 from the shell and leaves your policy gate silently disabled while looking configured. If a hook is enforcing something, exit 2 or return JSON, and watch the first run to confirm it is actually firing.&lt;/p&gt;
&lt;p&gt;The precise one is JSON on stdout. Exit 0, print an object, and the harness reads fields off it. &lt;code&gt;PreToolUse&lt;/code&gt; puts its verdict inside &lt;code&gt;hookSpecificOutput&lt;/code&gt; and gets four outcomes: &lt;code&gt;allow&lt;/code&gt; skips the permission prompt, &lt;code&gt;deny&lt;/code&gt; kills the call, &lt;code&gt;ask&lt;/code&gt; forces a prompt, &lt;code&gt;defer&lt;/code&gt; parks it for a &lt;code&gt;-p&lt;/code&gt; caller to resume. The top-level &lt;code&gt;decision&lt;/code&gt; and &lt;code&gt;reason&lt;/code&gt; fields are deprecated for &lt;code&gt;PreToolUse&lt;/code&gt; specifically, though they remain the current format for &lt;code&gt;PostToolUse&lt;/code&gt; and &lt;code&gt;Stop&lt;/code&gt;. It is the kind of asymmetry you only discover by having a hook quietly do nothing.&lt;/p&gt;
&lt;p&gt;The draft-filter script uses the &lt;code&gt;PostToolUse&lt;/code&gt; form. It reads the edited path off stdin, exits early for anything under &lt;code&gt;src/lib&lt;/code&gt; where the helper legitimately lives, and only then greps:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; -Eq&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; &quot;getCollection\([[:space:]]*[&apos;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#005CC5&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;]blog[&apos;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#005CC5&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;]&quot;&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;$f&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  call&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;getCollection(&apos;blog&apos;)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  reason&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;${&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;} calls ${&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;} directly, bypassing the draft filter...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;  jq&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; --arg&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; reason&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;$reason&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; --arg&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; note&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;$note&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#005CC5&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;    &apos;{decision: &quot;block&quot;, reason: $reason, systemMessage: $note}&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;reason&lt;/code&gt; goes to Claude. &lt;code&gt;systemMessage&lt;/code&gt; goes to me, on screen, so I know the guard fired rather than discovering it in a transcript later. Building the string outside the &lt;code&gt;jq&lt;/code&gt; program is not stylistic: the literal &lt;code&gt;getCollection(&apos;blog&apos;)&lt;/code&gt; contains an apostrophe, which would close the single-quoted jq program mid-flight.&lt;/p&gt;
&lt;h2 id=&quot;determinism-cuts-both-ways&quot;&gt;Determinism cuts both ways&lt;/h2&gt;
&lt;p&gt;The hook has a bug. I hit it while writing this post.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; does not parse TypeScript, and it has no concept of a comment. So the guard fires on any file that merely mentions the call, and two files here mention it for entirely good reasons:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;site/src/types/content.ts:41&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  * component should be handling a raw `getCollection(&apos;blog&apos;)` result.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;site/src/pages/rss.xml.ts:5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  * lives. Never call `getCollection(&apos;blog&apos;)` here: a feed is cached and&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both are doc comments. Both are telling a reader not to do the thing. The second is a comment that exists to explain the rule, and it trips the guard that enforces the rule. Editing either file blocks every single time, and the model gets handed a paragraph accusing it of a violation that is not there.&lt;/p&gt;
&lt;p&gt;The cheap fix is to drop comment lines before matching, which is one more &lt;code&gt;grep&lt;/code&gt; in the pipeline and handles both cases:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; -vE&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; &apos;^[[:space:]]*(\*|//|/\*)&apos;&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;$f&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; -Eq&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; &quot;getCollection\(...&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is still a heuristic. It would miss a trailing comment on a line of real code, and it would fire on a string literal. The correct fix is parsing the file, which is more machinery than this rule is worth.&lt;/p&gt;
&lt;p&gt;The failure is worth sitting with, though, because it is not a quirk of one badly written script. It is the property I wanted, seen from the other side. A hook does not weigh context. It cannot notice that the match is inside a block comment, that the file declares types and runs no code, or that the sentence wrapped around the match is an instruction not to do it. It matched, so it fired, and it will keep firing that way on every run until I go and edit the shell myself.&lt;/p&gt;
&lt;p&gt;That is the same reason it works. What stops the hook being talked out of a real violation on turn ninety is exactly what stops it being reasoned into ignoring a comment on turn one, and you do not get to have one without the other.&lt;/p&gt;
&lt;p&gt;Which sets the design rule: make the pattern as narrow as you can while still catching the thing, and plan to pay for the false positives out of your own pocket rather than expecting the model to route around them. Mine is too wide. A guard that cries wolf at prose is a guard you start waving through, and at that point it has quietly turned back into a suggestion.&lt;/p&gt;
&lt;h2 id=&quot;arbitrary-shell-with-your-credentials&quot;&gt;Arbitrary shell, with your credentials&lt;/h2&gt;
&lt;p&gt;A hook is a command running as you, with your environment, your keys, and write access to everything you can write to. The docs say this plainly and I will repeat it: review a hook before you add it, the same way you would review anything else that runs unattended.&lt;/p&gt;
&lt;p&gt;The part worth knowing beyond that is the trust boundary. In an interactive session, Claude Code holds every settings-file hook back until you accept the workspace trust dialog for the folder, including hooks from your own &lt;code&gt;~/.claude/settings.json&lt;/code&gt;. In a &lt;code&gt;-p&lt;/code&gt; or SDK session there is no dialog and the folder is treated as trusted, which means hooks committed into a repository’s &lt;code&gt;.claude/settings.json&lt;/code&gt; run on a checkout you have never opened by hand. If you are scripting &lt;code&gt;claude -p&lt;/code&gt; across repositories you did not write, read their &lt;code&gt;.claude/&lt;/code&gt; directory first, or pass &lt;code&gt;--settings &apos;{&quot;disableAllHooks&quot;: true}&apos;&lt;/code&gt; for that run.&lt;/p&gt;
&lt;p&gt;You do not need to restart after editing a hook. Hook config is not snapshotted at session start: a file watcher picks up direct edits to settings files while the session is running, and a restart is the fallback for when the watcher misses a change rather than the routine. &lt;code&gt;/hooks&lt;/code&gt; lists what is currently registered, with the source file for each, which is the fastest way to answer “is this thing even loaded”.&lt;/p&gt;
&lt;p&gt;The tempting move, once hooks click, is to push everything into them. Resist it. Anything that needs the model to weigh a situation belongs in a &lt;a href=&quot;https://josh.naijora.com/blog/skills-and-progressive-disclosure/&quot;&gt;skill&lt;/a&gt;, which loads when the request looks relevant and then argues its case. Hooks are event-driven and skills are request-driven, and the test for which one you want is whether you would accept the rule being skipped on a bad day.&lt;/p&gt;
&lt;p&gt;For the draft filter the answer was no. So it is forty lines of bash that will go on being wrong about two comments until I get round to fixing them, and I will take that over a rule that only holds while the context is short.&lt;/p&gt;</content:encoded><category>AI Engineering</category><category>claude-code</category><category>hooks</category><category>automation</category><category>shell</category></item><item><title>MCP servers are capabilities, not knowledge</title><link>https://josh.naijora.com/blog/mcp-servers-are-capabilities-not-knowledge/</link><guid isPermaLink="true">https://josh.naijora.com/blog/mcp-servers-are-capabilities-not-knowledge/</guid><description>A server gives Claude something new to do, not something new to know. Which is why adding one never fixes a model that keeps forgetting your conventions.</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For most of a week I kept telling Claude that every colour in this site lives in &lt;code&gt;tokens.css&lt;/code&gt; and nowhere else, and it kept writing &lt;code&gt;#0f1115&lt;/code&gt; into component files anyway. Somewhere in that week I caught myself scrolling a list of MCP servers looking for the one that would fix it.&lt;/p&gt;
&lt;p&gt;There isn’t one. There cannot be one. Working out why is most of what you need to know about MCP.&lt;/p&gt;
&lt;h2 id=&quot;a-client-a-server-and-a-list-of-tools&quot;&gt;A client, a server, and a list of tools&lt;/h2&gt;
&lt;p&gt;Claude Code is an MCP client. A server is a separate thing that speaks the protocol: either a process on your machine that the client launches and talks to over stdin and stdout, or an endpoint it talks to over HTTP. Four transports are supported. &lt;code&gt;stdio&lt;/code&gt; for local processes, &lt;code&gt;http&lt;/code&gt; for remote servers, &lt;code&gt;sse&lt;/code&gt; for remote servers using server-sent events (deprecated, use HTTP where it exists), and &lt;code&gt;ws&lt;/code&gt; for a persistent WebSocket, which you can only configure as JSON because &lt;code&gt;claude mcp add --transport&lt;/code&gt; doesn’t accept it.&lt;/p&gt;
&lt;p&gt;Adding one is a line of shell:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;claude&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; mcp&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; --transport&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; stdio&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; playwright&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; npx&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; @playwright/mcp@latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;claude&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; mcp&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; --transport&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; context7&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; https://mcp.context7.com/mcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At session start the client connects to each configured server and asks what it offers. What comes back is a tool list: names, descriptions, and JSON input schemas.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Claude Code = the MCP client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       ├── stdio ──▶ playwright     npx, local child process&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       ├── http  ──▶ aws-knowledge  knowledge-mcp.global.api.aws&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       └── http  ──▶ context7       mcp.context7.com/mcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    tool list at session start&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            ▼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ┌────────────────── context window ──────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ system prompt · built-in tools · CLAUDE.md · files │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ ·················································· │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ MCP tool names       definitions fetched on demand │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing in that exchange is knowledge. &lt;code&gt;browser_navigate&lt;/code&gt; arrives as a name, a sentence of description, and a schema saying it wants a URL. That is a function signature plus a way to call it across a process boundary. The model still decides when to call it, and everything it knows about my project it knows from my files.&lt;/p&gt;
&lt;p&gt;So a server is the right answer to “Claude cannot open a browser” and the wrong answer to “Claude keeps forgetting our conventions”. The second problem is a file that gets read, or an event that fires whether the model agrees or not. It is never a new tool.&lt;/p&gt;
&lt;h2 id=&quot;scope-is-the-decision-people-skip&quot;&gt;Scope is the decision people skip&lt;/h2&gt;
&lt;p&gt;Three scopes, and the difference between them is who else gets the server.&lt;/p&gt;
&lt;p&gt;Local is the default. The server goes into &lt;code&gt;~/.claude.json&lt;/code&gt; under the entry for the current project’s path, so it loads in that project and nowhere else, and stays private to you. Project scope writes &lt;code&gt;.mcp.json&lt;/code&gt; at the repository root, which is the only scope that travels with a clone. User scope also lives in &lt;code&gt;~/.claude.json&lt;/code&gt;, at the top level, and loads in every project on your machine.&lt;/p&gt;
&lt;p&gt;When the same server name is defined in more than one place, precedence runs local, then project, then user, then plugin-provided servers, then claude.ai connectors. The whole entry from the winning source is used. Fields are not merged across scopes, which is worth knowing before you try to override one URL from a shared config and quietly lose the rest of the entry.&lt;/p&gt;
&lt;p&gt;Commit the project file. This repo’s is three servers and no secrets:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;  &quot;mcpServers&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;    &quot;playwright&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;      &quot;command&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;      &quot;args&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;@playwright/mcp@latest&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;    &quot;aws-knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;      &quot;type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;      &quot;url&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;https://knowledge-mcp.global.api.aws&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;    &quot;context7&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;      &quot;type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;      &quot;url&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;https://mcp.context7.com/mcp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That file is the difference between “here is how I verify a rendering change” and “here is how I verify a rendering change, and it works on your machine too”. A local-scoped server is a thing you have to describe to a teammate in Slack.&lt;/p&gt;
&lt;p&gt;The safety catch is that a repository cannot approve its own servers. Claude Code prompts before it will use anything from &lt;code&gt;.mcp.json&lt;/code&gt;, because otherwise cloning a repo would be enough to run a command from it. Approve one and the choice lands in your untracked &lt;code&gt;.claude/settings.local.json&lt;/code&gt; under &lt;code&gt;enabledMcpjsonServers&lt;/code&gt;, which is where mine still is, three names and nothing else in the file. &lt;code&gt;claude mcp reset-project-choices&lt;/code&gt; throws those choices away.&lt;/p&gt;
&lt;p&gt;The same key can go in the tracked file, which is what this repo does:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;  &quot;enabledMcpjsonServers&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;playwright&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;aws-knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;context7&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That copy lives in &lt;code&gt;.claude/settings.json&lt;/code&gt;, committed, and it still doesn’t skip the gate. A committed approval is ignored in a folder nobody has trusted yet, so a fresh clone shows the servers as pending until someone runs &lt;code&gt;claude&lt;/code&gt; there and accepts the workspace trust dialog. After that, the approval is already made and no one has to think about it again. That is the right shape: one human decision per checkout, not one per server per person.&lt;/p&gt;
&lt;h2 id=&quot;allowing-a-whole-server-is-a-decision-not-a-shortcut&quot;&gt;Allowing a whole server is a decision, not a shortcut&lt;/h2&gt;
&lt;p&gt;MCP tools are namespaced &lt;code&gt;mcp__&amp;#x3C;server&gt;__&amp;#x3C;tool&gt;&lt;/code&gt;, so Playwright’s navigate tool is &lt;code&gt;mcp__playwright__browser_navigate&lt;/code&gt;. Permission rules use the same names, and how much of the name you write is the whole question.&lt;/p&gt;
&lt;p&gt;A bare &lt;code&gt;mcp__playwright&lt;/code&gt; matches every tool the server provides. So does &lt;code&gt;mcp__playwright__*&lt;/code&gt;. A full name matches exactly one tool. Wildcards in an allow rule only work after a literal &lt;code&gt;mcp__&amp;#x3C;server&gt;__&lt;/code&gt; prefix, so the server segment has to be spelled out; an unanchored allow glob like &lt;code&gt;&quot;*&quot;&lt;/code&gt; or &lt;code&gt;&quot;mcp__*&quot;&lt;/code&gt; is skipped with a warning and approves nothing. Deny and ask rules are looser and take a bare &lt;code&gt;mcp__*&lt;/code&gt;, and a deny that matches by glob removes those tools from Claude’s context entirely rather than just refusing the call. MCP rules also take no parentheses, so you cannot match on an argument. Write one anyway and it is dropped at load time and reported in the invalid-settings dialog and in &lt;code&gt;claude doctor&lt;/code&gt; output, which is the kind of failure you can stare past for a week.&lt;/p&gt;
&lt;p&gt;So the shape of a rule I would write for a browser:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;  &quot;permissions&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#7EE787;--shiki-light:#005CC5&quot;&gt;    &quot;allow&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;      &quot;mcp__playwright__browser_navigate&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;      &quot;mcp__playwright__browser_take_screenshot&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;      &quot;mcp__playwright__browser_snapshot&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three tools, named, all of them read-shaped. Navigating and screenshotting a localhost build is not a decision I want to re-approve forty times an hour. The version that saves more typing is &lt;code&gt;&quot;mcp__playwright&quot;&lt;/code&gt; on its own, and that one also hands over &lt;code&gt;browser_run_code_unsafe&lt;/code&gt;, which does exactly what the name promises.&lt;/p&gt;
&lt;p&gt;The reason to bother is that the allow-list is where blast radius is actually set. Take the GitHub server, which authenticates with a fine-grained personal access token you generate yourself. Put &lt;code&gt;mcp__github&lt;/code&gt; in &lt;code&gt;allow&lt;/code&gt; and you have said yes, in advance, to every tool it exposes, including the ones that open pull requests and file issues. Claude will use them correctly almost every time. The failure case is not a model that turns on you, it’s a model that misreads a sentence and opens a PR against the wrong repository at two in the afternoon, and the token you minted decided how far that goes.&lt;/p&gt;
&lt;p&gt;Scope the token to the repositories you meant. Name the tools you meant. “Proceed with caution” is not a setting.&lt;/p&gt;
&lt;p&gt;One asymmetry to know: allow rules in the shared project settings file wait for workspace trust before they apply, while &lt;code&gt;deny&lt;/code&gt; and &lt;code&gt;ask&lt;/code&gt; rules apply immediately. Restrictions you commit take effect on a fresh clone. Permissions you commit do not, until someone says yes.&lt;/p&gt;
&lt;h2 id=&quot;what-a-connected-server-costs&quot;&gt;What a connected server costs&lt;/h2&gt;
&lt;p&gt;It used to be that every tool definition from every server sat in the window before you typed anything, and a chatty server with forty tools was a tax you paid on the first message of every session. That is now the opt-out rather than the default. Tool search is on by default: only tool names and each server’s instructions load at session start, and the full definitions are fetched when Claude decides it needs them. Descriptions and server instructions are truncated at 2KB each, so a server author who buries the important sentence at the bottom loses it.&lt;/p&gt;
&lt;p&gt;You can change the trade. &lt;code&gt;ENABLE_TOOL_SEARCH=false&lt;/code&gt; puts everything back upfront. &lt;code&gt;auto&lt;/code&gt; loads definitions upfront while they stay under 10% of the context window and defers them all past that line, and &lt;code&gt;auto:5&lt;/code&gt; moves the line to 5%. A single server can opt out with &lt;code&gt;&quot;alwaysLoad&quot;: true&lt;/code&gt; in its config entry, which also makes startup wait for that server’s tool list, capped at the five second connect timeout. Worth it for two tools Claude needs on every turn. Not worth it for a server you touch twice a week.&lt;/p&gt;
&lt;p&gt;The cost that still bites is output, not definitions. Claude Code warns when a single MCP tool result exceeds 10,000 tokens and truncates at 25,000 by default, with &lt;code&gt;MAX_MCP_OUTPUT_TOKENS&lt;/code&gt; to raise the ceiling. One call to a documentation server that returns a whole page is a larger context event than the entire tool list it came from. Everything in &lt;a href=&quot;https://josh.naijora.com/blog/context-is-the-budget/&quot;&gt;part one about context being a budget&lt;/a&gt; applies here, except the spending decision is being made by a process you did not write.&lt;/p&gt;
&lt;h2 id=&quot;the-one-that-earned-its-place&quot;&gt;The one that earned its place&lt;/h2&gt;
&lt;p&gt;Playwright is the server on this project I would fight to keep, and it is the clearest case of a capability I cannot fake with a shell.&lt;/p&gt;
&lt;p&gt;The site is visual. Claude cannot see it. Left alone, it writes a component, reads the file back, and tells me the layout is correct, which is an assertion about source code dressed up as an observation about a page. With the browser attached, the loop closes: build, serve the output, navigate, take a screenshot, look at what actually rendered, then change the code. The repo’s own &lt;code&gt;CLAUDE.md&lt;/code&gt; puts it more bluntly than I would, that screenshots are the only way to verify rendering rather than assume it.&lt;/p&gt;
&lt;p&gt;It comes with a real constraint. The Playwright server cannot open &lt;code&gt;file://&lt;/code&gt; URLs, so the freshly built &lt;code&gt;dist/&lt;/code&gt; directory sitting on disk is unreachable. Something has to serve it first:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;npx&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; astro&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;npx&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; serve&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; dist&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then navigate to localhost and start looking. The checklist that follows is boring on purpose: screenshot the affected routes, toggle the theme and screenshot again because this site ships dark and light and a token fix in one can break contrast in the other, then re-run with reduced motion emulated and confirm the animation genuinely stopped rather than got faster.&lt;/p&gt;
&lt;p&gt;None of that is knowledge Claude gained. It is a feedback signal it could not previously obtain, which is a different thing, and it changes what the model is able to be wrong about. Before, it could be wrong about the rendered page and never find out. Now it finds out in the same turn.&lt;/p&gt;
&lt;p&gt;That is the test I run before adding anything now. Finish the sentence “Claude cannot do this with a shell and a filesystem.” If the sentence won’t finish, what I wanted was for the model to remember something, and remembering is a file or an event, not a tool. The hex literals were the second one. No server was ever going to catch them, because catching them means running a grep on a file the instant it is written, which is a &lt;a href=&quot;https://josh.naijora.com/blog/hooks-run-whether-the-model-agrees-or-not/&quot;&gt;hook&lt;/a&gt;, and a hook does not need the model’s cooperation.&lt;/p&gt;</content:encoded><category>AI Engineering</category><category>claude-code</category><category>mcp</category><category>tooling</category><category>playwright</category></item><item><title>Custom commands for work that repeats</title><link>https://josh.naijora.com/blog/custom-commands-for-work-that-repeats/</link><guid isPermaLink="true">https://josh.naijora.com/blog/custom-commands-for-work-that-repeats/</guid><description>A slash command is a prompt you got tired of retyping. What changes when it becomes a file is that it can be reviewed, shared, and wrong in only one place.</description><pubDate>Thu, 27 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I typed the same paragraph into three sessions in one afternoon. It said that every colour on this site comes from a token in &lt;code&gt;tokens.css&lt;/code&gt;, that a hex literal anywhere else fails &lt;code&gt;npm run no-hex&lt;/code&gt;, and that dark is the default theme when &lt;code&gt;localStorage&lt;/code&gt; is empty. By the third time I had shortened it. The short version dropped the dark-default sentence, and the component I got back looked right in light and wrong in dark.&lt;/p&gt;
&lt;p&gt;That is not a prompting problem. It is a version control problem. I had three copies of one instruction, all slightly different, none of them authoritative, and no way to tell which was current except by reading my own scrollback.&lt;/p&gt;
&lt;p&gt;The fix is not clever. Put the paragraph in a file.&lt;/p&gt;
&lt;h2 id=&quot;the-file-is-the-command&quot;&gt;The file is the command&lt;/h2&gt;
&lt;p&gt;A markdown file in &lt;code&gt;.claude/commands/&lt;/code&gt; becomes a slash command named after the file. &lt;code&gt;.claude/commands/audit.md&lt;/code&gt; gives you &lt;code&gt;/audit&lt;/code&gt;. Commit it and everyone who clones the repo has &lt;code&gt;/audit&lt;/code&gt; too. The same file in &lt;code&gt;~/.claude/commands/&lt;/code&gt; gives you &lt;code&gt;/audit&lt;/code&gt; in every project on your machine instead. There is no registry to update, nothing to declare, and no build step.&lt;/p&gt;
&lt;p&gt;Anthropic has since folded custom commands into skills, and the two are now the same mechanism. A file at &lt;code&gt;.claude/commands/deploy.md&lt;/code&gt; and a directory at &lt;code&gt;.claude/skills/deploy/SKILL.md&lt;/code&gt; both produce &lt;code&gt;/deploy&lt;/code&gt; and behave identically. Existing command files keep working. What the skill form buys you is a directory, so a reference doc or a helper script can sit next to the prompt instead of being inlined into it. For anything longer than a screen, take the directory.&lt;/p&gt;
&lt;p&gt;Precedence, when the same name exists twice: enterprise beats personal, personal beats project, and a skill beats a command file. Plugin skills carry a &lt;code&gt;plugin-name:skill-name&lt;/code&gt; namespace and cannot collide with anything you wrote. In a monorepo, a &lt;code&gt;.claude/skills/&lt;/code&gt; directory below your working directory loads the first time Claude reads or edits a file inside it, and if its name clashes with one at the root it appears under a directory-qualified name like &lt;code&gt;apps/web:deploy&lt;/code&gt; while plain &lt;code&gt;/deploy&lt;/code&gt; still runs the root one.&lt;/p&gt;
&lt;p&gt;On restarting: Claude Code watches skill directories. Add or edit a &lt;code&gt;SKILL.md&lt;/code&gt; under &lt;code&gt;~/.claude/skills/&lt;/code&gt; or the project’s &lt;code&gt;.claude/skills/&lt;/code&gt; and the change lands in the session you are already sitting in. Two cases still need a restart. One is a top-level skills directory that did not exist when the session started, because there was nothing to watch. The other is a command or subagent file inside a directory you passed with &lt;code&gt;--add-dir&lt;/code&gt;, which Claude Code never watches at all.&lt;/p&gt;
&lt;h2 id=&quot;arguments-and-the-ways-to-spell-them&quot;&gt;Arguments, and the ways to spell them&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ARGUMENTS&lt;/code&gt; is everything you typed after the command name, as one string. It is the right choice when the argument is a description rather than a value, which is most of the time.&lt;/p&gt;
&lt;p&gt;For values, you can index. &lt;code&gt;$ARGUMENTS[0]&lt;/code&gt; is the first argument and &lt;code&gt;$0&lt;/code&gt; is shorthand for the same thing. Zero-based, which will catch you exactly once. Indexed arguments use shell-style quoting, so &lt;code&gt;/component ThemeToggle &quot;swaps the theme and remembers it&quot;&lt;/code&gt; puts the whole quoted phrase in &lt;code&gt;$1&lt;/code&gt;. If you would rather not count, declare names in the frontmatter with &lt;code&gt;arguments: [component, brief]&lt;/code&gt; and write &lt;code&gt;$component&lt;/code&gt; and &lt;code&gt;$brief&lt;/code&gt; in the body.&lt;/p&gt;
&lt;p&gt;The two behave differently when an argument is missing. A named placeholder expands to an empty string. An indexed one stays in the text as the literal characters &lt;code&gt;$2&lt;/code&gt;, which then reaches the model as part of your instructions. And if you pass arguments to a command whose body has no placeholder at all, Claude Code appends &lt;code&gt;ARGUMENTS: &amp;#x3C;what you typed&gt;&lt;/code&gt; to the end rather than dropping them, so a command you forgot to parameterise is untidy rather than broken.&lt;/p&gt;
&lt;p&gt;Here is the paragraph I kept retyping, as a file:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-dark-font-weight:bold;--shiki-light:#005CC5;--shiki-light-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;description: Build a new Astro component to this site&apos;s conventions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;argument-hint: [&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-dark-text-decoration:inherit;--shiki-light:#032F62;--shiki-light-text-decoration:underline&quot;&gt;component-name&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;] [what it should do]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-dark-font-weight:bold;--shiki-light:#005CC5;--shiki-light-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Build a component called &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`$0`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;What it needs to do: $1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Conventions that are not negotiable:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; Every colour, space and font size comes from a token in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;  `src/styles/tokens.css`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;. A hex literal in an &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`.astro`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; or &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`.css`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  file fails &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`npm run no-hex`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; Dark is the default theme when &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`localStorage`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; is empty. Render&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  and check both themes before calling it done.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; Islands cost page weight. &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`node scripts/budgets.mjs --check`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  exits 1 when a route goes over its budget.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the dark-default rule changes, it changes in one place, and the change shows up in a diff that someone can argue with.&lt;/p&gt;
&lt;h2 id=&quot;frontmatter-turns-a-prompt-into-a-contract&quot;&gt;Frontmatter turns a prompt into a contract&lt;/h2&gt;
&lt;p&gt;Command files take the same frontmatter as skills, with two exceptions: &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;paths&lt;/code&gt; are ignored in a command file. The fields that earn their line:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;description&lt;/code&gt; says what the command does and when it applies. It is also what Claude reads when deciding whether to reach for the command without being asked, so vague descriptions cause both mis-triggering and silence.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;argument-hint&lt;/code&gt; shows in autocomplete. &lt;code&gt;[component-name] [what it should do]&lt;/code&gt; is worth ten seconds of typing because it is the only documentation anyone will read.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allowed-tools&lt;/code&gt; lists tools Claude may use without a permission prompt during the turn that invoked the command. The grant clears when you send your next message, so it is per-turn, not per-session.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disallowed-tools&lt;/code&gt; removes tools from the pool while the command is active, on the same per-turn basis.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt; and &lt;code&gt;effort&lt;/code&gt; pin the model or the effort level for the command. A mechanical formatting command does not need your expensive model.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disable-model-invocation: true&lt;/code&gt; means only you can invoke it. I set this on anything with side effects. You do not want Claude deciding your code looks ready to deploy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One thing worth knowing about &lt;code&gt;allowed-tools&lt;/code&gt; before you clone somebody’s repo and start a session in it: workspace trust does not gate that field. A project command can grant itself broad tool access, and Claude Code honours it whenever the command is invoked. Read the frontmatter of commands that arrive with a checkout.&lt;/p&gt;
&lt;p&gt;Keep the body short for a reason that has nothing to do with taste. Once a command is invoked, its rendered content enters the conversation as a message and stays there for the rest of the session. Claude Code does not re-read the file on later turns. Every line you wrote is a recurring cost against the same window your actual work is competing for, so write standing instructions rather than a tutorial, and move the long reference material into a skill directory where it can sit unread until something needs it.&lt;/p&gt;
&lt;h2 id=&quot;the-backtick-that-runs-before-claude-reads-anything&quot;&gt;The backtick that runs before Claude reads anything&lt;/h2&gt;
&lt;p&gt;This is the feature that turns a saved prompt into something closer to a script. A &lt;code&gt;!`command`&lt;/code&gt; in the body runs before the file is sent to the model, and the output replaces the placeholder. Claude receives data, not an instruction to go and fetch data. For several commands in a row, open a fenced block whose info string is a single &lt;code&gt;!&lt;/code&gt; and put one command per line inside it.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;!&lt;/code&gt; is only recognised at the start of a line or immediately after whitespace. Put it after another character, as in &lt;code&gt;KEY=!`cmd` &lt;/code&gt;, and the placeholder stays as literal text and the command never runs.&lt;/p&gt;
&lt;p&gt;Now the part that cost me an afternoon. A non-zero exit code aborts the entire invocation, not just that placeholder. Claude never sees any of the file, and you get &lt;code&gt;Shell command failed for pattern &quot;...&quot;&lt;/code&gt; instead of an answer. My &lt;code&gt;no-hex&lt;/code&gt; script exits 1 precisely when it has found something worth telling me about, which is the exact case I wanted the command for. Append &lt;code&gt;|| true&lt;/code&gt; to anything that exits non-zero by design.&lt;/p&gt;
&lt;p&gt;Injected commands also never prompt for permission. If the permission check returns anything other than allow, including a rule that would normally ask you, the invocation aborts. Pre-approve with &lt;code&gt;allowed-tools&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-dark-font-weight:bold;--shiki-light:#005CC5;--shiki-light-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;description: Find and fix vulnerable dependencies, then prove&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  nothing broke&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;allowed-tools: Bash(npm audit *) Bash(npm run *) Bash(npx astro *)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;disable-model-invocation: true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-dark-font-weight:bold;--shiki-light:#005CC5;--shiki-light-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-dark-font-weight:bold;--shiki-light:#005CC5;--shiki-light-font-weight:bold&quot;&gt;## Current state&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Advisories: !&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`npm --prefix site audit || true`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Outdated: !&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`npm --prefix site outdated || true`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-dark-font-weight:bold;--shiki-light:#005CC5;--shiki-light-font-weight:bold&quot;&gt;## What to do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; Apply what &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`npm audit fix`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; resolves on its own. Do not pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;   `--force`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;. A major version bump is a decision, not a fix.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; For anything left, read the advisory and say whether it reaches&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;   shipped code. A prototype-pollution finding in a build-only&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;   dependency of a static site is not the same risk as one in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;   something that goes to the browser.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; Run &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`npm run build`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;`npx astro check`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;   `node scripts/budgets.mjs --check`&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;. All three, in that order.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;4.&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; Report every advisory you did not fix, with the reason.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both &lt;code&gt;npm audit&lt;/code&gt; and &lt;code&gt;npm outdated&lt;/code&gt; exit non-zero when they find something, so both need the &lt;code&gt;|| true&lt;/code&gt;. That is not a quirk of those two commands. It is true of most check tools, because exiting non-zero on a finding is what makes them usable in CI, and it means the tools most worth injecting are the ones most likely to abort the command that injects them.&lt;/p&gt;
&lt;p&gt;You can also name files with an &lt;code&gt;@&lt;/code&gt; reference in the body and Claude Code attaches them, the same way &lt;code&gt;@&lt;/code&gt; works when you type it into the prompt. Putting &lt;code&gt;@site/src/styles/tokens.css&lt;/code&gt; at the top of the component command saves a lookup Claude would otherwise do on its own.&lt;/p&gt;
&lt;h2 id=&quot;a-command-is-invoked-a-skill-is-reached-for&quot;&gt;A command is invoked, a skill is reached for&lt;/h2&gt;
&lt;p&gt;That is the whole distinction, and since the two are now one file format, it comes down to a frontmatter field. &lt;code&gt;disable-model-invocation: true&lt;/code&gt; makes something invocation-only: it stays out of Claude’s context until you type the slash. &lt;code&gt;user-invocable: false&lt;/code&gt; makes it reach-for-only, for background knowledge that is not a meaningful action to take. Leave both off and you get both behaviours. &lt;a href=&quot;https://josh.naijora.com/blog/skills-and-progressive-disclosure/&quot;&gt;Part 6&lt;/a&gt; covers what that second mode is actually for.&lt;/p&gt;
&lt;p&gt;The line that matters more day to day is the one between a command file and &lt;code&gt;CLAUDE.md&lt;/code&gt;. &lt;code&gt;CLAUDE.md&lt;/code&gt; loads at the start of every session, relevant or not, and that is &lt;a href=&quot;https://josh.naijora.com/blog/context-is-the-budget/&quot;&gt;the entire cost model&lt;/a&gt;. It should hold facts that apply broadly: the build commands, the conventions that differ from the language defaults, the gotchas nobody could infer from reading the code. A procedure that only matters when you are doing one particular thing has no business being in context while you debug something else.&lt;/p&gt;
&lt;p&gt;My test is a grammar test. If it reads as an instruction, it goes in &lt;code&gt;CLAUDE.md&lt;/code&gt;. If it reads as a procedure with steps, it goes in a file with a slash in front of it.&lt;/p&gt;
&lt;p&gt;The paragraph I kept retyping is now eleven lines in a file that anyone on the project can open, and the last time it was wrong, it was wrong in exactly one place.&lt;/p&gt;</content:encoded><category>AI Engineering</category><category>claude-code</category><category>slash-commands</category><category>automation</category><category>workflow</category></item><item><title>Context is the budget</title><link>https://josh.naijora.com/blog/context-is-the-budget/</link><guid isPermaLink="true">https://josh.naijora.com/blog/context-is-the-budget/</guid><description>Claude Code spends a fixed slice of the window before you type a word. Most sessions that go badly are budget failures, not model failures.</description><pubDate>Wed, 26 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This site was built by nine agents working in parallel on disjoint sets of files. The rule that makes that safe is written down in the repo: an agent that needs something outside its owned paths reports it instead of creating it. One evening I finished the layout slice and, instead of starting a fresh session, pasted the next brief into the same one.&lt;/p&gt;
&lt;p&gt;Twenty minutes later the content agent had imported a component from the layout group and written into a directory it did not own.&lt;/p&gt;
&lt;p&gt;Nothing about that was a reasoning failure. It had read the ownership rules. It had also sat through two hours of me approving edits across the whole &lt;code&gt;src/&lt;/code&gt; tree, and as far as the transcript was concerned that was the established shape of the work. The rule was one line in a file loaded at startup. The counter-evidence was a hundred tool calls deep and much fresher.&lt;/p&gt;
&lt;p&gt;I have come to think that is what almost every bad session actually is. Not the model being stupid, the window being wrong. And a bigger window does not rescue you: a million tokens of capacity just means you can accumulate more irrelevant material before you notice the answers getting vaguer. The skill worth building is not prompting. It is deciding what Claude is allowed to see.&lt;/p&gt;
&lt;h2 id=&quot;the-spend-before-you-type&quot;&gt;The spend before you type&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/context&lt;/code&gt; prints the window as a grid with a breakdown by category. Here is a reading from one of my sessions, a few dozen messages in, on a model configured with a one-million-token window:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  one session, one million tokens available&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ┌───────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ system prompt         8.9k  ┐                         │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ tool definitions       11k  │  fixed overhead, ~23k,  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ skill descriptions    2.1k  │  spent before I typed   │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ agent definitions      778  ┘                         │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├───────────────────────────────────────────────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ conversation         21.4k     everything I actually  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │                                said and did           │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├───────────────────────────────────────────────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ autocompact buffer     33k     reserved, not mine     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └───────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             43k of 1M used&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your numbers will be different. The shape is the part that generalises: when I ran this, the setup cost more than the conversation did. Twenty-three thousand tokens of system prompt, tool schemas, skill descriptions and agent definitions were in the window before I said a word, and none of it was about my project.&lt;/p&gt;
&lt;p&gt;Two entries in that list are worth understanding rather than skimming. The autocompact buffer is reserved space, not free space. Claude Code compacts automatically as you approach the limit, and it needs room to do the summarising, so the working ceiling is lower than the headline number.&lt;/p&gt;
&lt;p&gt;The skill descriptions line is one sentence per skill, not the skills themselves. Bodies load only when a skill is used, which is a deliberate design and the subject of &lt;a href=&quot;https://josh.naijora.com/blog/skills-and-progressive-disclosure/&quot;&gt;part six&lt;/a&gt;. But the descriptions are unconditional. Install thirty plugins and you are paying rent on all thirty in every session, including the ones about Postman collections while you are debugging a CSS grid.&lt;/p&gt;
&lt;h2 id=&quot;where-the-instructions-live&quot;&gt;Where the instructions live&lt;/h2&gt;
&lt;p&gt;Persistent instructions come from a hierarchy of &lt;code&gt;CLAUDE.md&lt;/code&gt; files, loaded from the broadest scope down to the most specific and concatenated rather than overridden. All of it lands in context together.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  loaded at launch, machine-wide first, working dir last&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ┌───────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ managed policy   /Library/Application Support/        │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │                  ClaudeCode/CLAUDE.md                 │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├───────────────────────────────────────────────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ user             ~/.claude/CLAUDE.md                  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │                  ~/.claude/rules/*.md                 │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├───────────────────────────────────────────────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ project          ./CLAUDE.md or ./.claude/CLAUDE.md   │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │                  .claude/rules/*.md                   │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├───────────────────────────────────────────────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ local            ./CLAUDE.local.md   (gitignored)     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └───────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  loaded later, only when Claude reads a file they match&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ┌───────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ nested           src/api/CLAUDE.md                    │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ path-scoped      .claude/rules/*.md with paths:       │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └───────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every directory above your working directory contributes, so launching in &lt;code&gt;foo/bar/&lt;/code&gt; picks up &lt;code&gt;foo/CLAUDE.md&lt;/code&gt; too. In a monorepo that is how you end up reading another team’s conventions all day, and &lt;code&gt;claudeMdExcludes&lt;/code&gt; in your local settings is the fix.&lt;/p&gt;
&lt;p&gt;The split between the two boxes is the useful part. Everything in the first is always present. Everything in the second is conditional, which makes it nearly free until it is relevant. So the question for each rule you write is not “is this true?” but “is this true often enough to pay for on every single turn?”&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/init&lt;/code&gt; writes the first version of the project file for you by exploring the codebase and summarising what it finds. Treat that output as a draft. What it produces well is the derivable stuff, directory layouts and build commands, and that is exactly the material worth deleting later: Claude can rediscover a folder structure in one tool call, and paying for it on every turn is a bad trade. What earns permanent residence is the opposite, the things no amount of reading the code will reveal. Which decisions are already settled. Which mistake this codebase invites.&lt;/p&gt;
&lt;p&gt;Keep each file under about 200 lines. That is a real threshold rather than a stylistic one, because a longer file measurably reduces adherence: your rule ends up competing with everything else in the window for attention. This repo’s &lt;code&gt;CLAUDE.md&lt;/code&gt; opens by declaring a budget for itself and pointing at &lt;code&gt;AGENTS.md&lt;/code&gt; for everything about the project. That structure is not tidiness. &lt;code&gt;CLAUDE.md&lt;/code&gt; holds the handful of facts needed on every turn, and the rest is a document Claude reads when the task calls for it.&lt;/p&gt;
&lt;p&gt;That distinction is worth being precise about, because &lt;code&gt;@&lt;/code&gt; means two different things. Inside a memory file, &lt;code&gt;@AGENTS.md&lt;/code&gt; is an import: the file is expanded and loaded at launch, so splitting content across imports organises your instructions without saving a single token. A plain markdown link is not an import. It costs one tool call at the moment the information is needed, and nothing at all the rest of the time. I use links.&lt;/p&gt;
&lt;h2 id=&quot;the-part-of-the-budget-you-did-not-write&quot;&gt;The part of the budget you did not write&lt;/h2&gt;
&lt;p&gt;There is a second memory system alongside &lt;code&gt;CLAUDE.md&lt;/code&gt;, and it is on by default. As you work, Claude saves notes to itself: your role and working preferences, corrections you gave it, project decisions it cannot derive from the code, and pointers to things outside the repo. They live per repository under &lt;code&gt;~/.claude/projects/&amp;#x3C;project&gt;/memory/&lt;/code&gt;, with a &lt;code&gt;MEMORY.md&lt;/code&gt; index whose first 200 lines load at the start of every session and topic files that are read on demand.&lt;/p&gt;
&lt;p&gt;I like it more than I expected to. It absorbs the corrections you would otherwise type twice, and unlike &lt;code&gt;CLAUDE.md&lt;/code&gt; it costs you nothing to maintain. But it is worth being clear-eyed about what it is: instructions in your startup budget that you did not author and have not read. Run &lt;code&gt;/memory&lt;/code&gt; occasionally and look. Mine holds a note that this project’s commits must never list Claude as an author, which is correct and which I am glad I do not have to repeat, and it got there because I said it once in a session I no longer remember.&lt;/p&gt;
&lt;p&gt;One detail matters if you work the way this repo does. Auto memory is keyed to the git repository, so every worktree of the same repo shares one directory. Ten worktrees off one repository, all reading and writing the same notes.&lt;/p&gt;
&lt;h2 id=&quot;naming-the-file-instead-of-hunting-for-it&quot;&gt;Naming the file instead of hunting for it&lt;/h2&gt;
&lt;p&gt;In a prompt, &lt;code&gt;@&lt;/code&gt; is a file path mention with autocomplete behind it, and its value is almost entirely about what it prevents.&lt;/p&gt;
&lt;p&gt;Ask “where do we validate the JWT?” and Claude does what you would do: globs for likely filenames, greps for the term, opens four files, discards three. Every one of those reads lands in your window permanently and the three wrong ones stay there, being wrong, for the rest of the session. File reads dominate context usage in practice, and the reads that cost the most are the speculative ones.&lt;/p&gt;
&lt;p&gt;Say &lt;code&gt;@src/lib/auth.ts&lt;/code&gt; and the search phase does not happen. One read, one file, and the window contains exactly the file you meant.&lt;/p&gt;
&lt;p&gt;This is the same instinct as delegating exploration to a subagent, which is the right move when you genuinely do not know where something lives, and which &lt;a href=&quot;https://josh.naijora.com/blog/sub-agents-are-a-context-decision/&quot;&gt;part five&lt;/a&gt; is about. When you do know, just say so. Being specific in a prompt is a token optimisation dressed as politeness.&lt;/p&gt;
&lt;h2 id=&quot;compact-keeps-what-it-learned-clear-throws-it-away&quot;&gt;Compact keeps what it learned, clear throws it away&lt;/h2&gt;
&lt;p&gt;Two commands free space and they are not interchangeable.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/compact&lt;/code&gt; replaces the conversation with a structured summary and keeps going. Startup content comes back from disk: project-root &lt;code&gt;CLAUDE.md&lt;/code&gt;, auto memory, the plan from plan mode, and up to five of the most recently modified files Claude touched. What gets summarised away is the conversation itself, which is where every correction you typed lives. This is the single best argument for writing rules down: an instruction you gave in chat may not survive compaction, and one in &lt;code&gt;CLAUDE.md&lt;/code&gt; is re-injected every time.&lt;/p&gt;
&lt;p&gt;You can steer it. &lt;code&gt;/compact focus on the auth bug fix&lt;/code&gt; keeps what you choose instead of what the automatic pass guesses, and a “Compact Instructions” section in &lt;code&gt;CLAUDE.md&lt;/code&gt; sets that preference permanently.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/clear&lt;/code&gt; starts a new conversation with an empty context. Everything learned is gone.&lt;/p&gt;
&lt;p&gt;The rule I use: same problem, compact; different problem, clear. And the second half of that matters more than people expect, because carrying context across unrelated tasks is not merely wasteful, it is actively misleading. Old conversation crowds out the files you need next, and it costs tokens on every subsequent message. Worse, it is evidence. The model is reasoning about what you appear to be doing, and two hours of layout work is a strong argument that you are still doing layout work. That is precisely how my content agent talked itself into editing another group’s files.&lt;/p&gt;
&lt;p&gt;Mid-turn there are two smaller levers. &lt;code&gt;Esc&lt;/code&gt; interrupts immediately and keeps the work done so far, which is how you stop a plan that has expanded past what you asked for without discarding the useful half. Press &lt;code&gt;Esc&lt;/code&gt; twice on an empty prompt and you get the rewind menu, which restores files and conversation to an earlier point, and can also summarise from or up to a chosen message when only part of the session is worth keeping.&lt;/p&gt;
&lt;h2 id=&quot;buying-reasoning-on-purpose&quot;&gt;Buying reasoning on purpose&lt;/h2&gt;
&lt;p&gt;Plan mode is the one place I spend context deliberately. &lt;code&gt;Shift+Tab&lt;/code&gt; cycles the permission modes and plan is one of them, or prefix a single prompt with &lt;code&gt;/plan&lt;/code&gt;. Claude reads, explores, and writes a proposal without touching your source. You approve it or send it back.&lt;/p&gt;
&lt;p&gt;The trade is real. Planning costs a research pass you then pay for again during implementation, and for a two-line fix that is silly. For anything touching more than about three files it has been consistently worth it, because the failure it prevents is the expensive one: forty minutes of confident edits in the wrong direction, which you now have to read, understand, and unpick.&lt;/p&gt;
&lt;p&gt;Reasoning effort is the other dial, set with &lt;code&gt;/effort&lt;/code&gt; and running &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;, &lt;code&gt;xhigh&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt; on current models, with &lt;code&gt;high&lt;/code&gt; as the usual default. These are token spend, straightforwardly. &lt;code&gt;xhigh&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; buy deeper reasoning by generating a great deal more of it, and &lt;code&gt;max&lt;/code&gt; is prone to overthinking a task that did not need it. If you want one turn to think harder without changing the session, put &lt;code&gt;ultrathink&lt;/code&gt; in the prompt.&lt;/p&gt;
&lt;p&gt;None of these levers help if the window is already full of yesterday’s problem. Clear first, then spend.&lt;/p&gt;</content:encoded><category>AI Engineering</category><category>claude-code</category><category>context</category><category>agents</category><category>ai-engineering</category></item><item><title>Designing a Read-Heavy Service That Survives Its Own Success</title><link>https://josh.naijora.com/blog/read-heavy-service-design/</link><guid isPermaLink="true">https://josh.naijora.com/blog/read-heavy-service-design/</guid><description>A long-form walk through caching, replication lag, stampede control, and pagination for a service whose read traffic grows faster than its writes.</description><pubDate>Wed, 26 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most services that fall over in production do not fall over because a write
path got slow. They fall over because a read path that was cheap at a thousand
requests per minute stayed structurally identical at a hundred thousand, and
nobody re-derived the assumptions underneath it.&lt;/p&gt;
&lt;p&gt;This is a long fixture post. It exists so the article template has something
with real structural depth to render: nested headings, code, a wide table, a
blockquote, and enough sections that the table of contents crosses the point
where part numbering needs zero-padding. If you are reading it as content
rather than as a test, the engineering in it is still meant to be correct.&lt;/p&gt;
&lt;h2 id=&quot;the-shape-of-the-problem&quot;&gt;The shape of the problem&lt;/h2&gt;
&lt;p&gt;A read-heavy service is one where the ratio of reads to writes is high enough
that read cost dominates every capacity decision you make. Catalogue services,
profile lookups, feed rendering, pricing, and permission checks all land here.
The characteristic failure is not a slow query. It is a query that was always
slow and only became visible once concurrency rose far enough that the
queueing delay in front of it exceeded the service time inside it.&lt;/p&gt;
&lt;p&gt;That distinction matters because the two problems have different fixes. A slow
query wants an index. A queueing problem wants fewer arrivals, more servers,
or shorter service time, and adding an index only helps through the third.&lt;/p&gt;
&lt;h2 id=&quot;establish-the-read-and-write-ratio-first&quot;&gt;Establish the read and write ratio first&lt;/h2&gt;
&lt;p&gt;Before choosing any mechanism, write down the actual ratio. Not the ratio you
assume from the domain, the ratio your logs report. Teams routinely guess
100:1 for a workload that measures 6:1, then spend a quarter building cache
infrastructure that saves very little.&lt;/p&gt;
&lt;h3 id=&quot;measuring-it-without-a-metrics-stack&quot;&gt;Measuring it without a metrics stack&lt;/h3&gt;
&lt;p&gt;If you have no instrumentation yet, the database will usually tell you. On
PostgreSQL:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;SELECT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  schemaname,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  relname,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  seq_scan &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; idx_scan            &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; reads,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;  n_tup_ins &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; n_tup_upd &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; n_tup_del &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; writes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; pg_stat_user_tables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; reads &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;DESC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;LIMIT&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 20&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those counters are cumulative since the last stats reset, so take two samples
an hour apart and diff them. A single reading tells you about the lifetime of
the process, which is rarely the period you care about.&lt;/p&gt;
&lt;h3 id=&quot;what-a-low-ratio-implies&quot;&gt;What a low ratio implies&lt;/h3&gt;
&lt;p&gt;If reads and writes are within an order of magnitude of each other, caching
buys less than it costs. The invalidation logic, the extra failure mode, and
the operational surface of another stateful system are all real, and they are
worth paying for at 100:1 and usually not at 5:1.&lt;/p&gt;
&lt;h2 id=&quot;the-latency-budget&quot;&gt;The latency budget&lt;/h2&gt;
&lt;p&gt;Give the endpoint a number before you design it. Something like: 95th
percentile under 120 milliseconds at the edge, of which 30 milliseconds is
network and TLS, leaving 90 milliseconds of server time.&lt;/p&gt;
&lt;p&gt;Then decompose the 90 into the calls the handler actually makes. A handler
that makes four sequential dependency calls has roughly 22 milliseconds each,
and any dependency whose own p95 exceeds that is a design problem you now
know about in advance rather than after launch.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A latency budget is useful mainly because it converts an argument about
taste into an argument about arithmetic. “This feels slow” is hard to
resolve. “This call has 22 milliseconds and takes 60” is not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;caching-is-a-consistency-decision&quot;&gt;Caching is a consistency decision&lt;/h2&gt;
&lt;p&gt;The reason cache design is hard is that it is not really a performance
decision. Choosing a cache is choosing how stale a reader is allowed to be,
and that is a product question wearing infrastructure clothes.&lt;/p&gt;
&lt;h3 id=&quot;cache-aside&quot;&gt;Cache-aside&lt;/h3&gt;
&lt;p&gt;The application checks the cache, misses, reads the database, and populates
the cache itself. Simple, and the default for good reason. The cost is that
every caller must implement the pattern correctly, and one caller that forgets
to populate turns into a permanent miss for that key shape.&lt;/p&gt;
&lt;h3 id=&quot;read-through&quot;&gt;Read-through&lt;/h3&gt;
&lt;p&gt;The cache sits in the read path and fetches on miss. Callers see one
interface. This removes the class of bug above and moves it into whatever
component implements the fetch, which is usually a smaller and better tested
surface.&lt;/p&gt;
&lt;h3 id=&quot;write-through-and-write-behind&quot;&gt;Write-through and write-behind&lt;/h3&gt;
&lt;p&gt;Write-through updates cache and database together, so readers never see a
stale entry the writer just replaced. Write-behind acknowledges the write
after the cache update and persists asynchronously, which is faster and
introduces a window where an acknowledged write is not yet durable. Use
write-behind only where losing that window is genuinely acceptable.&lt;/p&gt;
&lt;h2 id=&quot;choosing-a-cache-key&quot;&gt;Choosing a cache key&lt;/h2&gt;
&lt;p&gt;Cache keys are a schema. Treat them like one.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Include a version prefix, so a shape change is a deploy rather than a
flush: &lt;code&gt;v3:user:1234:profile&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Include every input that affects the value. Locale, currency, and
permission scope are the three most commonly forgotten.&lt;/li&gt;
&lt;li&gt;Do not include anything that varies per request but not per value, such as
a trace identifier. That is how you get a cache with a zero percent hit
rate and a very confident owner.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A key that omits an input does not produce a cache miss. It produces a
correct-looking response containing another user’s data, which is why this
list is short and worth re-reading.&lt;/p&gt;
&lt;h2 id=&quot;stampedes-and-how-they-actually-happen&quot;&gt;Stampedes and how they actually happen&lt;/h2&gt;
&lt;p&gt;The textbook stampede is a popular key expiring and a thousand concurrent
requests all missing at once, all querying the database, all writing the same
value back. The database sees a thousand identical queries where it expected
one.&lt;/p&gt;
&lt;p&gt;The version that actually bites is subtler. Keys written at the same time
expire at the same time. A deploy that warms a cache, a cron job that
populates a batch, or a Redis restart all create cohorts of keys with
synchronised expiry, and the stampede arrives on a schedule.&lt;/p&gt;
&lt;p&gt;Two mitigations, applied together:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;// 1. Jitter the TTL so cohorts decorrelate.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;ttl &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; base &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; time.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Duration&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(rand.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Int63n&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int64&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(base&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;)))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;// 2. Collapse concurrent misses onto one fetch.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; group &lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;singleflight&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;c &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Cache&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) ([]&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; v, ok &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; c.redis.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(ctx, key); ok {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; v, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;	v, err, _ &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; group.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Do&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(key, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() (&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;		val, err &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; c.origin.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Fetch&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(ctx, key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, err&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;		c.redis.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Set&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(ctx, key, val, &lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;jitter&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(c.ttl))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; val, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, err&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; v.([]&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;singleflight&lt;/code&gt; deduplicates within one process. Across a fleet of fifty
processes you still get fifty origin fetches instead of fifty thousand, which
is usually sufficient. If it is not, a short distributed lock on the key is
the next step, with a timeout so a crashed holder cannot wedge the key
permanently.&lt;/p&gt;
&lt;h2 id=&quot;read-replicas-and-the-replication-lag-tax&quot;&gt;Read replicas and the replication lag tax&lt;/h2&gt;
&lt;p&gt;Replicas move read load off the primary and hand you a new correctness
problem in exchange. A user who writes and then immediately reads may hit a
replica that has not applied the write yet, and the interface appears to have
lost their change.&lt;/p&gt;
&lt;p&gt;The usual fixes, in increasing order of cost:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Route reads that follow a write from the same session to the primary for a
short window. Cheap, covers the common case, needs session affinity.&lt;/li&gt;
&lt;li&gt;Track the write position and require the replica to have applied at least
that position, falling back to the primary if it has not. On PostgreSQL
this is &lt;code&gt;pg_current_wal_lsn()&lt;/code&gt; compared against
&lt;code&gt;pg_last_wal_replay_lsn()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Read from the primary for anything the user can observe as their own
action, and use replicas only for aggregate or third-party views.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Whichever you choose, alert on replication lag as a first-class signal.
Silent lag growth turns a correctness guarantee into a coin flip without
producing a single error.&lt;/p&gt;
&lt;h2 id=&quot;denormalisation-and-the-write-amplification-it-buys&quot;&gt;Denormalisation and the write amplification it buys&lt;/h2&gt;
&lt;p&gt;Precomputing a read shape at write time converts an expensive join into a
single key lookup. The cost is write amplification: one logical write becomes
several physical writes, and every additional copy is a place the data can
diverge.&lt;/p&gt;
&lt;p&gt;Denormalise when the read is hot, the write is rare, and the divergence is
detectable. A materialised view with a scheduled refresh and a consistency
check that runs nightly is a reasonable middle position, because it fails
loudly rather than quietly.&lt;/p&gt;
&lt;h2 id=&quot;pagination-that-survives-deep-offsets&quot;&gt;Pagination that survives deep offsets&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;LIMIT 20 OFFSET 100000&lt;/code&gt; asks the database to produce a hundred thousand and
twenty rows and discard all but twenty. It gets slower linearly with depth,
and it produces duplicate or missing rows when the underlying data changes
between page requests.&lt;/p&gt;
&lt;p&gt;Keyset pagination fixes both:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; id, title, published_at&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; articles&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (published_at, id) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, $&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; published_at &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;DESC&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, id &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;DESC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;LIMIT&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 20&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tuple comparison requires a matching composite index on
&lt;code&gt;(published_at DESC, id DESC)&lt;/code&gt;. The tradeoff is that you lose the ability to
jump to an arbitrary page number, which almost no user does and almost every
specification asks for.&lt;/p&gt;
&lt;h2 id=&quot;backpressure-at-the-edge&quot;&gt;Backpressure at the edge&lt;/h2&gt;
&lt;p&gt;When a dependency degrades, a service without backpressure converts a slow
dependency into an outage. Requests queue, memory grows, garbage collection
pauses lengthen, and the service fails at something unrelated to the original
problem.&lt;/p&gt;
&lt;p&gt;Bound the queue. Set a concurrency limit per dependency, reject over the limit
with a 503 and a &lt;code&gt;Retry-After&lt;/code&gt;, and set client timeouts shorter than the
upstream’s own timeout so retries do not stack. A load shedder that returns
errors quickly is a better neighbour than one that accepts everything and
answers nothing.&lt;/p&gt;
&lt;h2 id=&quot;send-less-over-the-wire&quot;&gt;Send less over the wire&lt;/h2&gt;
&lt;p&gt;Serialisation and transfer are part of the latency budget and are usually the
part nobody has looked at. A handler that assembles its response in 8
milliseconds and then serialises 400 kilobytes of JSON has not been optimised,
it has been measured in the wrong place.&lt;/p&gt;
&lt;h3 id=&quot;trim-the-response-shape&quot;&gt;Trim the response shape&lt;/h3&gt;
&lt;p&gt;Return the fields the caller uses. A sparse fieldset parameter, or separate
summary and detail representations, removes more bytes than any compression
setting. This is also the change that most often reveals a client quietly
depending on a field nobody documented, so ship it behind a flag.&lt;/p&gt;
&lt;h3 id=&quot;compression-is-not-free-either&quot;&gt;Compression is not free either&lt;/h3&gt;
&lt;p&gt;Gzip at level 9 on a large payload can cost more CPU than the transfer time it
saves on a fast connection. Level 4 to 6 is the usual sweet spot, and Brotli
at level 4 beats gzip at level 6 on both size and speed for text. Measure on
your own payloads rather than trusting a benchmark run on someone else’s.&lt;/p&gt;
&lt;h3 id=&quot;idempotency-makes-retries-safe&quot;&gt;Idempotency makes retries safe&lt;/h3&gt;
&lt;p&gt;Once clients retry, every non-idempotent write becomes a duplicate risk.
Accept an &lt;code&gt;Idempotency-Key&lt;/code&gt; header on writes, store the key with the response
for a bounded window, and replay the stored response on a repeat. Retries then
stop being a correctness hazard and become the ordinary traffic they should
have been.&lt;/p&gt;
&lt;h2 id=&quot;what-to-measure&quot;&gt;What to measure&lt;/h2&gt;





























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Signal&lt;/th&gt;&lt;th&gt;Where it lives&lt;/th&gt;&lt;th&gt;Healthy&lt;/th&gt;&lt;th&gt;Investigate&lt;/th&gt;&lt;th&gt;Page&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Cache hit rate&lt;/td&gt;&lt;td&gt;Redis / app metrics&lt;/td&gt;&lt;td&gt;&gt; 92%&lt;/td&gt;&lt;td&gt;80 to 92%&lt;/td&gt;&lt;td&gt;&amp;#x3C; 80% sustained 10 min&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Origin fetch rate&lt;/td&gt;&lt;td&gt;App metrics&lt;/td&gt;&lt;td&gt;Flat&lt;/td&gt;&lt;td&gt;Rising with traffic&lt;/td&gt;&lt;td&gt;Step change after deploy&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Replication lag&lt;/td&gt;&lt;td&gt;Database&lt;/td&gt;&lt;td&gt;&amp;#x3C; 500 ms&lt;/td&gt;&lt;td&gt;0.5 to 5 s&lt;/td&gt;&lt;td&gt;&gt; 5 s for 2 min&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Endpoint p95&lt;/td&gt;&lt;td&gt;Edge / RUM&lt;/td&gt;&lt;td&gt;&amp;#x3C; 120 ms&lt;/td&gt;&lt;td&gt;120 to 300 ms&lt;/td&gt;&lt;td&gt;&gt; 300 ms for 5 min&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Endpoint p99&lt;/td&gt;&lt;td&gt;Edge / RUM&lt;/td&gt;&lt;td&gt;&amp;#x3C; 400 ms&lt;/td&gt;&lt;td&gt;400 ms to 1 s&lt;/td&gt;&lt;td&gt;&gt; 1 s for 5 min&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Connection pool saturation&lt;/td&gt;&lt;td&gt;App metrics&lt;/td&gt;&lt;td&gt;&amp;#x3C; 60%&lt;/td&gt;&lt;td&gt;60 to 85%&lt;/td&gt;&lt;td&gt;&gt; 85% for 2 min&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Shed request rate&lt;/td&gt;&lt;td&gt;Load shedder&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;Any sustained&lt;/td&gt;&lt;td&gt;&gt; 1% of traffic&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The table is deliberately wider than the prose column. On a narrow viewport it
should scroll inside its own container rather than forcing the page body to
scroll sideways, which is one of the things this fixture is here to check.&lt;/p&gt;
&lt;h2 id=&quot;failure-modes-worth-rehearsing&quot;&gt;Failure modes worth rehearsing&lt;/h2&gt;
&lt;p&gt;Design reviews catch design problems. Operational problems need rehearsal.&lt;/p&gt;
&lt;h3 id=&quot;cache-cold-start&quot;&gt;Cache cold start&lt;/h3&gt;
&lt;p&gt;Restart the cache in a staging environment under production-shaped load and
watch what the origin does. If the database saturates, your warmup path is
the real capacity limit and the steady-state numbers are fiction.&lt;/p&gt;
&lt;h3 id=&quot;replica-promotion&quot;&gt;Replica promotion&lt;/h3&gt;
&lt;p&gt;Promote a replica during business hours in staging. Measure how long
connection pools take to notice, how many requests fail during the switch,
and whether any code path silently kept a stale connection.&lt;/p&gt;
&lt;h3 id=&quot;dependency-brownout&quot;&gt;Dependency brownout&lt;/h3&gt;
&lt;p&gt;Inject 2 seconds of latency into a downstream call rather than failing it
outright. Slow dependencies are harder to survive than dead ones, because
nothing trips a circuit breaker configured only for errors.&lt;/p&gt;
&lt;h2 id=&quot;when-to-stop-optimising&quot;&gt;When to stop optimising&lt;/h2&gt;
&lt;p&gt;Stop when the next change costs more in complexity than it returns in
capacity or latency, measured against the budget you wrote down at the start.
A service that meets its p95 target with a 92% cache hit rate and one replica
does not need a second cache tier. It needs to be left alone so the team can
work on something else.&lt;/p&gt;
&lt;p&gt;Further reading worth the time: Marc Brooker on
&lt;a href=&quot;https://brooker.co.za/blog/&quot;&gt;timeouts and retries&lt;/a&gt;, and the AWS Builders’
Library entry on
&lt;a href=&quot;https://aws.amazon.com/builders-library/caching-challenges-and-strategies/&quot;&gt;caching challenges and strategies&lt;/a&gt;.
Both are more careful about failure modes than most conference talks on the
same subject.&lt;/p&gt;</content:encoded><category>System Design</category><category>system-design</category><category>caching</category><category>databases</category><category>reliability</category><category>performance</category></item><item><title>RAII and deterministic destruction versus a garbage collector</title><link>https://josh.naijora.com/blog/raii-and-deterministic-destruction/</link><guid isPermaLink="true">https://josh.naijora.com/blog/raii-and-deterministic-destruction/</guid><description>C++ has no finally block and does not need one. Destructors run at a known point, in a known order, and that guarantee is what replaces defer and try-with-resources.</description><pubDate>Tue, 25 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;C++ has no &lt;code&gt;finally&lt;/code&gt;. It has no &lt;code&gt;defer&lt;/code&gt;, no &lt;code&gt;with&lt;/code&gt;, no &lt;code&gt;using&lt;/code&gt;, no try-with-resources. Every other language I work in has grown some version of that construct, and the one with manual memory management never needed it.&lt;/p&gt;
&lt;p&gt;The reason is a single guarantee about destructors.&lt;/p&gt;
&lt;h2 id=&quot;the-guarantee&quot;&gt;The guarantee&lt;/h2&gt;
&lt;p&gt;A destructor is the method named after the class with a tilde in front. One per class, no arguments, no return type, and you never call it:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Player&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;private:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;    std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; name;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    Player&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    ~Player&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It runs automatically when the object is destroyed. For an object with a name, declared inside a block, that moment is when control leaves the block. Not eventually. Not at the next collection. At the closing brace.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;    Player&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; slayer;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;    Player&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; josh {&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;Josh&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;    Player&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; hero {&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;Hero&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;    // ... use them&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // three destructors have now run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three objects on the stack, three destructors, all before the next line executes. The compiler emits those calls. You cannot forget them, and there is no path out of that block that skips them.&lt;/p&gt;
&lt;p&gt;That last part is the interesting one.&lt;/p&gt;
&lt;h2 id=&quot;it-holds-on-the-paths-you-did-not-write&quot;&gt;It holds on the paths you did not write&lt;/h2&gt;
&lt;p&gt;Early return, break, an exception thrown four frames down: all of them leave the block, so all of them run the destructors. An exception propagating through unwinds each scope on the way out and destroys every local object it passes.&lt;/p&gt;
&lt;p&gt;Which means this is complete:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; process&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;    std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;ofstream&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; log {&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;run.log&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // opened here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    risky&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;();&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;                         // may throw&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;                                    // closed here, thrown or not&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is no second code path to write. Compare the shape you would write in a language without the guarantee:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;FileWriter&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; log&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    log &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; FileWriter&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;run.log&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    risky&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;finally&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (log &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) log.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The C++ version is not shorter because C++ is terser. It is shorter because the cleanup moved into the type. Anyone who uses &lt;code&gt;ofstream&lt;/code&gt; gets the correct behaviour without knowing it needed handling, and cannot get it wrong by writing the call site badly. That is the pattern people mean by RAII: acquire the resource in the constructor, release it in the destructor, and let scope do the rest.&lt;/p&gt;
&lt;p&gt;The name is about memory but the idea is not. File handles, sockets, mutex locks, database transactions, anything with a matching pair of operations.&lt;/p&gt;
&lt;h2 id=&quot;construction-and-destruction-are-mirror-images&quot;&gt;Construction and destruction are mirror images&lt;/h2&gt;
&lt;p&gt;The order is fixed and worth knowing, because it is what makes composed objects safe.&lt;/p&gt;
&lt;p&gt;Within a scope, objects are destroyed in reverse order of construction. Under inheritance, the base is constructed first and destroyed last:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Derived d;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  construction              destruction&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ───────────────▶          ◀───────────────&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Base()                    ~Derived()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Derived()                 ~Base()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The base part has to exist before the derived constructor runs, because the derived class may use it. On the way out, the derived part has to be dismantled before the base it was built on disappears. Reverse order is not a convention, it is the only order that is safe.&lt;/p&gt;
&lt;p&gt;The same logic covers members. A class holding a connection and a buffer built from it destroys them in an order where the dependency still exists while it is needed.&lt;/p&gt;
&lt;h2 id=&quot;what-you-give-up&quot;&gt;What you give up&lt;/h2&gt;
&lt;p&gt;This guarantee only applies to objects the compiler knows the lifetime of, which means objects with a name and a scope. Reach for &lt;code&gt;new&lt;/code&gt; and you leave that world:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Player &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;enemy &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; Player {&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;Enemy&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;delete&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; enemy;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;    // destructor runs here, because you said so&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the destructor runs when you call &lt;code&gt;delete&lt;/code&gt;, and the compiler will not remind you. Forget it and the destructor never runs at all. Between the &lt;code&gt;new&lt;/code&gt; and the &lt;code&gt;delete&lt;/code&gt;, any early return leaks.&lt;/p&gt;
&lt;p&gt;That is the trade the whole language argument is about. The compiler gives you exact, predictable cleanup, and charges you for being wrong about ownership.&lt;/p&gt;
&lt;p&gt;Modern C++ mostly buys its way out with &lt;code&gt;unique_ptr&lt;/code&gt; and &lt;code&gt;shared_ptr&lt;/code&gt;, which are objects with destructors that own a heap allocation. The scope guarantee comes back, applied to something on the heap. RAII solving the problem RAII created.&lt;/p&gt;
&lt;h2 id=&quot;against-a-garbage-collector&quot;&gt;Against a garbage collector&lt;/h2&gt;
&lt;p&gt;Go and Java give you the opposite trade. Memory is never your problem, and in exchange you lose the timing.&lt;/p&gt;
&lt;p&gt;A garbage collector answers “is anything still referencing this?” and it answers it whenever it feels like it. That is exactly right for memory, where the only question is whether the bytes can be reused. It is wrong for a file handle, a socket, or a lock, because those have a limit that is nothing to do with memory pressure. You can exhaust a connection pool while the heap is nearly empty, and the collector has no reason to act.&lt;/p&gt;
&lt;p&gt;So the construct comes back, out in the open:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;f, err &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; os.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Open&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;run.log&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; err }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;defer&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; f.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Close&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;defer&lt;/code&gt; is the same guarantee, scoped to the function rather than the block, and opted into per call site. It is a good design. It is also visible in a way RAII is not: every caller has to remember it, and forgetting is silent.&lt;/p&gt;
&lt;p&gt;Java’s finalizers tried to be destructors and are deprecated for the reason above. They run at collection time, which is to say at no time you can plan around.&lt;/p&gt;
&lt;p&gt;Neither model is the better one. Deterministic destruction costs you the ownership question on every allocation. Garbage collection costs you the timing on every non-memory resource. Knowing which cost you are paying is what tells you where the bug is going to be, and after six years the resource leaks I have chased were never in the C++.&lt;/p&gt;</content:encoded><category>C++</category><category>c++</category><category>memory</category><category>resource-management</category><category>fundamentals</category></item><item><title>Go Worker Pools: Bounded Concurrency Without Leaking Goroutines</title><link>https://josh.naijora.com/blog/go-worker-pools-bounded-concurrency/</link><guid isPermaLink="true">https://josh.naijora.com/blog/go-worker-pools-bounded-concurrency/</guid><description>How to size a worker pool, propagate cancellation with context, and shut down cleanly so no goroutine outlives the request that spawned it.</description><pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post exists in a different category to the AWS material, which makes it
the fixture that proves &lt;code&gt;/blog/category/go/&lt;/code&gt; renders independently of
&lt;code&gt;/blog/category/aws-cloud/&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;why-bound-anything&quot;&gt;Why bound anything&lt;/h2&gt;
&lt;p&gt;An unbounded &lt;code&gt;go func()&lt;/code&gt; per item is fine until the input is a million rows.
Then you discover that goroutines are cheap but the file descriptors,
database connections, and downstream rate limits they contend for are not.&lt;/p&gt;
&lt;h2 id=&quot;the-shape&quot;&gt;The shape&lt;/h2&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; Process&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;items&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; []&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Item&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;workers&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;	g, ctx &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; errgroup.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;WithContext&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(ctx)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;	in &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; make&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;chan&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Item&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;	g.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Go&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;		defer&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; close&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(in)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;		for&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; _, it &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; items {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;			select&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;			case&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; in &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x3C;-&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; it:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;			case&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x3C;-&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;ctx.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Done&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;				return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; ctx.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Err&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;	for&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; workers; i&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;		g.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Go&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;			for&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; it &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; in {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;				if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; handle&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(ctx, it); err &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;					return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; err&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;				}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;		})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; g.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Wait&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-part-people-get-wrong&quot;&gt;The part people get wrong&lt;/h2&gt;
&lt;p&gt;The producer must select on &lt;code&gt;ctx.Done()&lt;/code&gt;. Without it, a worker returning an
error cancels the context, the remaining workers stop reading, and the
producer blocks forever on a send that nobody will receive.&lt;/p&gt;
&lt;h2 id=&quot;sizing-the-pool&quot;&gt;Sizing the pool&lt;/h2&gt;
&lt;p&gt;For CPU-bound work, &lt;code&gt;runtime.GOMAXPROCS(0)&lt;/code&gt; is the starting point. For
IO-bound work the right number is whatever the slowest downstream dependency
tolerates, which you find by measurement rather than by arithmetic.&lt;/p&gt;</content:encoded><category>Go</category><category>go</category><category>concurrency</category><category>patterns</category></item><item><title>AWS Disaster Recovery Strategies: RTO, RPO, and the Four Recovery Patterns</title><link>https://josh.naijora.com/blog/aws-disaster-recovery-strategies/</link><guid isPermaLink="true">https://josh.naijora.com/blog/aws-disaster-recovery-strategies/</guid><description>Detailed study notes explaining RTO, RPO, backup and restore, pilot light, warm standby, and active-active DR.</description><pubDate>Fri, 21 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This one is not part of the study-notes series. It sits in the same category,
which makes it the post that proves category pages list series and non-series
articles together.&lt;/p&gt;
&lt;h2 id=&quot;the-two-numbers&quot;&gt;The two numbers&lt;/h2&gt;
&lt;p&gt;Recovery time objective is how long you can be down. Recovery point objective
is how much data you can afford to lose. Every DR pattern is a point on the
curve between those two numbers and what you are willing to pay.&lt;/p&gt;
&lt;h2 id=&quot;backup-and-restore&quot;&gt;Backup and restore&lt;/h2&gt;
&lt;p&gt;Cheapest, slowest. Snapshots go to S3 or a cross-Region copy, and recovery
means provisioning infrastructure from scratch. RTO in hours, RPO in hours.&lt;/p&gt;
&lt;h2 id=&quot;pilot-light&quot;&gt;Pilot light&lt;/h2&gt;
&lt;p&gt;A minimal core of the system runs continuously in the recovery Region, usually
the database replica and nothing else. Compute is provisioned on failover.
RTO in tens of minutes.&lt;/p&gt;
&lt;h2 id=&quot;warm-standby&quot;&gt;Warm standby&lt;/h2&gt;
&lt;p&gt;A scaled-down but functional copy of the whole system runs in the second
Region. Failover is a traffic switch followed by a scale-up. RTO in minutes.&lt;/p&gt;
&lt;h2 id=&quot;multi-site-active-active&quot;&gt;Multi-site active-active&lt;/h2&gt;
&lt;p&gt;Both Regions serve production traffic. RTO approaches zero and so does RPO,
and the cost is a full second environment plus the work of keeping data
consistent across it.&lt;/p&gt;
&lt;h2 id=&quot;picking-one&quot;&gt;Picking one&lt;/h2&gt;
&lt;p&gt;The pattern is chosen by the business, not by the platform team. Ask what an
hour of downtime costs. If nobody can answer, that is the finding to report
before you build anything.&lt;/p&gt;</content:encoded><category>AWS &amp; Cloud</category><category>aws</category><category>reliability</category><category>disaster-recovery</category></item><item><title>AWS Global Infrastructure: Regions, Zones, and Edge Locations</title><link>https://josh.naijora.com/blog/aws-cloud-practitioner-part-4/</link><guid isPermaLink="true">https://josh.naijora.com/blog/aws-cloud-practitioner-part-4/</guid><description>AWS Cloud Practitioner study notes on Regions, Availability Zones, CloudFront edge locations, Local Zones, Wavelength Zones, and Direct Connect.</description><pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The hierarchy is small enough to memorise and precise enough that the exam
will punish a loose reading of it.&lt;/p&gt;
&lt;h2 id=&quot;regions&quot;&gt;Regions&lt;/h2&gt;
&lt;p&gt;A Region is a named geographic area containing multiple Availability Zones.
Regions are isolated from each other by design: nothing replicates between
them unless you configure it to.&lt;/p&gt;
&lt;h3 id=&quot;choosing-a-region&quot;&gt;Choosing a Region&lt;/h3&gt;
&lt;p&gt;Compliance first, then latency to your users, then service availability, then
price. Newer services do not launch everywhere at once, so the third check
catches more people than expected.&lt;/p&gt;
&lt;h2 id=&quot;availability-zones&quot;&gt;Availability Zones&lt;/h2&gt;
&lt;p&gt;An AZ is one or more discrete data centres with independent power, cooling,
and networking, connected to sibling AZs over low-latency links. Spreading
across AZs is the standard answer to “how do I survive a data centre failure”.&lt;/p&gt;
&lt;h2 id=&quot;edge-locations&quot;&gt;Edge locations&lt;/h2&gt;
&lt;p&gt;CloudFront’s cache sits at edge locations, of which there are far more than
Regions. Route 53 and AWS Global Accelerator use the same footprint.&lt;/p&gt;
&lt;h2 id=&quot;the-specialised-zones&quot;&gt;The specialised zones&lt;/h2&gt;
&lt;p&gt;Local Zones place compute close to a metropolitan area for latency-sensitive
workloads. Wavelength Zones sit inside telecom providers’ 5G networks. Both
are extensions of a parent Region rather than Regions in their own right.&lt;/p&gt;
&lt;h2 id=&quot;direct-connect&quot;&gt;Direct Connect&lt;/h2&gt;
&lt;p&gt;A dedicated private link between your premises and AWS. It is not part of the
Region hierarchy, but questions about consistent network performance usually
want it as the answer.&lt;/p&gt;</content:encoded><category>AWS &amp; Cloud</category><category>aws</category><category>global-infrastructure</category><category>study-notes</category></item><item><title>What compiling actually means</title><link>https://josh.naijora.com/blog/what-compiling-actually-means/</link><guid isPermaLink="true">https://josh.naijora.com/blog/what-compiling-actually-means/</guid><description>Source becomes object code, object code becomes a program, and the linker sits between them. The model that go build and every bundler hide from you.</description><pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The first C++ error that genuinely stopped me was not a syntax error. It was this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;undefined reference to `Account::withdraw(double)&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;collect2: error: ld returned 1 exit status&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every line of my code was correct. The class was declared, the method was declared, the call matched the declaration. The compiler had no complaint at all. Something called &lt;code&gt;ld&lt;/code&gt; did, and I had never heard of it.&lt;/p&gt;
&lt;p&gt;That error is only confusing if you think “compiling” is one step. It is at least three, and they fail in different ways for different reasons.&lt;/p&gt;
&lt;h2 id=&quot;the-pipeline&quot;&gt;The pipeline&lt;/h2&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  main.cpp ─┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            │  preprocessor      compiler        linker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  file1.cpp ─┼──▶ (text only) ──▶ (per file) ──▶ (whole program) ──▶ main.exe&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            │                        │                 ▲&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  file2.cpp ─┘                       ▼                 │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                 main.o ───────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                 file1.o ──────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                 file2.o ──────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                                       │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          C++ standard library ────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          other libraries    ─────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four source files go in. One executable comes out. In between, each source file is turned into its own object file, and only at the very end does anything look at the program as a whole.&lt;/p&gt;
&lt;p&gt;That last sentence is the whole article, really. Everything confusing about C++ builds follows from it.&lt;/p&gt;
&lt;h2 id=&quot;the-preprocessor-does-not-understand-c&quot;&gt;The preprocessor does not understand C++&lt;/h2&gt;
&lt;p&gt;Before the compiler sees anything, a separate program rewrites your source as text. It handles the lines beginning with &lt;code&gt;#&lt;/code&gt;, and it has no idea what a class is.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;#include &amp;#x3C;iostream&gt;&lt;/code&gt; does not “import” anything. It opens that file, pastes its entire contents where the directive was, and does the same for every &lt;code&gt;#include&lt;/code&gt; inside it, recursively. Comments are stripped here too. By the time the compiler runs, your 40-line file might be 30,000 lines.&lt;/p&gt;
&lt;p&gt;This is why include guards exist:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;#ifndef&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; ACCOUNT_H&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; ACCOUNT_H&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Account&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;#endif&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If two files both include &lt;code&gt;account.h&lt;/code&gt;, the naive paste would define &lt;code&gt;Account&lt;/code&gt; twice in the same translation unit, and defining the same class twice is an error. The guard makes the second paste expand to nothing. It is a textual fix for a textual problem, which is exactly the level the preprocessor works at.&lt;/p&gt;
&lt;h2 id=&quot;each-source-file-is-compiled-alone&quot;&gt;Each source file is compiled alone&lt;/h2&gt;
&lt;p&gt;The compiler takes one preprocessed file and produces one object file: your code in machine form, plus a table of the names it defines and the names it still needs.&lt;/p&gt;
&lt;p&gt;The important word is &lt;em&gt;alone&lt;/em&gt;. When the compiler builds &lt;code&gt;main.o&lt;/code&gt;, it has never seen &lt;code&gt;account.cpp&lt;/code&gt;. It cannot check whether &lt;code&gt;Account::withdraw&lt;/code&gt; exists, because it has no way to look.&lt;/p&gt;
&lt;p&gt;So it takes your word for it. That is what a declaration is:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;double&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; withdraw&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;double&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; amount&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // I promise this exists somewhere&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The compiler needs the shape of the thing to generate a correct call: how many arguments, what types, what comes back. It does not need the body. It emits the call with a hole where the address should go, records “I need &lt;code&gt;Account::withdraw(double)&lt;/code&gt;” in the object file, and moves on.&lt;/p&gt;
&lt;h2 id=&quot;the-linker-resolves-the-promises&quot;&gt;The linker resolves the promises&lt;/h2&gt;
&lt;p&gt;The linker collects every object file and every library, and matches each unresolved name against the definitions. Fill in the addresses, write out one executable.&lt;/p&gt;
&lt;p&gt;When it cannot find a match, you get the error I opened with. &lt;code&gt;ld&lt;/code&gt; is the linker. The message is not saying your code is wrong. It is saying you promised a function existed and nobody delivered one.&lt;/p&gt;
&lt;p&gt;Once you know that, the causes are a short list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You declared it and never wrote the body.&lt;/li&gt;
&lt;li&gt;You wrote the body but never added that file to the build.&lt;/li&gt;
&lt;li&gt;You wrote the body with a slightly different signature, so the name the linker wants and the name you defined are two different names.&lt;/li&gt;
&lt;li&gt;It lives in a library you did not link.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The third one catches everyone at least once. &lt;code&gt;withdraw(double)&lt;/code&gt; and &lt;code&gt;withdraw(int)&lt;/code&gt; are separate functions to C++, and the mismatch is invisible until link time.&lt;/p&gt;
&lt;p&gt;The rule of thumb that has never failed me: if the error names a &lt;em&gt;line&lt;/em&gt;, it is the compiler and the problem is in that file. If the error names a &lt;em&gt;symbol&lt;/em&gt;, it is the linker and the problem is somewhere else in the build.&lt;/p&gt;
&lt;h2 id=&quot;what-build-and-clean-mean&quot;&gt;What “build” and “clean” mean&lt;/h2&gt;
&lt;p&gt;Building is compiling plus linking. Your IDE does both when you press the button and hides the object files, which is why the two stages blur together.&lt;/p&gt;
&lt;p&gt;“Clean” deletes the object files. It is useful for exactly one reason: object files are cached, and if the build system’s idea of what changed is wrong, you can be linking last week’s &lt;code&gt;file2.o&lt;/code&gt; against today’s &lt;code&gt;main.o&lt;/code&gt;. Clean, then rebuild, forces everything through both stages again. On Windows the result is &lt;code&gt;.exe&lt;/code&gt;; on macOS and Linux it usually has no extension at all.&lt;/p&gt;
&lt;h2 id=&quot;why-this-matters-if-you-write-go-or-node&quot;&gt;Why this matters if you write Go or Node&lt;/h2&gt;
&lt;p&gt;You may never run a linker by hand. The model still explains things you do hit.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;go build&lt;/code&gt; runs the same stages and prints a single error stream, so the distinction is invisible until you use cgo. Then a missing C library produces a linker error in Go’s output, with the same shape and the same causes as the one above.&lt;/p&gt;
&lt;p&gt;Node has no build step for your own code, but native modules do. When &lt;code&gt;npm install&lt;/code&gt; fails partway through compiling something with node-gyp, it is running this pipeline. “Symbol not found” from a native module at require time is a link failure that got deferred to runtime.&lt;/p&gt;
&lt;p&gt;Dynamic libraries are the same idea with the last step postponed. A &lt;code&gt;.so&lt;/code&gt; or &lt;code&gt;.dylib&lt;/code&gt; is resolved when the program starts rather than when it is built, which is why you can ship a working binary and still get “library not found” on someone else’s machine.&lt;/p&gt;
&lt;p&gt;The pattern underneath all of them: something recorded a name it needed, and later something else had to find it. Knowing which half failed tells you where to look.&lt;/p&gt;</content:encoded><category>C++</category><category>c++</category><category>compilers</category><category>linking</category><category>fundamentals</category></item><item><title>Six Advantages of Cloud Computing</title><link>https://josh.naijora.com/blog/aws-cloud-practitioner-part-3/</link><guid isPermaLink="true">https://josh.naijora.com/blog/aws-cloud-practitioner-part-3/</guid><description>AWS Cloud Practitioner study notes on the six advantages of cloud computing, from variable costs and elasticity to global deployment.</description><pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;AWS publishes six advantages and the exam quotes them almost verbatim, so it
is worth knowing them as phrases rather than as concepts you can paraphrase.&lt;/p&gt;
&lt;h2 id=&quot;trade-capital-expense-for-variable-expense&quot;&gt;Trade capital expense for variable expense&lt;/h2&gt;
&lt;p&gt;You stop buying servers ahead of demand and start paying for what you consume.
The exam wording is “capital expense” and “variable expense”, not CapEx and
OpEx.&lt;/p&gt;
&lt;h2 id=&quot;benefit-from-massive-economies-of-scale&quot;&gt;Benefit from massive economies of scale&lt;/h2&gt;
&lt;p&gt;Aggregate demand across all AWS customers produces lower per-unit prices than
any single organisation could negotiate.&lt;/p&gt;
&lt;h2 id=&quot;stop-guessing-capacity&quot;&gt;Stop guessing capacity&lt;/h2&gt;
&lt;p&gt;Capacity planning becomes a runtime decision rather than a procurement one.
This is the advantage most often confused with elasticity in questions.&lt;/p&gt;
&lt;h2 id=&quot;increase-speed-and-agility&quot;&gt;Increase speed and agility&lt;/h2&gt;
&lt;p&gt;New resources are minutes away rather than weeks, which changes what
experiments are worth running.&lt;/p&gt;
&lt;h2 id=&quot;stop-spending-money-running-and-maintaining-data-centres&quot;&gt;Stop spending money running and maintaining data centres&lt;/h2&gt;
&lt;p&gt;Racking, cooling, and physical security stop being your problem.&lt;/p&gt;
&lt;h2 id=&quot;go-global-in-minutes&quot;&gt;Go global in minutes&lt;/h2&gt;
&lt;p&gt;Deploying into additional Regions is a configuration change, not a project.&lt;/p&gt;
&lt;h2 id=&quot;a-note-on-the-update&quot;&gt;A note on the update&lt;/h2&gt;
&lt;p&gt;This part was revised after the CLF-C02 refresh reworded the fourth advantage.
The &lt;code&gt;updatedDate&lt;/code&gt; field on this post exists so the article template has a real
case to render.&lt;/p&gt;</content:encoded><category>AWS &amp; Cloud</category><category>aws</category><category>cloud-fundamentals</category><category>study-notes</category></item><item><title>Cloud Computing Models and Deployment Strategies</title><link>https://josh.naijora.com/blog/aws-cloud-practitioner-part-2/</link><guid isPermaLink="true">https://josh.naijora.com/blog/aws-cloud-practitioner-part-2/</guid><description>AWS Cloud Practitioner study notes on IaaS, PaaS, SaaS, public cloud, hybrid, on-premises, and multi-cloud deployments.</description><pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Two axes get confused constantly in exam questions: &lt;em&gt;what layer you rent&lt;/em&gt; and
&lt;em&gt;where the hardware lives&lt;/em&gt;. They are independent, and questions frequently
combine them to see whether you have noticed.&lt;/p&gt;
&lt;h2 id=&quot;the-service-models&quot;&gt;The service models&lt;/h2&gt;
&lt;p&gt;IaaS gives you the virtual machine and leaves the operating system to you.
PaaS hands over the runtime and keeps you out of patching. SaaS gives you the
finished application and no infrastructure surface at all.&lt;/p&gt;
&lt;h3 id=&quot;where-the-boundary-moves&quot;&gt;Where the boundary moves&lt;/h3&gt;
&lt;p&gt;EC2 is IaaS. Elastic Beanstalk sits at PaaS because it manages the platform
underneath your deployable artifact. WorkMail is SaaS. The useful test is to
ask what you are still responsible for patching.&lt;/p&gt;
&lt;h2 id=&quot;the-deployment-models&quot;&gt;The deployment models&lt;/h2&gt;
&lt;p&gt;Public cloud means everything runs on provider infrastructure. Hybrid keeps
some workloads on-premises and connects them, usually over Direct Connect or a
site-to-site VPN. On-premises with virtualisation is sometimes marketed as
private cloud, which the exam accepts.&lt;/p&gt;
&lt;h2 id=&quot;why-hybrid-persists&quot;&gt;Why hybrid persists&lt;/h2&gt;
&lt;p&gt;Data residency rules, latency-bound machinery, and depreciation schedules on
hardware that still has years left on it. Hybrid is rarely a technical
preference and usually a constraint someone inherited.&lt;/p&gt;</content:encoded><category>AWS &amp; Cloud</category><category>aws</category><category>cloud-fundamentals</category><category>study-notes</category></item><item><title>What the AWS Cloud Practitioner Exam Actually Tests</title><link>https://josh.naijora.com/blog/aws-cloud-practitioner-part-1/</link><guid isPermaLink="true">https://josh.naijora.com/blog/aws-cloud-practitioner-part-1/</guid><description>Where the CLF-C02 domains sit, how the question style differs from the associate exams, and what to study first.</description><pubDate>Wed, 12 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Cloud Practitioner exam is often described as the easy one. That framing
is unhelpful. It is a &lt;em&gt;broad&lt;/em&gt; exam rather than a deep one, and breadth is its
own kind of difficulty when the service catalogue keeps growing.&lt;/p&gt;
&lt;h2 id=&quot;the-four-domains&quot;&gt;The four domains&lt;/h2&gt;
&lt;p&gt;CLF-C02 splits into cloud concepts, security and compliance, cloud technology
and services, and billing. The weighting matters more than the names: roughly
half the exam sits in the services domain, so time spent memorising the
pricing calculator UI is time badly spent.&lt;/p&gt;
&lt;h2 id=&quot;how-the-questions-read&quot;&gt;How the questions read&lt;/h2&gt;
&lt;p&gt;Associate-level questions describe a scenario and ask you to pick an
architecture. Practitioner questions usually describe an outcome and ask which
service produces it. The distinction sounds small and changes how you study:
you need recall of what each service is &lt;em&gt;for&lt;/em&gt;, not how to configure it.&lt;/p&gt;
&lt;h2 id=&quot;what-to-read-first&quot;&gt;What to read first&lt;/h2&gt;
&lt;p&gt;Start with the shared responsibility model. Almost every security question
resolves to a single judgement about which side of the line a task falls on,
and getting that instinct early makes the rest of the security domain cheap.&lt;/p&gt;
&lt;h2 id=&quot;notes-on-this-series&quot;&gt;Notes on this series&lt;/h2&gt;
&lt;p&gt;Each part stands alone. They are numbered because the concepts compound, not
because you have to read them in order.&lt;/p&gt;</content:encoded><category>AWS &amp; Cloud</category><category>aws</category><category>certification</category><category>study-notes</category></item><item><title>What a function call does to memory</title><link>https://josh.naijora.com/blog/what-a-function-call-does-to-memory/</link><guid isPermaLink="true">https://josh.naijora.com/blog/what-a-function-call-does-to-memory/</guid><description>A frame gets pushed, a frame gets popped, and the ceiling was fixed when the thread was created. Which is why goroutines are cheap and Node has a maximum call stack.</description><pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RangeError: Maximum call stack size exceeded&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Node throws that. Go prints &lt;code&gt;fatal error: stack overflow&lt;/code&gt; and takes the process with it. C++ usually just segfaults and tells you nothing. Three languages, three levels of politeness, one cause, and for years the best I could do was “I recursed too far”.&lt;/p&gt;
&lt;p&gt;Which is true and explains nothing. Why is there a maximum at all? Why does the heap never fail this way? Why can I run a hundred thousand goroutines on a machine that would fall over at a hundred thousand threads?&lt;/p&gt;
&lt;p&gt;Same question, three times. C++ is where I had to answer it.&lt;/p&gt;
&lt;h2 id=&quot;the-stack-is-a-stack&quot;&gt;The stack is a stack&lt;/h2&gt;
&lt;p&gt;The data structure, not a metaphor for one. A stack of books: last one on is the first one off.&lt;/p&gt;
&lt;p&gt;The call stack holds one entry per call currently in progress. Each entry is a stack frame, or an activation record if you prefer the older name: that call’s parameters, its local variables, and the address to jump back to. Calling a function pushes one. Returning pops it.&lt;/p&gt;
&lt;p&gt;You cannot jump into the middle of the stack or out of it. A function returns to its caller and nowhere else, so frames come off in exactly the reverse of the order they went on.&lt;/p&gt;
&lt;h2 id=&quot;one-call-in-detail&quot;&gt;One call, in detail&lt;/h2&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; func2&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; y&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; z&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    x &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; y &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; z;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; func1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; result {};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    result &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    func2&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(result, a, b);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; result;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; x {&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}, y {&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}, z {};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    z &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; func1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(x, y);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;        // 60&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three functions, and at the deepest point all three are live at once. &lt;code&gt;main&lt;/code&gt; is waiting on &lt;code&gt;func1&lt;/code&gt;, which is waiting on &lt;code&gt;func2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;result&lt;/code&gt; lives in &lt;code&gt;func1&lt;/code&gt;’s frame. &lt;code&gt;y&lt;/code&gt; and &lt;code&gt;z&lt;/code&gt; live in &lt;code&gt;func2&lt;/code&gt;’s frame, as copies of what was passed in. And &lt;code&gt;x&lt;/code&gt; is a reference, so it holds the address of &lt;code&gt;result&lt;/code&gt; and points down into the frame below:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ┌──────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ func2   y=10  z=20  x ───────┼──┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │         return address       │  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├──────────────────────────────┤  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ func1   a=10  b=20           │  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │         result = 30 ◀────────┼──┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │         return address       │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├──────────────────────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │ main    x=10  y=20  z        │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └──────────────────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;func2&lt;/code&gt; adds 10 and 20 to whatever that reference points at, so &lt;code&gt;result&lt;/code&gt; becomes 60. Then its frame is gone, &lt;code&gt;func1&lt;/code&gt; returns 60, and that frame goes too.&lt;/p&gt;
&lt;p&gt;The bookkeeping underneath is mechanical, and the compiler emits all of it: push the parameters and the address to come back to, jump, and on the way out restore the caller’s frame along with any registers the callee saved.&lt;/p&gt;
&lt;p&gt;ABIs differ in the details and much of it happens in registers, but the shape holds. A fixed cost per call, paid whether or not the function does anything. That is why &lt;code&gt;inline&lt;/code&gt; exists.&lt;/p&gt;
&lt;h2 id=&quot;why-a-local-dies-at-the-closing-brace&quot;&gt;Why a local dies at the closing brace&lt;/h2&gt;
&lt;p&gt;Local variables are not preserved between calls. That gets taught as a scope rule, and scope is real: an identifier is visible only inside the block where it was declared.&lt;/p&gt;
&lt;p&gt;But the reason the value is gone next time has nothing to do with visibility. The frame it lived in was popped.&lt;/p&gt;
&lt;p&gt;One keyword changes the answer:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; tick&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    static&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; calls {&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // not in the frame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    calls&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;static&lt;/code&gt; moves that variable out of the activation record entirely. It is initialised once, the first time control reaches the declaration, and holds its value across every later call because there is no frame to take it away. Leave off the initialiser and it is zero rather than garbage. Globals are the same storage with wider visibility.&lt;/p&gt;
&lt;p&gt;Storage duration, not scope.&lt;/p&gt;
&lt;h2 id=&quot;the-four-regions&quot;&gt;The four regions&lt;/h2&gt;
&lt;p&gt;Everything a running program has is one of four things:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ┌──────────────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │  Heap, also called the free store            │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │  new and delete, sized at run time           │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │                                              │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │                          ▼ grows             │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├──────────────────────────────────────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │                          ▲ grows             │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │                                              │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │  Stack                                       │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │  one frame per call in progress              │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├──────────────────────────────────────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │  Static and global                           │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │  one slot each, alive for the whole run      │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├──────────────────────────────────────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │  Code                                        │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │  the compiled instructions themselves        │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └──────────────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is a schematic, not an address map: on Linux the stack sits high and grows down while the heap sits low and grows up. What holds everywhere is the regions and their lifetimes. Code never changes, the static area lasts the whole run, the stack tracks call depth exactly, and the two growable regions face each other because they compete for the same unallocated space.&lt;/p&gt;
&lt;h2 id=&quot;why-the-stack-has-a-ceiling-and-the-heap-does-not&quot;&gt;Why the stack has a ceiling and the heap does not&lt;/h2&gt;
&lt;p&gt;A frame is not an object anything tracks. It is a range of bytes underneath a pointer, and the pointer lives in a register. Pushing subtracts from it. Popping adds. Allocation and deallocation are one arithmetic instruction each, with no free list, no metadata and no fragmentation, which is why stack allocation is effectively free.&lt;/p&gt;
&lt;p&gt;The price is contiguity. Every live frame sits at a known offset from that pointer, and pointers into those frames are scattered through registers and other frames. Growing the region means moving it somewhere bigger, which invalidates all of them. C++ cannot find them: it has no idea which words in a frame are addresses and which are integers that happen to look like addresses.&lt;/p&gt;
&lt;p&gt;So the size is chosen once, when the thread is created, and never changes. On Linux the main thread typically gets eight megabytes, set by &lt;code&gt;ulimit -s&lt;/code&gt;; additional threads get whatever you asked for at creation.&lt;/p&gt;
&lt;p&gt;The heap never made that promise. It hands out individual blocks that need not be near each other, and can ask the operating system for more pages anywhere in the address space. &lt;code&gt;new&lt;/code&gt; fails when the machine runs out, not when a region fills up.&lt;/p&gt;
&lt;p&gt;Run off the end and you hit a guard page the kernel put there for exactly this. The program dies. Handling it would mean calling a function, and there is no room left for the frame.&lt;/p&gt;
&lt;p&gt;Two ways to get there, and only one is famous. A single frame can be too large by itself: a local array of a few million elements is a few million elements of stack, requested in one instruction. The other route is recursion, which changes nothing about the mechanism. Each call gets its own frame, so &lt;code&gt;factorial(n)&lt;/code&gt; bottoms out at n+1 of them, one per multiplication waiting on the one below.&lt;/p&gt;
&lt;p&gt;So a recursion limit is a frame budget, not an arbitrary rule, and how many frames fit depends on how large each one is. That is why nobody quotes a precise number: add a local buffer to the recursive function and the depth you can reach drops. The limit did not change. The frames got fatter. C++ does not guarantee tail-call elimination either, so a recursion that is fine at &lt;code&gt;-O2&lt;/code&gt; can be a crash at &lt;code&gt;-O0&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-this-explains-in-go-and-node&quot;&gt;What this explains in Go and Node&lt;/h2&gt;
&lt;p&gt;V8 gives each isolate a fixed stack, well under a megabyte by default and raisable with &lt;code&gt;--stack-size&lt;/code&gt;. It surfaces as a &lt;code&gt;RangeError&lt;/code&gt;, which makes it look like an ordinary catchable exception, and the appearance is the only thing that differs. The depth at which it throws shifts with what your functions carry.&lt;/p&gt;
&lt;p&gt;Go genuinely does something else. A goroutine starts with a two kilobyte stack, and the compiler emits a check in the prologue of every function that needs one: is there room for this frame? If not, the runtime allocates a larger stack, copies the live frames into it, rewrites every pointer that referred to the old location, and lets the function carry on as though nothing happened. The stack grows by moving.&lt;/p&gt;
&lt;p&gt;Copying is exactly what C++ cannot do. Fixing up those pointers requires knowing which words in every live frame point into the stack, and the Go runtime knows because it already needs precise maps for the garbage collector. That information does not exist in a compiled C++ binary.&lt;/p&gt;
&lt;p&gt;You feel that twice. A goroutine costs two kilobytes to start rather than a thread’s full reservation plus a kernel task, so a hundred thousand of them is unremarkable. And deep recursion behaves differently: the goroutine keeps growing until it hits the runtime’s own ceiling, one gigabyte on 64-bit by default, instead of dying at whatever its thread was handed. When it does, you get &lt;code&gt;fatal error: stack overflow&lt;/code&gt;, and &lt;code&gt;recover&lt;/code&gt; will not save you for the same reason C++ cannot.&lt;/p&gt;
&lt;p&gt;It is also why cgo forbids C code from keeping a Go pointer after the call returns: the stack it points into may not be there later.&lt;/p&gt;
&lt;p&gt;All of it comes down to one decision, made once per language: is the region allowed to move? Go said yes and pays a bounds check in every function prologue. C++ said no and hands you a segfault instead.&lt;/p&gt;</content:encoded><category>C++</category><category>c++</category><category>memory</category><category>call-stack</category><category>fundamentals</category></item><item><title>Pointer, reference, or value, and how to choose</title><link>https://josh.naijora.com/blog/pointer-reference-or-value/</link><guid isPermaLink="true">https://josh.naijora.com/blog/pointer-reference-or-value/</guid><description>C++ makes every function state whether the callee gets a copy, another name for your object, or an address that might be null. Go asks the same thing, quietly.</description><pubDate>Tue, 04 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Four spellings of the same function, and nothing compiles until you pick one:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; process&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Report&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;          // a copy, yours to wreck&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; process&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Report&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;         // the caller&apos;s report, under another name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; process&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Report&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // the same, read-only&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; process&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Report&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;         // its address, or nothing at all&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most languages settle this by category and then never mention it again. Primitives copy, objects don’t, and the call site looks identical either way. C++ puts the answer in the signature of every function you write, and the answer is load-bearing: it decides what the call costs, whether the caller’s data can change under them, and whether “no report” is a thing you are able to say.&lt;/p&gt;
&lt;h2 id=&quot;what-a-copy-actually-costs&quot;&gt;What a copy actually costs&lt;/h2&gt;
&lt;p&gt;Pass by value and the parameter is a fresh object built from the argument. For an &lt;code&gt;int&lt;/code&gt; that is four bytes into a register. There is no cheaper thing a function can do, and no reason to complicate it. If the parameter is an &lt;code&gt;int&lt;/code&gt;, a &lt;code&gt;char&lt;/code&gt;, a &lt;code&gt;double&lt;/code&gt;, a &lt;code&gt;bool&lt;/code&gt;, take it by value and stop thinking about it.&lt;/p&gt;
&lt;p&gt;The trap is that &lt;code&gt;sizeof&lt;/code&gt; does not tell you what a copy costs. On my machine &lt;code&gt;sizeof(std::string)&lt;/code&gt; is 24 bytes, and &lt;code&gt;sizeof(std::vector&amp;#x3C;std::string&gt;)&lt;/code&gt; is also 24. Those 24 bytes are a pointer, a length, and a capacity. Copying the object runs its copy constructor, which allocates a fresh buffer and copies every element in, and for a vector of strings that is one allocation for the vector and another for each string it holds. A parameter that measures 24 bytes can cost you a dozen trips to the heap. Those go by reference.&lt;/p&gt;
&lt;h2 id=&quot;arrays-were-never-given-the-choice&quot;&gt;Arrays were never given the choice&lt;/h2&gt;
&lt;p&gt;There is one type C++ refuses to copy for you, and it teaches the rest of the model. This compiles, and it does what the name says:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; zeroArray&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; numbers&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;[], &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;size_t&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; size&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;size_t&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; i {&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}; i &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; size; i&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;        numbers[i] &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; myNumbers[] {&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    zeroArray&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(myNumbers, &lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;      // myNumbers is now all zeros&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;code&gt;&amp;#x26;&lt;/code&gt; in that signature. No &lt;code&gt;*&lt;/code&gt; either. The function still reached into &lt;code&gt;main&lt;/code&gt; and overwrote data it does not own, because an array argument decays to a pointer to its first element, and what arrives is an address. It is also why the size has to be handed over separately: the length did not survive the decay, and &lt;code&gt;sizeof&lt;/code&gt; inside the function measures a pointer.&lt;/p&gt;
&lt;p&gt;If you did not want that, say so:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; printArray&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; numbers&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;[], &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;size_t&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; size&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now any write through &lt;code&gt;numbers&lt;/code&gt; is a compile error. Same address, less authority. Handing over the address and withholding permission to write is exactly what a &lt;code&gt;const &amp;#x26;&lt;/code&gt; parameter is, arrived at from the other direction.&lt;/p&gt;
&lt;h2 id=&quot;one-name-or-two&quot;&gt;One name or two&lt;/h2&gt;
&lt;p&gt;A reference is an alias. It has to be bound to something that already exists at the moment you declare it, and from then on it is a second name for that storage.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; num {&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;ref {num};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;num &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;      // ref is 200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;ref &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 300&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;      // num is 300&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  int num {100};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  int *ptr {&amp;#x26;num};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  int &amp;#x26;ref {num};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ptr:  [ 0x61ff1c ] ──┐             a pointer is an object,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                       │             its value is an address,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                       ▼             and it can be reassigned&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    [ 100 ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    ▲     ▲          num and ref are two names&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                num ┘     └ ref      for one piece of storage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything else about references falls out of that picture. One cannot be null, because there was an object there when you bound it. One cannot be left uninitialised. And one cannot be pointed somewhere else afterwards, because assignment writes through to the object it names. That is precisely what you want from an alias and precisely what makes it useless as a cursor. A reference behaves like a const pointer that dereferences itself.&lt;/p&gt;
&lt;p&gt;The property that catches people is subtler:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; square&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; n; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;square&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(num);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;    // fine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;square&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;      // error: 5 has no storage to alias&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add &lt;code&gt;const&lt;/code&gt; and it compiles, because the compiler may materialise a temporary and bind a const reference to it. So dropping &lt;code&gt;const&lt;/code&gt; from a reference parameter also declares that callers may not pass an expression result. Correct for an out-parameter. Baffling the first time it fires on &lt;code&gt;f(a + b)&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;a-pointer-is-a-variable-whose-value-is-an-address&quot;&gt;A pointer is a variable whose value is an address&lt;/h2&gt;
&lt;p&gt;Which sounds like a definition and is actually the whole feature list. It can be reassigned, it can be null, and it can be uninitialised and full of garbage that happens to read as a plausible address.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;double&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; highTemp {&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;100.7&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;double&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; lowTemp {&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;37.2&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;double&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;tempPtr {&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;highTemp};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;tempPtr &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;lowTemp;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;        // now names something else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;tempPtr &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nullptr&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;         // now names nothing&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A reference answers “which object” once, at birth. A pointer keeps that answer in a variable you are free to change. That freedom is also how an uninitialised pointer sits in a codebase for a year before somebody dereferences it on a Friday afternoon.&lt;/p&gt;
&lt;h2 id=&quot;picking-one&quot;&gt;Picking one&lt;/h2&gt;
&lt;p&gt;Take it by value when the type is small and cheap to copy and the function has no business touching the original. Simple types, mostly, and you will know them by name.&lt;/p&gt;
&lt;p&gt;Take it by &lt;code&gt;const &amp;#x26;&lt;/code&gt; when the copy is expensive and the function only reads. This is the default for &lt;code&gt;std::string&lt;/code&gt;, &lt;code&gt;std::vector&lt;/code&gt;, and any class of yours with an allocation inside it. The function gets an address, the compiler enforces the read-only half, and callers can still pass a temporary.&lt;/p&gt;
&lt;p&gt;Take it by &lt;code&gt;&amp;#x26;&lt;/code&gt; when the function is meant to modify the caller’s object and there is always an object to modify. &lt;code&gt;swap(int &amp;#x26;a, int &amp;#x26;b)&lt;/code&gt; has no better spelling.&lt;/p&gt;
&lt;p&gt;Reach for a pointer when absence is a legitimate answer. This is the case people try to be clever about and lose, because references cannot express it at all. Half the data structures worth writing depend on a pointer going null: end of list, empty subtree, no parent. If your parameter might mean “no node”, it has to be a pointer. When you want absence without mutation, &lt;code&gt;const T *const&lt;/code&gt; gives you something that may be null, may not be repointed, and may not write through. C++17’s &lt;code&gt;std::optional&lt;/code&gt; covers optional values you own outright; a pointer remains how you say “maybe a handle to something someone else owns”.&lt;/p&gt;
&lt;h2 id=&quot;go-asks-the-same-question-with-different-punctuation&quot;&gt;Go asks the same question with different punctuation&lt;/h2&gt;
&lt;p&gt;Go has pointers and it has values, and nothing in between. There are no references in the C++ sense, which means there is no way to declare a parameter that aliases the caller’s variable and is guaranteed not to be nil. So every time you want the callee to see your object rather than a photograph of it, you pass a pointer, and you have taken on nil as a possible value whether the domain wanted one or not.&lt;/p&gt;
&lt;p&gt;That absence is why receivers are the thing Go developers get wrong first:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;c &lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Counter&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;IncValue&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() { c.n&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; }   &lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;// increments a copy, discards it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;c &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Counter&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;IncPtr&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;()  { c.n&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; }   &lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;// increments the caller&apos;s Counter&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;IncValue&lt;/code&gt; compiles, runs, mutates nothing, and reports no error anywhere. It is &lt;code&gt;void scale(Report r)&lt;/code&gt; with different punctuation, and the fix is the same one: the callee needs the caller’s storage, not a snapshot of it.&lt;/p&gt;
&lt;p&gt;The cost side matches too. A value receiver copies the whole struct on every call, so a method set hanging off a struct with a few slices and a &lt;code&gt;time.Time&lt;/code&gt; in it is copying that struct on each invocation to no purpose. Go then adds a wrinkle C++ does not have: the method set of &lt;code&gt;T&lt;/code&gt; holds only the value-receiver methods while &lt;code&gt;*T&lt;/code&gt; holds both, so mixing receivers on one type gives you interface satisfaction that depends on whether the thing in your hand is a value or a pointer. The usual advice, pick one receiver kind per type and stick to it, is really advice to make this decision once for the type instead of thirty times for its methods.&lt;/p&gt;
&lt;p&gt;JavaScript deserves precision here, because “objects are passed by reference” is the phrase everyone uses and it is wrong. JavaScript passes everything by value. For an object, the value being passed is a reference. Reassign the parameter inside the function and the caller sees nothing; mutate the object it refers to and the caller sees everything. That is &lt;code&gt;Report *r&lt;/code&gt;, minus the null check and minus any way to ask for the other three.&lt;/p&gt;
&lt;p&gt;Every language decides this at every function boundary. Most decide for you, once, by category, and leave you to discover at runtime which one you got.&lt;/p&gt;</content:encoded><category>C++</category><category>c++</category><category>pointers</category><category>references</category><category>fundamentals</category></item><item><title>What virtual actually changes</title><link>https://josh.naijora.com/blog/what-virtual-actually-changes/</link><guid isPermaLink="true">https://josh.naijora.com/blog/what-virtual-actually-changes/</guid><description>A base-class pointer holding a derived object runs the base method. Why C++ binds at compile time, what virtual costs per object, and how Go&apos;s itab differs.</description><pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Two lines that do not do what they look like they do:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Account &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;p &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; Trust&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;p-&gt;&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;withdraw&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;        // Account::withdraw&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Trust&lt;/code&gt; inherits from &lt;code&gt;Account&lt;/code&gt; and defines its own &lt;code&gt;withdraw&lt;/code&gt;. The object sitting on the heap is a &lt;code&gt;Trust&lt;/code&gt;. The call runs the &lt;code&gt;Account&lt;/code&gt; version.&lt;/p&gt;
&lt;p&gt;The first time I hit this I assumed I had broken the inheritance somewhere. I had not. That is C++ behaving exactly as specified, and the specification is the reverse of what every other language I write had trained me to expect.&lt;/p&gt;
&lt;h2 id=&quot;the-compiler-binds-to-the-type-of-the-pointer&quot;&gt;The compiler binds to the type of the pointer&lt;/h2&gt;
&lt;p&gt;Deciding which function a call refers to is called binding, and C++ does it at compile time by default: the compiler reads the declared type of the expression you called through and picks that type’s method.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;p&lt;/code&gt; is declared &lt;code&gt;Account *&lt;/code&gt;. That is the whole input to the decision. The compiler does not know, and will not go looking, that the &lt;code&gt;new Trust()&lt;/code&gt; one line above put a &lt;code&gt;Trust&lt;/code&gt; at that address. The pointer says &lt;code&gt;Account&lt;/code&gt;, so the call is wired to &lt;code&gt;Account::withdraw&lt;/code&gt; before the program ever runs.&lt;/p&gt;
&lt;p&gt;On objects with names this is invisible: declared type and actual type are the same thing.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Savings b;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;b.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;withdraw&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;    // Savings::withdraw, as expected&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Redefining a base method in a derived class works fine, and you can still reach the original on purpose:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Savings&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Account&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; deposit&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;double&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; amount&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;        amount &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; interest;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;        Account&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;deposit&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(amount);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // the base version, deliberately&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is a real technique, and it is also the ceiling of what static binding gives you. Hold a mixed collection of accounts, want each one to behave like the thing it is, and you are stuck:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; displayAccount&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Account&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;acc&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    acc.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;();&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;     // Account::display. Always.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four account types can be passed to that function. One implementation runs.&lt;/p&gt;
&lt;h2 id=&quot;virtual-defers-the-decision&quot;&gt;virtual defers the decision&lt;/h2&gt;
&lt;p&gt;Mark the base method &lt;code&gt;virtual&lt;/code&gt; and every one of those call sites changes behaviour without being edited:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Account&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    virtual&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; withdraw&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;double&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; amount&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    virtual&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; display&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    virtual&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; ~Account&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;virtual&lt;/code&gt; is an instruction to the compiler to stop resolving the call. Emit a lookup instead, and let the running program ask the object what it is.&lt;/p&gt;
&lt;p&gt;Three conditions have to hold together: inheritance, a virtual method, and a call made through a base-class pointer or reference. Call on a plain object and you get static binding again, correctly, because a &lt;code&gt;Savings&lt;/code&gt; variable can only ever contain a &lt;code&gt;Savings&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The reference case matters more than it looks. It gives you dynamic dispatch with nothing on the heap:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;Trust t;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;displayAccount&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(t);&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // Trust::display&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;displayAccount&lt;/code&gt; was not touched. Only &lt;code&gt;display&lt;/code&gt; was.&lt;/p&gt;
&lt;p&gt;Virtual is sticky. Once a method is virtual in a base class it is virtual in every class beneath it, whether or not the derived declaration repeats the keyword.&lt;/p&gt;
&lt;h2 id=&quot;getting-the-signature-wrong-is-silent&quot;&gt;Getting the signature wrong is silent&lt;/h2&gt;
&lt;p&gt;An override has to match the base declaration exactly: name, parameters, const qualification, return type. Miss any part and you have not overridden anything, you have redefined, and redefinition is bound statically.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Base&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    virtual&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; sayHello&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt; /* ... */&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Derived&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Base&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    virtual&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; sayHello&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() {&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt; /* ... */&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // no const. Not an override.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That compiles clean. It runs. Through a &lt;code&gt;Base *&lt;/code&gt; it calls &lt;code&gt;Base::sayHello&lt;/code&gt; forever, and the word &lt;code&gt;virtual&lt;/code&gt; sitting right there in the derived class makes it look correct. I lost an afternoon to the missing-const version.&lt;/p&gt;
&lt;p&gt;C++11 turned the silence into a compiler error:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;virtual&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; sayHello&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;override&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // error: does not override&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;override&lt;/code&gt; costs nothing at runtime and catches the entire family of typo. Put it on every override you write.&lt;/p&gt;
&lt;p&gt;And any class with a virtual function needs a public virtual destructor. Delete a derived object through a base pointer without one and the derived destructor never runs, which the standard calls undefined behaviour.&lt;/p&gt;
&lt;h2 id=&quot;where-the-cost-lives&quot;&gt;Where the cost lives&lt;/h2&gt;
&lt;p&gt;The standard describes the behaviour, not the machinery. Every implementation I have looked at builds the same machinery anyway: a table.&lt;/p&gt;
&lt;p&gt;A class with virtual functions gets one table of function pointers, laid out at compile time and shared by every object of that class. Each object gets one hidden pointer to its class’s table, written in during construction.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            Trust vtable, one per class&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    p ──▶ ┌──────────┐      ┌────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          │ vptr ────┼─────▶│ [0]  Trust::withdraw   │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ├──────────┤      │ [1]  Trust::display    │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          │ balance  │      │ [2]  Trust::~Trust     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          │ name     │      └────────────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          └──────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A virtual call becomes: read the vptr out of the object, index a fixed slot, call through whatever pointer is in it. The slot number is decided at compile time, so nothing is searched. &lt;code&gt;withdraw&lt;/code&gt; is slot 0 in &lt;code&gt;Account&lt;/code&gt;’s table and slot 0 in every table derived from it, which is why the call site never needs to know which class it landed in.&lt;/p&gt;
&lt;p&gt;That buys two costs. Every object of a polymorphic class carries a pointer it did not ask for: eight bytes on a 64-bit target, the same eight whether the class declares one virtual function or forty.&lt;/p&gt;
&lt;p&gt;The bigger cost never shows up in &lt;code&gt;sizeof&lt;/code&gt;. The compiler cannot inline through the table, because it does not know the target, so the call blocks every optimisation that inlining would have unlocked downstream. Once per request, this is nothing. In a tight loop over a million elements, it is why somebody starts talking about templates.&lt;/p&gt;
&lt;p&gt;Which is the argument for making it opt-in. You pay for dispatch at the point where you asked for it.&lt;/p&gt;
&lt;h2 id=&quot;a-class-doing-an-interfaces-job&quot;&gt;A class doing an interface’s job&lt;/h2&gt;
&lt;p&gt;C++ has no &lt;code&gt;interface&lt;/code&gt; keyword. It has enough parts lying around that you can build one.&lt;/p&gt;
&lt;p&gt;A pure virtual function is a virtual function declared with &lt;code&gt;= 0&lt;/code&gt; and usually no body:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Shape&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    virtual&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; draw&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    virtual&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; rotate&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    virtual&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; ~Shape&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One pure virtual function makes the class abstract, and an abstract class cannot be instantiated. &lt;code&gt;Shape s;&lt;/code&gt; will not compile. Neither will &lt;code&gt;new Shape()&lt;/code&gt;. You reach a &lt;code&gt;Shape&lt;/code&gt; only through a pointer or reference to a concrete class derived from it, and a class becomes concrete only by overriding every pure virtual function it inherited.&lt;/p&gt;
&lt;p&gt;Now take a class that is nothing but public pure virtual functions. It names a set of services and implements none of them, and any class that wants to provide them says so by inheriting and implementing all of them. Every method, exactly matching. That is an interface, assembled from parts that were not designed for the job.&lt;/p&gt;
&lt;p&gt;The example that made it land for me was a printable interface:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Printable&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    friend&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;ostream&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;operator&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;ostream&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;os&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Printable&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;obj&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    virtual&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; print&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;ostream&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;os&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    virtual&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; ~Printable&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;ostream&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;operator&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;ostream&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;os&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; Printable&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt;obj&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    obj.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(os);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; os;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One &lt;code&gt;operator&amp;#x3C;&amp;#x3C;&lt;/code&gt;, written once, taking a reference to something abstract. &lt;code&gt;obj.print(os)&lt;/code&gt; is a virtual call through a base reference, so it runs the concrete class’s &lt;code&gt;print&lt;/code&gt;. Anything that inherits &lt;code&gt;Printable&lt;/code&gt; and implements &lt;code&gt;print&lt;/code&gt; becomes streamable, and the streaming code never learns its name.&lt;/p&gt;
&lt;p&gt;You will see these named &lt;code&gt;I_Shape&lt;/code&gt; or &lt;code&gt;IShape&lt;/code&gt; out in the wild, a convention that exists because the compiler has nothing to say about intent here. It sees an abstract class. Only the name tells you it was meant as a contract.&lt;/p&gt;
&lt;h2 id=&quot;go-was-told-nothing&quot;&gt;Go was told nothing&lt;/h2&gt;
&lt;p&gt;Go’s interfaces satisfy structurally. &lt;code&gt;*os.File&lt;/code&gt; has a &lt;code&gt;Write&lt;/code&gt; method with the right signature, so &lt;code&gt;*os.File&lt;/code&gt; is an &lt;code&gt;io.Writer&lt;/code&gt;. Neither type was ever told about the other, and the check happens at the assignment, on the shape of the method set. C++ cannot do that: &lt;code&gt;Circle&lt;/code&gt; has to name &lt;code&gt;Shape&lt;/code&gt; in its own declaration, which means you can never make a type you do not own satisfy an interface you just wrote.&lt;/p&gt;
&lt;p&gt;Go still dispatches through a table. An interface value is two words wide:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    var w io.Writer = f&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    w ──▶ ┌──────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          │ itab ────────┼──▶ methods of *File, as io.Writer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          │ data ────────┼──▶ the *File itself&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          └──────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The itab is that table. It holds the concrete type’s descriptor plus pointers to exactly the methods this interface requires, and it is keyed by the pair of types rather than by the concrete type alone. &lt;code&gt;*os.File&lt;/code&gt; used as an &lt;code&gt;io.Writer&lt;/code&gt; and &lt;code&gt;*os.File&lt;/code&gt; used as an &lt;code&gt;io.ReadWriteCloser&lt;/code&gt; produce two different itabs. The compiler and linker build the ones that are statically obvious; the runtime builds and caches the rest on first use.&lt;/p&gt;
&lt;p&gt;So the itab is not the type’s method table, and it is not carried by the value. That second part is the real difference. The pointer lives in the interface value, which means an &lt;code&gt;*os.File&lt;/code&gt; you never assign to an interface has no itab near it and pays nothing. C++ puts the vptr inside the object, so every instance of a polymorphic class pays whether or not one call is ever dispatched dynamically. Go moved the cost to the boundary where dispatch happens.&lt;/p&gt;
&lt;p&gt;Java went the other way entirely: instance methods are virtual by default and you opt out with &lt;code&gt;final&lt;/code&gt;, the C++ default read backwards, with a JIT that claws back most of the cost by devirtualizing call sites it can prove see only one type.&lt;/p&gt;
&lt;p&gt;All three end up calling through a table of function pointers. What differs is who holds the pointer to that table, and when somebody filled it in. C++ is the one that makes you say out loud which methods were worth it.&lt;/p&gt;</content:encoded><category>C++</category><category>c++</category><category>polymorphism</category><category>interfaces</category><category>fundamentals</category></item><item><title>Exception hierarchies, and what Go rejected</title><link>https://josh.naijora.com/blog/error-hierarchies-and-what-go-rejected/</link><guid isPermaLink="true">https://josh.naijora.com/blog/error-hierarchies-and-what-go-rejected/</guid><description>A constructor cannot return an error code, so C++ throws. The std::exception tree, what catching a base class buys you, and where errors.As lands.</description><pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;Account&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Account&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; name, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;double&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; balance)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    : name{name}, balance{balance} {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (balance &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 0.0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;        throw&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; IllegalBalanceException{};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A constructor has no return value. It cannot hand back &lt;code&gt;false&lt;/code&gt;, it cannot hand back an error code, and there is no half-built object it would be safe to give you instead. When the arguments are wrong, or the file it was told to open is not there, or the memory it needed did not arrive, there is exactly one way out.&lt;/p&gt;
&lt;p&gt;That is the strongest argument for exceptions I know, and it is not really about style. Some functions have no channel to report failure through.&lt;/p&gt;
&lt;h2 id=&quot;where-a-class-can-throw-and-where-it-must-not&quot;&gt;Where a class can throw, and where it must not&lt;/h2&gt;
&lt;p&gt;Methods throw the way plain functions do. Nothing new there.&lt;/p&gt;
&lt;p&gt;Constructors are the interesting case. If you cannot establish the invariant the class promises, throw, and the object never comes into existence:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    auto&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; moes &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;make_unique&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;CheckingAccount&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;Moe&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt;10.0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;    // use moes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; IllegalBalanceException &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;ex) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;    std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::cerr &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; &quot;Couldn&apos;t create account&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#005CC5&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The program continues. There is no partially valid &lt;code&gt;Account&lt;/code&gt; sitting in a variable waiting for someone to forget to check it.&lt;/p&gt;
&lt;p&gt;Destructors are the opposite rule, and it is absolute. Do not throw from one. Since C++11 they are &lt;code&gt;noexcept&lt;/code&gt; by default anyway, so the compiler already assumes you won’t. The reason is the unwinding machinery: when an exception isn’t caught in the current scope, C++ walks back up the call stack looking for a handler, and every scope it leaves on the way runs the destructors for the objects in it. A destructor that throws at that moment is throwing a second exception while the first is still in flight. The original handler is never reached. The program terminates instead, and you debug a crash whose actual cause was three frames and one type away.&lt;/p&gt;
&lt;h2 id=&quot;throw-objects-catch-references&quot;&gt;Throw objects, catch references&lt;/h2&gt;
&lt;p&gt;Throw an object, not a primitive. &lt;code&gt;throw 0;&lt;/code&gt; compiles fine and a &lt;code&gt;catch (int &amp;#x26;ex)&lt;/code&gt; somewhere up the stack will handle it, but now the integer zero is your error protocol and any other function that throws an int is competing for the same handler. A named type is unambiguous:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; DivideByZeroException&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; { };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; NegativeValueException&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; { };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;double&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; calculateMpg&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; miles&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#E36209&quot;&gt; gallons&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (gallons &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;        throw&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; DivideByZeroException&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (miles &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; gallons &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;        throw&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; NegativeValueException&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; static_cast&amp;#x3C;double&gt;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(miles) &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; gallons;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Throw by value, catch by reference. Catching by value copies the thrown object into the handler’s parameter, and if the handler’s type is a base class, the copy is only the base part. The derived half is sliced off, and a virtual call that should have reached your override reaches the base implementation instead. Catch by &lt;code&gt;const&lt;/code&gt; reference and you get the actual object.&lt;/p&gt;
&lt;h2 id=&quot;the-tree&quot;&gt;The tree&lt;/h2&gt;
&lt;p&gt;The standard library ships a hierarchy, rooted at &lt;code&gt;std::exception&lt;/code&gt;. Every class in it implements one virtual function:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;virtual&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;what&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; noexcept&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It returns a C-style string describing what happened. That is the entire interface.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  std::exception&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├── logic_error          a bug in your code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   ├── invalid_argument&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   ├── domain_error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   ├── length_error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   └── out_of_range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├── runtime_error        a condition from the world&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   ├── range_error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   ├── overflow_error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   ├── underflow_error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │   └── system_error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └── bad_alloc            the allocator gave up&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That split down the middle is the part that travels. A &lt;code&gt;logic_error&lt;/code&gt; means a precondition was violated by the caller: you indexed past the end, you passed an argument the function documented as illegal. It was checkable before the call and somebody didn’t check. A &lt;code&gt;runtime_error&lt;/code&gt; means the world said no. The number didn’t fit, the syscall failed, the resource wasn’t there.&lt;/p&gt;
&lt;p&gt;The two need completely different handling. One is a defect and the correct response is usually to fail loudly, because retrying a bug just runs the bug again. The other is a condition, and retrying, backing off, or degrading is exactly right. Almost every error taxonomy I have seen since is trying to draw this same line, usually badly.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bad_alloc&lt;/code&gt; hangs off the root rather than either branch, which is honest. Running out of memory is not your logic and not really the world’s either.&lt;/p&gt;
&lt;h2 id=&quot;deriving-into-it&quot;&gt;Deriving into it&lt;/h2&gt;
&lt;p&gt;Nothing stops you from adding to the tree. Derive publicly from &lt;code&gt;std::exception&lt;/code&gt; and your class is one, so it can be caught anywhere a &lt;code&gt;std::exception&lt;/code&gt; is expected:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; IllegalBalanceException&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;exception&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;public:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    IllegalBalanceException&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;noexcept&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    ~IllegalBalanceException&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; char&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;what&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; noexcept&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt; override&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt; &quot;Illegal balance exception&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dynamic dispatch does the rest at runtime. Real error codes, offending values, and context all go in as members, set by the constructor, and &lt;code&gt;what()&lt;/code&gt; reports them. Keep &lt;code&gt;noexcept&lt;/code&gt; on it and mean it: throwing from a function marked &lt;code&gt;noexcept&lt;/code&gt; doesn’t propagate, it terminates.&lt;/p&gt;
&lt;p&gt;And now catching by category works:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    reconcile&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(batch);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::out_of_range &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;ex) {&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;  // the one I want to report&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    report_bad_index&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(ex.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;what&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::logic_error &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;ex) {&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;   // anything else that is my fault&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    abort_batch&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(ex.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;what&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;::runtime_error &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;ex) {&lt;/span&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt; // the world&apos;s problem, try again&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;    requeue&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(batch, ex.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;what&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Handlers are tested in source order and the first type that matches wins, so the ordering is load-bearing. Swap the first two and the specific handler is dead code, because &lt;code&gt;out_of_range&lt;/code&gt; is a &lt;code&gt;logic_error&lt;/code&gt; and the broader one gets there first. There is also &lt;code&gt;catch (...)&lt;/code&gt;, which catches everything and gives you no object at all, so you learn that something failed and nothing else.&lt;/p&gt;
&lt;p&gt;Written down like that, it looks like a clean win. One handler covers a branch of the tree, new exception types slot in without touching call sites, and the categories mean something.&lt;/p&gt;
&lt;h2 id=&quot;what-it-costs-which-is-real&quot;&gt;What it costs, which is real&lt;/h2&gt;
&lt;p&gt;I have maintained the TypeScript version of this idea: a base &lt;code&gt;AppError&lt;/code&gt;, a dozen subclasses, &lt;code&gt;instanceof&lt;/code&gt; checks at the boundary. Same design, same two problems.&lt;/p&gt;
&lt;p&gt;Look again at &lt;code&gt;calculateMpg&lt;/code&gt;. Its signature is &lt;code&gt;double calculateMpg(int, int)&lt;/code&gt;. Nothing in that tells you it can throw, or what. To find out, you read the body, and then you read the bodies of everything it calls, and then you read the bodies of those. The information exists and it is nowhere near where you need it.&lt;/p&gt;
&lt;p&gt;C++ did try to fix this. You could once list the types a function might throw in its signature, and it was removed in C++17, because the check happened at runtime rather than at compile time. A violation didn’t fail your build. It called &lt;code&gt;std::unexpected&lt;/code&gt; in production. What survives is &lt;code&gt;noexcept&lt;/code&gt;, which carries one bit: throws, or doesn’t.&lt;/p&gt;
&lt;p&gt;The deeper cost is that every call becomes a potential exit. &lt;code&gt;reconcile(batch);&lt;/code&gt; looks like a statement that runs and returns. It is also a jump to a handler somewhere up the stack that you cannot see from here, possibly skipping the rest of this function, and the only clue is the try block wrapped around it. Control flow that doesn’t appear at the call site is control flow you will forget exists, and the place you forget is the place the resource leaks.&lt;/p&gt;
&lt;p&gt;Java took the other road and made the compiler enforce it: &lt;code&gt;throws IOException&lt;/code&gt; is part of the method contract, and callers must handle it or declare it themselves. It works exactly as advertised, and what people do with it is wrap everything in an unchecked exception to make the compiler stop talking.&lt;/p&gt;
&lt;h2 id=&quot;where-go-landed&quot;&gt;Where Go landed&lt;/h2&gt;
&lt;p&gt;Go looked at all of this and refused the mechanism entirely. Errors are ordinary values, returned like any other value:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;cfg, err &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt; loadConfig&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(path)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79C0FF;--shiki-light:#005CC5&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; fmt.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;starting worker: &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#005CC5&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, err)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;%w&lt;/code&gt; verb wraps rather than formats, so the returned error carries the original inside it. Do that at each layer and you build a chain. Then you interrogate the chain:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-dark-default github-light&quot; style=&quot;background-color:#0d1117;--shiki-light-bg:#fff;color:#e6edf3;--shiki-light:#24292e; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; errors.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Is&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(err, fs.ErrNotExist) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;    // matched a sentinel value anywhere in the chain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; pathErr &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;fs&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#FFA657;--shiki-light:#6F42C1&quot;&gt;PathError&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt; errors.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;As&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(err, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;pathErr) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B949E;--shiki-light:#6A737D&quot;&gt;    // matched a type, and pathErr now points at it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;    log.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF;--shiki-light:#6F42C1&quot;&gt;Printf&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;bad path: &lt;/span&gt;&lt;span style=&quot;color:#FF7B72;--shiki-light:#005CC5&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF;--shiki-light:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;, pathErr.Path)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E6EDF3;--shiki-light:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;errors.As&lt;/code&gt; walks the chain, finds the first error whose concrete type is assignable to your target, and assigns it. Which is catch-by-base-class, arrived at from the opposite direction. C++ starts with a type hierarchy and searches the call stack for a handler that matches. Go starts with values and searches a wrapped chain for a type that matches. Both are a type-directed search for the first thing that fits.&lt;/p&gt;
&lt;p&gt;The difference is not expressiveness. It is where the search happens. In Go it is a function call on a line you wrote, in the function you are reading, and if you delete it nothing invisible takes over. In C++ it is the language, and it runs whether or not anyone is looking.&lt;/p&gt;
&lt;p&gt;I’ll be honest about the part that doesn’t favour Go: &lt;code&gt;(Config, error)&lt;/code&gt; doesn’t tell you what can fail either. It is one bit. C++ hands you a genuinely richer description of the failure and hides it; Go hands you almost nothing and puts it directly in your face on every line. Neither signature answers the question you actually have.&lt;/p&gt;
&lt;p&gt;But one visible bit beats a taxonomy you have to go looking for. The errors that have cost me real time were never the ones whose type I knew. They were the ones I didn’t know could happen at all.&lt;/p&gt;</content:encoded><category>C++</category><category>c++</category><category>exceptions</category><category>error-handling</category><category>fundamentals</category></item></channel></rss>