<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Go Error Design at Scale on Atharva Pandey</title><link>https://atharva.page/series/go-error-design-at-scale/</link><description>Recent content in Go Error Design at Scale on Atharva Pandey</description><generator>Hugo</generator><language>en-us</language><copyright>Copyright ©</copyright><lastBuildDate>Sat, 14 Jun 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://atharva.page/series/go-error-design-at-scale/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 8: Production Error Architecture — Designing the error system for a real service</title><link>https://atharva.page/post/go/go-errors-production-architecture/</link><pubDate>Sat, 14 Jun 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-errors-production-architecture/</guid><description>&lt;p&gt;This is the lesson where everything comes together. Over the previous seven lessons we&amp;rsquo;ve looked at sentinels and typed errors, wrapping strategy, error classification, where to log, how to translate at layer boundaries, and when panic is actually defensible. Now I want to show you what all of that looks like assembled into a complete, production-ready error system for a real API service.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to build the full error architecture for a hypothetical orders API. By the end you&amp;rsquo;ll have a template you can adapt directly — the error type hierarchy, the middleware, the structured logging, and the client-facing error codes. This is the code I wish I&amp;rsquo;d had when I started building services in Go.&lt;/p&gt;</description></item><item><title>Lesson 7: Panic, Recover, and When They're Actually Justified — Panic is not error handling</title><link>https://atharva.page/post/go/go-errors-panic-recover/</link><pubDate>Tue, 13 May 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-errors-panic-recover/</guid><description>&lt;p&gt;I&amp;rsquo;ve seen &lt;code&gt;panic(err)&lt;/code&gt; used as error handling more times than I&amp;rsquo;d like to admit — including in codebases I helped build. The reasoning always sounds logical at the time: &amp;ldquo;this error should never happen, and if it does, the program state is corrupt anyway, so why not just panic?&amp;rdquo; The problem is that &amp;ldquo;should never happen&amp;rdquo; is a statement about your expectations, not about reality. And &amp;ldquo;program state is corrupt&amp;rdquo; is almost never true — one request failed, the other thousand are still fine.&lt;/p&gt;</description></item><item><title>Lesson 6: Error Boundaries Across Layers — Translate at the border, don't leak internals</title><link>https://atharva.page/post/go/go-errors-boundaries/</link><pubDate>Sat, 19 Apr 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-errors-boundaries/</guid><description>&lt;p&gt;One of the most subtle security issues I&amp;rsquo;ve encountered in Go APIs isn&amp;rsquo;t an authentication bug or a missing authorization check — it&amp;rsquo;s a SQL error message showing up in a JSON response. Something like &lt;code&gt;{&amp;quot;error&amp;quot;: &amp;quot;pq: duplicate key value violates unique constraint \&amp;quot;users_email_key\&amp;quot;&amp;quot;}&lt;/code&gt;. The frontend is now showing your users your database schema. Not great.&lt;/p&gt;
&lt;p&gt;This happens because somewhere between the repository and the HTTP response, someone got lazy with error translation. The raw database error bubbled all the way up and got written directly into the response. Error boundaries exist to prevent exactly this — they&amp;rsquo;re the translation points between layers, where internal implementation details get converted to appropriate external representations.&lt;/p&gt;</description></item><item><title>Lesson 5: Logging vs Returning — Log at the boundary, return everywhere else</title><link>https://atharva.page/post/go/go-errors-logging-vs-returning/</link><pubDate>Mon, 17 Mar 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-errors-logging-vs-returning/</guid><description>&lt;p&gt;There&amp;rsquo;s a smell I encounter in almost every codebase I&amp;rsquo;ve reviewed — including ones I wrote early in my Go career. Someone discovers that &lt;code&gt;err != nil&lt;/code&gt; and, out of an abundance of caution, they log it right there. Then return it. Then the caller logs it again. Then the middleware logs it a third time. By the time a single failed database query reaches the response, it&amp;rsquo;s appeared in the logs four times with slightly different messages and you can&amp;rsquo;t tell whether four things failed or one thing failed four times.&lt;/p&gt;</description></item><item><title>Lesson 4: Operational vs Domain Errors — Not all errors deserve the same treatment</title><link>https://atharva.page/post/go/go-errors-operational-vs-domain/</link><pubDate>Fri, 28 Feb 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-errors-operational-vs-domain/</guid><description>&lt;p&gt;One of the more expensive lessons I learned was treating all errors the same. When a database connection drops, you retry. When a user sends an invalid email address, you return a 400 and explain what&amp;rsquo;s wrong. When someone tries to access a resource that doesn&amp;rsquo;t belong to them, you return a 403. These are completely different situations — different causes, different remedies, different communication needs — and collapsing them into a single &lt;code&gt;err != nil&lt;/code&gt; branch produces services that retry permanent failures, expose internal details to users, and log noise that drowns out real alerts.&lt;/p&gt;</description></item><item><title>Lesson 3: Wrapping Strategy — Every wrap should add context, never noise</title><link>https://atharva.page/post/go/go-errors-wrapping-strategy/</link><pubDate>Tue, 04 Feb 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-errors-wrapping-strategy/</guid><description>&lt;p&gt;There&amp;rsquo;s a certain kind of error message I&amp;rsquo;ve come to dread in production logs: &lt;code&gt;auth: db: sql: connection refused&lt;/code&gt;. Technically, it contains the full path from the handler down to the socket. Practically, it tells me nothing I couldn&amp;rsquo;t figure out from the stack trace — and it takes ten seconds to parse. That error message is the output of wrapping done wrong: every layer added its name reflexively, without thinking about what the reader actually needs.&lt;/p&gt;</description></item><item><title>Lesson 2: errors.Is and errors.As — Matching errors through the wrapping chain</title><link>https://atharva.page/post/go/go-errors-is-as/</link><pubDate>Thu, 09 Jan 2025 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-errors-is-as/</guid><description>&lt;p&gt;Go 1.13 shipped what I consider the most important error-handling improvement the language has had: &lt;code&gt;errors.Is&lt;/code&gt;, &lt;code&gt;errors.As&lt;/code&gt;, and the &lt;code&gt;%w&lt;/code&gt; verb. Before that, wrapping errors with context meant breaking the ability to inspect them. You&amp;rsquo;d wrap with &lt;code&gt;fmt.Errorf(&amp;quot;context: %v&amp;quot;, err)&lt;/code&gt; and then the original error was gone — you could log it, but you couldn&amp;rsquo;t match it. Callers would resort to string matching or just give up and let every error map to a 500.&lt;/p&gt;</description></item><item><title>Lesson 1: Sentinel vs Typed Errors — Know your error before you handle it</title><link>https://atharva.page/post/go/go-errors-sentinel-vs-typed/</link><pubDate>Sat, 14 Dec 2024 00:00:00 +0000</pubDate><guid>https://atharva.page/post/go/go-errors-sentinel-vs-typed/</guid><description>&lt;p&gt;When I first started writing Go seriously, I treated errors like fire: try to avoid them, and when you can&amp;rsquo;t, put them out as fast as possible with an &lt;code&gt;if err != nil { return err }&lt;/code&gt;. It took a few production incidents — and a lot of reading other people&amp;rsquo;s codebases — before I understood that errors aren&amp;rsquo;t just signals. They&amp;rsquo;re data. And how you design that data determines how well your callers can respond to failures.&lt;/p&gt;</description></item></channel></rss>