Prototyping is derisking

In the debate between upfront requirements documents and prototyping, I've always been squarely on the side of prototyping. Here's why.

There are some valid concerns that could push towards spending more time gathering requirements and writing design documents, instead of going right into the code and hack things out. You don't want to waste time building something that nobody wants, after all. Implementation time is expensive, so any time spent thinking about the path in advance can prevent a lot of work down the line.

There is also a healthy amount of distrust towards the engineer's natural tendency to get building. Let's admit it, it's the fun part.

Yet I feel this is misguided. Both design documents and prototyping serve the same purpose: to explore a problem, not to make the final product. In the same way that starting from the prototype and going all the way to the end product often makes a janky result, refining the design documents endlessly is only working with a fictional version of the future. Anything is possible until the first line of code is written, any big decisions can be changed later, it has not cost anything yet, if you don't account for the time spent in meetings and document reviews.

For me, when starting a new project, the most important task is derisking it. I want to know if the problem is worth solving, if the solution is feasible, if the product is something that people want.

Design documents are okay at this, but they only deal in potential issues. It is a good thing to discuss with users in advance, to get a good sense of their needs and pain points.

But from that point on, you want to maximize the amount of learning you can do with the least amount of time. Prototyping is the best way to do this.

The important part is not setting out to make a final product from the start. It is about exploring the problem space, and finding the best solution. Assume that you will throw away everything you have written. So the goal is to make the smallest implementation that allows you to learn something.

Here's what I generally try to assess:

But it would be a mistake to explore all of it at once. There's too much risk of getting sidetracked. Instead, timebox it. Set aside 1 to 3 days and see how far you get. In my experience with moderately complex problems, even 3 days can be too much - by then you've usually moved past prototyping and started building real features. As a data point, the Rust to eBPF compilation took me about 3 days. Knowing if it's doable and having a rough time estimation is already good data, because you can come back to your team and discuss the next steps with useful data. Maybe that's the point where you must ask more questions from users, maybe you decide to table it for now, until you have more time to allocate.

You are allowed to go through multiple alternating (or concurrent) phases of prototyping and designing. They are not mutually exclusive, and you can use the data from one to inform the next. The important part is to maximize how much you learn. Get more data, infer more questions, go back to gather data, and repeat.

The approach you take to prototyping matters too. To that end, the prototype should be adapted to the project frame. For more research oriented projects, start from a blank state. If it has to be integrated in an existing project though, start inside it, because integrating properly can take a lot of time. For the migration from macro based parsers to function based parsers in nom, I had a hybrid approach where I deactivated most of the library to start from a smaller version, and validate the solution, then see how it could expand to the entire library.

As a side point, I know people have complained that Rust is a bad language for prototyping because they spend more time fighting the compiler than writing code, but for this style of prototyping, it is actually pretty efficient. The compiler will quickly raise edge cases you might have missed. You can easily ignore them for now (unwrap is your friend), but they help you start thinking about the proper architecture needed to handle them properly. While a language that would not complain about edge cases and errors would allow you to write the code slightly faster, you would only discover the issues much later, when they get more expensive to fix.

Also, keep in mind for which purpose you are prototyping. If you are testing an unproven solution, you need to come back with working code and a good explanation. For a new feature that isn't planned yet, focus instead on creating a compelling demo that shows what it can do and outlines clear next steps.

Ultimately, be mindful of how much time and energy you invest in the prototype. The job is to make the product, get users to operate it and keep it working. Anything else, whether it is prototyping, design documents, meetings, etc, that could slow down that job, should be kept to a viable minimum.

Prototyping remains one of our most powerful tools for managing risk in software development. It bridges the gap between abstract planning and concrete implementation, helping us validate our assumptions quickly and cheaply. While it's tempting to either skip straight to production code or get lost in planning, good prototyping finds the sweet spot: just enough exploration to make informed decisions, but not so much that we lose sight of the real goal: shipping working software that solves real problems.

Remember: the best prototype isn't the one with the cleanest code or the most features. It's the one that teaches you what you need to know to move forward with confidence.