A photo of a field

Breaking down complex projects into smaller, shippable increments

Engineering
Picture of Lisa Karlin Curtis
Lisa Karlin Curtis

Building a complex new product can be scary. What if no-one gets value from it? What if it doesn't work? What if it's hard to change?

One way to mitigate these risks is to break down the product into smaller shippable increments, allowing you to capture feedback early and confirming the most important assumptions before fully committing to a solution.

🚢 Why ship?

At incident.io, we believe that shipping is the heartbeat of any product company (see a great Intercom blog post on the subject). Shipping helps us as a company by:

  • Delivering value: Our goal is always to deliver value for our customers - if they can use something we build a week earlier, that's good news for them which means good news for us.
  • Gathering feedback: The earlier our customers can see the new feature, the sooner we can get feedback on the approach we're taking, particularly in terms of UX. Customer feedback is really important for us to shape our product, helping us continue to solve impactful problems in an intuitive way.
  • Validating an approach: We want to fail fast: if our approach doesn't work from a technical standpoint, we'd like to know as soon as possible so we don't continue to invest in something that isn't working.

When we're building something complicated, these things are even more important than usual. So rather than waiting until we've reached the final form, we look for ways that we can ship chunks of the new product along the way.

⌛ Won't this take longer?

Shipping smaller increments along the way can feel like you're taking a detour; slowing the team down on their way to the real prize. But it's not that simple. Due to the benefits of shipping we discussed above, gathering feedback and validating technical assumptions means the team is less likely to waste effort. That can often shorten the time to complete the product you really want, rather than the product or design you imagined at the start.

Splitting a large project into several shippable increments has a number of other benefits:

  • Shipping incrementally gives a team clear achievable goals to work towards, and keeps everyone pulling in the same direction. Shipping also feels great, and provides energy and momentum to keep the team motivated through a large project.
  • Most teams have unplanned work (e.g. bugs or customer requests) which can't be left untouched for a full multi-month project. The natural pause-points created after shipping an increment are a great time to address this work, which is much easier than having it constantly distract throughout the project.
  • Splitting the project reduces uncertainty. Software engineers tend to significantly underestimate the final 20% of a project, so if you plan on a multi-month project scale you might find yourself significantly underestimating a very large slice of the pie. It's generally better for external stakeholders to deliver smaller increments of work as predictability can be more valuable than absolute speed.

🕵️ Finding shippable increments

It's all well and good talking about shipping smaller increments, but how can we break down a complex product?

Our first instinct (correctly) is to find a feature which delivers value to a customer. This is likely to be a subset of the functionality that your complex product can deliver.

A useful way of finding these increments is to think about use-cases: what might someone be trying to do with your new product? Are there ways that you can enable just a single use-case without having to build the entire product?

Ideally, you want to have a list of candidate use-cases which you think are compelling and can support with a sub-set of the original product. You'll also need a sketch of what would be needed to deliver each one.

⚖️ Selecting & prioritising

Once you've got a list of candidates, you need to choose which one(s) to deliver, and how to sequence the work. As with all engineering and product problems, there are no easy answers: there are a number of factors we need to trade-off with each other. We can return to our three objectives:

  • How much value does this feature deliver? Will people use it?
  • Will this feature enable us to gather feedback? Are we exposing new UX patterns or concepts which we intend to use in the final product?
  • Will this feature validate our approach? Will it use the bits of the technical design which are most novel or most difficult?

The way we prioritise these three objectives should be specific to the product that you're building. Which of these is most true?

  • I'm worried that our customers will lose faith in us if we stop shipping valuable things (Delivering value)
  • I'm worried that the product we're building won't add value (Gathering feedback)
  • I'm worried that the technical design isn't great, and it might be slow or fragile (Validating an approach)

When building a house, you have to build the foundations before you build the walls. Software is no different; there'll be parts of the code you have to write first. This is something we also need to consider: if supporting this use-case would actually require you to build 95% of the final product, there might not be much point shipping this subset first.

Another way of framing this approach is to prioritise reducing uncertainty. Uncertainty about whether the product is valuable, or whether the technology works.

Graph
      illustrating that shipping multiple times during a project reduces
      uncertainty

Depending on the size of the product, you might just want to choose a single use-case here, or you may want to choose a number that you deliver in sequence. Generally, the larger the end-product, the more good candidates you're likely to find. Equally, don't force it: if there are no good candidates, you might want to take a different approach.

🤫 An alternative: silent shipping

Some projects might be better served by silently shipping parts of the product. This usually means taking something that your existing product already does, and doing some rewiring so it's now using your new code.

This is useful when you're trying to validate the technical approach (rather than gathering feedback or delivering value). You can get your code running in production, see how it performs, see how it feels to build and debug against, without needing to find a shippable unit which delivers new value.

Note that as this doesn't provide the opportunity for feedback, or direct value to customers, we'd generally prefer to ship a user-facing feature where possible, but this is a useful fallback when that isn't feasible.

💭 Closing thoughts

When faced with a large scary project, first think about what makes it scary. Is it uncertainty about the product, the technical approach, or perhaps both? Break down the project into smaller shippable increments, designed to reduce this uncertainty and keep the team shipping, to give you confidence in your approach and retain momentum and focus.

See related articles

A photo of Isaac Seymour
New Joiners

New Joiner: Isaac Seymour

incident.io
Picture of incident.io

incident.io

1 min read
Golang tests using the mocked Slack client
Engineering

Using context.Context to mock API clients

Lawrence Jones
Picture of Lawrence Jones

Lawrence Jones

7 min read
The AICPA SOC Logo
Article

We've successfully completed our SOC 2 audit

Chris Evans
Picture of Chris Evans

Chris Evans

2 min read