<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on A Blog for the Uninitialized</title>
        <link>https://www.nilpointer.blog/posts/</link>
        <description>Recent content in Posts on A Blog for the Uninitialized</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <copyright>&lt;a href=&#34;https://www.apache.org/licenses/LICENSE-2.0&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Licensed under Apache 2.0&lt;/a&gt;</copyright>
        <lastBuildDate>Tue, 19 May 2026 17:21:43 -0700</lastBuildDate>
        <atom:link href="https://www.nilpointer.blog/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>Should dependencies always be kept up to date?</title>
            <link>https://www.nilpointer.blog/posts/2026/05/should-dependencies-always-be-kept-up-to-date/</link>
            <pubDate>Tue, 19 May 2026 17:21:43 -0700</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/05/should-dependencies-always-be-kept-up-to-date/</guid>
            <description>&lt;p&gt;Something came up at work today related to the topic &lt;a href=&#34;https://www.nilpointer.blog/posts/2026/05/just-because-you-can-doesnt-mean-you-should/&#34;
  
  
&gt;I discussed yesterday&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Should dependencies always be kept up to date?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I work in the Developer Experience (DevX) realm right now, and the current perspective on handling the potential security concerns that &lt;a href=&#34;https://red.anthropic.com/2026/mythos-preview/&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;Mythos&lt;/a&gt; is highlighting is that we should immediately update all code (third party) dependencies to their respective, latest versions.&lt;/p&gt;
&lt;p&gt;While that perspective is valid for my company right now (there&amp;rsquo;s so much more to the story there), this isn&amp;rsquo;t always a valid perspective to take.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Something came up at work today related to the topic <a href="https://www.nilpointer.blog/posts/2026/05/just-because-you-can-doesnt-mean-you-should/"
  
  
>I discussed yesterday</a>.</p>
<p><strong>Should dependencies always be kept up to date?</strong></p>
<p>I work in the Developer Experience (DevX) realm right now, and the current perspective on handling the potential security concerns that <a href="https://red.anthropic.com/2026/mythos-preview/"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Mythos</a> is highlighting is that we should immediately update all code (third party) dependencies to their respective, latest versions.</p>
<p>While that perspective is valid for my company right now (there&rsquo;s so much more to the story there), this isn&rsquo;t always a valid perspective to take.</p>
<p><strong>And this isn&rsquo;t a new concept, either.</strong></p>
<p>If you go deep underground into the most secure places like government cold storage or nuclear facilities, you&rsquo;ll often find <em>ancient</em> technology. Think <a href="https://en.wikipedia.org/wiki/Floppy_disk"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>8 inch floppy disks</a> still running core infrastructure from post-WW2 days.</p>
<p>Why would you let something languish like this?</p>
<p>Well, updating a nuclear reactor to be fully online suddenly potentially exposes its edges to the world. And getting security protocols to work effectively is <em>hard</em>. And scary when you consider the real life damage that nuclear material can cause if handled incorrectly when a bug is triggered.</p>
<p>The Unix / Linux ecosystem has pockets of this as well - a lot of core utils like <code>ls</code> and <code>cat</code> are generally full featured for what they were intended and don&rsquo;t exactly need to be updated. Because, well, it <em>does what it does</em>, and people have built other critical infra on top of this expected behavior.</p>
<p>I digress a bit, but my bigger point in today&rsquo;s society is a bit more like this:</p>
<p><strong>If you are concerned about your old dependencies leaving you exposed to zero-day bugs and exploits, you should also be equally as concerned about new ones introducing you to <em>new</em> ones.</strong></p>
<p>Sure, it&rsquo;s <em>highly</em> likely that newer versions of code are far more secure than the old ones, but the chance that the newer versions introduced (or reintroduced) past bugs is certainly <em>not zero</em>.</p>
<p>I&rsquo;ve worked with a lot of engineers over the years who immediately shut down attempts to update their project dependencies. It&rsquo;s introducing unknowns into code that &ldquo;works just fine&rdquo; in most cases, at least when it comes to competent and often old-school engineers.</p>
<p>On one hand, I really don&rsquo;t like that mentality. But on the other, I kind of revere it. We are constantly asking ourselves in software questions like &ldquo;but when is it <em>done</em>?&rdquo; and rarely have an answer.</p>
<p>So make sure your dependency graph is as up to date as is reasonable. It&rsquo;s a good practice to have!</p>
<p>But also don&rsquo;t do it and expect yourself to be immune to future late-night bug-bashes once Mythos is released to the world.</p>
]]></content>
        </item>
        
        <item>
            <title>Just because you can doesn&#39;t mean you should</title>
            <link>https://www.nilpointer.blog/posts/2026/05/just-because-you-can-doesnt-mean-you-should/</link>
            <pubDate>Mon, 18 May 2026 19:36:33 -0700</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/05/just-because-you-can-doesnt-mean-you-should/</guid>
            <description>&lt;p&gt;Everyone today is, and I say this begrudgingly, rightfully concerned about the new &lt;a href=&#34;https://red.anthropic.com/2026/mythos-preview/&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;Mythos model&lt;/a&gt; announced by Anthropic this past week.&lt;/p&gt;
&lt;p&gt;Basically this model is quite advanced to the point where it is discovering bugs and &amp;ldquo;zero day&amp;rdquo; (aka unknown) issues on 30 year old tooling that no one has touched for years.&lt;/p&gt;
&lt;p&gt;Which is alarming.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I suppose.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At least I know my company is currently freaking out about it. As is nearly every other company inundated with massive amounts of tech debt who have willfully turned a blind eye to it for literal &lt;em&gt;years&lt;/em&gt;. Suddenly we all have only six weeks to get ourselves prepared for this (dystopian?) future.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Everyone today is, and I say this begrudgingly, rightfully concerned about the new <a href="https://red.anthropic.com/2026/mythos-preview/"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Mythos model</a> announced by Anthropic this past week.</p>
<p>Basically this model is quite advanced to the point where it is discovering bugs and &ldquo;zero day&rdquo; (aka unknown) issues on 30 year old tooling that no one has touched for years.</p>
<p>Which is alarming.</p>
<p><strong>I suppose.</strong></p>
<p>At least I know my company is currently freaking out about it. As is nearly every other company inundated with massive amounts of tech debt who have willfully turned a blind eye to it for literal <em>years</em>. Suddenly we all have only six weeks to get ourselves prepared for this (dystopian?) future.</p>
<p>Present freaking-out excluded, we&rsquo;ve also managed to do a whole lot in the world of computers over the last 30 years. No one was the wiser as to these long-dormant bugs.</p>
<p>Say Anthropic never created this model, either intentionally or not. Would that <em>actually</em> be a problem in our world today?</p>
<p>Our AI overloads would definitely use the same excuse <em>&ldquo;if not us then someone else!&rdquo;</em> so we should feel grateful that they&rsquo;re telling us all ahead of its release.</p>
<p><strong>It honestly sounds a lot like the ever-looming quantum future of computing.</strong></p>
<p>Quantum computing, the little if anything I know about it, is essentially computing that does not rely on typical zeros and ones when it comes to representing data in memory registers. Imagine if you had a computer that could handle anything from zero to, say, a hundred within each bit in some way.</p>
<p>Today&rsquo;s cryptography just wouldn&rsquo;t keep up in that world. So things like 2048 bit encryption really have no chance against a potential threat like that. At least as I understand it.</p>
<p>Some of this <em>feels</em> similarly.</p>
<p><strong>Progress for the sake of progress isn&rsquo;t actually progress for humankind.</strong></p>
<p>Not always at least.</p>
<p>My opinion here is more political actually. If we never allowed a company to quite literally steal the world&rsquo;s data to train their models, then perhaps they&rsquo;d never have created Skynet, I mean, Mythos in the first place.</p>
<p>Would we be worse off for it? Probably not.</p>
<p><strong>Meanwhile, in the trenches of my day to day job&hellip;</strong></p>
<p>I had a pull request to review today that had over 351 comments on it, 35 different commits, modifying only 3 files, nearly all of which was generated by Claude. A huge portion of it was rewriting slight variations of other code that already existed in the same repository. Or better yet, redefining standard library code, a known issue in a lot of these actions cited by others.</p>
<p>This is proposed core build CI infrastructure code!</p>
<p>So on one side, we have these ridiculously overpowered and world-ending models put out by companies worth trillions of dollars&hellip;</p>
<p>And on the other hand, we have a bunch of AI slop becoming ever-increasingly relied on by lazy engineers who are squeezed to death by management who really have no idea how else to consider themselves successful in their jobs.</p>
<p><strong>What a world we live in.</strong></p>
<p>I went earlier on a walk with my dog and saw people out on their evening walks. They were on their bikes riding home. People were active, enjoying the beautiful weather outside today.</p>
<p>If we accidentally (or purposely, slowly) sign on to having AI run our bank accounts and suddenly we onboard Skynet to the world around us, what&rsquo;s the point of it all?</p>
<p>We&rsquo;re human, we should be doing human things. Isn&rsquo;t that the point of technology, to better <em>add</em> to our lives?</p>
<p>If instead of usurp the beauty that is life with AI models that start to make autonomous decisions <em>and</em> we get lazier and lazier in letting it do all the important work for us, we rob ourselves of a better life. We instead place all control and money into the hands of a few who won&rsquo;t know how to utilize it when that day comes.</p>
<p>Or worse yet, they&rsquo;ll simply weaponize it against us. Imagine rioters with pitchforks charging the nearest data center, but then seeing others fight against the action because, well, that&rsquo;s their banking and picture data too!</p>
<p>I imagine it to be somewhere between Wall-E and the Terminator movies.</p>
<p>Realistically, this is where government oversight and anti-trust laws should help us balance new features and technology with the desire to be human.</p>
<p>So again&hellip;</p>
<p><strong><em>Just because we can doesn&rsquo;t mean we should.</em></strong></p>
]]></content>
        </item>
        
        <item>
            <title>There&#39;s a (real) problem with AI compute</title>
            <link>https://www.nilpointer.blog/posts/2026/05/theres-a-real-problem-with-ai-compute/</link>
            <pubDate>Wed, 06 May 2026 16:13:14 -0700</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/05/theres-a-real-problem-with-ai-compute/</guid>
            <description>&lt;p&gt;I&amp;rsquo;ll make no apologies for being an AI skeptic. I maintain that position today, despite being an active subscriber of Google&amp;rsquo;s Gemini AI. I really like it for guided learning.&lt;/p&gt;
&lt;p&gt;Regardless, the primary problem coming for AI is very similar to what caused some issues during the early days of the internet. Back then, the internet was new and companies were scrambling to build a bunch of high throughput fiber optic cable across the US.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I&rsquo;ll make no apologies for being an AI skeptic. I maintain that position today, despite being an active subscriber of Google&rsquo;s Gemini AI. I really like it for guided learning.</p>
<p>Regardless, the primary problem coming for AI is very similar to what caused some issues during the early days of the internet. Back then, the internet was new and companies were scrambling to build a bunch of high throughput fiber optic cable across the US.</p>
<p>Unfortunately, the &ldquo;last mile&rdquo; problem still existed, and despite you maybe living only a mile away from one of the fiber hubs, you still had a 50 year old phone line trying to send that fiber optic internet to your house.</p>
<p><strong>womp, womp</strong></p>
<p>This created a weird problem during the early days of high speed internet - when, initially, the internet backbones had effectively <em>too much</em> capacity compared to their actual use. &ldquo;Dark&rdquo; fiber lines actually existed at this time, meaning that they were either not transmitting internet traffic due to lack of the need for it, or they were simply under utilized.</p>
<p>Imagine having spent a bunch of money to build those lines, only to realize that they weren&rsquo;t being used as you had hoped. How did that look on your balance sheets?</p>
<p><strong>Fast forward to today.</strong></p>
<p>Add to our story an oblivious billionarie, Kevin Leary, of Shark Tank notoriety. In particular, let&rsquo;s talk about his actions this past week where he successfully strongarmed a set of local county board members in Utah to approve his &ldquo;Stratos&rdquo; data center.</p>
<p><strong>This is a <em>40,000 acre</em> data center that&rsquo;s going to use up to 9 gigawatts of power.</strong></p>
<p>That&rsquo;s a data center that&rsquo;s 8 miles long. It&rsquo;s bigger than the entirety of Bryce Canyon national park.</p>
<p><strong>And 9 gigawatts of power is 3 times as much power as the entire state of Utah currently consumes!</strong></p>
<p>Besides this effort being terribly mishandled optically, it also showcases a huge problem that Wall Street has yet to come to grips with.</p>
<p><strong>The demands for AI model training and serving is simply insatiable and do not align with real world constraints.</strong></p>
<p>And this isn&rsquo;t because I&rsquo;m getting grumpier with time and hate progress in the tech space. Part of this is due to the sheer amount of compute that&rsquo;s required to train new models these days, a number that seems to only increase over time.</p>
<p><strong>Let&rsquo;s explain.</strong></p>
<p>Imagine with me that this thing is built and it starts consuming 9 gigawatts of power, and let&rsquo;s ignore other issues like pollution and its water usage in the desert region that is Utah.</p>
<p>Given that Utah taxpayers are already vying for their own usage of 3 gigawatts of power (in the form of their electric bills), a few things will necessarily have to happen:</p>
<ul>
<li>more power plants will have to be built, along with more infrastructure to transmit more power to more places.</li>
<li>prices for electricity with have to go up to effectively throttle what is available amongst all interested parties.</li>
</ul>
<p>In the first case, you can&rsquo;t exactly stand up a bunch of new power plants and infrastructure overnight. You need to truck in concrete, do environmental reviews, hire engineers, design plans, and a lot more - all of which take time and (currently) require a lot of approvals. Are these power plants going to be run via coal or gas? Who has to live next to them?</p>
<p><em>What if Utah simply buys electricity from a neighboring state?</em></p>
<p>Same story is going to apply here. Electricity requires infrastructure, especially over long distances. Given normal economic supply and demand, neighboring states may find their own electricity infrastructure strained to supply Utah&rsquo;s needs, which typically means their prices will go up too.</p>
<p>All because I want to occasionally generate a picture of a banana or create a fun borderline-copyright-infringed avatar for my profile somewhere.</p>
<p><strong>I digress.</strong></p>
<p>In the second case from above, you&rsquo;re effectively taunting the public with class warfare. Basically, you&rsquo;re enacting Hunger Games rules.</p>
<p>There&rsquo;s already a growing anti-billionaire sentiment in this country, and giving special tax deals to them while footing Joe the taxpayer with the bill is <em>not</em> going to be an easy sell. Even in the conservative state of Utah.</p>
<p><strong>There&rsquo;s a third thing that may happen here.</strong></p>
<p>This is why I mentioned the fiber-optic story earlier.</p>
<p><em>What if the data center is built but Utah can&rsquo;t supply it with enough power?</em></p>
<p>Does this mean that the data center is going to sit half used or unused entirely? What kind of environmental impact is that going to additionally add here?</p>
<p>What will happen to all the money that various investors put into this project? Are they going to disappear to zero via a plummeting stock price? Is there another taxpayer funded bailout in the works?</p>
<p><strong>The takeaway?</strong></p>
<p>Do we really want our public infrastructure, paid for with public taxpayer funds, to go to sweetheart deals with billionaires and/or multinational companies? The same ones will like to &ldquo;socialize the costs, privatize the profits&rdquo;?</p>
<p>If the past is any indication of the future, all the dollars they receive from the actions made via these data centers aren&rsquo;t exactly going to be shared by the public who helped facilitate the building of them.</p>
<p>Not to mention that companies like these will abandon these locations when they are no longer profitable to maintain them.</p>
<p>So I guess the real question comes down to this - with the limited resources of our existing power grid, do we feel like the <em>best</em> usage of it should be to power the data centers at the heart of a few tech companies pumping out new AI models?</p>
<p>The same models that are becoming increasingly expensive, by the way, per subscription. Let&rsquo;s not forget that!</p>
<p>I think you already know my opinion on the matter.</p>
]]></content>
        </item>
        
        <item>
            <title>A word about company loyalty</title>
            <link>https://www.nilpointer.blog/posts/2026/04/a-word-about-company-loyalty/</link>
            <pubDate>Thu, 02 Apr 2026 12:47:15 -0700</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/04/a-word-about-company-loyalty/</guid>
            <description>&lt;p&gt;Early in my career, I was given a great piece of advice. Advice that I&amp;rsquo;ve recently lost track of following, unfortunately.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can be as loyal as you want to a company, they will never be loyal back.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think it goes without saying in today&amp;rsquo;s environment, but I think it&amp;rsquo;s important to reiterate again.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Today, Oracle laid off 30,000 employees, many of whom have been there 30 or more years!&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Early in my career, I was given a great piece of advice. Advice that I&rsquo;ve recently lost track of following, unfortunately.</p>
