We know our priorities
(and their order)

  1. Make our customers succeed. Solving customer problems is always the top priority. This can range from talking to them to help them overcome an obstacle or understand something, to fixing an issue that's blocking them, to jumping onto new bugs that we introduce.
  2. Deliver the value the team has already created. This means releasing changes we've already implemented. Shipping little and often means our customers benefit as quickly as possible from our hard work, our feedback cycle is tighter and we're able to react to shifting priorities or external factors more effectively.
  3. Facilitate our colleagues' progress. In particular, we make sure there's nobody currently blocked on their work that we could unblock. This might involve reviewing/merging/testing another engineer's code, finishing up someone else's work if they're busy with something important, or pairing with someone to help them move past an obstacle.
  4. Move our own tasks forward. Engineers have a lot of trust and autonomy to complete tasks. This might mean getting your head down and smashing out some code, resolving technical discussions or making time with UX to get designs in place for a feature.

Strong priorities are only useful if we keep an eye on them. We constantly evaluate our work against our priorities, and leave ourselves in a position to pivot if and when what we're working on isn't the highest priority thing any more. Being able to react quickly sets us apart from the competition and enables to make sure we're always working on the thing that will have the highest impact.

We release good now, rather than perfect next week

Perfect is the enemy of good. We try to slice our work into the smallest useful chunks possible so that we can deliver value to our users as soon as possible. This lets us pivot faster, lowers the risk of each release, and gets us feedback on all aspects of what we're building as early as possible.

Sometimes it turns out that we were going in the wrong direction and we can course-correct without wasting any time. Sometimes it turns out what we've already built solves the problem well enough and we can move on to something else that's higher impact. Sometimes we don't agree completely on an approach but the one we've got is good enough for now.

We take ownership

We don't always know the answers and we don't always know how best to proceed. That's okay — but we make sure that when we pick up a piece of work, we own it. That means pushing it forwards until it comes to a conclusion, even (and especially) if that means bringing in other members of the team or company to help. It doesn't necessarily mean doing all of the work or solving all of the problems. In fact, sometimes owning a task means identifying that it isn't worth doing at all any more.

We reject process for the sake of process

Processes are necessary sometimes, but sometimes they just get in our way without providing much real benefit. We aim to strip out all process except where it's absolutely necessary and provides real value. Where it is valuable, we ask how we can make it as easy as possible for everyone involved - can we make it optional? Can we automate it?

When a particular process stops being valuable, we're not afraid to tweak it or remove it entirely, and we value open and honest feedback about what is and isn't working. A process that made sense for a team of 8 people 2 years ago isn't guaranteed to make sense for a team of 20 today.

We fight silos

Silos don't help anyone. We believe that when knowledge and experience are spread widely, great things happen, and that when they're concentrated in only one or two people we're missing opportunities. Having three people that mostly know how a thing works is preferable to having one person that knows everything about that thing (examples of a "thing" include: a feature, a chunk of code, a technology, a user, a process.)

We're not cookie cutter humans and we can't all work on everything equally, but we actively encourage and empower each other to pick up work in any area. We seek out opportunities to work in an area if it helps to break down a silo, and encourage others to take those opportunities too.

Consistency is good

There are loads of ways to do anything. Sometimes we'll settle on something that isn't what we'd all consider as the best approach, and often we'll all have a different idea of what the best approach is, but we strive for consistency.

The benefit of consistency is flexibility: by following established and agreed upon patterns we lower the barriers for different people to work across any area of the app (see We fight silos). It makes implementing similar features and making cross-cutting changes simpler. That isn't to say we're dogmatic or inflexible - we should all be open to changing our approach where there's a clear benefit - but when that happens we all need to be bought into changing as a team.

We're constructive

Candid, kind feedback is at the core of everything we do. It's important that our feedback is honest, but it's also important that it provides solutions or alternatives, not just problems. Providing reasons why an approach isn't working is only of real value when we have another option that we can agree on. We strive to help our teammates find those solutions and move past obstacles. As reviewers of each others' work we always share our opinions on how we think things could be improved, or point out things that we don't think are quite right, but we recognize that not everything can or should be improved right now.

We don't point fingers
(ship happens)

Bugs happen all the time. Even in production. When that happens, there's no value in blaming individuals. There is value in spending time to understand the root cause and how we, as a team, can avoid the same thing happening again. Mistakes have value as long as we can learn from them.

We understand the problem before we try to solve it

We don't blindly build features. We take the time to understand what problem we're trying to solve and why, whether that means talking to the customer that was having a problem, or investigating the root cause of a bug thoroughly to make sure we understand it. We try to understand all of the reasons that a problem happens and examine at which level we should try to fix it.

Sometimes the problem isn't in the code — perhaps the messaging or overall approach is wrong. Solving an issue without understanding it risks improving things for one or two customers but having a negative impact on the majority, or having little impact on anyone but increasing the complexity of the product we have to maintain.

We treat causes not symptoms

It's often a lot easier to fix a specific issue or bug by introducing a special case than finding a root cause. Unfortunately, these only ever build up over time, and eventually all of your problems become interactions between these edge cases which are very hard to reason about. Instead, we prefer to find the real root cause and fix that.

Having a crisp collective understanding of our domain and our code is fundamental to building systems that are reliable and are still easy to change years down the line. This applies to customer issues too — see We understand the problem before we try to solve it.

We identify the compromises that we're making

As software engineers we have to make trade-offs all the time — between different approaches to a problem, between time to get a job done and coming up with the best possible solution, and between different priorities of all kinds. Sometimes we have to decide that introducing some technical debt is the most pragmatic way forward, or that we're going to support one customer workflow and not another.

Identifying these trade-offs and making sure we're all aware of them is key to making sure we make good decisions, create good solutions, and spend our time effectively. We won't choose the best trade-off 100% of the time, but knowing the compromises we've made helps us to correct our course later.