Curious about our tech stack, internal processes, and our engineering principles?
As a small product-focused team we find it useful to have a guiding principle to make technical decisions easier.
None of us are obsessed with specific technologies or programming languages. We're more concerned about how to deliver user value than adopting the latest frameworks.
We like lindy tools that will stand the test of time and are proudly late adopters of new engineering trends.
Most of the technical decisions can be traced back to the all-time tested principle:
Keep things simple
Why do we keep things simple
Simplicity is a prerequisite for reliability.
Great programmers build reliable systems. Therefore great programmers focus on understanding and searching for simplicity.
Simple is a fascinating word. The origins of this word are sim and plex, which means one fold or one twist. And that characteristic about being about one twist means that it cannot be twisted on itself.
The opposite of simple, is complex, which means folded together.
If we want to build software that is reliable, we need to keep it simple. This means we need to make sure it does not fold together and get tangled like a pair of earphones.
How we keep things simple
We value individual contribution over collaboration.
When n people have to communicate among themselves, as n increases, the output decreases.
Collaboration adds complexity to your output following this formula: n(n − 1)/2
We're currently a team of 6. So 6 × (6 – 1)/2 = 15 channels of communication.
Each team member adds overhead to our effectiveness to execute. As startups are all about speed, we need to be careful about this.
For this reason, our early team is only experienced, individual contributors.
Much as a surgical team during surgery is led by one surgeon performing the most critical work while directing the team to assist with less critical parts, we have owners of projects develop critical system components while the rest of a team provides them what is needed at the right time.
Each project has an explicit problem to solve for our end users.
Each project has a lead, to understand, scope, engineer, and direct work until the problem is solved.
Here's the google doc template we use to track the progress of our projects:
Build it the boring way
We use battle-tested technologies in the most boring and opinionated way possible.
This means that we try to stick as much as possible to the happy path and do things in the most standard way.
As a result, it made the choice of our programming languages and frameworks extremely easy. We use Rails for our backend and React and Typescript for our frontend.
Even when using React, we do it the boring way, for example using Redux for global state management.
Buying over building
SaaS tools don't add communication overhead and don't distract us from our goals.
So as a rule of thumb, we buy the best off-the-shelf solution for everything that is not the core problem that we're trying to solve.
Even if the solution is more expensive than building and maintaining something in-house, if we free up some energies from accidental complexity, we can channel more focus towards our goals.
Shipping is made of two parts, writing code and releasing it to your users.
Adding new features requires the software to change. The more changes happen at the same time the more complex the transition between the two versions get.
So the way we make changes to our software is by adding and deploying small incremental changes and deploying them to our users.
We don't do releases and we don't do staging. We are always releasing straight to our customers.
Everything we build gets broken down into a weekly task that gets shared in our weekly changelog.
Every line of code we ship gets deployed live to production within 2 minutes to all of our end users.
When features are a work in progress we released them under a feature flag and enable them first for our test accounts, and then slowly to all of our users. This means that if we ship a defective feature we don't need to roll back a big change, but we can simply turn that feature off for the impacted users.
This admin panel we built for managing feature flags can be used by anyone on the team, even non-technical people.
In order to be able to ship this fast to end-users, we built a deployment pipeline.
We started out with a simple Render service that was deployed on every commit, but over the past year, we outgrew that setup. We slowly upgraded our infrastructure trying to keep things as simple as possible and use established technologies.
With this setup, we push updates to our end-users at least 30 times per day.
Some of our users complain that we push too many updates to the product while they're using it. Others love to click on our "Refresh" button as it excites them to keep getting new improvements and fixes.
As we grow as a company we'll keep learning and iterating on how we work, to ensure our output keeps increasing and gets more reliable.
I'll make sure I keep sharing what works for us as we grow. In the hope that it will make you improve how you work and how you make decisions.