<blockquote>
<p>You can be as loyal as you want to a company, they will never be loyal back.</p>
</blockquote>
<p>I think it goes without saying in today&rsquo;s environment, but I think it&rsquo;s important to reiterate again.</p>
<p><strong>Today, Oracle laid off 30,000 employees, many of whom have been there 30 or more years!</strong></p>
<p>It&rsquo;s the biggest tech layoff in the last 12 months, and it&rsquo;s absolutely sad to see what appears to be an arbitrary algorithm that selected the employees to be cut.</p>
<p>What makes this particularly galling, too, is that last year, Oracle posted a <strong>95%</strong> increase in net income!</p>
<p>An additional claimed reason for this layoff is that this action will free up more cash for data center build out.</p>
<p><strong>For AI, of course.</strong></p>
<p>This LinkedIn <a href="https://www.linkedin.com/posts/chrisdyer7_oracle-just-posted-a-95-jump-in-net-income-activity-7445473561965707264-M8oz"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>blog post</a> stated some of the above, but chose to take the route of comparing Oracle with IKEA, a business that they feel is weathering the current storm very differently.</p>
<p><strong>This all gives me pause.</strong></p>
<p>I&rsquo;ve been where I&rsquo;ve been for a <em>long</em> time, at least a long time for &ldquo;big tech&rdquo;. I&rsquo;ve seen a few layoffs already and while I&rsquo;ve been lucky to not get caught up in any of them, I feel like just because I&rsquo;m here does <em>not</em> mean I need to stay out of a sake of &ldquo;loyalty&rdquo;.</p>
<p>Companies will cut you without a second thought, whether you&rsquo;ve been there <a href="https://www.linkedin.com/posts/linasbeliunas_wild-somebody-spent-34-years-at-oracle-share-7445458103946575872-e6mZ"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>3 years or 33 years</a>, so <em>do not expect anything less</em> from them.</p>
<p>That is all for today.</p>
]]></content>
        </item>
        
        <item>
            <title>Interviewing in Europe vs the US</title>
            <link>https://www.nilpointer.blog/posts/2026/03/interviewing-in-europe-vs-the-us/</link>
            <pubDate>Sat, 21 Mar 2026 16:08:11 -0700</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/03/interviewing-in-europe-vs-the-us/</guid>
            <description>&lt;p&gt;Over the last several years, my spouse and myself have often considered the prospect of leaving the USA for somewhere in Europe. We both love to visit and vacation there, and despite the significantly reduced salaries by comparison, people are consistently rated as being happier with their lives.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The EU has stronger labor laws.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This manifests itself in a few ways that maybe you wouldn&amp;rsquo;t consider.&lt;/p&gt;
&lt;p&gt;When interviewing for a European-based company, and one that isn&amp;rsquo;t necessarily an offshoot of an existing company based out of the US, job interviews tend to be more formal and a bit more tech-direct skills (i.e., not soft skills) focused.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Over the last several years, my spouse and myself have often considered the prospect of leaving the USA for somewhere in Europe. We both love to visit and vacation there, and despite the significantly reduced salaries by comparison, people are consistently rated as being happier with their lives.</p>
<p><strong>The EU has stronger labor laws.</strong></p>
<p>This manifests itself in a few ways that maybe you wouldn&rsquo;t consider.</p>
<p>When interviewing for a European-based company, and one that isn&rsquo;t necessarily an offshoot of an existing company based out of the US, job interviews tend to be more formal and a bit more tech-direct skills (i.e., not soft skills) focused.</p>
<p><a href="https://www.reddit.com/r/cscareerquestionsEU/comments/dymdrj/how_does_the_american_and_european_interview"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Reddit</a> has a lot more to read on the topic.</p>
<p><strong>This showcases in how people are hired and fired.</strong></p>
<p>From <a href="https://bluematterconsulting.com/insights/blog/recruiting-differences-europe-us/"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>bluematterconsulting.com</a>, I found a good description of this:</p>
<blockquote>
<p>Across Europe, the concept of at-will employment does not exist, so employees have much greater protections than in the US. If an employer wants to terminate an employee, then it must do so for well-documented, established reasons. In addition, the process of dismissing an employee is usually a lengthy one, with required warnings, consultations, notice periods, and the like. An employer that fails to follow the proper procedures and does not afford an employee due process is open to a wrongful termination suit.</p>
</blockquote>
<p>Hiring definitely feels more deliberate over there, and when layoffs occur, companies can&rsquo;t simply do it overnight and uproot people&rsquo;s lives like they do here in the US (ahem, healthcare).</p>
<p>I&rsquo;ve known managers who have had to handle a layoff for some of their EU based employees and doing so took them months. They also had to show legal <em>proof</em> as to why they were no longer needed.</p>
<p><strong>Feedback after interviewing is also different.</strong></p>
<p>When we get rejected from a potential future employer during the course of an interview cycle, we here in the US are largely told &ldquo;sorry, but no thank you&rdquo; via a simple email.</p>
<p>You really don&rsquo;t get much more than that.</p>
<p>You basically forever get to ask yourself &ldquo;<em>What did I do wrong?</em>&rdquo; or &ldquo;<em>Why did they not hire me?</em>&rdquo; and you basically will never know the true answer unless you get very lucky with the recruiter you worked with and they give you a little bit more detail.</p>
<p>It&rsquo;s not typical for them to provide anything specific to you due to the market (and law) favoring the employer side of the relationship.</p>
<p>However, when interviewing for an EU based position, <em>you&rsquo;ll absolutely get told why you weren&rsquo;t hired</em>. It&rsquo;s probably not very easy to hear, but it does afford you the opportunity to uplevel yourself for the next time you apply elsewhere.</p>
<p><strong>Personally, I&rsquo;d rather have the EU based approach.</strong></p>
<p>Too many times I&rsquo;ve been told &ldquo;sorry, but no&rdquo; leaving only weird, unanswered questions in my head and my psyche.</p>
]]></content>
        </item>
        
        <item>
            <title>Jensen Huang on AI Layoffs</title>
            <link>https://www.nilpointer.blog/posts/2026/03/jensen-huang-on-ai-layoffs/</link>
            <pubDate>Fri, 20 Mar 2026 16:56:02 -0700</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/03/jensen-huang-on-ai-layoffs/</guid>
            <description>&lt;p&gt;&lt;em&gt;This is my commentary from a blog post I found &lt;a href=&#34;https://www.livemint.com/technology/tech-news/out-of-ideas-nvidia-ceo-jensen-huang-dismisses-ai-job-loss-fears-blames-recent-layoffs-on-lack-of-imagination-11773995361646.html&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Jensen Huang is the current CEO for Nvidia, the man being credited with taking the company to stratopheric heights in the last 10 years. This is mainly because Nvidia creates the best GPUs that are used in the core model training algorithms for anything AI related.&lt;/p&gt;
