How to evolve a product
Jim Fisher
Posted on June 28, 2024
So you started your startup — now how do you find a great product ASAP? At our startup, Granola, we rapidly evolved a product using principles from natural selection! We made our software friendly to mutation by using repetitive code, all in one blob, with no tests. For parallel selection of the best variants, we manually sent programs to target users. And to make our variants friendly to extinction, we avoided all launches, landing pages, and docs. Beware: big-company "best practices" make bad early-stage startups!
The greatest startup story of all time
This guy is Pikaia. He lived around 500 million years ago, grew to around 4cm long, and perhaps swam like an eel. Shortly after, along with most other species, he disappeared.
And yet Pikaia lives on: his descendants are you and me, rulers of this planet, with influence even beyond the solar system, their images engraved forever on the Pioneer spacecrafts! I believe the Cambrian explosion is the best startup story of all time, full of rapid prototyping and forgotten pivots! Like our early-stage startup, natural selection is a search for new, better designs in a shifting landscape.
Some believe that evolution has to be slow, but that's just false. Darwin's theory was influenced by pigeon breeders, who were able to find wonderful new pigeon designs within strikingly short time periods. I like to think of good startups like these breeders: tastefully using mutation and selection to find wonderful new products. My claim: when designing startups, we can draw surprisingly concrete lessons from how natural selection works! For my first example: let's look at nature's attitude toward copy-paste.
Avoid abstractions. Use copy-paste-edit!
How did your arms and legs come to be? Notice a difference between you and Pikaia: Pikaia has lots of repeated segments, but your body has just a few specialized segments. This happens so often in evolution that it has a name: it’s called Williston’s Law. Roughly, it says that nature loves to copy-paste and edit. Nature takes a versatile body part like a leg or a tooth, duplicates it many times, gives each one a chance to specialize, then kills off the excess.
Nature's love of copy-paste can be seen genetically as well. When you were still an embryo, your body plan was drawn out by 39 different "Hox" genes. Each Hox gene corresponds to one part of your body. But our early ancestors had just one Hox gene! Nature then copy-pasted and edited it many times, and now we have 39 of them, each with a different function. In Computer Science courses, they gave us clear, static requirements, and we were supposed to implement these with "clean code". But programs designed for certain, static requirements look very different to programs designed for uncertainty and change!
Say you want to add a new kind of "big button" component. Instead of introducing an abstract "button" concept, try duplicating the existing button component, and allowing it to evolve along its own path. Even if your "button" abstraction is right under current requirements, it can stop you from implementing new requirements! What's more, repetitive copy-paste code is friendly to AI-assisted change.
Avoid splitting stuff up. Use one big blob!
Why does the peacock not ditch that stupid enormous tail? Well, the males can’t, because the females select for it. But the peahen can’t ditch her selection choice either: she'd have sons with small tails, who wouldn’t get selected!
Protocols inhibit change, and the peacock has a broken protocol that equates "large tails" with "fitness". An asexual peacock could ditch the tail. But with two sexes, there’s no way to change without global coordination. Nature's lesson for us: avoid splitting stuff up. And especially avoid any splits that create distributed systems! It’s easier to change one big thing than to change many small things talking to each other. Your professor is not here to punish you any more. It's okay to keep everything in one file!
Even the earliest prototypes often look like this: a client, a database, and an API between them. But with this design, you’ve already introduced two splits!
By contrast, the first prototype of Granola was an isolated desktop app. It talks to the things it needs to, but is otherwise a single blob.
We eventually had to have some server-side stuff. Typically this means building an API layer. But we avoided that by using Supabase, which lets clients directly interact with your central database. Instead of two interfaces slowing you down, we just had one.
Avoid auto-update. Use direct distribution!
Here are some of the bizarre life forms that lived alongside our ancestor Pikaia. Life is always exploring many variants in parallel! That’s the only way to quickly explore a giant possibility space. Yet most software distribution is sequential. v1
then v2
then v3
, and so on. Web apps are constrained to a single “production” version being explored by users. Mobile apps are constrained to a single version published on an app store. Bad for exploration.
Instead, our app is directly distributed via a .dmg file. This means we can make some radical code changes, cut a new build, and send it to a single user. This is great for evolving a product, because we're evaluating many versions in parallel.
(You might be thinking: "But we have feature flags." Good, but you can't make radical changes on a feature flag, because your single production version must act as a superposition of all possible software changes.) In early life, avoid linear auto-update. Instead, use direct distribution.
Help your software versions die
Opabinia lived alongside our ancestor. He had five eyes on stalks, and a single, central claw. But Opabinia's descendants are not alive today — perhaps because he was a stupid design. Most of the Cambrian forms were evolutionary dead ends.
Without death, natural selection doesn’t work! And yet much conventional advice makes it hard for us to kill our bad software versions.
What is a "dead" software version? It's one with no more references to it. Think garbage collection. So, to let your software die, avoid unnecessary references to it. Let's look at common references we can avoid.
Avoid automated tests. Test manually!
So you’ve made a software change, but now the tests are broken. Don't feel bad: it's the tests' fault, not yours!
Tests are chains, stopping your program from running free. The tests say: "Bad code! Stop changing! Go back and be the previous program!" So avoid writing tests. Just test the things that are unlikely to change. Test everything else manually.
(Note: type-checking is different. It tests correctness properties that apply to any program. A good type-checker lets you move fast without breaking as many things.)
Avoid unnecessary users. Build for yourself!
You've experienced this too: you want to remove something from your product, but some obscure users loudly complain, so you give in. Conventional advice says to launch ASAP. But people using your software keep it alive, and so they can slow down change. Instead, avoid all unnecessary users. Build for a small set of target users. (That target might just be yourself!)
Avoid landing pages. Onboard manually!
Conventional advice is to focus on top-of-funnel experience. Build a landing page to sell the product. Write docs and build a slick onboarding into the app. But all these user-facing resources reference a specific version of your product! And so they make it harder to kill that version.
At Granola, we deliberately spent a long time pre-launch. We targeted a small niche, and manually onboarded everyone. This reduced distractions, made it easier to throw stuff away, and helped us practice for when we eventually did build a landing page and onboarding.
Best practices for pre-launch software are very different
Conventional advice says that good code is "clean code" with high test coverage, and that teams prioritize early launches, continuous delivery, and top-of-funnel experience. But in an early startup, your sole focus is to search for a product, and these "best practices" can slow down this search. Try optimizing for change, by using low-abstraction copy-paste, all in one place, with no tests. Distribute your app manually, and onboard people manually. That's what we did. After 12 months, we now think we've found our product, and it might be time to re-evaluate our approach. If you’d like to join our journey, get in touch!
Posted on June 28, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024