Draft, Delete, Develop
If I think of the best programmer I know, even they don’t have the ability to think through an entire complex coding problem without actually doing the programming. They have to see all the details before they really get it, and usually, they have to do it and then revise because there was a better way.
That’s a shortened version of a quote I heard from Casey Muratori a year ago, and it stuck with me. It made me rethink how I approached larger coding problems, realising I was often committing to solutions too early.
I’d spend a lot of time upfront thinking about how the code should look, shaping a solution based on prior knowledge and past experience rather than a deep understanding of the problem. I’d start coding and as obstacles appeared, I’d adapt that single solution because I was subconsciously locked into it. I’d get it working, adjust it to be ‘right’ according to my initial mental model, and then move on—never forcing myself to step back and critically assess whether it was really the best approach.
That’s why, over the past year, I’ve changed how I tackle bigger coding tasks. Focusing on building solutions through facts rather than feel.
In practice, this means drafting a working solution, throwing it away and then developing it again.
Draft
I treat this first attempt as pure exploration. I’m trying to get to a working solution, but my focus is on learning. I’m trying to uncover anything that will help build a clearer, more complete picture of the problem.
I’ll run into unexpected issues, uncover hidden dependencies, and gain a better feel for how the architecture should evolve. Along the way, I note what feels clunky, what works well, and what should be structured differently.
Delete
Then, I throw it all away and start fresh with a blank canvas.
This step forces me to reflect and eliminates the temptation to keep building on a solution, which might have been flawed from the start.
I revisit my notes and begin to map out a clearer vision of how my final solution should take shape.
Develop
By this point, I (hopefully) understand the system surrounding my problem. I know my way around the dependencies, I know what problems will arise, and I know how I want the architecture to pan out.
The second time around, my solutions are usually:
- Simpler – I know which abstractions are useful and which aren’t
More robust – I’ve already encountered edge cases and can design around them
Easier to maintain – Because it’s built with real-world constraints in mind
Why I find this approach works
It forces me to think critically about what I’m building. It removes the autopilot. I get time to explore, step away to reflect, and then return to build a solution that fits the real constraints. By going through this process, I let the facts rather than intuition shape a better design.