&lt;p&gt;What I wanted to quickly write about was about something he said during a recent interview with Jim Cramer (yes, &lt;em&gt;that&lt;/em&gt; Jim Cramer) that reasonated with me.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><em>This is my commentary from a blog post I found <a href="https://www.livemint.com/technology/tech-news/out-of-ideas-nvidia-ceo-jensen-huang-dismisses-ai-job-loss-fears-blames-recent-layoffs-on-lack-of-imagination-11773995361646.html"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>here</a>.</em></p>
<p>Jensen Huang is the current CEO for Nvidia, the man being credited with taking the company to stratopheric heights in the last 10 years. This is mainly because Nvidia creates the best GPUs that are used in the core model training algorithms for anything AI related.</p>
<p>What I wanted to quickly write about was about something he said during a recent interview with Jim Cramer (yes, <em>that</em> Jim Cramer) that reasonated with me.</p>
<p>We currently live in a world where a huge portion of the tech community, i.e., software engineers particularly, live in a near constant state of anxiety. Their (our) jobs are potentially being automated away and it seems that we can&rsquo;t go a single quarter without hearing about some huge company laying off a bunch of employees.</p>
<p><strong>The justification? AI.</strong></p>
<p>Never mind the old adage &ldquo;putting the cart before the horse&rdquo;, firing employees before you&rsquo;ve actually realized the net gain from the technology that claims to reduce the need for them.</p>
<p>Apparently Jenson sings a slightly different tune. First, the setup:</p>
<blockquote>
<p>Huang was talking about the ability of AI to elevate the capabilities of humans when CNBC&rsquo;s Jim Cramer asked him why companies are laying off people, saying they want to do more with less.</p>
</blockquote>
<p>His response is what I&rsquo;d like to highlight here:</p>
<blockquote>
<p>A frustrated Huang responded, “Because you&rsquo;re out of imagination. For companies with imagination, you will do more with less. For companies that are, you know, when the leadership is just out of ideas, they have nothing else to do, they have no reason to imagine greater than they are. Then when they have more capability, you know, they don&rsquo;t do more.”</p>
</blockquote>
<p>As someone currently living through some of this and experiencing more and more consistent layoffs, along with &ldquo;forced <a href="https://mbrandolph.medium.com/the-performance-improvement-plan-is-cruel-and-unusual-3cc1892d3dfd"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>PIP</a>&rsquo;s&rdquo; or even quotas for them, this really struck home.</p>
<p>He&rsquo;s compared engineers&rsquo; usage of AI similar to how CAD is used by mechanical engineers - i.e., it&rsquo;s a <em>tool</em> that you use to become more productive. So he&rsquo;s definitely not an AI skeptic.</p>
<p><strong>But I echo what he said here so much.</strong></p>
<p>I&rsquo;ve had so much pressure to simply &ldquo;use AI&rdquo; in my career lately that I genuinely feel like its required usage is missing the mark entirely.</p>
<p>What exactly are you <em>doing</em> with AI? Is it to simply use a tool or is it to use that tool to accomplish some task that furthers the goal of the company?</p>
<p>Is its usage making the customer&rsquo;s experience better, however you define the customer to be?</p>
<p>Or is it just a way to pad your numbers and make it look like you&rsquo;re a <a href="https://adhithiravi.medium.com/who-is-a-10x-engineer-a3dd4b6f8007"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>10x engineer</a>? And by extension, is this an attempt to make your <em>company</em> look like it&rsquo;s doing more than it really is?</p>
<p><strong>I think the simple, required usage of AI is the wrong metric.</strong></p>
<p>It seems that a lot of companies these days, in their attempts to skirt from the repurcussions of the <a href="https://www.nilpointer.blog/posts/2026/02/enshittification/"
  
  
>enshittification</a> of their products, are using this as a scapegoat to throw their employees under the bus. And hopefully prop up their failing stock prices temporarily until their respective golden gooses are killed.</p>
<p>But that&rsquo;s the <em>next</em> CEO&rsquo;s problem, I suppose.</p>
<p>We&rsquo;ve known for years that tracking developer&rsquo;s commit and PR counts isn&rsquo;t a good way to measure engineer productivity, why would this suddenly be different?</p>
]]></content>
        </item>
        
        <item>
            <title>On Regular Expressions</title>
            <link>https://www.nilpointer.blog/posts/2026/03/on-regular-expressions/</link>
            <pubDate>Fri, 13 Mar 2026 10:48:58 -0700</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/03/on-regular-expressions/</guid>
            <description>&lt;p&gt;Way back when, I was primarily a Perl developer. I actually did my first interview for LinkedIn in Perl. I always liked that the language was created by a linguist (I legitimately miss using &lt;code&gt;unless&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Today, though, besides the occasional one-liner, I think Perl is most known for its contributions to how we all (now) use regular expressions (this post isn&amp;rsquo;t going into what they are or how to use them).&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Way back when, I was primarily a Perl developer. I actually did my first interview for LinkedIn in Perl. I always liked that the language was created by a linguist (I legitimately miss using <code>unless</code>).</p>
<p>Today, though, besides the occasional one-liner, I think Perl is most known for its contributions to how we all (now) use regular expressions (this post isn&rsquo;t going into what they are or how to use them).</p>
<p>However, there&rsquo;s a bit more detail here that I&rsquo;ve only recently filled in.</p>
<p><strong>First, there was POSIX.</strong></p>
<p>In the traditional Unix world, both &ldquo;basic&rdquo; (BRE) and &ldquo;extended&rdquo; (ERE) POSIX-based regular expressions were / are used.</p>
<p>There&rsquo;s a bit more here than I&rsquo;d like to go into, but in basic POSIX regular expressions, you have to escape parentheses with <code>\(</code> and use character classes like <code>[[:space:]]</code>.</p>
<p>The most typical case for when you&rsquo;d need to use these patterns are when using the unix shell command <code>grep</code>. You can also use the extended regular expression set by adding the <code>-E</code> flag or simply using the similarly-named <code>egrep</code> CLI.</p>
<p><strong>Then, there was Perl.</strong></p>
<p>Perl introduced another way to handle regular expressions, effectively adding some syntactic sugar that we&rsquo;re all quite familiar with these days.</p>
<ul>
<li><code>\s</code> for any whitespace character (or <code>\S</code> for a non-whitespace character)</li>
<li><code>\d</code> for a digit character (or a <code>\D</code> for a non-digit character)</li>
<li>etc.</li>
</ul>
<p>These are basically now the de facto &ldquo;regular expressions&rdquo; set that we use across all the major languages these days. Making this distinction I think is important, if only for historical purposes, especially since I always remember being confused why someone would use <code>[[:space:]]</code> instead of simply <code>\s</code>.</p>
<p><strong>Enter the concept of &ldquo;Backtracking&rdquo;.</strong></p>
<p>One of the most handy aspects to regular expressions are the usage of special references such as <code>$1</code> or <code>$2</code>. These are called &ldquo;backreferences&rdquo; and refer to a previously matched set of characters within a defined set of parentheses. It can make things quite dynamic when looking through text.</p>
<p>I remember when first discovering them how much they felt like magic to me.</p>
<p>How we use them conveniently also helps us understand how the language&rsquo;s runtime actually evaluates text against a given regular expression.</p>
<p><strong>Backtracking is great. Until it&rsquo;s not.</strong></p>
<p>This is what I wanted to mention here - turns out there are two primary ways in which these regular expressions <em>are actually evaluated</em>.</p>
<ul>
<li><code>PCRE</code> or Perl Compatible Regular Expressions</li>
<li><code>RE2</code>, originally a C++ evaluation algorithm created by Google</li>
</ul>
<p>There is also a third option, the <a href="https://docs.rs/regex/1.1.9/regex/"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>rust regex crate</a>, which largely follows the same patterns and rules as <code>RE2</code> does, as far as I understand it.</p>
<p>In <code>PCRE</code>, when the respective language runtime iterates over the bytes in a string to evaluate it against a supplied regular expression, it effectively runs through it as if it were a tree. This means traversing its branches, depth first, looking for what can be matched. When a &ldquo;branch&rdquo; doesn&rsquo;t match the condition, the algorithm backtracks up a step and continues to a different branch.</p>
<p>However, in <code>RE2</code>, a state machine is created and each state can be evaluated, one at a time.</p>
<p><strong>What does this mean?</strong></p>
<p>Well in short, this means that <code>RE2</code> does NOT support these backreferences and is slower than <code>PCRE</code> is. <em>At least for smaller, simpler use cases.</em></p>
<p>This is where things get interesting.</p>
<p>If considering this as a &ldquo;big oh&rdquo; runtime algorithm, <code>PCRE</code>, while very flexible and awesome for most use cases, <em>can suddenly be horrifically slow</em>. It&rsquo;s usually not, but it gets slow very fast as things get complex. And it can also exhaust memory and all of your cpu threads.</p>
<p><code>RE2</code>, by comparison, is consistently linear in its big oh runtime. This means it&rsquo;s &ldquo;slower&rdquo; for the simpler use cases by comparison, but is consistent and significantly faster and uses less memory.</p>
<p><strong><code>RE2</code> is used in Golang, and it was a deliberate decision to do so.</strong></p>
<p>I love to read the <a href="https://www.reddit.com/r/golang/"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>r/Golang</a> subreddit and someone was asking why Golang&rsquo;s regex pattern matching was <a href="https://www.reddit.com/r/golang/comments/1rr2evh/why_is_gos_regex_so_slow/"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>&ldquo;so slow&rdquo;</a> (they provided benchmarks). I was confused as to why my favorite language would be slandered in such a way.</p>
<p>But it got me deep diving into the topic and I learned some things!</p>
<p><strong>What really helped me understand this better was a post-mortem write up.</strong></p>
<p>I think the Appendix portion of Cloudflare&rsquo;s write up / post-mortem from an incident they had in 2019 is the best way to understand more of all this. In short, they had a huge outage which was traced down ultimately to a complex regex pattern that, using <code>PCRE</code> for its evaluation, exhausted threads and memory.</p>
<p>I really liked what they wrote up on the topic, and they included some gifs showing just how many backtracking iterations can occur on even simplistic looking regular expressions.</p>
<p>The link to the appendix section: <a href="https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/#appendix-about-regular-expression-backtracking"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/#appendix-about-regular-expression-backtracking</a></p>
<p>It&rsquo;s a bit dense, but it&rsquo;s worth a read!</p>
]]></content>
        </item>
        
        <item>
            <title>When HashMaps get &#34;slow&#34;</title>
            <link>https://www.nilpointer.blog/posts/2026/03/when-hashmaps-get-slow/</link>
            <pubDate>Thu, 12 Mar 2026 14:46:15 -0700</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/03/when-hashmaps-get-slow/</guid>
            <description>&lt;p&gt;Many years ago, I took a Data Structures class. I wasn&amp;rsquo;t even in the major that I would later graduate in, but there were a few topics that I actually remembered from back then.&lt;/p&gt;
&lt;p&gt;At least to some extent. I remember the &lt;em&gt;content&lt;/em&gt;, but the &lt;em&gt;context&lt;/em&gt; was a bit, dare I say &lt;em&gt;unknown&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;What we were discussing one day were HashMaps. We had previously discussed Linked Lists and a few other types, and I knew of the &lt;em&gt;name&lt;/em&gt; &amp;ldquo;HashMap&amp;rdquo;, but what exactly were they?&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Many years ago, I took a Data Structures class. I wasn&rsquo;t even in the major that I would later graduate in, but there were a few topics that I actually remembered from back then.</p>
<p>At least to some extent. I remember the <em>content</em>, but the <em>context</em> was a bit, dare I say <em>unknown</em>.</p>
<p>What we were discussing one day were HashMaps. We had previously discussed Linked Lists and a few other types, and I knew of the <em>name</em> &ldquo;HashMap&rdquo;, but what exactly were they?</p>
<p>In short, we were discussing:</p>
<ul>
<li>how to allocate an array of data somewhere in memory</li>
<li>using a &ldquo;hash function&rdquo; to take an input parameter (a key) and &ldquo;hash&rdquo; it using a simplistic function (using the modulus function)</li>
<li>locate that hashed location in our backing array</li>
<li>either retrieving the value stored there or inserting one to it</li>
</ul>
<p>The question inevitably came up - <em>what if that location already has something in it?</em></p>
<p>Well, we&rsquo;ll just move on to the next slot that&rsquo;s free. Simple enough, right?</p>
<p>It all makes far more sense now, but I remember we finally came across the idea of using a Linked List to &ldquo;chain&rdquo; values in the slot instead of placing a singular value in the hashed location.</p>
<p>The idea was to help combat problems that arise when the hash table gets full enough. Suddenly looking for an empty spot is hardly any better than just starting at the first spot of the array and iterating over it one spot at a time.</p>
<p><strong>So does this mean HashMaps aren&rsquo;t O(1)?</strong></p>
<p>I find that a lot of LeetCode problems generally just assume that all hashmaps are simply <code>O(1)</code> access / insert time.</p>
<p>How does that compare with any of the above, then? Shouldn&rsquo;t it be <code>O(n)</code> big-Oh time for both reads and writes? Even reads have to search for the same hashed location, which may not immediately have the item you&rsquo;re looking for.</p>
<p>Turns out this is a very well-known issue, and was actually a legitimate problem for languages like PHP and Java up until 2011 or so.</p>
<p><strong>When hash maps get slow.</strong></p>
<p>If an overly simplistic hashing function, like the one we did in in my Data Structures class, were used, we&rsquo;d likely find ourselves colliding with other values rather quickly. And performing a secondary hash then can become a heavier operation on the CPU just to locate some data location.</p>
<p>What if you were unfortunate to the point where all items were hashed to the same spot in your hash map, requiring a linked list to store all of your values? Finding an item there would certainly be slow, as it would have to both hash the key, then go look linearly through the linked list.</p>
<p>Similarly, what if someone were actively trying to crash your system? Say they sent a bunch of similar requests to a <code>POST</code> endpoint, which caused a lot of collisions to happen, or possibly filled up your hash quickly, requiring the backing array to be resized over and over? This is a slow operation usually that requires <em>all the data in the hashmap</em> to be copied to the newly resized array.</p>
<p>Java, as it turns out, implemented a <a href="https://en.wikipedia.org/wiki/Red%e2%80%93black_tree"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>red-black tree</a> data structure instead of using the linked list implementation to handle these cases. It also created a far more random hashing function than before, using <a href="https://en.wikipedia.org/wiki/SipHash"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>SIPHash</a>, implemented first in 2012. This, combined with the red-black tree instead of a linked list, made the max time when &ldquo;chaining&rdquo; occurs go from <code>O(n)</code> to <code>O(log n)</code>.</p>
<p><strong>This is significant for larger sized maps.</strong></p>
<p>As seems to always be the case when it comes to computer science concepts, everything has trade-offs. A little bit of extra overhead will technically slow down small maps, but will significantly reduce the problems you can run into later. This is particularly the case when:</p>
<ul>
<li>the map fills beyond a 0.75 &ldquo;occupied&rdquo; ratio and data needs to be expanded</li>
<li>keys get significantly larger</li>
<li>the whole map gets large enough so it has to be allocated to the heap</li>
</ul>
<p>Golang recently implemented Swiss Tables in <code>v1.24+</code> (<a href="https://go.dev/blog/swisstable"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>see the blog post</a>) which is an additional extension to all of this, which is how I came across writing this post about it all. In it, they discuss breaking down a hash map into smaller tables, each of which contain bits that trade some bit space for information that avoids unnecessary chaining and similar look up operations.</p>
<p>It&rsquo;s a fascinating read if you&rsquo;re curious, especially since it has reduced map operation times for larger maps by <a href="https://github.com/golang/go/issues/54766#issuecomment-2542444404"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>as much as 60%</a>!</p>
<p>There&rsquo;s far more to consider there, but suffice it to say, a little bit of extra &ldquo;record keeping&rdquo; can significantly improve performance when things get large or get complicated.</p>
]]></content>
        </item>
        
        <item>
            <title>Building strings in Golang</title>
            <link>https://www.nilpointer.blog/posts/2026/03/building-strings-in-golang/</link>
            <pubDate>Tue, 10 Mar 2026 15:00:17 -0700</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/03/building-strings-in-golang/</guid>
            <description>&lt;p&gt;&lt;strong&gt;How do I build a string in Golang?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Building a string seems trivial, and it generally is, but it&amp;rsquo;s a great way to dive into further to understand some of the basics around what makes Golang so interesting.&lt;/p&gt;
&lt;p&gt;First of all, let&amp;rsquo;s note that a &lt;em&gt;string&lt;/em&gt; in Golang is &lt;em&gt;immutable&lt;/em&gt;. That means whenever you want to append to a string, the existing string &lt;em&gt;plus what you&amp;rsquo;re appending&lt;/em&gt; needs to be copied to a new string in memory. This means that when optimizing code, we need to be mindful when we write code that performs these actions many times.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>How do I build a string in Golang?</strong></p>
<p>Building a string seems trivial, and it generally is, but it&rsquo;s a great way to dive into further to understand some of the basics around what makes Golang so interesting.</p>
<p>First of all, let&rsquo;s note that a <em>string</em> in Golang is <em>immutable</em>. That means whenever you want to append to a string, the existing string <em>plus what you&rsquo;re appending</em> needs to be copied to a new string in memory. This means that when optimizing code, we need to be mindful when we write code that performs these actions many times.</p>
<p>This is more of a common thing to think about in rust, but I digress.</p>
<p>In general, there are largely 4 primary ways to append to a string in Golang:</p>
<ul>
<li>Using string concatenation via the <code>+</code> or <code>+=</code> function.</li>
<li>Using the <code>fmt</code> module to use formatting directives.</li>
<li>Using a <code>bytes.Buffer</code> to collect bytes that represent strings.</li>
<li>Using the <code>strings.Builder</code> type from the <code>strings</code> library.</li>
</ul>
<p>The last two are quite different from the first two. I find that they&rsquo;re less often done because, well, people are either not as familiar with them or are more concerned about other, slower portions of their code.</p>
<p>Paying attention to <em>why</em> this is, though, is certainly helpful when writing other code that &ldquo;<a href="https://en.wikipedia.org/wiki/Code_smell"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>smells</a>&rdquo; similar.</p>
<p>So by way of example, in the same order:</p>
<pre tabindex="0"><code>var str string

// string concatenation
str = &#34;This &#34; + &#34;is &#34; + &#34;an &#34; + &#34;example&#34;

