Design by Decision Fatigue
Over the course of my career, I've been fortunate to build several large-scale software systems from scratch. Starting from an empty directory and blank IDE is a refreshing experience, and those early milestones like your first contributor or bit of customer feedback feel rewarding.
But in contrast, many of us spend our day jobs making what seem like tiny changes into endless piles upon piles of someone else's pre-built code. It can be frustrating and thankless.
It makes me wonder: How can we design software to not end up like those complex beasts someone else has to maintain, which were once fresh projects like mine, born with good intentions?
Early days
At almost every step in the early phases of a software project, you are one decision away from a disaster. This disaster won't end up rendering the entire project useless. Instead, it will manifest itself as tech debt. This debt has the potential to slow future progress down to an arduous grind; maybe not today, but at some point in the medium-to-long-term future.
With an empty directory staring back at you, there are plenty of technical choices to be made:
- Which language(s) do I use?
- Which frameworks do I use?
- How will I structure my code?
- What will I write myself, and when will I reach for a library?
- Once I've decided that, then which library should I use?
- How will I configure my program?
- How will my program communicate with others?
And these are just some of the big ones.
What about all of the tiny choices that we are constantly making as we code?
- Do I pass this variable as an argument to a function or store it somewhere more globally accessible?
- What do I name this thing?
- Which package does this class or function belong in?
- Should I introduce a layer of abstraction here?
- Should I use a
switch
here? - How will I handle this error?
While these questions may seem insignificant compared to the ones in the previous list, one must be careful. It can only take a few bad choices for your project to start heading in the wrong direction.

Well, no, I'm not suggesting that your software will instantly explode into a glorious heap of flames -- in fact, your code will more-or-less work as intended. But I've seen firsthand how a string of bad decisions can slowly crystallize your software by increasing the time and difficulty it takes to add new functionality and to refactor existing logic.
If at first you don't succeed...
Almost every successful software project that I've worked on has had at least one major rewrite in its early years. Software behaves like a living, growing organism as it evolves over time.
Users (or managers) start to demand new functionality or use-cases. Maybe you hit a scaling wall which requires a major overhaul of the project's architecture to fix. Foundational assumptions that seemed to be immutable suddenly transform into limitations that need to be overcome. Never is software "finished".
So if a few seemingly small decisions can turn the experience of working on a project into something out of a nightmare, then it's important to spend the proper amount of time weighing the options at each decision point.
Enter decision fatigue
With the constant decision-making that we have to do while building a greenfield project, at some point, decision fatigue will kick in. In my head, it usually sounds something along the lines of, "let's just go with this and I'll fix it later." While I can't be sure, I firmly believe that this thought is common among software developers of all ages and abilities.
Software developers around the world collectively write millions of lines of new code every day. With all of this new code comes a side dish of fatigue. Whether caused by overly strict product timelines or simply a lack of mental energy, the results of our decisions will get codified into higher level abstractions that themselves become ingrained into even higher level abstractions written by someone else. And then we're stuck with the decisions that we've made. After all, no one likes a breaking change in a dependency.
I call this Design by Decision Fatigue.

Our software takes the shape of the decisions that we've made in the past. And many of these decisions were simply the best ones that we could have made given the constraints under which we were working. At some point, we have to choose something and run with it. But what do we choose? How can we best prevent Decision Fatigue from ruining the software we've worked hard to build?
Combating decision fatigue
From the dawn of civilization, tools have helped humans to overcome obstacles. In the case of combating Decision Fatigue, there are several tools that I use to free up mental energy, unlock my creativity, and have fun coding instead of agonizing over what seems like an infinite number of possibilities.
Allow yourself to explore
When I have an important decision to make, I explore at least two options to ensure that I'm happy with my final choice. Most times, this involves writing code to get a feeling for each solution.
Some people can map this out mentally, but I prefer to write out my thoughts into code. So when it's time to make a decision, even if this means deleting large swaths of test code never to be seen again, I don't consider the exploration process to be a wasted effort.
Documenting decision points
If I come to the conclusion that I've made a wrong decision, at a minimum, this will result in a // todo:
comment in my code to document it. Or possibly a GitHub issue. For larger, more cross-cutting issues, I'll create a new git branch to fix the problem.
Maybe this branch will be merged directly into main
, or it might land as part of an even larger feature. The critical point is that I've recorded and persisted this decision inside my codebase, in a way that I can easily index.
Know when to refactor
Knowing when to fix a perceived problem now or later is more of an art than a science. But there is a mental model to help: Ruthlessly Deliver Value.
If an incorrect decision will significantly impact the value that I can deliver over the next few weeks, then it's time to fix that choice today. Note that this timeframe is "weeks" -- not "days", "months", "quarters", or "years".
When I'm working on a project as a solo developer at a near-full-time capacity, it could very easily take a few days or weeks to properly weigh the options for the solution to a problem. And the more important a decision is, the more time it's worth investing in your discovery and thought process.
Let tools make decisions for you
Much of my recent development work has been in Go, which, for better or worse, is an opinionated language. While some may not enjoy the infamous lack of error handling, implicit capitalization-as-scope, or the gofmt
tool, for me, these all remove various decisions that I have to make. This helps push the point of decision fatigue further into the future. As a result, I add fresh mental energy to my decision bank for more impactful decisions.
Organize your code

I spend a fair amount of my decision bank thinking about package structure and code dependencies. I do my best to group related functionality into a single package, using proper variable scoping to present a logical public API for other components of my program to import and use.
This acts as a value multiplier, allowing my future self to leverage the work that I've previously done. But nothing is set in stone, and if I come to the conclusion that my abstractions are wrong, I won't hesitate to refactor until I get to a representation that makes sense.
Tracer bullets
I'm a strong proponent of the Tracer Bullet software development methodology. Tracer bullets reduce the write-compile-run-evaluate feedback loop and allow you to focus on an end-to-end solution for a given problem.
This is critical for developing new projects, since a shorter feedback cycle increases the potential number of options to explore for any given decision. Sometimes it's better to just make an assumption and continue down the tracer's path, as long as you leave room for an escape hatch in case that assumption needs to change down the line.
Spend your innovation budget wisely
If time is a major constraint, prototype with tools that you already know. This reduces decision fatigue through the reuse of patterns with which you are already familiar. Eventually, once the project has a more solid foundation, you will be able to replace these tools with something flashier once you reach the limitations of your current stack.
If you feel strongly about a new technology's ability to solve your particular problem, it's true that greenfield projects are often the best times to try something new. I like the idea of keeping an innovation budget. Allow yourself to explore, but don't try too many new things at once. This way you can still move your project forward while also trying out new things.
Automation is your friend
Automation begets more automation. As a foundation is built, you can start to think of your code as its own platform. It's worth spending time on unrelated pieces of your platform that will decrease feature development time in the future. Even if this means pushing out the next milestone by a few days or weeks, your future self will thank you for the automation that you've added.
In summary: Know Thyself
It's important to remember that everyone is unique. Some people work best in the mornings, others late at night. Perhaps you can sling code for 10 hours a day, 6 days a week. Or maybe you prefer shorter, more intense spurts with long walks in between. Whatever your style is, it's important to know how to create an environment that will help you to thrive.
Software development is a mentally taxing profession and hobby. Take care of your mental health and give yourself the space to make sound decisions in whatever way you can.
This post laid out tools that work for me. If you want to share some of your own thoughts, don't hesitate to contact me to help me expand my own toolkit.
At the end of the day, software is shaped by decisions -- big and small. By managing decision fatigue, we build not just better software, but a better development experience.
Like building stuff? Consider applying for our open Core Engineer role if you love database performance.