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: ...

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 Nginx or Caddy. The point is to understand the basic workflow and show how Go makes it easy to write this sort of stuff. ...

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 worker goroutines in a fire-and-forget manner to make HTTP requests to the destination. This had two problems: It could create unlimited goroutines if many events arrived quickly (no backpressure) It might overload the destination system by making many concurrent requests (no concurrency control) In Python, I’d use just asyncio.Semaphore to limit concurrency. I’ve previously written about limiting concurrency with semaphores. Turns out, in Go, you could do the same with a buffered channel. Here’s how the naive version without any concurrency control looks: ...

Writing a TOTP client in Go

A TOTP based 2FA system has two parts. One is a client that generates the TOTP code. The other part is a server. The server verifies the code. If the client and the server-generated codes match, the server allows the inbound user to access the target system. The code usually expires after 30 seconds and then, you’ll have to regenerate it to be able to authenticate. As per RFC-6238, the server shares a base-32 encoded secret key with the client. Using this shared secret and the current UNIX timestamp, the client generates a 6-digit code. Independently, the server also generates a 6-digit code using the same secret string and its own current timestamp. If the user-entered client code matches the server-generated code, the auth succeeds. Otherwise, it fails. The client’s and the server’s current timestamp wouldn’t be an exact match. So the algorithm usually adjusts it for ~30 seconds duration. ...

Interface guards in Go

I love Go’s implicit interfaces. While convenient, they can also introduce subtle bugs unless you’re careful. Types expected to conform to certain interfaces can fluidly add or remove methods. The compiler will only complain if an identifier anticipates an interface, but is passed a type that doesn’t implement that interface. This can be problematic if you need to export types that are required to implement specific interfaces as part of their API contract. ...

Writing on well-trodden topics

I enjoy writing about software - the things I learn, the tools I use, and the work I do. Owing to the constraints of the corporate software world, more often than not, you can’t showcase your work or talk about them. At least that’s how it always has been throughout my career. At the same time, as you grow older and start having a life outside of the computer screen, you realize that working on OSS at the tail of a 40+ hour workweek is hard, and maintaining consistency is even harder. On that front, how do you keep track of your progress without losing your sense of purpose as the years fly by? ...

Go structured logging with slog

Before the release of version 1.21, you couldn’t set levels for your log messages in Go without either using third-party libraries or writing your own boilerplates. Coming from Python, I’ve always found this odd, considering that this capability has been in the Python standard library forever. However, it seems like the new log/slog subpackage in Go allows you to do that and a whole lot more. Apart from being able to add levels to log messages, slog also allows you to emit JSON-structured log messages and group them by certain attributes. The ability to do all this in-house is quite neat and I wanted to take it for a spin. The official documentation on this is on the terser side but still comprehensive. So, here, instead of repeating the same information, I wanted to write something for me that mainly highlights the most common cases. ...

Notes on exit interviews

If you’re a manager, then there’s no shortage of information for you on how to conduct exit interviews. But there aren’t many resources that focus on how to handle them from an employee’s perspective. I’ve been meaning to write a quick piece that isn’t biased by anyone else’s experience and is short enough so that I can quickly jog my memory in the future should the need arise. While I’ve participated in a few of them over the past five years, this text doesn’t attempt to combat the inexorable recency bias that may have seeped into the writing. ...

Taming conditionals with bitmasks

The 100k context window of Claude 2 has been a huge boon for me since now I can paste a moderately complex problem to the chat window and ask questions about it. In that spirit, it recently refactored some pretty gnarly conditional logic for me in such an elegant manner that it absolutely blew me away. Now, I know how bitmasks work and am aware of the existence of enum.Flag in Python. However, it never crossed my mind that flags can be leveraged to trim conditional branches in such a clever manner that Claude illustrated. But once I looked at the proposed solution, the whole thing immediately clicked for me. ...

Using DNS record to share text data

This morning, while browsing Hacker News, I came across a neat trick for sharing data via DNS TXT records. It can be useful for propagating a small amount of data in environments that restrict IP but allow DNS queries, or to bypass censorship. To test this out, I opened my domain registrar’s panel and created a new TXT type DNS entry with a base64 encoded message containing the poem A Poison Tree by William Blake. The message can now be queried and decoded with the following shell command: ...

Memory leakage in Python descriptors

Unless I’m hand rolling my own ORM-like feature or validation logic, I rarely need to write custom descriptors in Python. The built-in descriptor magics like @classmethod, @property, @staticmethod, and vanilla instance methods usually get the job done. However, every time I need to dig my teeth into descriptors, I reach for this fantastic Descriptor how-to guide by Raymond Hettinger. You should definitely set aside the time to read it if you haven’t already. It has helped me immensely to deepen my understanding of how many of the fundamental language constructs are wired together underneath. ...

Unix-style pipelining with Python's subprocess module

Python offers a ton of ways like os.system or os.spawn* to create new processes and run arbitrary commands in your system. However, the documentation usually encourages you to use the subprocess module for creating and managing child processes. The subprocess module exposes a high-level run() function that provides a simple interface for running a subprocess and waiting for it to complete. It accepts the command to run as a list of strings, starts the subprocess, waits for it to finish, and then returns a CompletedProcess object with information about the result. For example: ...

Enabling repeatable lazy iterations in Python

The current title of this post is probably incorrect and may even be misleading. I had a hard time coming up with a suitable name for it. But the idea goes like this: sometimes you might find yourself in a situation where you need to iterate through a generator more than once. Sure, you can use an iterable like a tuple or list to allow multiple iterations, but if the number of elements is large, that’ll cause an OOM error. On the other hand, once you’ve already consumed a generator, you’ll need to restart it if you want to go through it again. This behavior is common in pretty much every programming language that supports the generator construct. ...

Descending into the aether

Around a year ago, I ditched my fancy Linux rig for a beefed-up 16" MacBook Pro and ever since, it’s been my primary machine for both personal and work stuff. I love how this machine strikes a decent balance between power and portability. However, I often joke that this chonky boy is just a pound shy of being an ENIAC. It’s a beast of a machine when you need all that power, but certainly isn’t the most convenient contraption to lug around while flying. I work fully remote, but can’t get any work done while traveling and rarely ever need to tap into the full power this thing offers. ...

Escaping the template pattern hellscape in Python

Over the years, I’ve used the template pattern across multiple OO languages with varying degrees of success. It was one of the first patterns I learned in the primordial hours of my software engineering career, and for some reason, it just feels like the natural way to tackle many real-world code-sharing problems. Yet, even before I jumped on board with the composition over inheritance camp, I couldn’t help but notice how using this particular inheritance technique spawns all sorts of design and maintenance headaches as the codebase starts to grow. ...