// using formatting directives
str = fmt.Sprintf(&#34;I have %d examples&#34;, 4)

// using a byte buffer
var buf = new(bytes.Buffer)
buf.WriteString(&#34;This&#34;)
buf.WriteString(&#34;is&#34;)
buf.WriteString(&#34;an&#34;)
buf.WriteString(&#34;example&#34;)
str = buf.String()

// using strings.Builder
var bStr strings.Builder
bStr.Grow(4) // to pre-allocate memory
bStr.WriteString(&#34;This&#34;)
bStr.WriteString(&#34;is&#34;)
bStr.WriteString(&#34;an&#34;)
bStr.WriteString(&#34;example&#34;)
str = bStr.String()
</code></pre><p>In short, the <em>fastest</em> way of the above is the simple string concatenation use case. This is because during compilation, the Golang compiler actually performs a shortcut and makes this variable into a single constant, which is then referenced directly as a completed string from a special section of read-only memory.</p>
<p><strong>There&rsquo;s a caveat to this, however.</strong></p>
<p>If you&rsquo;re appending to a string within a <code>for</code> loop, the compiler often is <em>unable to infer</em> the combined constant size of the final string. This usually results in a bunch of extra copy actions and leftover memory that the garbage collector needs to come along later to clear out. Not great!</p>
<p><strong>The slowest?</strong></p>
<p>The slowest is the <code>fmt.Sprintf()</code> method, since it requires multiple calls to handle parsing <code>4</code> into a <code>string</code> type, then joins it together into a final string. It makes it incredibly easy for you as the developer, but it isn&rsquo;t fast.</p>
<p><strong>So how do each perform?</strong></p>
<p>When running some benchmarks on all four options, the <code>bytes.Buffer</code> and <code>strings.Builder</code> methods are quite similar - still much faster than the <code>fmt.Sprintf()</code> method, but slower than the compiler-optimized concatenation.</p>
<p>Without including too much code here, using <code>go test -bench=.</code> on my local computer:</p>
<pre tabindex="0"><code>❯ go test -bench=.
goos: darwin
goarch: arm64
pkg: github.com/tmswfrk/stringbuilder
cpu: Apple M3
BenchmarkSB/WithPlus-8         	31813908	        37.52 ns/op
BenchmarkSB/WithSprintf-8      	 5284426	       228.4 ns/op
BenchmarkSB/WithBuffer-8       	15300552	        81.16 ns/op
BenchmarkSB/WithStringBuilder-8     16196134	        79.69 ns/op
PASS
ok  	github.com/tmswfrk/stringbuilder	6.369s
</code></pre><p>In the above, the columns are, from left to right:</p>
<ul>
<li>the name of the test</li>
<li>the number of times that test was run</li>
<li>how many nanoseconds occurred, on average, per allocation (to heap memory)</li>
</ul>
<p>This means that the string concatentation method used the fewer number of allocations and ran the most times (due to its efficiency), while the formatting directive used the most allocations and ran the fewest number of times.</p>
<p><strong>When should I use the <code>strings.Builder</code> or <code>bytes.Buffer</code> methods?</strong></p>
<p>The <code>strings.Builder</code> and <code>bytes.Buffer</code> methods are really quite good when appending to a string and you don&rsquo;t know many times you need to do so.</p>
<p>If we try to append using a <code>for</code> loop as mentioned earlier, you&rsquo;re likely going to have a bad time. Let&rsquo;s check out some example code:</p>
<pre tabindex="0"><code>func withPlus(x string) string {
	return x + x + x + x + x + x + x + x + x + x
}

func withPlusForLoop(x string) string {
	for i := 0; i &lt; 10; i++ {
		x += x
	}
	return x
}

func withStringBuilder(x string) string {
	bb := &amp;strings.Builder{}
	for i := 0; i &lt; 10; i++ {
		bb.WriteString(x)
	}
	return bb.String()
}
</code></pre><p>When we test these using <code>go test -bench=.</code> you see some very interesting results:</p>
<pre tabindex="0"><code>❯ go test -bench=.
goos: darwin
goarch: arm64
pkg: github.com/tmswfrk/stringbuilder
cpu: Apple M3
BenchmarkSB/WithPlus-8         	31047177	        38.21 ns/op
BenchmarkSB/WithPlusForLoop-8  	  909802	         1234 ns/op
BenchmarkSB/WithStringBuilder-8     14542545	        74.36 ns/op
PASS
ok  	github.com/tmswfrk/stringbuilder	4.573s
</code></pre><p>Note <em>just how much slower</em> the string concatenation is with the for loop is!</p>
<p>True story, when attempting to run this from <code>0 &lt; i &lt; 1000</code>, my computer basically froze. Crazy.</p>
<p><strong>Where can I learn more?</strong></p>
<p>I did most of this when attending GopherCon in 2025, but there&rsquo;s a blog I&rsquo;d suggest you read for more in-depth information: <a href="https://alexanderobregon.substack.com/p/go-string-concatenation-costs"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>https://alexanderobregon.substack.com/p/go-string-concatenation-costs</a></p>
]]></content>
        </item>
        
        <item>
            <title>The STAR Method</title>
            <link>https://www.nilpointer.blog/posts/2026/03/the-star-method/</link>
            <pubDate>Wed, 04 Mar 2026 15:09:44 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/03/the-star-method/</guid>
            <description>&lt;p&gt;I&amp;rsquo;ve heard about this one for so long, but have never really sat down to look at it in more detail. It&amp;rsquo;s actually quite simple, so I&amp;rsquo;m not sure why I waited so long to do this.&lt;/p&gt;
&lt;p&gt;When needing to present to an audience, instead of rambling on about what you did, lightning-talk fashion, it&amp;rsquo;s best to put some structure around it.&lt;/p&gt;
&lt;p&gt;STAR is an acronym, pioneered (I think) mostly by Amazon for handling job interviews. But I feel like it&amp;rsquo;s helpful for any kind of technical presentation or interaction.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I&rsquo;ve heard about this one for so long, but have never really sat down to look at it in more detail. It&rsquo;s actually quite simple, so I&rsquo;m not sure why I waited so long to do this.</p>
<p>When needing to present to an audience, instead of rambling on about what you did, lightning-talk fashion, it&rsquo;s best to put some structure around it.</p>
<p>STAR is an acronym, pioneered (I think) mostly by Amazon for handling job interviews. But I feel like it&rsquo;s helpful for any kind of technical presentation or interaction.</p>
<p>It helps you set the stage and build up details to a final sense of completion.</p>
<p>I really like <a href="https://capd.mit.edu/resources/the-star-method-for-behavioral-interviews/"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>MIT&rsquo;s take on all of this</a>, so give that a read for additional information than what I have here.</p>
<p><strong>Situation</strong></p>
<p>Set the stage. What key details should people know when considering the initial context around what was later done?</p>
<p><em>My company had X repositories being built by XX build tool, which had become increasingly difficult to reason about.</em></p>
<p>Depending on who you&rsquo;re talking to, you may want to provide some measurable data behind a statement like this. Anything you provide here will really showcase your final result that you&rsquo;re building to in this conversation.</p>
<p><strong>Task</strong></p>
<p>What was your task in this situation? What were you assigned to do, or what did you determine needed to be done?</p>
<p><em>My company saw this as an issue and I was tasked with creating / onboarding us to a new build tool.</em></p>
<p>I want to make an important distinction here - being assigned a task is very different than you being the one to set the task.</p>
<p>The latter showcases leadership. You saw an issue and actively chose to provide a solution to it, while also aligning key stakeholders and actions on the work. Perhaps another topic to discuss in more detail!</p>
<p><strong>Action</strong></p>
<p>How did you take action? What did you do?</p>
<p>This seems to be the most important part to the STAR method (some say 60% of the conversation), but highlighting it after the previous two helps resolve the &ldquo;questions&rdquo; you&rsquo;ve set up previously.</p>
<p><em>I researched the market, got the budget approved, and allocated some local instances of XX build tool. Then I onboarded teams to using it by writing a distributed migration action&hellip;</em></p>
<p>This also needs to be catered to your audience, of course. If you&rsquo;re talking to a peer, perhaps you need to dive more into technical detail. Maybe even get into some whiteboarding if that&rsquo;s helpful.</p>
<p>But if you&rsquo;re talking to a manager, focus more on the big picture of what was done. Let them determine where you go into more detail, and have those items ready in case they do. It helps a <em>ton</em> to know your audience with this one!</p>
<p><strong>Result</strong></p>
<p>So&hellip;what happened? Did you finish the action? Did it work?</p>
<p><em>We got everyone onboarded to the new tool and improved our build times by X% and reduced our costs by $XX.</em></p>
<p>It helps to have measureable numbers here, too, because it helps to normalize the actions to your audience. How much a dollar is worth is subjective person to person, for example, but we all know what <em>a dollar is</em>.</p>
<p><strong>My take is this: STAR lets YOU set up your own questions and answers!</strong></p>
<p>That&rsquo;s how I see it, at least. And since you (hopefully) have a lot of situations that can be modeled after the STAR method, picking which one(s) to highlight is also an important skill in and of itself.</p>
<p>It&rsquo;s a bit psychological, I suppose, but if you&rsquo;re trying to highlight you as a candidate or your project as a potential for funding, controlling the narrative in this way is probably your best bet to getting the outcome you want.</p>
]]></content>
        </item>
        
        <item>
            <title>The Haversine Formula</title>
            <link>https://www.nilpointer.blog/posts/2026/03/the-haversine-formula/</link>
            <pubDate>Sun, 01 Mar 2026 21:01:44 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/03/the-haversine-formula/</guid>
            <description>&lt;p&gt;While doing some reading tonight, I came across a question.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do you calculate the distances between two GPS coordinates?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Turns out it&amp;rsquo;s by using a function called the &lt;a href=&#34;https://en.wikipedia.org/wiki/Haversine_formula&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;Haversine formula&lt;/a&gt;. And let me tell you, it looks kind of crazy at first.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&#34;https://www.bicyclewatercooler&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;ride bikes a lot&lt;/a&gt; and use a GPS computer to track my rides - I&amp;rsquo;ve always wondered how a device, likely emitting data points every second or so - kept track of this information. And then additionally, how services like &lt;a href=&#34;https://www.strava.com&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;Strava&lt;/a&gt; or Garmin actually overlay that information on to a map to show you where you&amp;rsquo;ve gone, how fast, at what elevation, etc.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>While doing some reading tonight, I came across a question.</p>
<p><strong>How do you calculate the distances between two GPS coordinates?</strong></p>
<p>Turns out it&rsquo;s by using a function called the <a href="https://en.wikipedia.org/wiki/Haversine_formula"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Haversine formula</a>. And let me tell you, it looks kind of crazy at first.</p>
<p>I <a href="https://www.bicyclewatercooler"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>ride bikes a lot</a> and use a GPS computer to track my rides - I&rsquo;ve always wondered how a device, likely emitting data points every second or so - kept track of this information. And then additionally, how services like <a href="https://www.strava.com"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Strava</a> or Garmin actually overlay that information on to a map to show you where you&rsquo;ve gone, how fast, at what elevation, etc.</p>
<p>So I found some code references on how to use this formula, but it didn&rsquo;t really make much sense to me. At least not until I found <a href="https://youtu.be/t6NkBRQ2Fz0?si=OuzGW_EjGF3vcDBA"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>this video</a> (which gets pretty heavy right in the last 30 seconds).</p>
<p><strong>GPS Coordinates aren&rsquo;t exactly like x, y coordinates.</strong></p>
<p>You can do some basic calculations between two GPS coordinates when they share a common numerical value, but that&rsquo;s not a very real-world example.</p>
<p>At first I thought, <em>maybe I could treat this like a Pythagorean Theorem problem</em>, until I realized that that assumption relied on things being in a flat plane.</p>
<p>And surely we have a better way to handle this than that. Earth isn&rsquo;t flat!</p>
<p>The better way to think about this is calculating the &ldquo;arc&rdquo; that is created between two similar points. Then, you can see this as a trigonometric problem, using the Earth&rsquo;s average radius as a data point.</p>
<p>With enough math, you can figure out this arc value &ldquo;length&rdquo;, aka distance. Basically you can use the trigonometric functions of a unit circle (with Earth&rsquo;s radius as its &ldquo;unit&rdquo;) if you imagine taking a slice of the Earth at the longitude you&rsquo;re comparing.</p>
<p><strong>Remember radians? I had nearly forgotten!</strong></p>
<p>Where things get more interesting though is when you want to compare two GPS points <em>from anywhere</em>.</p>
<p>The Haversine formula takes this into account. And it does so by imagining a similar &ldquo;unit circle slice&rdquo; but oriented differently, i.e., NOT along latitudinal or longitudial lines.</p>
<p>Really cool! The video above explains it a bit better and with visuals.</p>
<p><strong>So I decided to write up a small Golang function to calculate this value between two data points.</strong></p>
<p>Using a type that includes both a <code>Lat</code> and <code>Lng</code> value to combine values into a singular &ldquo;point&rdquo;, it looks a bit like this.</p>
<pre tabindex="0"><code>type TrackPoint struct {
  Lat float64
  Lng float64
}

func haversine(t1, t2 TrackPoint) float64 {
  // Earth&#39;s approx radius in meters
  const earthRadius = 6371000
	
  var (
    // radian calculations
    rLat1 = t1.Lat * math.Pi / 180.0
    rLat2 = t2.Lat * math.Pi / 180.0
    
    // delta values, converted to radians
    dLat  = (t2.Lat - t1.Lat) * math.Pi / 180.0
    dLng  = (t2.Lng - t1.Lng) * math.Pi / 180.0
  )
	
  // account for going over from -179 to +179 degrees
  // a simple subtraction would make this look like -358 degrees
  if dLng &gt; math.Pi {
    dLng -= 2 * math.Pi
  } else if dLng &lt; -math.Pi {
    dLng += 2 * math.Pi
  }
  // this is kind of like a mod function that helps 
  // when numbers wrap around a boundary, except it
  // +/- two radians / 360 degrees.

  // the math
  a := math.Pow(math.Sin(dLat/2), 2) + 
    math.Pow(math.Sin(dLng/2), 2) *
    math.Cos(rLat1) *
    math.Cos(rLat2)
	
  c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
	
  // turns out this is equivalent to above, and its been
  // way too long since Calculus 2 for me to know why
  //c := 2 * math.Asin(math.Sqrt(a))

  return earthRadius * c
}
</code></pre><p>Which can then be called via a main function:</p>
<pre tabindex="0"><code>func main() {
  // Big Ben in London (51.5007° N, 0.1246° W)
  // New York (40.6892° N, 74.0445° W)
  // Expected distance: 5574.840457 km
  var (
    london = TrackPoint{Lat: 51.5007, Lng: 0.1246}
    nyc    = TrackPoint{Lat: 40.6892, Lng: 74.0445}
  )

  fmt.Printf(&#34;Haversine distance between NYC and London: %f\n&#34;, 
    haversine(nyc, london))
}
</code></pre><p>Which then shows us:</p>
<blockquote>
<p>Haversine distance between NYC and London: 5574840.456849</p>
</blockquote>
<p>I find things like this particularly fascinating because, despite its complexity, it&rsquo;s something that&rsquo;s used in every day functions without anyone really realizing it.</p>
]]></content>
        </item>
        
        <item>
            <title>Enshittification</title>
            <link>https://www.nilpointer.blog/posts/2026/02/enshittification/</link>
            <pubDate>Sat, 28 Feb 2026 15:37:07 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/02/enshittification/</guid>
            <description>&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Cory_Doctorow&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;Cory Doctorow&lt;/a&gt; coined this term a while back and even wrote a book about it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Enshittification&amp;rdquo; is the gradual degradation of online platforms, where services initially provide high-quality, free, or cheap value to attract users, then pivot to prioritize advertisers, and finally extract all value for themselves, ultimately harming both users and business customers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;I love this term.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It describes so much of what I&amp;rsquo;ve seen in tech over my lifetime. Seriously great products and processes enabled by the &amp;ldquo;world wide web&amp;rdquo; of the mid to late 90&amp;rsquo;s, eventually to succumb to ads, paywalls, popups, and &amp;ldquo;nickel and dime&amp;rdquo; schemes in both physical and online products.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><a href="https://en.wikipedia.org/wiki/Cory_Doctorow"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Cory Doctorow</a> coined this term a while back and even wrote a book about it.</p>
<blockquote>
<p>&ldquo;Enshittification&rdquo; is the gradual degradation of online platforms, where services initially provide high-quality, free, or cheap value to attract users, then pivot to prioritize advertisers, and finally extract all value for themselves, ultimately harming both users and business customers.</p>
</blockquote>
<p><strong>I love this term.</strong></p>
<p>It describes so much of what I&rsquo;ve seen in tech over my lifetime. Seriously great products and processes enabled by the &ldquo;world wide web&rdquo; of the mid to late 90&rsquo;s, eventually to succumb to ads, paywalls, popups, and &ldquo;nickel and dime&rdquo; schemes in both physical and online products.</p>
<p>If you&rsquo;ve ever wondered by Outlook is now a subscription service instead of a standalone product that you&rsquo;d buy in <a href="https://en.wikipedia.org/wiki/CompUSA"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>CompUSA</a>, I mean, Best Buy? This is why.</p>
<p><strong>I don&rsquo;t run ads here, partly in protest to this trend.</strong></p>
<p>My websites that I run are served very efficiently from blog storage and I refuse to run ads on them that hinder your experience reading them. It&rsquo;s my response to this general market trend in hopes that I can align myself with old men yelling at clouds, smelling new books in a local book store, and oogling over some LP&rsquo;s.</p>
<p><strong>There are stages to &ldquo;enshittification&rdquo;.</strong></p>
<p>Cory describes &ldquo;enshittification&rdquo; in 3 stages.</p>
<blockquote>
<p>Stage 1: Good to Users. Platforms use subsidies or excellent service to attract a large user base, creating a &ldquo;lock-in&rdquo; effect.</p>
</blockquote>
<p>Think about how Facebook gradually took over from MySpace, for example.</p>
<p>The requirement to have a college email address made it feel more exclusive compared to MySpace, which additionally drew people in.</p>
<p>I was in college right as it was coming online, and everyone else had a Facebook account, why wouldn&rsquo;t I, a freshman in college, use it? Besides, MySpace felt so <em>high school</em>.</p>
<p>And with all of your friends and family on Facebook, why would you leave?</p>
<blockquote>
<p>Stage 2: Abuse Users for Business Customers. Once users are locked in, the platform degrades the user experience to favor business customers (advertisers, sellers) to generate revenue.</p>
</blockquote>
<p>At some point in 2009, I got addicted to a dumb game on Facebook called &ldquo;<a href="https://en.wikipedia.org/wiki/Mafia_Wars"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Mafia Wars</a>&rdquo;. I even created a second Facebook account to try to build up my &ldquo;mafia&rdquo; more quickly.</p>
<p>It &ldquo;gamified&rdquo; my time, which meant that I had to log in every few hours to perform some actions, then wait for reset timers to cool down.</p>
<p>Later, I got blasted by <a href="https://en.wikipedia.org/wiki/FarmVille"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Farmville</a> requests from other friends (and family). I even enjoyed the <a href="https://en.wikipedia.org/wiki/The_Simpsons:_Tapped_Out"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Simpsons: Tapped Out</a> for a while, too (which was admittedly hilarious in some ways).</p>
<p>Little did we realize that ads were creeping in, and microtransactions were happening so that we wouldn&rsquo;t have to wait as long for those timers to reset.</p>
<p><em>Besides, what&rsquo;s a few dollars for a game you spend a lot of time on?</em></p>
<p>It&rsquo;s just so <em>satisfying</em> to have that one special edition character roaming around your virtual Springfield. At least that&rsquo;s what the dopamine told me.</p>
<p>We as human beings started to <em>become the product</em> for our technical shareholders.</p>
<blockquote>
<p>Stage 3: Abuse Business Customers for Themselves. Finally, the platform squeezes the business customers, taking nearly all the value for shareholders and executives, leaving a broken, less usable platform.</p>
</blockquote>
<p>Enter today. What do we see most at Facebook these days? I&rsquo;ll share what I see:</p>
<ul>
<li>Posts shown to me that encourage me to get angry, because I&rsquo;m more likely to interact with them.</li>
<li>Misorganized posts and threads that make context absolutely devoid of meaning, creating content that&rsquo;s shouted into the void.</li>
<li>Ads that are disguised like posts and show up when scrolling <em>anywhere</em>.</li>
<li>Feed algorithms that sometimes deprioritize the things I&rsquo;d like to see.</li>
<li>Occasionally something good, usually over in the Marketplace side of things.</li>
<li>etc.</li>
</ul>
<p>There was even legal action in this space, specifically the <a href="https://en.wikipedia.org/wiki/Facebook%e2%80%93Cambridge_Analytica_data_scandal"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Cambridge-Analytica data scandal</a>, where political campaigns were altered due to the way these algorithms worked, often in nefarious and <em>orchestrated</em> ways.</p>
<p>Having <a href="https://www.autoblog.com/news/bmw-admits-charging-extra-for-heated-seats-was-a-mistake"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>BMW charge you per-month to use your own heated seats</a> was a big step into the world of enshittification, too.</p>
<p>But since law takes longer to litigate than &ldquo;innovation&rdquo; iterates, we are regularly left behind by the drastic and ill-conceived actions of a few tech companies. They pocket the money and run, then broker a reduced set of fines that usually don&rsquo;t reflect the real impact they&rsquo;ve left on us.</p>
<p><strong>So anyway&hellip;</strong></p>
<p>Probably the best way to read / learn about &ldquo;enshittification&rdquo; is directly from the source on YouTube, at CloudFest 2025: <a href="https://www.youtube.com/watch?v=_Ai-fC-2Bpo"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>https://www.youtube.com/watch?v=_Ai-fC-2Bpo</a></p>
<p>He even went on the <a href="https://www.youtube.com/watch?v=d2e-c9SF5nE"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Daily Show</a>, which was hilarious and amazing to watch.</p>
<p><strong>Up next, Norway?</strong></p>
<p>I wanted to mention it in today&rsquo;s blog post because even Norway has gotten in on it: <a href="https://www.youtube.com/watch?v=T4Upf_B9RLQ"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>https://www.youtube.com/watch?v=T4Upf_B9RLQ</a></p>
<p>It&rsquo;s a wonderful, tongue-in-cheek take on that whole situation we face ourselves in today. Give it a watch!</p>
<p>Perhaps with enough time, we all can refocus our attention away from the tech billionaries running the show now and back to ourselves, our friends, our family, and our own lives.</p>
<p>One can dream!</p>
]]></content>
        </item>
        
        <item>
            <title>Using the &#34;done&#34; channel in Go</title>
            <link>https://www.nilpointer.blog/posts/2026/02/using-the-done-channel-in-go/</link>
            <pubDate>Fri, 27 Feb 2026 14:29:51 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/02/using-the-done-channel-in-go/</guid>
            <description>&lt;p&gt;I wanted to document how a &lt;code&gt;done&lt;/code&gt; channel is most commonly used.&lt;/p&gt;
&lt;p&gt;Mainly because I&amp;rsquo;ve personally found it to be more confusing than it should be.&lt;/p&gt;
&lt;p&gt;It usually looks a bit like this, when following a worker pattern:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;func main() {
  var doneCh = make(chan struct{})

  // Perform some concurrent work
  // ...

  // Wait on that work
  for i := 0; i &amp;lt; 10; i++ {
    &amp;lt;-doneCh
  }
  
  // complete
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I intentially left out a bit here to highlight what a &amp;ldquo;done&amp;rdquo; channel actually looks like.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I wanted to document how a <code>done</code> channel is most commonly used.</p>
<p>Mainly because I&rsquo;ve personally found it to be more confusing than it should be.</p>
<p>It usually looks a bit like this, when following a worker pattern:</p>
<pre tabindex="0"><code>func main() {
  var doneCh = make(chan struct{})

  // Perform some concurrent work
  // ...

  // Wait on that work
  for i := 0; i &lt; 10; i++ {
    &lt;-doneCh
  }
  
  // complete
}
</code></pre><p>I intentially left out a bit here to highlight what a &ldquo;done&rdquo; channel actually looks like.</p>
<p>Following up on the <a href="https://www.nilpointer.blog/posts/2026/02/the-worker-pattern/"
  
  
>worker pattern</a> post yesterday, I also connected a few dots that previously I had never connected.</p>
<p><strong>A <code>done</code> channel does effectively the same thing as a <code>WaitGroup</code>.</strong></p>
<p>In the example above, the <code>&lt;-doneCh</code> command is a blocking operation. The command waits for something to be sent to the channel before it can read an empty struct from it. In this case, a send operation is performed at the end of a function being called from within a go routine.</p>
<p>Note that the <em>content</em> of the channel doesn&rsquo;t matter - only that something is sent to it. It&rsquo;s only there to block further operations while longer tasks finish their work.</p>
<p><strong>The <code>struct{}</code> type is a little bit special in Golang.</strong></p>
<p>Bare <code>struct{}</code> types in Golang allocate no memory. This is super helpful if you want to build a <code>Set</code> or something where you only care about the presence of a key, or in this case, the presence of anything in the channel to gate behavior.</p>
<p>From <a href="https://dave.cheney.net/2014/03/25/the-empty-struct"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Dave Cheney&rsquo;s blog</a> and using <code>unsafe.Sizeof()</code> to show memory usage of items, we can see that a <code>struct{}</code> takes no bytes.</p>
<pre tabindex="0"><code>var s struct{}
fmt.Println(unsafe.Sizeof(s))
// prints 0
</code></pre><p>So rather than using up more memory than you need to storing throwaway data, it&rsquo;s a great idea to use <code>chan struct{}</code> for a <code>done</code> channel.</p>
<p><strong>Going back to the example above&hellip;</strong></p>
<p>Using the previously discussed <a href="https://www.nilpointer.blog/posts/2026/02/the-worker-pattern/"
  
  
>worker pattern</a>:</p>
<pre tabindex="0"><code>func main() {
  var (
    workCh = make(chan int)
    doneCh = make(chan struct{})
    numWorkers = 10
  )

  // set up a worker pool
  for i := 0; i &lt; numWorkers; i++ {
    go doWork(i, workCh, doneCh)
  }

  // add task items to our work channel
  for i := 0; i &lt; 100; i++ {
    workCh &lt;- i
  }

  // close after adding all tasks
  close(workCh)

  // block using the doneCh
  for i := 0; i &lt; numWorkers; i++ {
    &lt;-doneCh
  }
  // complete!
}
</code></pre><p>One more important thing to add here is how the <code>doWork()</code> function operates!</p>
<p>Similar to how, prior to Golang <code>v1.25.0</code>, we had to pass in the <code>WaitGroup</code> to the go routine&rsquo;s function to decrement it, we have to do the same here with the <code>doneCh</code>.</p>
<pre tabindex="0"><code>func doWork(wid int, workCh chan int, doneCh chan struct{}) {
  for work := range workCh {
    // ... do something that takes a while
  }
  
  // send an empty struct to the channel
  doneCh &lt;- struct{}{}
}
</code></pre><p>(See the <a href="https://go.dev/play/p/HPvyDZLHInU"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Go Playground link</a> for the complete example)</p>
<p><strong>Note the similarities to a <code>WorkGroup</code>!</strong></p>
<p>Previously, we would:</p>
<ul>
<li><code>wg.Add(1)</code> prior to calling the <code>go doWork()</code> operation.</li>
<li>Pass in the <code>wg</code> to the function.</li>
<li><code>defer wg.Done()</code> to signal &ldquo;done&rdquo; when <code>doWork()</code> was completed.</li>
</ul>
<p>Whereas in this pattern, the exact same process occurs:</p>
<ul>
<li>Set up a blocking call on the done channel with <code>&lt;-doneCh</code>.</li>
<li>Pass in the <code>doneCh</code> to the function.</li>
<li>Send an empty struct to the channel when <code>doWork()</code> was completed.</li>
</ul>
<p>Just like you have to <code>wg.Add(1)</code> <em>for each worker&rsquo;s invocation of its go routine</em>, you have to set up a blocking &ldquo;wait&rdquo; on the <code>doneCh</code>.</p>
<p><strong>Using a done channel without the worker pattern</strong></p>
<p>Without using the worker pattern, you need to model this function using a single worker in-line function:</p>
<pre tabindex="0"><code>func main() {
	var (
		workCh = make(chan int)
		doneCh = make(chan struct{})
	)

  // set up a single worker
  go func() {
    for work := range workCh {
      // do some work until channel is closed
      // ...
    }
    // signal the end of the work tasks
    doneCh &lt;- struct{}{}
  }()

  // send all tasks to work channel
  for i := 0; i &lt; 100; i++ {
    workCh &lt;- i
  }

  // close when all tasks are sent
  close(workCh)

  // wait for the signal
  &lt;-doneCh

  // complete!
}
</code></pre><p>(See the <a href="https://go.dev/play/p/M3hXPcAqmDk"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Go Playground link</a>)</p>
<p>This is a good pattern to use if each task is simple and fast to perform, but adding each task is expensive.</p>
<p>All this being said, I&rsquo;d still recommend using the <code>sync.WaitGroup</code> method if possible, while also utilizing the newer constructs available in Golang &gt;= <code>v1.25.0</code>, since it&rsquo;s just far easier to reason about!</p>
<p>But this is still great to know, and I know I&rsquo;m glad I went back to review this one again.</p>
]]></content>
        </item>
        
        <item>
            <title>The Worker Pattern</title>
            <link>https://www.nilpointer.blog/posts/2026/02/the-worker-pattern/</link>
            <pubDate>Thu, 26 Feb 2026 16:09:17 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/02/the-worker-pattern/</guid>
            <description>&lt;p&gt;I really like using a worker pattern with &lt;a href=&#34;https://www.nilpointer.blog/posts/2026/02/golangs-unbuffered-and-buffered-channels/&#34;
  
  
