Derex.dev

Stop Fighting Go and Started Listening

For a long time, I wrote Go the way I wrote other languages: by instinct, by habit, and, honestly, by muscle memory from years of reaching for dependencies to solve my problems.

It wasn’t that I didn’t trust Go’s standard library. I just didn’t think about it. Need logging? Grab a package. Need an HTTP client? Surely, there’s a better one than the built-in net/http. I treated the standard library like a set of training wheels, something to outgrow once I needed “real” functionality.

But Go had other plans for me.

The Moment It Clicked

It started with a simple problem: logging.

I needed to log messages to a file. Easy enough. I wrote a function like this:

func logMessage(message string, file *os.File) {
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    logLine := fmt.Sprintf("%s %s\n", timestamp, message)
    file.Write([]byte(logLine))
}

It worked. But something about it felt… brittle. What if I wanted to log to the console instead? Or store logs in memory for testing? Or send them to a remote service? Each new destination meant rewriting this function.

That’s when I noticed something about os.File. It implemented io.Writer. And io.Writer wasn’t just for files—it was for anything that could accept a stream of bytes.

So I rewrote the function:

func logMessage(message string, writer io.Writer) {
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    logLine := fmt.Sprintf("%s %s\n", timestamp, message)
    writer.Write([]byte(logLine))
}

And just like that, my little function transformed.

Now, it didn’t care where the logs went. It could write to a file, sure, but also to os.Stdout, a bytes.Buffer, or even a network connection. I hadn’t added complexity. I had removed assumptions and in doing so, I had made my code far more powerful.

Go Was Trying to Teach Me Something

I had spent so much time trying to make Go work the way I wanted that I had ignored the patterns it was trying to show me.

Interfaces like io.Writer weren’t just there for convenience; they were a way of thinking. A way to decouple code, to make it composable, to let it flow.

I started seeing the same lesson everywhere:

  • The http.Handler interface made middleware trivial.
  • io.Reader and io.Writer allowed me to compose data transformations like UNIX pipes.
  • The built-in context package helped me control timeouts and cancellations without bolting on extra dependencies.

Go’s standard library wasn’t just a set of tools—it was a philosophy. One built around simplicity, composability, and minimal assumptions.

The Shift in Mindset

I used to reach for dependencies first. Now, I pause. I look at the standard library. I ask myself, What is Go trying to show me here?

Most of the time, the answer is already there—I just wasn’t listening.

Takeaway: The real power isn’t in external libraries; it’s in understanding the language’s own patterns. Once you stop fighting Go and start listening to it, everything changes.

Did I make a mistake? Please consider Send Email With Subject