DEV Community

Rhaqim
Rhaqim

Posted on

Journal Entry: When Overengineering Bit Me

Overengineering is a silent productivity killer.

I recently found myself debugging an application that kept hanging. On the surface, everything looked fine: no panics, no major memory spikes just random, hard-to-reproduce blocking behaviour. It was frustrating.

Eventually, I discovered the root cause: I had hundreds of goroutines sleeping silently in the background.

Here’s what I had done:

  • After sending a verification email, I launched a goroutine to sleep for 10 minutes and then update the database to clear the token.
  • Each user action spun up a new goroutine.
  • I wasn’t tracking or cancelling these goroutines. No context, no lifecycle awareness.
  • As if that wasn’t bad enough, the goroutines were all hitting a relational database to update tokens that only lived for a few minutes.

At first, it felt like the “Go” thing to do. Using goroutines and sleep timers to delay token deletion. But in reality, I had:

  • Introduced memory bloat.
  • Increased DB load for ephemeral data.
  • Ignored lifecycle cleanup.

Surely there had to be a better and overall cleaner way to achieve this goal, so, I stepped back and I asked myself: “Do I really need to store short-lived verification tokens in a full-blown SQL database?”.
The answer was obvious. (The answer is no, by the way. In case you weren’t sure.
Don’t feel too bad.)

Here’s how I fixed the bug:

  • I moved token storage to Redis with a simple TTL.
  • Removed all background goroutines tied to token expiration.
  • Simplified the code and reduced the mental load of managing side effects.

And just like that, after rocking back and forth wide-eyed for days like a crazy person in front of my computer, the blocking issues disappeared. Redis cleaned up expired keys automatically, and my backend was leaner and more predictable.

💡 Lesson Learned

A major takeaway from this experience is that the simplest solutions are often the
most effective.
If you’re using goroutines for background jobs with sleep timers, ask yourself: Do I need this process to live beyond the request lifecycle?, Should this be a scheduled job instead?, Can Redis or another TTL-based store simplify this logic?

It’s fine to build fast but it’s much better to build wisely. That’s how you keep your
sanity intact.

Top comments (0)