&gt;unbuffered channels&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;API calls, especially when each can be handled separately and independently, are easily implemented with this pattern, and I&amp;rsquo;ve seen it help tremendously when downloading artifacts for building in CI, for example.&lt;/p&gt;
&lt;p&gt;Instead of setting up a buffered channel with a capacity of 10 in an attempt to concurrently handle 10 requests, you can create an unbuffered channel and read from it using 10 workers.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I really like using a worker pattern with <a href="https://www.nilpointer.blog/posts/2026/02/golangs-unbuffered-and-buffered-channels/"
  
  
>unbuffered channels</a>.</p>
<p>API calls, especially when each can be handled separately and independently, are easily implemented with this pattern, and I&rsquo;ve seen it help tremendously when downloading artifacts for building in CI, for example.</p>
<p>Instead of setting up a buffered channel with a capacity of 10 in an attempt to concurrently handle 10 requests, you can create an unbuffered channel and read from it using 10 workers.</p>
<p>To do this in a coordinated fashion, however, it does require the usage of a <code>WaitGroup</code>, which is part of the <code>sync</code> package of the standard library.</p>
<p>Let&rsquo;s take a look:</p>
<pre tabindex="0"><code>func main() {
	var (
		wg         = new(sync.WaitGroup)
		ch         = make(chan int)
		numWorkers = 10
	)

	// set up a worker pool
	for w := 0; w &lt; numWorkers; w++ {
		wg.Go(func() {
			doSomething(ch)
		})
	}

	// add work items to our channel
	for i := 0; i &lt; 100; i++ {
		ch &lt;- i
	}

	// close the channel after adding everything
	close(ch)

	// wait for our workers to finish
	wg.Wait()
}

