I Keep Overengineering Things
3 min read
When I started building ArQueue (my distributed task processing system), I got stuck at the very beginning trying to design the perfect architecture. I kept going back and forth on how to structure the workers, what the job schema should look like, whether I should support multiple queue backends and the classic: How do I stuff AI into this. I hadn’t written any real code yet but I already had a bunch of TypeScript interfaces and abstractions that didn’t actually do anything.
After about two weeks of that, I realized that I was still just shuffling things around. The stuff I had designed wasn’t bad, it was just irrelevant at that point. I was planning for problems I didn’t have yet instead of solving the ones I did have.
Why it’s tempting
Building for every edge case feels like you’re being thorough. It feels good. And to be honest, it does feel more fun than fixing a layout bug. But a lot of the time it’s just a way to avoid the less interesting work that actually matters to users.
I’m trying to follow a simple rule now: if I see a pattern once, I just write the code. If I see it twice, I make a note. Third time, maybe I abstract. But not before. The first time you see a pattern you don’t really understand it well enough to generalize.
Duplication is far cheaper than the wrong abstraction. (Sandi Metz)
What I’ve noticed from just shipping
The projects where I learned the most were the ones I actually finished and put in front of people. Even if the code was rough. You find out pretty quickly what matters and what doesn’t when someone’s actually using the thing.
This isn’t an argument for writing messy code. It’s more that I’ve realized “good enough” is a real target, not a compromise. A personal project doesn’t need the same setup as a system handling millions of requests. I keep catching myself building for scale I’ll probably never hit. (Although I do hope I’m wrong)
What I’m trying instead
Mostly just trying to keep things simple and easy to change later. Small functions, clear names, tests that check tricky edge cases and not just implementation details. If I need to refactor later, fine. At least I shipped something first.
- Write the simplest version that works, then iterate if needed.
- Try to make code easy to delete. If it’s easy to remove, it’s easy to change.
- Measure before optimizing. My gut feeling about what’s slow has been wrong more than once.
- Shipping something incomplete teaches you more than planning something complete.
I still think about design. I still read about system architecture and try to apply it where it makes sense or is impactful. I’m just trying to be more honest with myself about when it actually makes sense vs. when I’m just procrastinating.