<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Idiomatic Go on Atharva Pandey</title><link>https://atharva.page/series/idiomatic-go/</link><description>Recent content in Idiomatic Go on Atharva Pandey</description><generator>Hugo</generator><language>en-us</language><copyright>Copyright ©</copyright><lastBuildDate>Mon, 09 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://atharva.page/series/idiomatic-go/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 10: Zero Values Are Useful — Go types that work before you touch them</title><link>https://atharva.page/post/go/go-idioms-zero-values/</link><pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-zero-values/</guid><description>&lt;p&gt;In most languages, a freshly declared variable is either uninitialized garbage you can&amp;rsquo;t touch safely, or it needs a constructor call before it does anything useful. Go takes a different approach: every variable always has a value. When you don&amp;rsquo;t provide one, Go assigns the zero value for the type. What makes this interesting is that Go&amp;rsquo;s standard library is full of types designed so that their zero value is immediately useful — no constructor required.&lt;/p&gt;</description></item><item><title>Lesson 12: Value vs Pointer Receivers — The method that silently does nothing</title><link>https://atharva.page/post/go/go-idioms-value-vs-pointer-receivers/</link><pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-value-vs-pointer-receivers/</guid><description>&lt;p&gt;Value vs pointer receivers is one of those topics that seems like a style preference until it silently breaks your program. The tell is a method that looks like it mutates a struct, compiles without complaint, but the mutations simply don&amp;rsquo;t persist. You add a log line, the value is right inside the method — and wrong the moment the method returns.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;A value receiver operates on a copy. Every time you call the method, Go copies the entire struct and passes the copy to the method. Mutations happen on the copy, which gets discarded when the method returns. The original is untouched.&lt;/p&gt;</description></item><item><title>Lesson 19: Table-Driven Tests — One test function, fifty test cases</title><link>https://atharva.page/post/go/go-idioms-table-driven-tests/</link><pubDate>Mon, 09 Feb 2026 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-table-driven-tests/</guid><description>&lt;p&gt;Most engineers know to write tests. Fewer think about how the test code itself should scale. When you need to cover thirty input variations of a function, duplicating the test body thirty times produces something that&amp;rsquo;s painful to read, painful to extend, and painful to debug when it fails. Table-driven tests are the pattern that scales. A slice of cases, one loop — your test code stays as clean as your production code.&lt;/p&gt;</description></item><item><title>Lesson 22: Small Packages Win — One package, one job</title><link>https://atharva.page/post/go/go-idioms-small-packages/</link><pubDate>Mon, 26 Jan 2026 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-small-packages/</guid><description>&lt;p&gt;There is a particular kind of Go codebase that&amp;rsquo;s immediately recognizable as written by someone still thinking in another language. It has a &lt;code&gt;utils&lt;/code&gt; package. Maybe a &lt;code&gt;common&lt;/code&gt; package. Possibly a &lt;code&gt;helpers&lt;/code&gt; folder with a file called &lt;code&gt;misc.go&lt;/code&gt;. Every function that doesn&amp;rsquo;t obviously belong somewhere ends up there, and over time these packages become the junk drawers of the codebase — bloated, unfocused, and imported by everything.&lt;/p&gt;
&lt;p&gt;Go has a better way. Small packages with narrow responsibilities. One package, one idea.&lt;/p&gt;</description></item><item><title>Lesson 7: Slices Are Views, Not Arrays — Mutations you didn''t ask for</title><link>https://atharva.page/post/go/go-idioms-slices-are-views/</link><pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-slices-are-views/</guid><description>&lt;p&gt;If you&amp;rsquo;re coming from Python, JavaScript, or Java, slices look familiar enough that you&amp;rsquo;ll assume you understand them. That assumption will hold right up until something mutates data you didn&amp;rsquo;t expect to be mutable, or a change you made inside a function mysteriously doesn&amp;rsquo;t show up outside it. Both surprises have the same root cause: a slice is not a copy of its data, it&amp;rsquo;s a window into an underlying array that may be shared with other slices.&lt;/p&gt;</description></item><item><title>Lesson 25: Simplicity Is a Language Feature — Why Go says no so you can ship</title><link>https://atharva.page/post/go/go-idioms-simplicity/</link><pubDate>Mon, 29 Dec 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-simplicity/</guid><description>&lt;p&gt;Every Go design decision that looks like a missing feature is actually a deliberate choice to remove cognitive overhead. No method overloading. No implicit conversions. One canonical formatter. Generics that arrived late and deliberately. Go&amp;rsquo;s simplicity is not an accident — it&amp;rsquo;s a feature, and one that pays compounding dividends as a codebase and team grow.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Languages that offer maximum expressiveness also offer maximum inconsistency. In Java or C++, method overloading sounds convenient:&lt;/p&gt;</description></item><item><title>Lesson 17: select Is Elegant — Waiting on multiple futures without blocking</title><link>https://atharva.page/post/go/go-idioms-select/</link><pubDate>Mon, 15 Dec 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-select/</guid><description>&lt;p&gt;Every non-trivial concurrent program eventually needs to wait on more than one thing at a time. Maybe you&amp;rsquo;re waiting on a job channel but also need to respond to cancellation. Maybe you want to fetch from a channel but bail out after a timeout. Sequential channel receives can&amp;rsquo;t do this — they block on one thing and miss everything else. &lt;code&gt;select&lt;/code&gt; is the solution, and it&amp;rsquo;s more powerful than it looks.&lt;/p&gt;</description></item><item><title>Lesson 9: range Gotchas — The loop variable that bit every Go team</title><link>https://atharva.page/post/go/go-idioms-range-gotchas/</link><pubDate>Mon, 01 Dec 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-range-gotchas/</guid><description>&lt;p&gt;&lt;code&gt;range&lt;/code&gt; looks harmless. Index and value, loop over a slice — what could go wrong? Quite a bit, it turns out. Some of the most insidious bugs I&amp;rsquo;ve seen in Go codebases come from assumptions about &lt;code&gt;range&lt;/code&gt; that seem obvious but are wrong. One was painful enough that Go 1.22 changed the fundamental loop semantics to fix it. Let&amp;rsquo;s walk through each gotcha and the correct pattern to use.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The classic range bug — the one that famously broke production code at teams large and small for years — is the loop variable capture problem. In Go versions before 1.22, every iteration of a &lt;code&gt;range&lt;/code&gt; loop reused the same loop variable. Taking its address multiple times gave you the same address every time.&lt;/p&gt;</description></item><item><title>Lesson 24: Prefer Plain Structs — Boring code is correct code</title><link>https://atharva.page/post/go/go-idioms-plain-structs/</link><pubDate>Mon, 17 Nov 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-plain-structs/</guid><description>&lt;p&gt;There is a particular brand of cleverness that feels deeply satisfying to write and deeply painful to maintain. The &lt;code&gt;AbstractServiceProviderFactory&lt;/code&gt;. The builder that returns a builder that configures a builder. The generic interface so abstract it could model anything and therefore models nothing well. Go&amp;rsquo;s culture pushes back hard against this tendency, and for good reason: I&amp;rsquo;ve seen more bugs traced to abstraction layers than to simple structs.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The most common form of over-engineering in Go is attempting to import Java-style construction patterns. Go doesn&amp;rsquo;t have constructors, so engineers invent them — and the result is code that&amp;rsquo;s harder to configure, not easier:&lt;/p&gt;</description></item><item><title>Lesson 11: Nil Slice vs Empty Slice — Same length, different meaning</title><link>https://atharva.page/post/go/go-idioms-nil-vs-empty-slice/</link><pubDate>Mon, 03 Nov 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-nil-vs-empty-slice/</guid><description>&lt;p&gt;Go has two ways to have a slice with zero elements, and they are not the same thing. Developers coming from Python, Ruby, or JavaScript expect an empty collection to just be an empty collection. In Go, the distinction between a nil slice and an empty slice is subtle enough that you can miss it for months — right up until a frontend engineer files a bug because your API is returning &lt;code&gt;null&lt;/code&gt; instead of &lt;code&gt;[]&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Lesson 18: sync.Mutex Is Often Simpler — Not everything needs a channel</title><link>https://atharva.page/post/go/go-idioms-mutex-simpler/</link><pubDate>Mon, 20 Oct 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-mutex-simpler/</guid><description>&lt;p&gt;After you absorb the Go concurrency philosophy — share memory by communicating — there&amp;rsquo;s a temptation to reach for channels every time two goroutines need to share data. Resist that. Channels are for coordination and ownership transfer. For shared mutable state that multiple goroutines read and write, a mutex is usually clearer, simpler, and faster. Using a channel where a mutex belongs is one of those things that looks idiomatic but isn&amp;rsquo;t.&lt;/p&gt;</description></item><item><title>Lesson 3: Multiple Return Values — Go functions don't hide their failures</title><link>https://atharva.page/post/go/go-idioms-multiple-return-values/</link><pubDate>Mon, 06 Oct 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-multiple-return-values/</guid><description>&lt;p&gt;In most languages, a function returns one thing and communicates failure through a side channel — an exception, a null, a magic sentinel value. Go&amp;rsquo;s approach is different: functions can return multiple values, and the convention is to use that to make failure explicit in the signature itself. Once you&amp;rsquo;ve used it for a while, hiding failures in side channels starts to feel dishonest.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Sentinel values are the old-school way to signal failure from a function. You pick some value that &amp;ldquo;shouldn&amp;rsquo;t&amp;rdquo; appear in normal results and treat it as an error signal:&lt;/p&gt;</description></item><item><title>Lesson 13: iota for Enums — Constants that count themselves</title><link>https://atharva.page/post/go/go-idioms-iota-enums/</link><pubDate>Mon, 22 Sep 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-iota-enums/</guid><description>&lt;p&gt;Go doesn&amp;rsquo;t have a built-in enum keyword. What it has is &lt;code&gt;iota&lt;/code&gt;, a constant counter that resets to zero at the start of each &lt;code&gt;const&lt;/code&gt; block and increments with every constant declaration. It sounds underwhelming. In practice it gives you typed enums, bitmask permissions, and self-maintaining constant sequences — all without any runtime overhead.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The naive approach to enums in Go is plain integer constants or string constants. Both work, but neither gives you type safety:&lt;/p&gt;</description></item><item><title>Lesson 20: internal Package Is Underrated — Compiler-enforced privacy for free</title><link>https://atharva.page/post/go/go-idioms-internal-package/</link><pubDate>Mon, 08 Sep 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-internal-package/</guid><description>&lt;p&gt;Go has two visibility levels: exported (starts with a capital letter) and unexported (doesn&amp;rsquo;t). Most engineers use only these two. But there&amp;rsquo;s a third option that the language gives you for free, and it&amp;rsquo;s more useful than most people realize. The &lt;code&gt;internal&lt;/code&gt; directory enforces that certain packages can only be imported by code within your own module — and the compiler, not documentation or convention, does the enforcing.&lt;/p&gt;
&lt;p&gt;Exported symbols are API commitments. Once something is exported and external code is depending on it, changing it is a breaking change. The &lt;code&gt;internal&lt;/code&gt; package is the escape hatch: share code across multiple packages in your own codebase without accidentally publishing an API surface you&amp;rsquo;ll have to maintain forever.&lt;/p&gt;</description></item><item><title>Lesson 5: Implicit Interfaces — The best decoupling you'll never declare</title><link>https://atharva.page/post/go/go-idioms-implicit-interfaces/</link><pubDate>Mon, 25 Aug 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-implicit-interfaces/</guid><description>&lt;p&gt;In Java or C#, you declare that a class implements an interface. You write &lt;code&gt;implements Runnable&lt;/code&gt;, and the compiler ties that class to that interface forever. Go doesn&amp;rsquo;t work that way. A type satisfies an interface the moment it has the right methods — no declaration, no explicit relationship. This sounds like a minor syntactic difference, but it changes how you design systems in ways that compound over time.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;When interfaces are declared by the implementor (the Java way), you end up with a few recurring problems.&lt;/p&gt;</description></item><item><title>Lesson 15: Goroutines Are Cheap, Not Free — 2KB that can eat your server</title><link>https://atharva.page/post/go/go-idioms-goroutines-cheap-not-free/</link><pubDate>Mon, 11 Aug 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-goroutines-cheap-not-free/</guid><description>&lt;p&gt;&amp;ldquo;Goroutines are cheap&amp;rdquo; is something you read in every Go introduction. It&amp;rsquo;s true. A goroutine starts with a 2KB stack and the runtime handles scheduling. Spinning up a thousand of them is trivial. The part the introductions leave out is that &amp;ldquo;cheap&amp;rdquo; is not &amp;ldquo;free,&amp;rdquo; and goroutines that you start and never stop are a leak — one that doesn&amp;rsquo;t crash your program, just slowly eats your memory and degrades your scheduler until something gives.&lt;/p&gt;</description></item><item><title>Lesson 23: Error Values, Not Exceptions — Errors you can actually inspect</title><link>https://atharva.page/post/go/go-idioms-error-values/</link><pubDate>Mon, 28 Jul 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-error-values/</guid><description>&lt;p&gt;In most languages, errors are events — they get thrown, they propagate up the call stack, and you catch them somewhere above. Go rejects this model entirely. In Go, an error is a value, just like an integer or a string. You pass it around, inspect it, wrap it with context, and check it right where it happens. Ignore it and your code doesn&amp;rsquo;t crash loudly; it quietly does the wrong thing, and you&amp;rsquo;ll find out at the worst possible moment.&lt;/p&gt;</description></item><item><title>Lesson 1: Error Handling — Your code is lying if it ignores errors</title><link>https://atharva.page/post/go/go-idioms-error-handling/</link><pubDate>Mon, 14 Jul 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-error-handling/</guid><description>&lt;p&gt;If you&amp;rsquo;re coming from Python, Java, or JavaScript, Go&amp;rsquo;s error handling will feel strange at first. There&amp;rsquo;s no &lt;code&gt;try/catch&lt;/code&gt;. No exceptions bubbling up the call stack. Instead, errors are just values — and you deal with them right where they happen. Ignore them and your code doesn&amp;rsquo;t crash loudly; it quietly lies to you, and you won&amp;rsquo;t find out until 2am when production is on fire.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The blank identifier &lt;code&gt;_&lt;/code&gt; is the most dangerous character in Go. Here&amp;rsquo;s what it looks like when engineers first start writing Go:&lt;/p&gt;</description></item><item><title>Lesson 2: defer for Cleanup — Put the cleanup next to the mess</title><link>https://atharva.page/post/go/go-idioms-defer-cleanup/</link><pubDate>Mon, 30 Jun 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-defer-cleanup/</guid><description>&lt;p&gt;Every time you open a file, acquire a lock, or start a database transaction, you&amp;rsquo;ve created a resource that needs to be released when you&amp;rsquo;re done with it. In most languages you manage this with &lt;code&gt;finally&lt;/code&gt; blocks or RAII patterns. Miss one cleanup and you&amp;rsquo;ve got a leak. Go has &lt;code&gt;defer&lt;/code&gt;, and once it clicks, you&amp;rsquo;ll wonder how you ever coded without it.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s what resource cleanup looks like without &lt;code&gt;defer&lt;/code&gt;. Real code, the kind that ships:&lt;/p&gt;</description></item><item><title>Lesson 14: context.Context — The parameter every function should take first</title><link>https://atharva.page/post/go/go-idioms-context/</link><pubDate>Mon, 16 Jun 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-context/</guid><description>&lt;p&gt;Without &lt;code&gt;context.Context&lt;/code&gt;, a function that makes a database call, fires an HTTP request, or runs a long computation has no way to be told to stop. The caller times out, the user closes the browser tab, the load balancer kills the connection — and your function keeps running, consuming CPU and holding database connections, doing work that nobody will ever see. &lt;code&gt;context.Context&lt;/code&gt; is how Go solves this.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;A function with no context cannot be cancelled. It runs until it finishes, or until the process dies. In an HTTP server handling hundreds of requests per second, this accumulates fast:&lt;/p&gt;</description></item><item><title>Lesson 21: Composition Over Inheritance — Small pieces, loosely joined</title><link>https://atharva.page/post/go/go-idioms-composition/</link><pubDate>Mon, 02 Jun 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-composition/</guid><description>&lt;p&gt;If you&amp;rsquo;ve come from Java or C++, you&amp;rsquo;re probably waiting for Go to show you its inheritance model. Where&amp;rsquo;s the &lt;code&gt;extends&lt;/code&gt; keyword? Where are the base classes? There aren&amp;rsquo;t any — Go made a deliberate choice to leave them out. After writing a few thousand lines of Go, most people agree it was the right call.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Object-oriented languages lean heavily on the &amp;ldquo;is-a&amp;rdquo; relationship. A &lt;code&gt;Dog&lt;/code&gt; is-a &lt;code&gt;Animal&lt;/code&gt;. A &lt;code&gt;Manager&lt;/code&gt; is-a &lt;code&gt;Employee&lt;/code&gt;. This sounds elegant until real-world complexity enters the picture: is a &lt;code&gt;FlyingFish&lt;/code&gt; a &lt;code&gt;Fish&lt;/code&gt; or a &lt;code&gt;Bird&lt;/code&gt;? Now you&amp;rsquo;re in multiple inheritance territory and things get messy fast. And in Go, the instinct to fake it with named fields creates its own noise:&lt;/p&gt;</description></item><item><title>Lesson 4: The comma ok Idiom — Two returns that save you from panics</title><link>https://atharva.page/post/go/go-idioms-comma-ok/</link><pubDate>Mon, 19 May 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-comma-ok/</guid><description>&lt;p&gt;There are three places in Go where a missing second return value means your program either silently does the wrong thing or blows up entirely: reading from a map with a missing key, asserting a type on an interface, and receiving from a closed channel. The language&amp;rsquo;s answer to all three is the same — a boolean second return that tells you whether the operation actually succeeded. This is the comma-ok idiom, and you&amp;rsquo;ll use it constantly.&lt;/p&gt;</description></item><item><title>Lesson 16: Channels Are for Coordination — Stop using channels as fancy mutexes</title><link>https://atharva.page/post/go/go-idioms-channels-coordination/</link><pubDate>Mon, 05 May 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-channels-coordination/</guid><description>&lt;p&gt;Channels are Go&amp;rsquo;s most recognizable concurrency feature, and also one of the most misused. The moment engineers learn about them, there&amp;rsquo;s a strong temptation to reach for a channel every time two goroutines need to interact. That instinct is wrong about half the time. Channels are for coordination — signaling events, distributing work, collecting results. They are not a universal replacement for shared state.&lt;/p&gt;
&lt;p&gt;Rob Pike&amp;rsquo;s line from his 2012 talk sums it up: &amp;ldquo;Do not communicate by sharing memory; share memory by communicating.&amp;rdquo; That&amp;rsquo;s a guiding philosophy, not an absolute rule.&lt;/p&gt;</description></item><item><title>Lesson 8: Capacity Matters — The allocation tax you''re paying without knowing</title><link>https://atharva.page/post/go/go-idioms-capacity-matters/</link><pubDate>Mon, 21 Apr 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-capacity-matters/</guid><description>&lt;p&gt;There&amp;rsquo;s a one-line fix that will make your hot paths faster, use less memory, and reduce GC pressure. It costs you nothing in readability. Most Go programmers know about it. Far fewer actually do it consistently. The fix is telling Go how big a slice is going to be before you start filling it.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;This is the pattern you reach for by reflex, especially if you&amp;rsquo;ve come from languages where dynamic arrays just grow as needed:&lt;/p&gt;</description></item><item><title>Lesson 6: Accept Interfaces, Return Structs — Flexibility in, certainty out</title><link>https://atharva.page/post/go/go-idioms-accept-interfaces-return-structs/</link><pubDate>Mon, 07 Apr 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-idioms-accept-interfaces-return-structs/</guid><description>&lt;p&gt;Here&amp;rsquo;s a mistake I see in almost every Go codebase written by people coming from Java or C#: they accept concrete types everywhere and return interfaces from constructors. It feels &amp;ldquo;enterprise-y&amp;rdquo;. It&amp;rsquo;s actually backwards. The idiomatic Go version is the opposite — accept the smallest interface that does the job, return the richest concrete type you have.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Most of the pain comes from accepting concrete types. Once you lock a function to a specific concrete type, every caller that doesn&amp;rsquo;t have exactly that type is stuck. Tests become integration tests. Swapping implementations requires rewriting functions. And the function itself becomes harder to compose.&lt;/p&gt;</description></item></channel></rss>