func doSomething(ch chan int) {
	// read from the channel
	for item := range ch {
		fmt.Printf(&#34;Doing some work for item %d\n&#34;, item)
		time.Sleep(time.Millisecond * 500)
	}
}
</code></pre><p>(<a href="https://go.dev/play/p/P-YYQ0VCR8c"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Go Playground link</a>)</p>
<p>Pretty sweet, isn&rsquo;t it?</p>
<p><strong>WorkGroup usage prior to Golang <code>v1.25.0</code> is different.</strong></p>
<p>Note that in versions of Golang prior to <code>v1.25.0</code>, the usage of a <code>WaitGroup</code> was slightly different. Instead of using:</p>
<pre tabindex="0"><code>for w := 0; w &lt; numWorkers; w++ {
	wg.Go(func() {
		doSomething()
	})
}
</code></pre><p>It would look more like this:</p>
<pre tabindex="0"><code>for w := 0; w &lt; numWorkers; w++ {
	// add an item of work
	wg.Add(1)
	
	// also pass in the wg to the func
	// and call as a go routine
	go doSomething(wg, ch)
}
</code></pre><p>Which also required you to pass in the <code>WaitGroup</code> to the function like so:</p>
<pre tabindex="0"><code>func doSomething(wg *sync.WaitGroup, ch chan in) {
	// signal done to the wg
	defer wg.Done()

	for item := range ch {
		//...
	}
}
</code></pre><p>I really like the new <code>wg.Go()</code> function, since it abstracts away the slightly awkward <code>wg.Add(1)</code> and <code>wg.Done()</code> function calls.</p>
<p><strong>Worker IDs in output can help with debugging.</strong></p>
<p>You can also pass in the worker id to the function being called for additional traceability. Then you can show which worker is doing which item of work.</p>
<pre tabindex="0"><code>func doSomething(wid int, ch chan int) {
	// read from the channel
	for item := range ch {
		fmt.Printf(&#34;Worker %d did work on item %d\n&#34;, 
			wid, item)
		time.Sleep(time.Millisecond * 500)
	}
}
</code></pre><p>Check out the <a href="https://go.dev/play/p/GUiOc6S6qHf"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Go Playground link</a> if you want to see it in action.</p>
<p>You can actually <em>see</em> concurrency in action by noticing the numbers scolling by &ldquo;out of order&rdquo;. Each request is going to take a different amount of time, and you can rely on the Golang runtime to handle all the context switching for you.</p>
<pre tabindex="0"><code>...
Worker 1 did work on item 67
Worker 9 did work on item 65
Worker 5 did work on item 68
Worker 4 did work on item 66
Worker 0 did work on item 69
Worker 2 did work on item 70
...
</code></pre><p><strong>It helps to have a &ldquo;unit of work&rdquo; defined as its own function.</strong></p>
<p>For readability and maintainability, I recommend having the actual &ldquo;work&rdquo; relegated to its own function.</p>
<p>And importantly, have that function be simply defined as a single unit of work, rather than comingling it with concurrent parameters (unless you need mutexes, for example, to gate certain non-thread-safe actions).</p>
<p>This makes the calling function essentially a wrapper that handles the concurrency. It&rsquo;s kind of a &ldquo;separation of concerns&rdquo;.</p>
<p>It also makes for easier unit testing - another topic for another day!</p>
]]></content>
        </item>
        
        <item>
            <title>Golang&#39;s unbuffered and buffered channels</title>
            <link>https://www.nilpointer.blog/posts/2026/02/golangs-unbuffered-and-buffered-channels/</link>
            <pubDate>Wed, 25 Feb 2026 16:40:18 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/02/golangs-unbuffered-and-buffered-channels/</guid>
            <description>&lt;p&gt;From the &lt;a href=&#34;https://go.dev/blog/codelab-share&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;official Go Blog&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Do not communicate by sharing memory; instead, share memory by communicating.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They&amp;rsquo;re awesome because they enable concurrency support &amp;ldquo;out of the box&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;They&amp;rsquo;re basically their own type that implements concurrent safe logic of sharing memory locations of data without the explicit usage of mutexes or locks. The example in the blog link above has some code blocks that show this quite well, actually.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;They can be buffered or unbuffered.&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>From the <a href="https://go.dev/blog/codelab-share"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>official Go Blog</a>:</p>
<blockquote>
<p>Do not communicate by sharing memory; instead, share memory by communicating.</p>
</blockquote>
<p>They&rsquo;re awesome because they enable concurrency support &ldquo;out of the box&rdquo;.</p>
<p>They&rsquo;re basically their own type that implements concurrent safe logic of sharing memory locations of data without the explicit usage of mutexes or locks. The example in the blog link above has some code blocks that show this quite well, actually.</p>
<p><strong>They can be buffered or unbuffered.</strong></p>
<p>This is implied in the way you define each type:</p>
<pre tabindex="0"><code>var unbuffered = make(chan int)
var buffered   = make(chan int, 3)
</code></pre><p><em>A great way to describe the difference is that an unbuffered channel will block the go routine it&rsquo;s being called from until it&rsquo;s &ldquo;filled&rdquo; with something.</em></p>
<p><strong>You can also specify their &ldquo;direction&rdquo;.</strong></p>
<p>This means you can make them &ldquo;bidirectional&rdquo;, &ldquo;send&rdquo;, or &ldquo;receive&rdquo;. Note the locations of the <code>&lt;-</code> identifier.</p>
<pre tabindex="0"><code>var bidirectional = make(chan int)
var sendOnly      = make(chan&lt;- int)
var receiveOnly   = make(&lt;-chan int)
</code></pre><p><strong>You can combine these attributes together.</strong></p>
<p>So you can have any combination of buffered, unbuffered, send, receive, or bidirectional.</p>
<pre tabindex="0"><code>var sendOnlyBuffered  = make(chan&lt;- int, 10)
var receiveUnbuffered = make(&lt;-chan int)
</code></pre><p><strong>You can change this in function headers.</strong></p>
<p>So this means you could have the following:</p>
<pre tabindex="0"><code>func main() {
  // create a bidirectional channel
  someChannel := make(chan int)

  myFunc(someChannel)
}

// specify channel in header as receive-only
func myFunc(ch &lt;-chan) {
  // ...
}
</code></pre><p>Not only does this convey meaning, but it can make sure that your accidentally try to write to a channel that you&rsquo;re only expected to read from. Especially important if you have a bunch of different go routines doing different things!</p>
<p><strong>Reading from channels is done via the <code>range</code> function.</strong></p>
<p>A few additional points about this before an example:</p>
<ul>
<li>to read from a channel, you can use the same <code>range</code> function similar to how you read from a slice.</li>
<li>due to this blocking behavior, reading from an unbuffered channel is often done within a separate goroutine.</li>
<li>it&rsquo;s always a good idea to close the channel when you&rsquo;re done using it, usually via a <code>defer</code>.</li>
</ul>
<pre tabindex="0"><code>func main() {
  // unbuffered
  var ch = make(chan int)
  defer close(ch)

  // see below
  go doSomething(ch)

  // populate the channel with items
  for i := 0; i &lt; 10; i++ {
    ch &lt;- i // &#34;send to&#34; the channel
  }
}

func doSomething(ch chan int) {
  for item := range ch {
    fmt.Printf(&#34;Item was read: %d\n&#34;, item)
  }
}
</code></pre><p>(see <a href="https://go.dev/play/p/yHpobaMikZ_J"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Go Playground link</a>)</p>
<p>This just iterates over a set of numbers as if it were a slice, but it showcases that channels can work similarly. While also allowing for concurrency <em>and</em> without needing to create and manage your own mutexes. Neat!</p>
<p><strong>How how would an unbuffered channel handle a longer task?</strong></p>
<p>Where things get interesting is when you simulate longer running operations in the above code.</p>
<p>Say you modify it a bit to include a <code>time.Sleep</code> statement to simulate a worker action (an api call, a db transaction, or something similar):</p>
<pre tabindex="0"><code>func doSomething(ch chan int) {
	for item := range ch {
		fmt.Printf(&#34;Item was read: %d\n&#34;, item)
		time.Sleep(time.Second) // simulate long process
	}
}
</code></pre><p>(another, separate <a href="https://go.dev/play/p/JzuweDGuFZI"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Go Playground link</a>)</p>
<p>When running this, you&rsquo;ll notice that the &ldquo;send to&rdquo; action isn&rsquo;t gated by anything in particular.</p>
<p><em>Yet we only read one item from the channel per second!</em></p>
<p>This means that the first <code>for</code> loop that populates the channel is being blocked because there&rsquo;s no &ldquo;buffer&rdquo; in the channel to queue later iterations up for later.</p>
<p><strong>Enter buffered channels, which have a capacity.</strong></p>
<p>This means that if you create one with a capacity of 2, but then try to send it 5 things, 3 of them will be blocked until the first two are read.</p>
<pre tabindex="0"><code>func main() {
  // make buffered channel with capacity 2
  var ch = make(chan int, 2)

  // same as before
  go writer(ch)

  // simulate slow process before reading
  time.Sleep(time.Second * 2)

  for item := range ch {
    fmt.Printf(&#34;Read %d from channel\n&#34;, item)
  }
}

func writer(ch chan int) {
  for i := 0; i &lt; 5; i++ {
      ch &lt;- i // this will block on 3, 4, 5
      fmt.Printf(&#34;wrote on iteration %d\n&#34;, i)

      // show delay between reads
      time.Sleep(time.Second)
  }
  close(ch)
}
</code></pre><p>(see <a href="https://go.dev/play/p/ByrDcG31Zr5"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Go Playground link</a>)</p>
<p>So yeah, channels are pretty great. But they can be a bit tricky to grok at first, given that they look and act a bit differently than other parts of the Golang ecosystem.</p>
<p>Either way, expect to hear more from me about channels soon!</p>
]]></content>
        </item>
        
        <item>
            <title>A thought on debugging</title>
            <link>https://www.nilpointer.blog/posts/2026/02/a-thought-on-debugging/</link>
            <pubDate>Tue, 24 Feb 2026 13:48:08 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/02/a-thought-on-debugging/</guid>
            <description>&lt;blockquote&gt;
&lt;p&gt;Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Brian_Kernighan&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;Brian W. Kernighan&lt;/a&gt; is attributed to that quote, and it (regularly) reasonates a lot with me.&lt;/p&gt;
&lt;p&gt;I think sometimes as engineers, we actually &lt;em&gt;like&lt;/em&gt; complexity. It feels fun to build something really interesting and technical in scope, and we often confuse simple topics like &amp;ldquo;simple&amp;rdquo;, &amp;ldquo;technical&amp;rdquo;, or even &amp;ldquo;beautiful&amp;rdquo;, &amp;ldquo;clean&amp;rdquo;, &amp;ldquo;elegant&amp;rdquo;&amp;hellip;the list goes on.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<blockquote>
<p>Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?</p>
</blockquote>
<p><a href="https://en.wikipedia.org/wiki/Brian_Kernighan"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Brian W. Kernighan</a> is attributed to that quote, and it (regularly) reasonates a lot with me.</p>
<p>I think sometimes as engineers, we actually <em>like</em> complexity. It feels fun to build something really interesting and technical in scope, and we often confuse simple topics like &ldquo;simple&rdquo;, &ldquo;technical&rdquo;, or even &ldquo;beautiful&rdquo;, &ldquo;clean&rdquo;, &ldquo;elegant&rdquo;&hellip;the list goes on.</p>
<p>I actually also like this variation on the same quote:</p>
<blockquote>
<p>“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.”</p>
</blockquote>
<p>Supposedly by Martin Goldin (although I can&rsquo;t find a definitive account of which Martin Goldin this is).</p>
<p><strong>It&rsquo;s only gonna get worse with AI.</strong></p>
<p>I don&rsquo;t know if you&rsquo;ve heard, but there&rsquo;s this thing called &ldquo;AI&rdquo; and it&rsquo;s supposed to make life better for all of us and automate us all out of existence.</p>
<p>I&rsquo;ve seen it being used a <em>lot</em> lately, with engineers priding themselves on how they don&rsquo;t actually need to write code anymore, relying on AI agents like Claude, ChatGPT, Gemini, etc to do it for them.</p>
<p><strong>Nevermind the actual &ldquo;engineering&rdquo; part of all this.</strong></p>
<p>Regardless, the more coding you offload to an agent to do on your behalf, the less <em>you</em> know about how it actually works.</p>
<p>And the <em>more</em> complicated it&rsquo;s going to be to figure things out when they (inevitably) go wrong.</p>
<p>Unless you <em>very</em> proactively review it and fix these PRs as they come, of course. But given how often we all see <code>&quot;LGTM&quot;</code> on our Github PR&rsquo;s, I have a feeling that this may very quickly spiral out of control if we don&rsquo;t manage it.</p>
<p><strong>So let&rsquo;s take a step back.</strong></p>
<p>If AI works for you and it simplifies your life, great. But be aware of what it&rsquo;s doing.</p>
<p>Otherwise, that same agent you used to create that cool feature is going to become your crutch to debug and fix things when it goes wrong.</p>
<p>So when you&rsquo;re paged at 3am to fix a site outage, you&rsquo;re going to be leaning pretty heavily on your prompt engineering skills, along with maybe a few thoughts and prayers from your asleep coworkers, to fix the issue.</p>
<p>Is that better or worse than it is today? Time will tell, I&rsquo;m sure.</p>
]]></content>
        </item>
        
        <item>
            <title>Azure and rsync</title>
            <link>https://www.nilpointer.blog/posts/2026/02/azure-and-rsync/</link>
            <pubDate>Mon, 23 Feb 2026 17:05:10 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/02/azure-and-rsync/</guid>
            <description>&lt;p&gt;I&amp;rsquo;m not actually referring to &lt;em&gt;the&lt;/em&gt; &lt;code&gt;rsync&lt;/code&gt; command that a lot of us know and love.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m referring to &lt;code&gt;azcopy sync&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I sometimes use this to copy over files from the generated &lt;code&gt;public/&lt;/code&gt; folder that &lt;code&gt;Hugo&lt;/code&gt; creates for me to my Azure blob store. That&amp;rsquo;s where this site is stored.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&#34;https://www.nilpointer.blog/posts/2026/02/azure-currently-runs-this-site/&#34;
  
  
