Stacked middleware vs embedded delegation in Go

Middleware is usually the go-to pattern in Go HTTP servers for tweaking request behavior. Typically, you wrap your base handler with layers of middleware—one might log every request, while another intercepts specific routes like /special to serve a custom response. However, I often find the indirections introduced by this pattern a bit hard to read and debug. I recently came across the embedded delegation pattern while browsing the Gin1 repo. Here, I explore both patterns and explain why I usually start with delegation whenever I need to modify HTTP requests in my Go services. ...

March 6, 2025

Why does Go's io.Reader have such a weird signature?

I’ve always found the signature of io.Reader a bit odd: type Reader interface { Read(p []byte) (n int, err error) } Why take a byte slice and write data into it? Wouldn’t it be simpler to create the slice inside Read, load the data, and return it instead? // Hypothetical; what I *thought* it should be Read() (p []byte, err error) This felt more intuitive to me—you call Read, and it gives you a slice filled with data, no need to pass anything. ...

February 8, 2025

Go slice gotchas

Just like any other dynamically growable container structure, Go slices come with a few gotchas. I don’t always remember all the rules I need to be aware of. So this is an attempt to list some of the most common mistakes I’ve made at least once. Slices are views over arrays In Go, a slice is a lightweight wrapper around an array. Instead of storing data itself, it keeps track of three things: a pointer to an underlying array where the data is stored, the number of elements it currently holds, and the total capacity before it needs more space. The Go runtime defines it like this: ...

February 6, 2025

Running only a single instance of a process

I’ve been having a ton of fun fiddling with Tailscale1 over the past few days. While setting it up on a server, I came across this shell script2 that configures the ufw firewall on Linux to ensure direct communication across different nodes in my tailnet. It has the following block of code that I found interesting (added comments for clarity): #!/usr/bin/env bash # Define the path for the PID file, using the script's name to ensure uniqueness PIDFILE="/tmp/$(basename "${BASH_SOURCE[0]%.*}.pid")" # Open file descriptor 200 for the PID file exec 200>"${PIDFILE}" # Try to acquire a non-blocking lock; exit if the script is already running flock -n 200 \ || { echo "${BASH_SOURCE[0]} script is already running. Aborting..."; exit 1; } # Store the current process ID (PID) in the lock file for reference PID=$$ echo "${PID}" 1>&200 # Do work (in the original script, real work happens here) sleep 999 Here, flock is a Linux command that ensures only one instance of the script runs at a time by locking a specified file (e.g., PIDFILE) through a file descriptor (e.g., 200). If another process already holds the lock, the script either waits or exits immediately. Above, it bails with an error message and exit code 1. ...

December 31, 2024

Function types and single-method interfaces in Go

People love single-method interfaces (SMIs) in Go. They’re simple to implement and easy to reason about. The standard library is packed with SMIs like io.Reader, io.Writer, io.Closer, io.Seeker, and more. One cool thing about SMIs is that you don’t always need to create a full-blown struct with a method to satisfy the interface. You can define a function type, attach the interface method to it, and use it right away. This approach works well when there’s no state to maintain, so the extra struct becomes unnecessary. However, I find the syntax for this a bit abstruce. So, I’m jotting down a few examples here to reference later. ...

December 22, 2024

Topological sort

I was fiddling with graphlib in the Python stdlib and found it quite nifty. It processes a Directed Acyclic Graph (DAG), where tasks (nodes) are connected by directed edges (dependencies), and returns the correct execution order. The “acyclic” part ensures no circular dependencies. Topological sorting is useful for arranging tasks so that each one follows its dependencies. It’s widely used in scheduling, build systems, dependency resolution, and database migrations. For example, consider these tasks: ...

October 13, 2024

Writing a circuit breaker in Go

Besides retries, circuit breakers1 are probably one of the most commonly employed resilience patterns in distributed systems. While writing a retry routine is pretty simple, implementing a circuit breaker needs a little bit of work. I realized that I usually just go for off-the-shelf libraries for circuit breaking and haven’t written one from scratch before. So, this is an attempt to create a sloppy one in Go. I picked Go instead of Python because I didn’t want to deal with sync-async idiosyncrasies or abstract things away under a soup of decorators. ...

October 6, 2024

ETag and HTTP caching

One neat use case for the HTTP ETag header is client-side HTTP caching for GET requests. Along with the ETag header, the caching workflow requires you to fiddle with other conditional HTTP headers like If-Match or If-None-Match. However, their interaction can feel a bit confusing at times. Every time I need to tackle this, I end up spending some time browsing through the relevant MDN docs123 to jog my memory. At this point, I’ve done it enough times to justify spending the time to write this. ...

April 10, 2024

Crossing the CORS crossroad

Every once in a while, I find myself skimming through the MDN docs to jog my memory on how CORS1 works and which HTTP headers are associated with it. This is particularly true when a frontend app can’t talk to a backend service I manage due to a CORS error2. MDN’s CORS documentation is excellent but can be a bit verbose for someone just looking for a way to quickly troubleshoot and fix the issue at hand. ...

March 12, 2024

Dysfunctional options pattern in Go

Ever since Rob Pike published the text on the functional options pattern1, there’s been no shortage of blogs, talks, or comments on how it improves or obfuscates configuration ergonomics. While the necessity of such a pattern is quite evident in a language that lacks default arguments in functions, more often than not, it needlessly complicates things. The situation gets worse if you have to maintain a public API where multiple configurations are controlled in this manner. ...

