Engineering at a startup can be a frustrating experience.
I started my software engineering career in my early twenties at a tiny pre-revenue startup. Most of my teammates were also relatively inexperienced, and we made some really boneheaded decisions that weren’t ultimately fatal (thankfully). If I could send a letter back through time, this is what I’d tell my younger self about how to survive the crazy experience of being an engineer at an early stage startup:
- Focus on the important decisions
- Expect to throw away most code
- Invest in your build system
- Don’t prematurely optimize your architecture
- Don’t roll your own version of technology that isn’t central to your business
Most decisions don’t matter that much. It doesn’t matter what you name that variable. It doesn’t matter if you use an interface or an abstract class. However, some foundational decisions really matter, like the core technology or database that you’re building on. Screw one of these up and you can literally cause yourself years of misery. Focus on the important decisions (and consider that when picking your core technology, the boring solution is often the right one).
You’re going to throw away almost all of your code. You’ll think that you have a great understanding of your potential customers, build a bunch of features to sell them, and find that they’re all useless because it turns out that it’s really, really, hard to separate people from their money. When one of these aborted MVPs fails, and even afterwards as you nurture the tiny flame of initial product / market fit, you’ll often delete disturbingly large amounts of code. Expect this cycle to repeat multiple times.
When you’re at a Big Company, you can afford to undertake a massive refactor that will take 3 months to build and pay dividends in a year. When you’re at a startup, you might be dead in a year. When you don’t have product / market fit, the only thing that matters is iteration speed.
Until your product has at least 10 paying customers (that is, a Real Business™), keep in mind that…
- You don’t need 95% test coverage.
- 100 LOC methods are gross but probably ok.
- Copy/paste can be your friend – definitely not your best friend, but perhaps your crazy friend who you hang out with after 2am on Saturday night.
- Ultra polished, style-guide-ready perfect code is probably a waste of time.
Invest in a fast and reliable build system. See above – you’re going to throw away almost all of your code, so you might as well get efficient at churning new stuff out quickly. This will both save you time and also mute the inevitable pain of rewrites and starting from scratch when early products don’t pan out.
Fancy design patterns pay dividends when you have 100 engineers, but right now you don’t even have 10 people at the entire company. If you’re making a decision for the benefit of other developers, make sure they’re sitting in the room with you. If you’re making a decision for the benefit of hypothetical future teammates, you’re prematurely optimizing your architecture for a future that may never exist.
The next time you’re tempted to use a design pattern because it “seems cool,” you need to take a deep breath, step into a cold shower, and rewrite your feature in the simplest way possible. If your product survives, there’ll be plenty of time to use all the recursion you want. Until then, focus on making your code understandable and easy to iterate upon.
And finally, if you ever have the thought “I need to write my own version of ${X}”, and X is not directly related to your core business, realize that you are almost certainly wasting your time. Do not write your own database code, do not roll your own authentication, and for the love of God do not write your own networking code. Even if you’re successful and don’t accidentally create a gaping security vulnerability, you’ll have created an unmaintainable monster for the next poor soul who inherits your mess.