&gt;another post&lt;/a&gt;, I wrote about a handy VSCode plugin that I use when uploading changes to this site. Unfortunately, this one deletes everything and re-uploads everything.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I&rsquo;m not actually referring to <em>the</em> <code>rsync</code> command that a lot of us know and love.</p>
<p>I&rsquo;m referring to <code>azcopy sync</code>.</p>
<p>I sometimes use this to copy over files from the generated <code>public/</code> folder that <code>Hugo</code> creates for me to my Azure blob store. That&rsquo;s where this site is stored.</p>
<p>In <a href="https://www.nilpointer.blog/posts/2026/02/azure-currently-runs-this-site/"
  
  
>another post</a>, I wrote about a handy VSCode plugin that I use when uploading changes to this site. Unfortunately, this one deletes everything and re-uploads everything.</p>
<p>A bit much, right?</p>
<p>Well, the alternative here is to use <code>azcopy sync</code>. It involves a few things&hellip;</p>
<p><strong>Download the azcopy binary</strong></p>
<p>The best place to do this is to locate the right binary for your OS + Architecture from the main Azure page: <a href="https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-v10"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-v10</a>.</p>
<p><strong>Generate a SAS key for a certain amount of time</strong></p>
<p>Do this within the Azure portal. In the <code>$web</code> container within your storage account, you can generate a SAS token using the <code>...</code> button to its right.</p>
<p>From there, define whatever time interval you want for this token (shorter is better, as is the case with cryptography) and optionally set it against your IP address that you want run the <code>sync</code> operation from.</p>
<p>Make sure to give it the right permissions, usually <code>read</code>, <code>write</code>, <code>delete</code>, and <code>list</code>, especially if you plan to use the <code>--delete-destination</code> flag.</p>
<p><strong>It&rsquo;s important to keep track of this key!</strong></p>
<p>Once you navigate away from this page, you won&rsquo;t be able to see the key again. You&rsquo;ll instead have to create another one - luckily, there&rsquo;s no limit to these apparently!</p>
<p><strong>Run the right command</strong></p>
<p>Make sure you follow this pattern:</p>
<pre tabindex="0"><code>azcopy sync &#34;&lt;local-directory-path&gt;&#34; &#34;https://&lt;storage-account-name&gt;.blob.core.windows.net/&lt;container-name&gt;&#34;
</code></pre><p>If you&rsquo;re using this at all like I am, you&rsquo;ll need to escape the <code>$</code> character from the <code>$web</code> container that prepends all of this. Or just use single quotes as opposed to double ones.</p>
<p>And you&rsquo;ll also need to make sure the full SAS key is added to the URL, too.</p>
<p>So in total, something like this.</p>
<pre tabindex="0"><code>azcopy sync &#39;/Users/&lt;$USER&gt;/code/some-hugo-project/public/&#39; &#39;https://mystorageacct.blob.core.windows.net/$web\?sp=racwdli&amp;st=2024-11-21T06:24:19Z&amp;se=2025-11-21T14:24:19Z&amp;sip=123.456.789.987&amp;spr=https&amp;sv=2022-11-02&amp;sr=c&amp;sig=&lt;someOtherGobbletyGook&gt;&#39;
</code></pre><p>Fun!</p>
<p>The irony in this is that I&rsquo;m 100% going to just use the VSCode plugin for pushing this change. But once the site gets bigger, I&rsquo;ll definitely need to set up a new SAS key and run the <code>sync</code> command instead.</p>
<p><strong>You don&rsquo;t technically need a SAS key to do this, btw.</strong></p>
<p>I just find it easier. Using <code>azcopy login</code> will create an &ldquo;Azure AD&rdquo; or &ldquo;Entra ID&rdquo; and that can be used more directly rather than creating a whole separate SAS token. With the way my subscription is configured in Azure, I&rsquo;ve found logging in like this to be a bit cumbersome, so I just use the SAS token instead.</p>
]]></content>
        </item>
        
        <item>
            <title>Thoughts on Pushing Back on Management</title>
            <link>https://www.nilpointer.blog/posts/2026/02/thoughts-on-pushing-back-on-management/</link>
            <pubDate>Sun, 22 Feb 2026 16:43:50 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/02/thoughts-on-pushing-back-on-management/</guid>
            <description>&lt;p&gt;I recently learned about a man by the name of Roger Boisjoly, who, in the 80&amp;rsquo;s, was involved in the Challenger shuttle project. In 2012, &lt;a href=&#34;https://www.npr.org/sections/thetwo-way/2012/02/06/146490064/remembering-roger-boisjoly-he-tried-to-stop-shuttle-challenger-launch&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;NPR put together some information&lt;/a&gt; about him and his involvement in the project.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re unfamiliar, this was the shuttle that blew up on its way up out of our atmosphere. I remember less of the actual event myself, but was well aware of it because my elementary school was named after &lt;a href=&#34;https://en.wikipedia.org/wiki/Christa_McAuliffe&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;Christa McAuliffe&lt;/a&gt;. She was to be the first teacher in space.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I recently learned about a man by the name of Roger Boisjoly, who, in the 80&rsquo;s, was involved in the Challenger shuttle project. In 2012, <a href="https://www.npr.org/sections/thetwo-way/2012/02/06/146490064/remembering-roger-boisjoly-he-tried-to-stop-shuttle-challenger-launch"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>NPR put together some information</a> about him and his involvement in the project.</p>
<p>If you&rsquo;re unfamiliar, this was the shuttle that blew up on its way up out of our atmosphere. I remember less of the actual event myself, but was well aware of it because my elementary school was named after <a href="https://en.wikipedia.org/wiki/Christa_McAuliffe"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Christa McAuliffe</a>. She was to be the first teacher in space.</p>
<p>What does this have to do with management, you say? Take a read from NPR:</p>
<blockquote>
<p>[Boisjoly] found disturbing the data he reviewed about the booster rockets that would lift Challenger into space. Six months before the Challenger explosion, he predicted &ldquo;a catastrophe of the highest order&rdquo; involving &ldquo;loss of human life&rdquo; in a memo to managers at Thiokol.</p>
<p>The problem, Boisjoly wrote, was the elastic seals at the joints of the multi-stage booster rockets. They tended to stiffen and unseal in cold weather and NASA&rsquo;s ambitious shuttle launch schedule included winter lift-offs with risky temperatures, even in Florida.</p>
</blockquote>
<p>It continues:</p>
<blockquote>
<p>On January 27, 1986, the forecast for the next morning at the Kennedy Space Center included a launch-time temperature as low as 30 degrees Fahrenheit. NASA had never launched in temperatures that cold and Boisjoly and his four colleagues at Thiokol headquarters in Utah concluded it would be too dangerous too launch.</p>
<p>Three weeks later, he told NPR&rsquo;s Daniel Zwerdling in an unrecorded and confidential interview, &ldquo;I fought like Hell to stop that launch. I&rsquo;m so torn up inside I can hardly talk about it, even now.&rdquo;</p>
</blockquote>
<p>I live my life in software, and it literally never has any real impact to human life, so I cannot even <em>fathom</em> how this man must have felt. That quote is just&hellip;difficult to read, let alone comprehend fully.</p>
<p><strong>In a well-running organization, it&rsquo;s our duty to communicate risks appropriately.</strong></p>
<p>I&rsquo;ve recently been through a lot professionally. As a &ldquo;developer&rsquo;s developer&rdquo; (my self-inflated title), I&rsquo;ve found myself working tirelessly in the realm of DevX to provide a better set of tooling for my (mostly Golang) developers.</p>
<p>Unfortunately, I&rsquo;ve often struggled to communicate and align with management over various priorities.</p>
<p>In a well functioning organization, I firmly believe that management <em>listens</em> to its engineering staff.</p>
<p><strong>In DevX and internal tooling, engineers are effectively &ldquo;customers&rdquo; in the retail sense.</strong></p>
<p>Management should be asking them:</p>
<ul>
<li>What do they struggle with day to day?</li>
<li>What processes would you like to see simplified or added?</li>
<li>How well do they understand the existing build and configuration behavior across their supported environments?</li>
</ul>
<p>It&rsquo;s pretty rare to see a management chain that is as familiar with the day to day technical efforts of the engineers they oversee. In my experience, this is often driven from a &ldquo;top down&rdquo; or &ldquo;bottom up&rdquo; approach. Both require good communication, which I think is the point I&rsquo;m getting at here.</p>
<p><strong>Having a top-down approach can often be dicey, and not having good communication makes it a recipe for disaster.</strong></p>
<p>Having said this, however, engineers aren&rsquo;t always right, either. &ldquo;Bottom up&rdquo; approaches without the right workforce can also lead to a lot of technical challenges, mostly discovered later.</p>
<p>But if you:</p>
<ul>
<li>Do the right hiring</li>
<li>Listen to those employees on the ground doing the work</li>
<li>Dogfood the code that you&rsquo;re supplying to your engineering workforce</li>
</ul>
<p>&hellip;you&rsquo;ll likely find yourself working well within an organization that values its technical workforce.</p>
<p>Managing people is hard enough - why make it more difficult by refusing to communicate?</p>
<p>Getting back to Roger Boisjoly&hellip;</p>
<p><em>If you don&rsquo;t speak up, management won&rsquo;t know, and the organization will suffer.</em></p>
<p><em>If you do speak up and management doesn&rsquo;t listen, the organization will suffer.</em></p>
<p>Strangely enough, I&rsquo;ve found that a lot of engineers these days <em>don&rsquo;t</em> feel empowered to speak up. Tech has become cutthroat and with nearly constant layoffs, people generally just want to collect a paycheck and do other, hopefully more interesting things with their lives.</p>
<p>But sometimes, just sometimes, people speak up and others listen.</p>
<p>And things get <em>better</em>.</p>
<p>One can only imagine if management had listened to Roger on January 27th, 1986 and had chosen to postpone that launch.</p>
]]></content>
        </item>
        
        <item>
            <title>A Golang Slice gotcha that...got me</title>
            <link>https://www.nilpointer.blog/posts/2026/02/a-golang-slice-gotcha-that...got-me/</link>
            <pubDate>Sat, 21 Feb 2026 12:33:28 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/02/a-golang-slice-gotcha-that...got-me/</guid>
            <description>&lt;p&gt;I was doing some self study the other day, feeling relatively confident building out a &lt;a href=&#34;https://en.wikipedia.org/wiki/Trie&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;Trie&lt;/a&gt; for an autocomplete function. When checking my work with Gemini, I was recommended something like this as a possible function signature:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;func (t *Trie) collect(n *TrieNode, pfx string, results *[]string) {
  // ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Where I started to spiral was the &lt;strong&gt;&lt;code&gt;*[]string&lt;/code&gt;&lt;/strong&gt; type in the final input parameter.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That&amp;rsquo;s weird looking, isn&amp;rsquo;t it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In Golang, slices and arrays are often confused. You may say: &lt;em&gt;&amp;ldquo;But aren&amp;rsquo;t slices copied by reference?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I was doing some self study the other day, feeling relatively confident building out a <a href="https://en.wikipedia.org/wiki/Trie"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Trie</a> for an autocomplete function. When checking my work with Gemini, I was recommended something like this as a possible function signature:</p>