March 6, 2024

Strategy pattern in Go

These days, I don’t build hierarchical types through inheritance even when writing languages that support it. Type composition has replaced almost all of my use cases where I would’ve reached for inheritance before. I’ve written1 about how to escape the template pattern2 hellscape and replace that with strategy pattern3 in Python before. While by default, Go saves you from shooting yourself in the foot by disallowing inheritance, it wasn’t obvious to me how I could apply the strategy pattern to make things more composable and testable. ...

February 17, 2024

Anemic stack traces in Go

While I like Go’s approach of treating errors as values as much as the next person, it inevitably leads to a situation where there isn’t a one-size-fits-all strategy for error handling like in Python or JavaScript. The usual way of dealing with errors entails returning error values from the bottom of the call chain and then handling them at the top. But it’s not universal since there are cases where you might want to handle errors as early as possible and fail catastrophically. Yet, it’s common enough that we can use it as the base of our conversation. ...

February 10, 2024

Retry function in Go

I used reach for reflection whenever I needed a Retry function in Go. It’s fun to write, but gets messy quite quickly. Here’s a rudimentary Retry function that does the following: It takes in another function that accepts arbitrary arguments. Then tries to execute the wrapped function. If the wrapped function returns an error after execution, Retry attempts to run the underlying function n times with some backoff. The following implementation leverages the reflect module to achieve the above goals. We’re intentionally avoiding complex retry logic for brevity: ...

February 4, 2024

Type assertion vs type switches in Go

Despite moonlighting as a gopher for a while, the syntax for type assertion and type switches still trips me up every time I need to go for one of them. So, to avoid digging through the docs or crafting stodgy LLM prompts multiple times, I decided to jot this down in a gobyexample1 style for the next run. Type assertion Type assertion in Go allows you to access an interface variable’s underlying concrete type. After a successful assertion, the variable of interface type is converted to the concrete type to which it’s asserted. ...

January 31, 2024

Omitting dev dependencies in Go binaries

As of now, unlike Python or NodeJS, Go doesn’t allow you to specify your development dependencies separately from those of the application. However, I like to specify the dev dependencies explicitly for better reproducibility. While working on a new CLI tool1 for checking dead URLs in Markdown files, I came across this neat convention: you can specify dev dependencies in a tools.go file and then exclude them while building the binary using a build tag. ...

January 21, 2024

Eschewing black box API calls

I love dynamically typed languages as much as the next person. They let us make ergonomic API calls like this: import httpx # Sync call for simplicity r = httpx.get("https://dummyjson.com/products/1").json() print(r["id"], r["title"], r["description"]) or this: fetch("https://dummyjson.com/products/1") .then((res) => res.json()) .then((json) => console.log(json.id, json.type, json.description)); In both cases, running the snippets will return: 1 'iPhone 9' 'An apple mobile which is nothing like apple' Unless you’ve worked with a statically typed language that enforces more constraints, it’s hard to appreciate how incredibly convenient it is to be able to call and use an API endpoint without having to deal with types or knowing anything about its payload structure. You can treat the API response as a black box and deal with everything in runtime. ...

January 15, 2024

Rate limiting via Nginx

I needed to integrate rate limiting into a relatively small service that complements a monolith I was working on. My initial thought was to apply it at the application layer, as it seemed to be the simplest route. Plus, I didn’t want to muck around with load balancer configurations, and there’s no shortage of libraries that allow me to do this quickly in the app. However, this turned out to be a bad idea. In the event of a DDoS1 or thundering herd2 incident, even if the app rejects the influx of inbound requests, the app server workers still have to do a minimal amount of work. ...

January 6, 2024

Configuring options in Go

Suppose, you have a function that takes an option struct and a message as input. Then it stylizes the message according to the option fields and prints it. What’s the most sensible API you can offer for users to configure your function? Observe: // app/src package src // Option struct type Style struct { Fg string // ANSI escape codes for foreground color Bg string // Background color } // Display the message according to Style func Display(s *Style, msg string) {} In the src package, the function Display takes a pointer to a Style instance and a msg string as parameters. Then it decorates the msg and prints it according to the style specified in the option struct. In the wild, I’ve seen 3 main ways to write APIs that let users configure options: ...

September 5, 2023

Dummy load balancer in a single Go script

I was curious to see if I could prototype a simple load balancer in a single Go script. Go’s standard library and goroutines make this trivial. Here’s what the script needs to do: Spin up two backend servers that’ll handle the incoming requests. Run a reverse proxy load balancer in the foreground. The load balancer will accept client connections and round-robin them to one of the backend servers; balancing the inbound load. Once a backend responds, the load balancer will relay the response back to the client. For simplicity, we’ll only handle client’s GET requests. Obviously, this won’t have SSL termination, advanced balancing algorithms, or session persistence like you’d get with Nginx1 or Caddy2. The point is to understand the basic workflow and show how Go makes it easy to write this sort of stuff. ...

August 30, 2023

Limit goroutines with buffered channels

I was cobbling together a long-running Go script to send webhook messages to a system when some events occur. The initial script would continuously poll a Kafka topic for events and spawn new goroutines to make HTTP requests to the destination. This had two problems: It could create unlimited goroutines if many events arrived quickly It might overload the destination system by making many concurrent requests In Python, I’d use just asyncio.Semaphore to limit concurrency. I’ve previously written about this here1. Turns out, in Go, you could do the same with a buffered channel. Here’s how the naive version looks: ...

August 23, 2023