<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Go on Tachera W Sasi, Backend &amp; Infra Engineer (Go, Self-Hosted, Observability)</title><link>https://tacherasasi.github.io/tags/go/</link><description>Recent content in Go on Tachera W Sasi, Backend &amp; Infra Engineer (Go, Self-Hosted, Observability)</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Mon, 04 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tacherasasi.github.io/tags/go/index.xml" rel="self" type="application/rss+xml"/><item><title>Why io.Copy Is One of Go's Most Powerful Functions</title><link>https://tacherasasi.github.io/posts/why-io-copy-is-one-of-gos-most-powerful-functions-9222940b2358/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/why-io-copy-is-one-of-gos-most-powerful-functions-9222940b2358/</guid><description>&lt;h2 id="tags-design-pattern-in-golang-io"&gt;&amp;mdash;2&amp;quot;
tags: [&amp;ldquo;design-pattern-in-golang&amp;rdquo;, &amp;ldquo;io&amp;rdquo;]&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*Nfqq3EkbvVg74Hi1Avbo3A.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Most Go developers use io.Copy() early in their journey.&lt;/p&gt;
&lt;p&gt;Usually for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;copying files&lt;/li&gt;
&lt;li&gt;downloading HTTP responses&lt;/li&gt;
&lt;li&gt;writing buffers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But many never realize this tiny function is sitting at the center of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;proxies&lt;/li&gt;
&lt;li&gt;reverse proxies&lt;/li&gt;
&lt;li&gt;SSH tunnels&lt;/li&gt;
&lt;li&gt;database gateways&lt;/li&gt;
&lt;li&gt;streaming systems&lt;/li&gt;
&lt;li&gt;TCP forwarders&lt;/li&gt;
&lt;li&gt;container runtimes&lt;/li&gt;
&lt;li&gt;load balancers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;is deceptively powerful.&lt;/p&gt;
&lt;p&gt;Especially when you realize:&lt;em&gt;in Go, sockets are just streams.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And streams are just:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;io.Reader&lt;/li&gt;
&lt;li&gt;io.Writer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once that clicks, networking becomes MUCH simpler.&lt;/p&gt;
&lt;h3 id="the-beautiful-simplicity"&gt;The Beautiful Simplicity&lt;/h3&gt;
&lt;p&gt;Consider this tiny PostgreSQL TCP proxy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;io&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;net&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LISTEN_ADDR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;:5433&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PG_ADDR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;localhost:5432&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LISTEN_ADDR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PG_ADDR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;No packet parsing.&lt;br&gt;
No protocol handling.&lt;br&gt;
No PostgreSQL driver.&lt;/p&gt;
&lt;p&gt;Yet this already works as a real TCP proxy.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because TCP connections in Go implement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;io.Reader&lt;/li&gt;
&lt;li&gt;io.Writer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can read from them&lt;/li&gt;
&lt;li&gt;write to them&lt;/li&gt;
&lt;li&gt;stream data between them&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="understanding-the-core-idea"&gt;Understanding the Core Idea&lt;/h3&gt;
&lt;p&gt;This line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;means:&lt;em&gt;continuously read bytes from&lt;/em&gt; &lt;em&gt;client and write them into&lt;/em&gt; &lt;em&gt;server&lt;/em&gt; And this line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;means:&lt;em&gt;continuously read bytes from&lt;/em&gt; &lt;em&gt;server and write them into&lt;/em&gt; &lt;em&gt;client&lt;/em&gt; Together they create:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bidirectional communication&lt;/li&gt;
&lt;li&gt;full duplex streaming&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="full-duplex-communication"&gt;Full Duplex Communication&lt;/h3&gt;
&lt;p&gt;Most network protocols are two-way conversations.&lt;/p&gt;
&lt;p&gt;The client sends data.&lt;br&gt;
The server responds.&lt;br&gt;
Both sides can speak independently.&lt;/p&gt;
&lt;p&gt;Your browser and a web server.&lt;br&gt;
SSH client and SSH server.&lt;br&gt;
PostgreSQL client and PostgreSQL server.&lt;/p&gt;
&lt;p&gt;All of them rely on duplex communication.&lt;/p&gt;
&lt;p&gt;Visually:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;client &amp;lt;=========&amp;gt; proxy &amp;lt;=========&amp;gt; server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Your proxy becomes a pipe.&lt;/p&gt;
&lt;p&gt;And io.Copy() is what keeps bytes flowing through that pipe.&lt;/p&gt;
&lt;h3 id="why-goroutines-matter-here"&gt;Why Goroutines Matter Here&lt;/h3&gt;
&lt;p&gt;This part is critical:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Without the goroutine, communication would block.&lt;/p&gt;
&lt;p&gt;You need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one stream for client -&amp;gt; server&lt;/li&gt;
&lt;li&gt;another stream for server -&amp;gt; client&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;simultaneously.&lt;/p&gt;
&lt;p&gt;That’s what enables real-time two-way communication.&lt;/p&gt;
&lt;p&gt;The goroutine handles one direction.&lt;br&gt;
The main goroutine handles the other.&lt;/p&gt;
&lt;p&gt;Tiny code.&lt;br&gt;
Massive capability.&lt;/p&gt;
&lt;h3 id="sockets-are-just-streams"&gt;Sockets Are Just Streams&lt;/h3&gt;
&lt;p&gt;This is one of the most important concepts in systems programming.&lt;/p&gt;
&lt;p&gt;In Go:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Conn&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A socket behaves like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a file&lt;/li&gt;
&lt;li&gt;a buffer&lt;/li&gt;
&lt;li&gt;stdin/stdout&lt;/li&gt;
&lt;li&gt;an HTTP body&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything becomes composable through interfaces.&lt;/p&gt;
&lt;p&gt;This is why Go networking feels elegant.&lt;/p&gt;
&lt;p&gt;You stop thinking:&lt;em&gt;“I’m working with TCP packets”&lt;/em&gt; and start thinking:&lt;em&gt;“I’m moving streams between readers and writers”&lt;/em&gt; That abstraction is incredibly powerful.&lt;/p&gt;
&lt;h3 id="memory-efficiency"&gt;Memory Efficiency&lt;/h3&gt;
&lt;p&gt;Another reason io.Copy() is excellent:&lt;br&gt;
it streams data incrementally.&lt;/p&gt;
&lt;p&gt;It does NOT:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;load everything into memory&lt;/li&gt;
&lt;li&gt;allocate giant buffers&lt;/li&gt;
&lt;li&gt;wait for full payloads&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Internally, it uses reusable buffers and streams chunks continuously.&lt;/p&gt;
&lt;p&gt;This makes it ideal for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;large files&lt;/li&gt;
&lt;li&gt;long-lived connections&lt;/li&gt;
&lt;li&gt;proxies&lt;/li&gt;
&lt;li&gt;streaming systems&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even multi-GB transfers remain memory efficient.&lt;/p&gt;
&lt;h3 id="this-pattern-exists-everywhere"&gt;This Pattern Exists Everywhere&lt;/h3&gt;
&lt;p&gt;Once you notice it, you’ll see this pattern everywhere.&lt;/p&gt;
&lt;h3 id="reverse-proxies"&gt;Reverse Proxies&lt;/h3&gt;
&lt;p&gt;Forward HTTP traffic between clients and servers.&lt;/p&gt;
&lt;h3 id="ssh-tunnels"&gt;SSH Tunnels&lt;/h3&gt;
&lt;p&gt;Forward encrypted streams between hosts.&lt;/p&gt;
&lt;h3 id="database-proxies"&gt;Database Proxies&lt;/h3&gt;
&lt;p&gt;Like PgBouncer or custom DB gateways.&lt;/p&gt;
&lt;h3 id="vpns"&gt;VPNs&lt;/h3&gt;
&lt;p&gt;Moving packets through encrypted streams.&lt;/p&gt;
&lt;h3 id="load-balancers"&gt;Load Balancers&lt;/h3&gt;
&lt;p&gt;Forwarding connections across machines.&lt;/p&gt;
&lt;h3 id="container-systems"&gt;Container Systems&lt;/h3&gt;
&lt;p&gt;Streaming logs, exec sessions, and IPC.&lt;/p&gt;
&lt;p&gt;A huge amount of infrastructure software is basically:&lt;em&gt;carefully managed stream forwarding.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="the-hidden-superpower-of-go"&gt;The Hidden Superpower of Go&lt;/h3&gt;
&lt;p&gt;Go’s standard library encourages composition over complexity.&lt;/p&gt;
&lt;p&gt;Instead of giant networking frameworks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;readers&lt;/li&gt;
&lt;li&gt;writers&lt;/li&gt;
&lt;li&gt;interfaces&lt;/li&gt;
&lt;li&gt;goroutines&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;become enough to build real infrastructure.&lt;/p&gt;
&lt;p&gt;This is why Go became dominant in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cloud infrastructure&lt;/li&gt;
&lt;li&gt;Kubernetes tooling&lt;/li&gt;
&lt;li&gt;networking software&lt;/li&gt;
&lt;li&gt;distributed systems&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The primitives are small.&lt;br&gt;
But they scale extremely far.&lt;/p&gt;
&lt;h3 id="final-thoughts"&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;io.Copy() looks boring at first.&lt;/p&gt;
&lt;p&gt;But behind that tiny function is a core systems programming idea:&lt;em&gt;move streams between abstractions efficiently&lt;/em&gt; Once you deeply understand that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;proxies make sense&lt;/li&gt;
&lt;li&gt;tunnels make sense&lt;/li&gt;
&lt;li&gt;reverse proxies make sense&lt;/li&gt;
&lt;li&gt;networking becomes less magical&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And you start realizing:&lt;br&gt;
some of the most powerful infrastructure on the internet is built from surprisingly small primitives.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=9222940b2358" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@tacherasasi/why-io-copy-is-one-of-gos-most-powerful-functions-9222940b2358?source=rss-9a41d7ec29fb------2"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>The One-Word Bug That Broke Our Worker for 6 Days</title><link>https://tacherasasi.github.io/posts/the-one-word-bug-that-broke-our-worker-for-6-days-9cadd534ee77/</link><pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/the-one-word-bug-that-broke-our-worker-for-6-days-9cadd534ee77/</guid><description>&lt;h2 id="tags-devops-docker-golang"&gt;&amp;mdash;2&amp;quot;
tags: [&amp;ldquo;devops&amp;rdquo;, &amp;ldquo;docker&amp;rdquo;, &amp;ldquo;golang&amp;rdquo;]&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*CtVFn53iNGx3lQ2MEe3JKw.png" alt=""&gt;&lt;/p&gt;
&lt;h3 id="for-six-days-our-system-was-lying-to-us"&gt;For six days, our system was lying to us&lt;/h3&gt;
&lt;p&gt;Not crashing. Not failing loudly. Just quietly pretending everything was fine while doing absolutely nothing.&lt;/p&gt;
&lt;p&gt;And the cause?&lt;/p&gt;
&lt;p&gt;One wrong word in a shell script.&lt;/p&gt;
&lt;h3 id="everything-looked-fine"&gt;Everything Looked Fine&lt;/h3&gt;
&lt;p&gt;We run a pretty standard setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Go API (core)&lt;/li&gt;
&lt;li&gt;A background worker (minion)&lt;/li&gt;
&lt;li&gt;Redis for queues&lt;/li&gt;
&lt;li&gt;Docker Compose holding it all together&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We recently moved bulk SMS processing into the worker using Asynq. Clean separation, better scalability, the usual win.&lt;/p&gt;
&lt;p&gt;We deployed.&lt;/p&gt;
&lt;p&gt;The API responded perfectly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;200 OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#34;bulk SMS queued successfully
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Except… no SMS was ever sent.&lt;/p&gt;
&lt;h3 id="the-worker-was-dead-silent"&gt;The Worker Was Dead Silent&lt;/h3&gt;
&lt;p&gt;First instinct: check the worker logs.&lt;/p&gt;
&lt;p&gt;Nothing.&lt;/p&gt;
&lt;p&gt;No errors. No panics. No trace of the task ever being picked up.&lt;/p&gt;
&lt;p&gt;Just one quiet warning after retries were exhausted.&lt;/p&gt;
&lt;p&gt;Meanwhile, everything else worked:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;login alerts ✅&lt;/li&gt;
&lt;li&gt;email notifications ✅&lt;/li&gt;
&lt;li&gt;scheduled jobs ✅&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only bulk SMS failed.&lt;/p&gt;
&lt;p&gt;At this point, we did what engineers always do when the signal is weak:&lt;/p&gt;
&lt;p&gt;We guessed.&lt;/p&gt;
&lt;h3 id="we-investigated-the-wrong-things"&gt;We Investigated the Wrong Things&lt;/h3&gt;
&lt;p&gt;We went deep into the stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Suspected Redis corruption&lt;/li&gt;
&lt;li&gt;Questioned queue priorities&lt;/li&gt;
&lt;li&gt;Looked for serialization bugs&lt;/li&gt;
&lt;li&gt;Added panic recovery middleware&lt;/li&gt;
&lt;li&gt;Logged full payloads&lt;/li&gt;
&lt;li&gt;Added startup diagnostics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We deployed again.&lt;/p&gt;
&lt;p&gt;Still nothing.&lt;/p&gt;
&lt;p&gt;And this is where most people waste days. We almost did.&lt;/p&gt;
&lt;p&gt;Because we were debugging behavior… not reality.&lt;/p&gt;
&lt;h3 id="the-subtle-clue"&gt;The Subtle Clue&lt;/h3&gt;
&lt;p&gt;There was one tiny inconsistency.&lt;/p&gt;
&lt;p&gt;Our worker used to log:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Worker started
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We had changed it to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Worker started (server + scheduler running)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After deploy, the logs still showed the old message.&lt;/p&gt;
&lt;p&gt;That should have been the end of the investigation right there.&lt;/p&gt;
&lt;p&gt;Our code wasn’t running.&lt;/p&gt;
&lt;h3 id="the-real-problem"&gt;The Real Problem&lt;/h3&gt;
&lt;p&gt;The issue wasn’t in Go.&lt;br&gt;
It wasn’t in Redis.&lt;br&gt;
It wasn’t in queues.&lt;/p&gt;
&lt;p&gt;It was in this script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;docker compose build --no-cache api &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;docker compose up -d --remove-orphans&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this config:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;core&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;minion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Look closely.&lt;/p&gt;
&lt;p&gt;We were building api.&lt;/p&gt;
&lt;p&gt;There is no api.&lt;/p&gt;
&lt;p&gt;Docker didn’t complain. It didn’t warn. It didn’t fail.&lt;/p&gt;
&lt;p&gt;It just did nothing.&lt;/p&gt;
&lt;p&gt;And then docker compose up happily started containers using old cached images.&lt;/p&gt;
&lt;p&gt;So for six days:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The API sometimes rebuilt&lt;/li&gt;
&lt;li&gt;The worker never rebuilt&lt;/li&gt;
&lt;li&gt;The system kept deploying successfully&lt;/li&gt;
&lt;li&gt;Our fixes never actually shipped&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We were debugging code that wasn’t even running.&lt;/p&gt;
&lt;h3 id="the-fix-was-embarrassing"&gt;The Fix Was Embarrassing&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- docker compose build --no-cache api
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+ docker compose build --no-cache core minion
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;Two words instead of one.&lt;/p&gt;
&lt;p&gt;We deployed again.&lt;/p&gt;
&lt;p&gt;Immediately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New startup logs appeared&lt;/li&gt;
&lt;li&gt;Bulk SMS tasks were picked up&lt;/li&gt;
&lt;li&gt;Jobs executed in milliseconds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;System fixed.&lt;/p&gt;
&lt;h3 id="what-this-actually-teaches"&gt;What This Actually Teaches&lt;/h3&gt;
&lt;h3 id="1-if-your-fix-doesnt-change-behavior-assume-it-never-deployed"&gt;1. If your fix doesn’t change behavior, assume it never deployed&lt;/h3&gt;
&lt;p&gt;Stop overthinking.&lt;/p&gt;
&lt;p&gt;Before touching Redis, queues, or concurrency… ask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Is my new binary even running?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you skip this, you deserve the hours you’re about to lose.&lt;/p&gt;
&lt;h3 id="2-silent-failures-are-worse-than-crashes"&gt;2. Silent failures are worse than crashes&lt;/h3&gt;
&lt;p&gt;Crashes force action.&lt;/p&gt;
&lt;p&gt;Silent no-ops pass CI, pass deploys, and waste days.&lt;/p&gt;
&lt;p&gt;docker compose build should not succeed when the service doesn’t exist.&lt;/p&gt;
&lt;p&gt;But it does.&lt;/p&gt;
&lt;p&gt;So you have to defend against that yourself.&lt;/p&gt;
&lt;h3 id="3-your-deployment-pipeline-is-part-of-your-codebase"&gt;3. Your deployment pipeline is part of your codebase&lt;/h3&gt;
&lt;p&gt;People treat scripts like they’re disposable.&lt;/p&gt;
&lt;p&gt;They’re not.&lt;/p&gt;
&lt;p&gt;That one-line deploy.sh caused more damage than any bug in the Go code.&lt;/p&gt;
&lt;p&gt;Review it. Test it. Break it intentionally.&lt;/p&gt;
&lt;h3 id="4-logging-is-not-optional-for-workers"&gt;4. Logging is not optional for workers&lt;/h3&gt;
&lt;p&gt;If your worker can fail quietly, it will.&lt;/p&gt;
&lt;p&gt;You need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;startup fingerprints (commit hash, build time)&lt;/li&gt;
&lt;li&gt;task-level logs&lt;/li&gt;
&lt;li&gt;explicit success/failure visibility&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Otherwise, you’re blind.&lt;/p&gt;
&lt;h3 id="5-rename-things-properly-or-dont-rename-them-at-all"&gt;5. Rename things properly or don’t rename them at all&lt;/h3&gt;
&lt;p&gt;api became core.&lt;/p&gt;
&lt;p&gt;The script didn’t.&lt;/p&gt;
&lt;p&gt;That mismatch cost six days.&lt;/p&gt;
&lt;p&gt;Refactors don’t end when the code compiles.&lt;/p&gt;
&lt;h3 id="the-real-lesson"&gt;The Real Lesson&lt;/h3&gt;
&lt;p&gt;This wasn’t a debugging failure.&lt;/p&gt;
&lt;p&gt;It was a discipline failure.&lt;/p&gt;
&lt;p&gt;We trusted the pipeline without verifying it.&lt;br&gt;
We chased complex explanations before ruling out simple ones.&lt;br&gt;
We debugged symptoms instead of checking fundamentals.&lt;/p&gt;
&lt;p&gt;And that’s how a one-word mistake beats experienced engineers.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=9cadd534ee77" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@tacherasasi/the-one-word-bug-that-broke-our-worker-for-6-days-9cadd534ee77?source=rss-9a41d7ec29fb------2"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>After Using 10+ Languages, Go Is Still My Favorite for Backend Systems</title><link>https://tacherasasi.github.io/posts/after-using-10-languages-go-is-still-my-favorite-for-backend-systems-9148165fa6dc/</link><pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/after-using-10-languages-go-is-still-my-favorite-for-backend-systems-9148165fa6dc/</guid><description>&lt;h2 id="tags-programming-languages-golang-backend-development-scalable-applications"&gt;&amp;mdash;2&amp;quot;
tags: [&amp;ldquo;programming-languages&amp;rdquo;, &amp;ldquo;golang&amp;rdquo;, &amp;ldquo;backend-development&amp;rdquo;, &amp;ldquo;scalable-applications&amp;rdquo;]&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*AYRooeSNqJ_pq64abMMcsg.png" alt=""&gt;Golang Fanatic Here&lt;/p&gt;
&lt;h3 id="introduction"&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Over the past few years I’ve experimented with a lot of programming languages.&lt;/p&gt;
&lt;p&gt;PHP, JavaScript, TypeScript, Python, Java, Go, Rust, C, C++, and several others. I enjoy exploring languages because every language teaches you something about how software should be built.&lt;/p&gt;
&lt;p&gt;Some languages are great for rapid prototyping.&lt;br&gt;
Some shine in performance-critical environments.&lt;br&gt;
Others dominate specific ecosystems.&lt;/p&gt;
&lt;p&gt;But when it comes to &lt;strong&gt;building backend systems&lt;/strong&gt; , I keep coming back to Go.&lt;/p&gt;
&lt;p&gt;Not because it’s the most advanced language.&lt;br&gt;
Not because it has the most features.&lt;/p&gt;
&lt;p&gt;Actually, the opposite.&lt;/p&gt;
&lt;p&gt;Go wins because it focuses on &lt;strong&gt;simplicity, performance, and practical tooling&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="1-simplicity"&gt;1. Simplicity&lt;/h3&gt;
&lt;p&gt;One of Go’s biggest strengths is that the language is intentionally small.&lt;/p&gt;
&lt;p&gt;There are no complex generics systems like in C++.&lt;br&gt;
No endless abstractions like in some object-oriented languages.&lt;br&gt;
No massive meta-programming layers.&lt;/p&gt;
&lt;p&gt;At first this feels limiting.&lt;/p&gt;
&lt;p&gt;But after building real systems, you realize something important: &lt;strong&gt;most backend problems don’t need complex language features.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;They need:&lt;/p&gt;
&lt;p&gt;• readable code&lt;br&gt;
• predictable behavior&lt;br&gt;
• easy maintenance&lt;/p&gt;
&lt;p&gt;Go enforces a style that makes codebases easier to understand, especially in teams.&lt;/p&gt;
&lt;p&gt;There’s a reason many companies adopt Go for large services: &lt;strong&gt;engineers can read each other’s code easily.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="2-concurrency-that-actually-feels-simple"&gt;2. Concurrency That Actually Feels Simple&lt;/h3&gt;
&lt;p&gt;Modern backend systems are inherently concurrent.&lt;/p&gt;
&lt;p&gt;You’re dealing with:&lt;/p&gt;
&lt;p&gt;• multiple HTTP requests&lt;br&gt;
• database operations&lt;br&gt;
• background jobs&lt;br&gt;
• external services&lt;br&gt;
• queues and event streams&lt;/p&gt;
&lt;p&gt;In many languages, concurrency means dealing with complex thread models, locks, or async frameworks.&lt;/p&gt;
&lt;p&gt;Go solves this elegantly with &lt;strong&gt;goroutines and channels&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Starting a concurrent task is as simple as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go processRequest()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That single keyword launches a lightweight concurrent task.&lt;/p&gt;
&lt;p&gt;Behind the scenes Go handles scheduling, making it incredibly efficient.&lt;/p&gt;
&lt;p&gt;This model makes it easy to build systems like:&lt;/p&gt;
&lt;p&gt;• API servers&lt;br&gt;
• job processors&lt;br&gt;
• streaming services&lt;br&gt;
• high-throughput workers&lt;/p&gt;
&lt;p&gt;without drowning in concurrency complexity.&lt;/p&gt;
&lt;h3 id="3-fast-compilation-changes-your-workflow"&gt;3. Fast Compilation Changes Your Workflow&lt;/h3&gt;
&lt;p&gt;This is something many developers underestimate.&lt;/p&gt;
&lt;p&gt;Go compiles &lt;strong&gt;extremely fast&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That means your development loop becomes:&lt;/p&gt;
&lt;p&gt;edit → build → run → repeat&lt;/p&gt;
&lt;p&gt;And the build step takes seconds.&lt;/p&gt;
&lt;p&gt;Compare that to some compiled languages where builds can take minutes on larger projects.&lt;/p&gt;
&lt;p&gt;Fast compilation encourages experimentation and rapid iteration.&lt;/p&gt;
&lt;p&gt;It also makes Go great for:&lt;/p&gt;
&lt;p&gt;• CLI tools&lt;br&gt;
• microservices&lt;br&gt;
• infrastructure tooling&lt;/p&gt;
&lt;h3 id="4-tooling-that-just-works"&gt;4. Tooling That Just Works&lt;/h3&gt;
&lt;p&gt;One thing Go gets very right is its &lt;strong&gt;tooling philosophy&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Many languages rely heavily on external tools and third-party plugins.&lt;/p&gt;
&lt;p&gt;Go ships with most of what you need built in:&lt;/p&gt;
&lt;p&gt;• gofmt for automatic code formatting&lt;br&gt;
• go test for testing&lt;br&gt;
• go build for compiling&lt;br&gt;
• go mod for dependency management&lt;br&gt;
• profiling and benchmarking tools&lt;/p&gt;
&lt;p&gt;Because these tools are standardized, Go projects tend to look and behave consistently.&lt;/p&gt;
&lt;p&gt;You spend less time configuring tools and more time building software.&lt;/p&gt;
&lt;h3 id="5-why-go-is-excellent-for-apis"&gt;5. Why Go Is Excellent for APIs&lt;/h3&gt;
&lt;p&gt;Go has become one of the most popular languages for building APIs, and for good reason.&lt;/p&gt;
&lt;p&gt;It hits the sweet spot between &lt;strong&gt;performance and simplicity&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The standard library alone gives you a powerful HTTP server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;http.HandleFunc(&amp;#34;/api&amp;#34;, handler)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;http.ListenAndServe(&amp;#34;:8080&amp;#34;, nil)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No heavy frameworks required.&lt;/p&gt;
&lt;p&gt;This makes Go ideal for:&lt;/p&gt;
&lt;p&gt;• REST APIs&lt;br&gt;
• microservices&lt;br&gt;
• internal tooling services&lt;br&gt;
• high-performance backends&lt;/p&gt;
&lt;p&gt;It’s also incredibly easy to deploy because Go compiles to a &lt;strong&gt;single static binary&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;No runtime dependencies.&lt;br&gt;
No complicated deployments.&lt;/p&gt;
&lt;p&gt;Just ship the binary and run it.&lt;/p&gt;
&lt;h3 id="final-thoughts"&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Go isn’t perfect.&lt;/p&gt;
&lt;p&gt;It’s not the most expressive language.&lt;br&gt;
It’s not the best choice for everything.&lt;br&gt;
And sometimes it feels intentionally minimal.&lt;/p&gt;
&lt;p&gt;But for backend systems, that minimalism becomes a strength.&lt;/p&gt;
&lt;p&gt;You get:&lt;/p&gt;
&lt;p&gt;• simple code&lt;br&gt;
• strong performance&lt;br&gt;
• built-in tooling&lt;br&gt;
• great concurrency&lt;/p&gt;
&lt;p&gt;After using more than 10 programming languages, Go remains the language I trust the most when building backend systems.&lt;/p&gt;
&lt;p&gt;Not because it’s flashy.&lt;/p&gt;
&lt;p&gt;Because it &lt;strong&gt;gets the job done reliably.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=9148165fa6dc" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@tacherasasi/after-using-10-languages-go-is-still-my-favorite-for-backend-systems-9148165fa6dc?source=rss-9a41d7ec29fb------2"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Escape Analysis in Go: Where Does Your Data Actually Live?</title><link>https://tacherasasi.github.io/posts/escape-analysis-in-go-where-does-your-data-actually-live-d1a0430003d8/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/escape-analysis-in-go-where-does-your-data-actually-live-d1a0430003d8/</guid><description>&lt;h2 id="2"&gt;&amp;mdash;2&amp;quot;&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/300/1*S0qxt-hT88hXkOFCj-nSBw.jpeg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If you’ve been writing Go for a while, you’ve probably heard the terms &lt;strong&gt;stack&lt;/strong&gt; and &lt;strong&gt;heap&lt;/strong&gt; tossed around. But have you ever wondered how Go decides where to put your variables? That’s exactly what &lt;strong&gt;escape analysis&lt;/strong&gt; is about and understanding it can help you write faster, more efficient Go code.&lt;/p&gt;
&lt;h3 id="stack-vs-heap-a-quick-refresher"&gt;Stack vs Heap: A Quick Refresher&lt;/h3&gt;
&lt;p&gt;Think of memory in two buckets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stack&lt;/strong&gt; Fast, automatically managed. When a function is called, variables are pushed onto the stack. When the function returns, they’re gone. No cleanup needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heap&lt;/strong&gt; Slower, but longer-lived. Variables here are managed by Go’s garbage collector (GC). They stick around as long as something is referencing them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The stack is cheap. The heap costs more allocating on the heap puts pressure on the GC, which can slow your program down.&lt;/p&gt;
&lt;h3 id="so-what-is-escape-analysis"&gt;So What Is Escape Analysis?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Escape analysis&lt;/strong&gt; is what the Go compiler does at compile time to figure out: &lt;em&gt;“Does this variable need to live on the heap, or can it stay on the stack?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If a variable is only used inside the function it was created in, it can safely live on the stack. But if a variable “escapes” meaning something outside the function needs to access it it has to be moved to the heap.&lt;/p&gt;
&lt;p&gt;The compiler does this automatically. You don’t have to think about it most of the time. But understanding when things escape can help you avoid unnecessary heap allocations.&lt;/p&gt;
&lt;h3 id="a-simple-example"&gt;A Simple Example&lt;/h3&gt;
&lt;p&gt;go&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, x is created and used inside main. It doesn&amp;rsquo;t escape anywhere. The compiler keeps it on the stack.&lt;/p&gt;
&lt;p&gt;Now consider this:&lt;/p&gt;
&lt;p&gt;go&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newNumber&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// we&amp;#39;re returning a pointer to x &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, we’re returning a &lt;strong&gt;pointer&lt;/strong&gt; to x. That means after newNumber returns, something outside the function is still holding a reference to x. The stack for newNumber is gone at that point so Go moves x to the heap. It has &lt;em&gt;escaped&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="how-to-see-whats-escaping"&gt;How to See What’s Escaping&lt;/h3&gt;
&lt;p&gt;Go gives you a handy compiler flag to inspect escape analysis decisions:&lt;/p&gt;
&lt;p&gt;bash&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go build -gcflags&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-m&amp;#34;&lt;/span&gt; ./...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You’ll see output like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./main.go:6:2: moved to heap: x
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./main.go:10:13: ... argument does not escape
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This tells you exactly which variables the compiler decided to allocate on the heap. It’s a great debugging tool when you’re trying to optimize performance.&lt;/p&gt;
&lt;h3 id="common-reasons-a-variable-escapes"&gt;Common Reasons a Variable Escapes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Returning a pointer to a local variable&lt;/strong&gt; As shown above if you return &amp;amp;x, x has to live on the heap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Storing a value in an interface&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;go&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you assign a concrete value to an interface, Go often needs to heap-allocate it because the interface value needs to carry around type information.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Sending to a channel or storing in a slice/map&lt;/strong&gt; If the runtime can’t prove the value won’t outlive the current scope, it plays it safe and puts it on the heap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Closures capturing variables&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;go&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;makeCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, count is captured by the closure returned from makeCounter. Since the closure can be called long after makeCounter returns, count escapes to the heap.&lt;/p&gt;
&lt;h3 id="does-this-matter-in-practice"&gt;Does This Matter in Practice?&lt;/h3&gt;
&lt;p&gt;For most applications, not really Go’s garbage collector is fast and well-tuned. But in performance-sensitive code (think: game loops, high-frequency trading, large-scale data pipelines), reducing heap allocations can make a meaningful difference.&lt;/p&gt;
&lt;p&gt;Here’s a rough guide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Don’t return pointers to small, short-lived values&lt;/strong&gt; if you don’t need to. Return by value instead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pre-allocate slices&lt;/strong&gt; when you know the size upfront (make([]int, 0, 100)) to avoid repeated heap allocations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use&lt;/strong&gt;**-gcflags= &amp;ldquo;-m&amp;rdquo; or benchmarks** to identify hot paths with lots of allocations before optimizing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="key-takeaways"&gt;Key Takeaways&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Go automatically decides whether variables live on the &lt;strong&gt;stack&lt;/strong&gt; (fast, temporary) or the &lt;strong&gt;heap&lt;/strong&gt; (slower, GC-managed).&lt;/li&gt;
&lt;li&gt;This decision is made by the compiler using &lt;strong&gt;escape analysis&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A variable “escapes” to the heap when it needs to outlive the function it was created in.&lt;/li&gt;
&lt;li&gt;You can inspect escape decisions with go build -gcflags=&amp;quot;-m&amp;quot;.&lt;/li&gt;
&lt;li&gt;Understanding escape analysis helps you write more allocation-efficient Go code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The beauty of Go is that you don’t &lt;em&gt;have&lt;/em&gt; to think about this most of the time. But when performance matters, escape analysis is one of those things that separates good Go code from great Go code. Happy coding! 🚀&lt;/p&gt;
&lt;p&gt;&lt;img src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=d1a0430003d8" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@tacherasasi/escape-analysis-in-go-where-does-your-data-actually-live-d1a0430003d8?source=rss-9a41d7ec29fb------2"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>I Think I Accidentally Built a Full-Stack Framework</title><link>https://tacherasasi.github.io/posts/i-think-i-accidentally-built-a-full-stack-framework/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/i-think-i-accidentally-built-a-full-stack-framework/</guid><description>&lt;p&gt;&lt;em&gt;Look, I just wanted to share some files.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="the-innocent-beginning"&gt;The Innocent Beginning&lt;/h2&gt;
&lt;p&gt;It started, as these things always do, with a simple problem: I needed to share files between my devices. &amp;ldquo;I&amp;rsquo;ll just write a quick Go server,&amp;rdquo; I said. &amp;ldquo;It&amp;rsquo;ll take an afternoon,&amp;rdquo; I said. &amp;ldquo;It&amp;rsquo;ll be like 200 lines of code,&amp;rdquo; I said.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reader, it was not 200 lines of code.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="what-is-beamdrop-actually"&gt;What Is BeamDrop, Actually?&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/ekilie/beamdrop"&gt;BeamDrop&lt;/a&gt; is a self-hosted file sharing server. You run one command, and boom you&amp;rsquo;ve got a beautiful web interface for uploading, downloading, moving, copying, renaming, and searching files. It&amp;rsquo;s like Dropbox, if Dropbox was a single binary and didn&amp;rsquo;t sell your data to train AI models. &lt;em&gt;(No offense, Dropbox. Actually, some offense.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the pitch: &lt;code&gt;beamdrop -dir /path/to/stuff -p mysecretpassword&lt;/code&gt; and you&amp;rsquo;re live. QR code on the terminal so your phone can connect. Password protection. Shareable links with expiry. WebSocket-powered real-time stats. An S3-compatible API so your CI/CD pipeline can talk to it.&lt;/p&gt;
&lt;p&gt;One binary. No Docker required. No Node.js runtime. No &amp;ldquo;please install these 47 dependencies.&amp;rdquo; Just a single executable that runs on macOS, Linux, and Windows.&lt;/p&gt;
&lt;h2 id="how-we-got-here"&gt;How We Got Here&lt;/h2&gt;
&lt;h3 id="stage-1-the-go-server-day-1"&gt;Stage 1: The Go Server (Day 1)&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;I&amp;rsquo;ll embed the frontend right into the Go binary using &lt;code&gt;embed.FS&lt;/code&gt;. Single binary deployment. I&amp;rsquo;m a genius.&amp;rdquo;&lt;/p&gt;
&lt;h3 id="stage-2-the-react-frontend-week-1"&gt;Stage 2: The React Frontend (Week 1)&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;It needs a UI. I&amp;rsquo;ll use React. And Vite. And TypeScript. And Tailwind. And shadcn/ui. For a file server. This is totally proportional.&amp;rdquo;&lt;/p&gt;
&lt;h3 id="stage-3-the-database-week-2"&gt;Stage 3: The Database (Week 2)&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;I need shareable links, so I need a database. SQLite is fine. I&amp;rsquo;ll use GORM. Oh wait, the SQLite driver needs CGO and that breaks cross-compilation. Let me swap to a pure-Go SQLite implementation so the binary stays fully static. This is still a simple file server.&amp;rdquo;&lt;/p&gt;
&lt;h3 id="stage-4-the-s3-api-week-3"&gt;Stage 4: The S3 API (Week 3)&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;What if it also spoke S3? Like, the actual S3 API. With buckets and objects and presigned URLs. For a file server that I built because I wanted to share a PDF with my phone.&amp;rdquo;&lt;/p&gt;
&lt;h3 id="stage-5-the-realization-now"&gt;Stage 5: The Realization (Now)&lt;/h3&gt;
&lt;p&gt;I stared at my codebase. A Go backend with embedded frontend assets. React with client-side routing. SPA fallback handling. SQLite with migrations. WebSocket connections. JWT authentication. API key management. CORS middleware. Security headers. TLS support. Cross-platform builds.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I built a framework.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t mean to. I was just solving problems one at a time. &amp;ldquo;Oh, the frontend routes break on refresh because Go doesn&amp;rsquo;t know about React Router&amp;rdquo; so I added SPA fallback. &amp;ldquo;Oh, &lt;code&gt;CGO_ENABLED=0&lt;/code&gt; breaks SQLite&amp;rdquo; so I swapped to a pure-Go driver. &amp;ldquo;Oh, I need auth&amp;rdquo; JWT and password hashing. Each fix was reasonable in isolation. Together, they formed a full-stack application framework that compiles to a zero-dependency binary.&lt;/p&gt;
&lt;h2 id="the-architecture-that-i-definitely-planned-from-the-start"&gt;The Architecture (That I Definitely Planned From The Start)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌──────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Single Binary │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ┌────────────────────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ React + Vite + TS │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ (embedded via Go) │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └────────┬───────────────┘ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ┌────────▼───────────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ Go HTTP Server │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ┌──────┐ ┌──────────┐ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │ SPA │ │ API │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │Fallbk│ │ Routes │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └──────┘ └──────────┘ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ┌──────┐ ┌──────────┐ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │ Auth │ │WebSocket │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └──────┘ └──────────┘ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └────────┬───────────────┘ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ┌────────▼───────────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ SQLite (pure Go) │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ No CGO required │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └────────────────────────┘ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└──────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;&amp;ldquo;Yes, this was the plan all along.&amp;rdquo;&lt;/em&gt; Me, lying.&lt;/p&gt;
&lt;h2 id="things-i-learned-along-the-way"&gt;Things I Learned Along The Way&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. SPA routing in embedded Go servers is a trap.&lt;/strong&gt;
If you serve a React app from Go and someone refreshes on &lt;code&gt;/shares&lt;/code&gt;, Go goes &amp;ldquo;I don&amp;rsquo;t have a file called &lt;code&gt;shares&lt;/code&gt;, here&amp;rsquo;s a 404.&amp;rdquo; The fix: if the requested path has no file extension, serve &lt;code&gt;index.html&lt;/code&gt; and let React Router figure it out. Simple in hindsight. Three hours of debugging in practice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. CGO is the final boss of Go cross-compilation.&lt;/strong&gt;
&lt;code&gt;GOOS=linux GOARCH=arm64 go build&lt;/code&gt; is beautiful until one of your dependencies whispers &amp;ldquo;I need C.&amp;rdquo; Swapping &lt;code&gt;mattn/go-sqlite3&lt;/code&gt; for &lt;code&gt;glebarez/sqlite&lt;/code&gt; (which uses &lt;code&gt;modernc.org/sqlite&lt;/code&gt;, a machine-translated C-to-Go SQLite) was the move. Zero CGO. Builds everywhere. The binary is bigger but my sanity is intact.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;embed.FS&lt;/code&gt; is criminally underrated.&lt;/strong&gt;
Go&amp;rsquo;s &lt;code&gt;//go:embed&lt;/code&gt; directive lets you bake your entire frontend build into the binary. No separate static file serving. No &amp;ldquo;make sure the &lt;code&gt;dist&lt;/code&gt; folder is next to the executable.&amp;rdquo; It just works. Ship one file. Run it. Done.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. You don&amp;rsquo;t need a framework to build a framework.&lt;/strong&gt;
BeamDrop uses no web framework. No Gin, no Echo, no Fiber. Just &lt;code&gt;net/http&lt;/code&gt; and a &lt;code&gt;ServeMux&lt;/code&gt;. The standard library is enough. I&amp;rsquo;ll die on this hill while manually parsing URL paths.&lt;/p&gt;
&lt;h2 id="is-it-actually-a-framework-though"&gt;Is It Actually a Framework Though?&lt;/h2&gt;
&lt;p&gt;Look, if it has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A backend server with routing and middleware ✅&lt;/li&gt;
&lt;li&gt;An embedded frontend with client-side routing ✅&lt;/li&gt;
&lt;li&gt;A database with ORM and migrations ✅&lt;/li&gt;
&lt;li&gt;Authentication (passwords, JWT, API keys) ✅&lt;/li&gt;
&lt;li&gt;Real-time communication (WebSockets) ✅&lt;/li&gt;
&lt;li&gt;An API layer (REST + S3-compatible) ✅&lt;/li&gt;
&lt;li&gt;Cross-platform single-binary builds ✅&lt;/li&gt;
&lt;li&gt;TLS support ✅&lt;/li&gt;
&lt;li&gt;CORS and security headers ✅&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;hellip;then at what point does &amp;ldquo;my file sharing app&amp;rdquo; become &amp;ldquo;a full-stack framework with a file sharing app built on top of it&amp;rdquo;?&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have an answer. I just have a very over-engineered way to send a PDF to my phone.&lt;/p&gt;
&lt;h2 id="try-it"&gt;Try It&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Install (macOS Apple Silicon)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -L https://github.com/ekilie/beamdrop/releases/latest/download/beamdrop-darwin-arm64.tar.gz &lt;span class="p"&gt;|&lt;/span&gt; sudo tar -C /usr/local/bin -xzf -
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Run&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;beamdrop -dir ~/Documents -p secretpassword
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it. One binary. Full-stack. No regrets. Okay, some regrets. But the QR code in the terminal is really cool.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;BeamDrop is open source at &lt;a href="https://github.com/ekilie/beamdrop"&gt;github.com/ekilie/beamdrop&lt;/a&gt;. Star it if you&amp;rsquo;ve ever accidentally over-engineered something. So, star it.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Why Wails Wins at IPC for Go Desktop Apps (and How It Stacks Up Against Tauri &amp; Electron)</title><link>https://tacherasasi.github.io/posts/why-wails-wins-at-ipc-for-go-desktop-apps-and-how-it-stacks-up-against-tauri-electron-5a00b202cf09/</link><pubDate>Wed, 27 Aug 2025 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/why-wails-wins-at-ipc-for-go-desktop-apps-and-how-it-stacks-up-against-tauri-electron-5a00b202cf09/</guid><description>&lt;h2 id="tags-golang-wails"&gt;&amp;mdash;2&amp;quot;
tags: [&amp;ldquo;golang&amp;rdquo;, &amp;ldquo;wails&amp;rdquo;]&lt;/h2&gt;
&lt;p&gt;So you’re building a desktop app with web tech, and you hit the classic problem: &lt;em&gt;how do I get my native backend to talk nicely with my frontend?&lt;/em&gt; That’s where IPC (Inter-Process Communication) comes in the secret sauce that makes your Go (or Rust, or Node) logic play well with your slick React/Vue/HTML UI.&lt;/p&gt;
&lt;p&gt;Here’s my hot take: &lt;strong&gt;Wails does IPC better than anyone else right now.&lt;/strong&gt;&lt;br&gt;
It doesn’t just “let things talk.” It &lt;em&gt;represents&lt;/em&gt; your backend on the frontend, type-safe and all, with almost no effort on your part. Let me break down why that matters, and how it compares to Tauri and Electron.&lt;/p&gt;
&lt;h3 id="wails-ipc-that-feels-native"&gt;Wails: IPC That Feels Native&lt;/h3&gt;
&lt;p&gt;Wails is all about Go on the backend + whatever JS framework you like on the frontend. What makes its IPC stand out is how &lt;em&gt;automatic&lt;/em&gt; it feels. You bind your Go structs or methods, and Wails spits out JS/TS files for you no JSON wrangling, no boilerplate glue code.&lt;/p&gt;
&lt;p&gt;Example:&lt;br&gt;
A Go method like Greet(name string) string instantly becomes a JS import you can call like a normal function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Greet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;../wailsjs/go/main/App&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Boom. Done. Promise-based, error-handled.&lt;/p&gt;
&lt;p&gt;But the real flex? &lt;strong&gt;Struct mapping.&lt;/strong&gt; If you’ve got a Person struct in Go, Wails creates a Person class in JS, with constructors and helpers. Pass it JSON, pass it objectsWails just translates it all. Add JSON tags to your struct fields, and you’re living the dream.&lt;/p&gt;
&lt;p&gt;And when you update your Go code? A quick wails generate syncs everything. Your frontend always has the latest bindings, complete with TypeScript definitions. It really feels like you’re just calling local JS code, except Go is doing the heavy lifting behind the scenes.&lt;/p&gt;
&lt;h3 id="how-it-compares-tauri-vs-electron"&gt;How It Compares: Tauri vs. Electron&lt;/h3&gt;
&lt;p&gt;Let’s size it up against the two heavyweights.&lt;/p&gt;
&lt;h3 id="tauri-rust"&gt;Tauri (Rust)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Good:&lt;/strong&gt; Lightweight, secure, resource-friendly. Rust fans love it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPC style:&lt;/strong&gt; JS calls Rust functions via invoke(&amp;lsquo;command&amp;rsquo;, { args }). It’s JSON under the hood. Events exist for push messages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Downside:&lt;/strong&gt; Type safety isn’t automatic. You &lt;em&gt;can&lt;/em&gt; use tauri-bindgen for type-safe bindings, but it’s an extra step. Struct mapping is manual unless you go all-in on bindgen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; Fantastic performance, but you’ll spend more time wiring things up.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="electron-node"&gt;Electron (Node)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Good:&lt;/strong&gt; Huge ecosystem, proven in production (Slack, VS Code, etc.).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPC style:&lt;/strong&gt; Old-school channels with ipcRenderer.send/invoke and webContents.send. It works, but it’s raw.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Downside:&lt;/strong&gt; Zero codegen, zero type safety. You’re writing boilerplate for preload scripts and JSON messages. Structs? Forget it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; If you’re deep in Node land, it’s familiar. But compared to Wails, it feels… clunky.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tldr-comparison"&gt;TL;DR Comparison&lt;/h3&gt;
&lt;p&gt;Feature &lt;strong&gt;Wails (Go)&lt;/strong&gt; &lt;strong&gt;Tauri (Rust)&lt;/strong&gt; &lt;strong&gt;Electron (Node)&lt;/strong&gt; IPC Style Auto JS functions/classes JSON commands + events Manual channels/messages Code Generation Yes, built-in Optional (bindgen) None Type Safety Strong, auto TS Partial, extra setup Manual, if you bother Struct Mapping Automatic (JS classes) Manual / via bindgen Manual JSON Ease of Use High (feels native to JS) Medium (setup-heavy) Low (lots of boilerplate) Best For Go lovers + type safety fans Rust fans + perf seekers Node folks + ecosystem&lt;/p&gt;
&lt;h3 id="so-who-should-use-what"&gt;So, Who Should Use What?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pick Wails&lt;/strong&gt; if you want Go to feel like it lives &lt;em&gt;inside&lt;/em&gt; your frontend. It’s the smoothest, most type-safe IPC experience right now.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pick Tauri&lt;/strong&gt; if performance and security are your top concerns, and you don’t mind extra setup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pick Electron&lt;/strong&gt; if you need Node’s ecosystem or you’re building something huge where library support matters more than IPC elegance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Personally, Wails nails the sweet spot: less boilerplate, more productivity, and it just feels &lt;em&gt;right&lt;/em&gt;. And with Wails v3 on the horizon (better bindings, better tooling), it’s only getting stronger.&lt;/p&gt;
&lt;p&gt;That’s my two cents. What about you? Are you team Wails, Tauri, or Electron? 🚀&lt;/p&gt;
&lt;p&gt;Links if you want to dive deeper: &lt;a href="https://wails.io/"&gt;Wails&lt;/a&gt; | &lt;a href="https://tauri.app/"&gt;Tauri&lt;/a&gt; | &lt;a href="https://www.electronjs.org/"&gt;Electron&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=5a00b202cf09" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@tacherasasi/why-wails-wins-at-ipc-for-go-desktop-apps-and-how-it-stacks-up-against-tauri-electron-5a00b202cf09?source=rss-9a41d7ec29fb------2"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>