<pre tabindex="0"><code>func (t *Trie) collect(n *TrieNode, pfx string, results *[]string) {
  // ...
}
</code></pre><p>Where I started to spiral was the <strong><code>*[]string</code></strong> type in the final input parameter.</p>
<p><strong>That&rsquo;s weird looking, isn&rsquo;t it?</strong></p>
<p>In Golang, slices and arrays are often confused. You may say: <em>&ldquo;But aren&rsquo;t slices copied by reference?&rdquo;</em></p>
<p>And you would be wrong. Like I was. Everything is copied by value in Golang! Even when it appears like they aren&rsquo;t.</p>
<p><strong>Lets look at some source code&hellip;</strong></p>
<p>A quick look at the <a href="https://go.dev/src/runtime/slice.go"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>runtime/slice.go</a> file shows this as the slice data structure:</p>
<pre tabindex="0"><code>type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}
</code></pre><p>What does this mean, exactly?</p>
<ul>
<li>An &ldquo;array&rdquo; is the underlying type / struct that stores your data.</li>
<li>A &ldquo;slice&rdquo; is a wrapper struct that includes both the data you&rsquo;re storing and some metadata about that data.</li>
<li>A &ldquo;slice&rdquo; is technically a &ldquo;slice header&rdquo;.</li>
</ul>
<p>This is why when you instantiate an &ldquo;array&rdquo;, you need to provide it an explicit size, which basically is a type on its own:</p>
<pre tabindex="0"><code>var arr1 [4]string // creates 4 empty strings
var arr2 [3]string // different type!
</code></pre><p>Compare this to the far more familiar form(s):</p>
<pre tabindex="0"><code>var s1 []string
var s2 = make([]string, 3) // requires an initial size
var s3 = []string{&#34;something&#34;, &#34;goes&#34;, &#34;here&#34;}
</code></pre><p><strong>Getting back to my &ldquo;gotcha&rdquo;&hellip;</strong></p>
<p>When you pass a slice to another function, you most typically pass it as a <code>[]string</code>. It&rsquo;s easy and it looks nice if I don&rsquo;t say so myself.</p>
<p>And since most functions are written to return another <code>[]string</code> or possibly something else entirely, it&rsquo;s pretty rare that you have to think about what&rsquo;s really going on here.</p>
<p>Let&rsquo;s look at a contrived example:</p>
<pre tabindex="0"><code>var s = []string{&#34;something&#34;, &#34;goes&#34;, &#34;here&#34;}
fmt.Printf(&#34;original: %s\n&#34;, s)
modSlice(s) // function call, passes s by value
fmt.Printf(&#34;after return: %s\n&#34;, s)
</code></pre><p>Where <code>modSlice()</code> is defined as:</p>
<pre tabindex="0"><code>func modSlice(s []string) {
	fmt.Printf(&#34;inside func orig: %s\n&#34;, s)
	s = append(s, &#34;another&#34;)
	fmt.Printf(&#34;inside func modified: %s\n&#34;, s)
}
</code></pre><p>Should be fine, right? You expect the new entry, &ldquo;another&rdquo;, to be included in the final &ldquo;after return&rdquo; print statement.</p>
<p>Perhaps not:</p>
<pre tabindex="0"><code>original: [something goes here]
inside func orig: [something goes here]
inside func modified: [something goes here another]
after return: [something goes here]
</code></pre><p>(see <a href="https://go.dev/play/p/mm2ZuRRrQVu"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Go Playground link</a>)</p>
<p>It <em>looks</em> like the new entry isn&rsquo;t actually writing to the backing array.</p>
<p><strong>So why did this happen / not happen?</strong></p>
<p>If you list out the <code>s</code> variable from both the caller&rsquo;s perspective and from the function&rsquo;s perspective, you should effectively see the same item:</p>
<pre tabindex="0"><code>Caller: mySlice{array: 0x123, len: 3, cap: 10}
Function: mySlice{array: 0x123, len: 3, cap: 10}
</code></pre><p>After all, the function just received <em>a copy</em> of the slice (header). The <code>array</code> attribute includes <em>the same</em> array reference.</p>
<p>When we add a new entry to the slice via the <code>append</code> function, <code>mySlice</code> becomes larger in <code>len</code> while modifying the same memory behind the <code>array</code> parameter:</p>
<pre tabindex="0"><code>mySlice{array: 0x123, len: 4, cap: 10}
</code></pre><p>However, when we return back to the caller&rsquo;s stack and context, our <code>mySlice</code> variable still has:</p>
<pre tabindex="0"><code>mySlice{array: 0x123, len: 3, cap: 10}
</code></pre><p><strong>Note the <code>3</code> in the <code>len</code> parameter!</strong></p>
<p>When <code>fmt.Printf</code> receives the <code>s</code> slice in our original example, it only knows to print up to 3 items, effectively ignoring what <em>does indeed exist in memory</em> at the additional position we just added!</p>
<p><strong>Gemini&rsquo;s response</strong></p>
<p>Earlier I noted that Gemini told me to pass the slice by ref as <code>*[]string</code>.</p>
<p>This was to guarantee that when the <code>append</code> function was called inside the function, that it would modify <em>the same slice</em>. It would do the same memory manipulation as before, but would <em>also</em> update the <code>len</code> and <code>cap</code> struct attributes of the same slice, too.</p>
<p>More can be said about why this isn&rsquo;t the best idea (more allocations to the heap, confusing function signatures), so I rewrote my code to avoid this whole situation to begin with. But for completeness, if you were to make this change to the above contrived code:</p>
<pre tabindex="0"><code>func main() {
	var s = []string{&#34;something&#34;, &#34;goes&#34;, &#34;here&#34;}
	fmt.Printf(&#34;original: %s\n&#34;, s)
	modSlice(&amp;s) // note the reference
	fmt.Printf(&#34;after return: %s\n&#34;, s)
}

func modSlice(s *[]string) {
	fmt.Printf(&#34;inside func orig: %s\n&#34;, s)
	*s = append(*s, &#34;another&#34;) // both refs
	fmt.Printf(&#34;inside func modified: %s\n&#34;, s)
}
</code></pre><p>And then run it, you would see:</p>
<pre tabindex="0"><code>original: [something goes here]
inside func orig: &amp;[something goes here]
inside func modified: &amp;[something goes here another]
after return: [something goes here another]
</code></pre><p>(see <a href="https://go.dev/play/p/-Q_4G2dGhuo"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Go Playground link</a>)</p>
]]></content>
        </item>
        
        <item>
            <title>Azure (currently) runs this site</title>
            <link>https://www.nilpointer.blog/posts/2026/02/azure-currently-runs-this-site/</link>
            <pubDate>Fri, 20 Feb 2026 17:36:02 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/02/azure-currently-runs-this-site/</guid>
            <description>&lt;p&gt;In the world of cloud providers, there are generally three big ones - Google Cloud Platform (GCP), Microsoft Azure, and, of course, Amazon Web Services (AWS). AWS has by far the lion&amp;rsquo;s share of the market, but mainly because they were the first to said market.&lt;/p&gt;
&lt;p&gt;Each have enabled all kinds of development from small to medium companies so that they don&amp;rsquo;t have to set up a bunch of servers, letting them sit idle most of the time.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In the world of cloud providers, there are generally three big ones - Google Cloud Platform (GCP), Microsoft Azure, and, of course, Amazon Web Services (AWS). AWS has by far the lion&rsquo;s share of the market, but mainly because they were the first to said market.</p>
<p>Each have enabled all kinds of development from small to medium companies so that they don&rsquo;t have to set up a bunch of servers, letting them sit idle most of the time.</p>
<p>Much better than having Bob accidentally knock the power button on your production box under his desk, then lock the door and head home for the weekend.</p>
<p>That being said&hellip;</p>
<p><strong>This site is hosted on Azure.</strong></p>
<p>There has been a long standing program through LinkedIn where employees get a credit each month if they set up and use Azure. So that&rsquo;s what I&rsquo;ve been using to host this site and my other one, <a href="https://www.bicyclewatercooler.com"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>bicyclewatercooler.com</a>.</p>
<p>The short of it is this:</p>
<ul>
<li>I write blog content in the form of markdown (.md) files.</li>
<li>I compile them locally with Hugo.
<ul>
<li>I do this via <code>hugo --baseURL https://www.nilpointer.blog/</code> as it&rsquo;s easier to keep <code>localhost</code> in my <code>hugo.toml</code> during development.</li>
</ul>
</li>
<li>I upload the generated <code>public/</code> directory to my configured Azure blob store.</li>
<li>Sometimes I need to purge the CDN / FrontDoor configuration so that the new assets I upload are reflected to the public.</li>
</ul>
<p><strong>VSCode + Extensions works well for this.</strong></p>
<p>With a few plugins, I can easily upload the generated content directly to my static website storage. There are two extensions that are needed for this (at least I <em>think</em> both are needed):</p>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureresourcegroups"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Azure Resources</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurestorage"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Azure Storage</a></li>
</ul>
<p>The only &ldquo;gotcha&rdquo; here is that the plugin operation that uploads your files to your storage has to <em>delete everything and reupload everything</em>. Not a big deal for a site that can manage plenty of downtime (<em>&ldquo;it&rsquo;s just a website!&rdquo;</em>), but it gets annoying after a while. There&rsquo;s a way to do this via an <code>rsync</code> like operation, but I&rsquo;ll save that for another post.</p>
<p><strong>Overall, this all works quite well.</strong></p>
<p>Mainly because you don&rsquo;t have to set up any kind of web server to serve your content. You can just serve static contents via Microsoft&rsquo;s extensive CDN.</p>
<p>With small, non-dynamic content, it&rsquo;s quite efficient and basically costs pennies to serve each month.</p>
<p>If I ever have to move to a new provider, I&rsquo;m sure it won&rsquo;t be too difficult to start up something similar in one of the other providers!</p>
]]></content>
        </item>
        
        <item>
            <title>&#34;Clever&#34; Has No Place in CI</title>
            <link>https://www.nilpointer.blog/posts/2026/02/clever-has-no-place-in-ci/</link>
            <pubDate>Thu, 19 Feb 2026 20:18:15 -0800</pubDate>
            
            <guid>https://www.nilpointer.blog/posts/2026/02/clever-has-no-place-in-ci/</guid>
            <description>&lt;p&gt;I wanted to share this separately on my site because, well, it made quite the impression over on &lt;a href=&#34;https://www.linkedin.com/feed/update/urn:li:activity:7406476989181628416&#34;
  
  
    target=&#34;_blank&#34; 
    rel=&#34;noopener noreferrer&#34;
  
&gt;LinkedIn&lt;/a&gt;, where I initially posted it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hot take: the code used in infrastructure engineering shouldn&amp;rsquo;t be complicated, nor should it be clever. It actually should be rather dumb.&lt;/p&gt;
&lt;p&gt;If something goes wrong, you want it to be with the code you&amp;rsquo;re trying to build or ship, not an unexpected effect of an overly complex CI system.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I wanted to share this separately on my site because, well, it made quite the impression over on <a href="https://www.linkedin.com/feed/update/urn:li:activity:7406476989181628416"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>LinkedIn</a>, where I initially posted it.</p>
<blockquote>
<p>Hot take: the code used in infrastructure engineering shouldn&rsquo;t be complicated, nor should it be clever. It actually should be rather dumb.</p>
<p>If something goes wrong, you want it to be with the code you&rsquo;re trying to build or ship, not an unexpected effect of an overly complex CI system.</p>
<p>Delegate to native tooling everywhere you can. Don&rsquo;t be clever. Be clever elsewhere and save yourself the maintenance effort.</p>
</blockquote>
<p>I got a bunch of comments on the topic, so I guess labeling it a &ldquo;hot take&rdquo; was apropos.</p>
<p><strong>Why is this Controversial?</strong></p>
<p>In my experience, CI is quite an abstract topic, even for software engineers. But without proper guardrails, good intentions can go awry and get out of control <em>quickly</em>. Especially when you have distributed actions across multiple Github Actions configuration files and/or CLIs.</p>
<p>Some good takeaways that I liked:</p>
<blockquote>
<p>&ldquo;Most things can be solved in a simple way.&rdquo;</p>
</blockquote>
<p><a href="https://en.wikipedia.org/wiki/Occam%27s_razor"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Occam&rsquo;s Razor</a> approves this message, as do I.</p>
<blockquote>
<p>Kernighan&rsquo;s Law &ldquo;Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.&rdquo;</p>
</blockquote>
<p>Absolutely love <a href="https://www.laws-of-software.com/laws/kernighan/"
  
  
    target="_blank" 
    rel="noopener noreferrer"
  
>Kernighan&rsquo;s Law</a>. Debugging in inherently <em>hard</em>. Doing it in a highly customized and &ldquo;environment-assuming&rdquo; environment that <em>intentionally</em> isn&rsquo;t set up like your dev box is even <em>harder</em>.</p>
<blockquote>
<p>What I keep seeing with teams is that every time infra code gets “smart”, it’s usually compensating for missing decisions upstream. Then six months later no one knows why the pipeline behaves the way it does.</p>
</blockquote>
<p>Couple this with &ldquo;the road to hell is paved with good intentions&rdquo; and you have a recipe for all the custom infrastructure that keeps DevX engineers in business.</p>
<p><strong>Doing the Digital Janitor Work</strong></p>
<p>I recently had to sort out some GHA actions (is that redundant?) to fix some compatibility tests across some Go modules. It was initially configured in one way that never quite worked - until another engineer implemented a command-line <code>find | xargs</code> command to locate N other <code>go.mod</code> files.</p>
<p>Super abstract. But it DID work. One-liners are fun!</p>
<p>Diving into the issue, I found that in a previous setup step, we were zipping up some source files in a way that included their full, absolute paths. This meant that when extracting them in the other job, we suddenly had a bunch of duplicative paths that looked <em>an awful lot</em> like the previous path it was all starting from. Gross.</p>
<p>No one had bad intentions here, mind you, and I could have just continued dealing with it on my end. But I decided to fix the issue upstream, sorting out the zip action to better handle a shared Github runner.</p>
<p>I didn&rsquo;t need to, but leaving it in that state and then continuing to do my job would have made it far more difficult to reason about later.</p>
<p><strong>These kinds of things happen in software engineering.</strong></p>
<p>They just do. And I imagine that AI may make this worse.</p>
<p>But can we all agree that simpler, &ldquo;dumb&rdquo; CI is better when it comes to building and deploying code?</p>
]]></content>
        </item>
        
    </channel>
</rss>
