Fast-forwarding decision making

Leave a comment

All organisations waste a huge amount of time believing that they are making progress on decisions, when in fact they’re just involved in the theatre of decision making. This happens through indirect actions that feel like progress is being made, but in fact contribute nothing to it. Small changes can speed up progress dramatically.

This post is an expansion of an internal note that I circulated at work, mostly as advice to myself rather than for anyone in particular. This is because the behaviours that I’m going to outline are unbelievably easy to fall into when you are trying to create ideas, gain consensus, and ultimately make decisions. They feel like progress — little pushes that you can do and feel good about — but ultimately they just create delay.

I’ll pitch the takeaway up front, and it’s this: hold yourself accountable for making decisions and progressing discussions as quickly as possible, by whatever means necessary. Be restless while a decision hasn’t been made. Dead time is your enemy. Be creative about ways of shaving minutes, hours and days from a decision point.

Some scenarios to think about

Let me outline some scenarios where time gets lost. Then, for each, I’ll pitch some alternative ways for progress to be made, faster. These scenarios are simplified for the purpose of this post, but I’m pretty sure they’ll seem familiar to you.

Bug #1: Letting free calendar slots decide your timeline

The first scenario is tragically common. Here’s what happens.

In order to make a decision, you find the list of people that need to be involved in order to reach consensus. Let’s say in this particular case it’s seven people that represent several teams that your decision has an effect on.

Then you open up your calendar and find the next available free slot where the group can meet for an hour to talk the idea over. This happens to be in nine days’ time because of the busy schedules of one or two people in the group. “Great,” you think. “I managed to find a slot with all of us!” 

You smile, because you know it’s hard to find time with this group.

You put some notes in the calendar invite. You write that you want to discuss your current thinking about a new project that your team is doing next. You attach a short document with some notes outlining some different approaches you’d like to discuss and that you want help picking one. 

Then nine days pass where absolutely nothing happens.

Even though you thought you’d made progress — and wasn’t it amazing that you could even get this group together! — you’ve just allowed arbitrary availability in everyone’s schedule decide that your work is going to take another nine days. When you think about it that way, it’s a bit preposterous.

Instead, in an alternate universe, maybe the following happened.

After booking in the meeting and attaching your notes to the agenda, you send a message to the group outlining what your project is trying to achieve and that there are several ways to do it.

You enumerate the approaches in the message and highlight that you think that the second option is the most reasonable. You also say that you’ve reserved a meeting slot in the future if it needs some further debate. However, within 24 hours, all of the people in the group have replied asynchronously to say that they agree that the second option is the most viable, and they’re supportive of your team putting a prototype together. The meeting isn’t needed, and it gets cancelled. You then start building the prototype.

You just saved eight whole days in this alternate universe. And it was just by investing twenty minutes to write a message.

Bug #2: Being afraid of sharing anything other than perfection

Here’s another scenario.

You’re building that prototype, and it becomes apparent that there are several options that you could take to serve the generated data, each with clear advantages and disadvantages around speed, eventual consistency and access patterns.

Given that your team is producing a feature that others are going to use, you figure it’s a good idea to get their input. After all, they’re the customer. You spend an afternoon writing a document containing everything that the prototype has helped you learn so far, and you outline the options to serve the generated data.

However, you’re aware that there are some influential and senior engineers in the group that you’re going to be sending this to, so you want to make sure that you’re not wasting their time when they open it. 

You decide to take the next few days to make sure that your document contains absolutely everything that they would want to see. You spend hours constructing flow diagrams, putting together code samples, detailing and formatting example requests and responses, and referencing all of the different websites and resources that you’ve used in your research in the footnotes and appendix.

When you’re done, you’re proud: it’s a document of great beauty. In an act of pure Friday afternoon celebration, you hit the share button, list out the group that you’re sending it to, and boom: it’s done.

Then you sit and wait for the comments — and maybe even praise! — to roll in. Thirty minutes later, you see a message come through from one of the engineers. It says “we’ve got internal libraries that already handle this problem for you”, and they send you a link to the GitHub repository and the documentation. 

You’ve spent a week trying to solve a problem that’s already been solved. What a waste of time.

In an alternate universe, maybe the following happened.

After realising during the prototype that there were multiple ways in which data could be served from your feature, you decide to ask the teams that are going to use it for their input. After all, they’re the customer. 

You send them a group message: “Did you all have any thoughts on how you wanted to access this data from our feature? I’m just starting to look at this.” One of the engineers replies. “We’ve got internal libraries that already handle that for you, have a look,” and they send you a link to the GitHub repository and the documentation. 

You use that library and finish the prototype more quickly than you thought you would. It’s still only Monday afternoon.

Your alternate universe self just saved almost a week of work.

Bug #3: Relinquishing control into the ether

Just one more scenario, and then I’ll stop.

You’ve been working on hardening up your prototype so that you can deploy it into production for some initial testing with the other teams. Given that it needs to go into the live environment, you spend a few days writing documentation, increasing test coverage, and tidying up some of the hackier bits of your code from earlier in the week.

Once you’re done, you write up a detailed pull request description, and then you submit it for review. You see the CI system build it, run the tests and then produce a green checkmark saying that it’s good to go. You’re pretty pleased with what you’ve done, so you close the browser tab and you wait for those reviews to come in.

In tomorrow’s stand up meeting, you say that you’ve finished the work, but you’re just waiting for someone to review it. You find yourself saying the same thing the next day as well. And the next day. You get a message from the other team wondering when they’re going to be able to start trying your service out in production.

You’re a bit puzzled by the response. “I’m still waiting for reviews, and it’s been three days,” you say. “Why didn’t you tell us?” they reply. “We’ll look at it now.” One hour later, and after a few comments, it’s merged and deployed.

In an alternate universe, maybe the following happened.

After submitting the pull request, you send a message to the teams that are going to use your service to say that it’s available for them to look at. You ask that if they’ve got any time, you’d appreciate some eyes on it. “Sure,” one of the engineers says. “I’ll check it out shortly after I’ve finished this deploy.” One hour later, and after a few comments, it’s merged and deployed.

Your alternate universe self just saved three whole days by sending a message.

Now it’s your turn

Isn’t it amazing how much time we’re willing to waste without really thinking?

You have to take complete ownership over the speed in which decisions are made, and do whatever you can to bring those decisions in faster. Your project, team or company is all about progress: the speed in which you ship, iterate, or fail and learn. 

If something is slowing progress down, then what is it? And how can it be eradicated?

Go into the next couple of weeks with these scenarios in mind, and see whether you can identify them in yourself and others. Give some of the alternative scenarios a go instead. See how you get on. 

Slices of time saved every day, compounded over weeks and months, can actually be the factor that makes the difference between hitting an ambitious deadline and missing it. You might have the most brilliant programmers in the world, but how many days are being lost between the code? Weeks can be turned into days, and days can be turned into hours.

Removing uncertainty: the tip of the iceberg

comment 1

We spend a lot of time working on things that nobody else knows exists.

A seemingly simple act of serving up a photostream with infinite scroll may require global infrastructure, aggressive caching and data being mirrored at the edges of a CDN.

I’m sure there’s some kind of inverse correlation between the delight of a user experience and the challenge of the implementation in software at scale.

This is because in order for the tip of the iceberg to be visible and beautiful, it has to be supported by the 90% of it that exists underwater. The part that others aren’t able to see. 

The bigger the scale, the bigger the iceberg. 

The better the design, the simpler it appears to everyone else.

Great design fools the world into thinking that the underwater part of the iceberg doesn’t exist. But it does. And it’s hard.

You know the feeling: your design looks so incredibly simple, but the work to bring it to life is immense. And what’s worse is that others don’t understand why your estimates are so large, if you can even come up with reasonable estimates at all. 

So it’s no surprise that teams find this challenging to navigate. 

How can you get your project off the ground when you need to spend 90% of your time underwater building all of the supporting infrastructure? And how can you prevent the team from getting stuck or producing no visible work for a long period of time?

Well, you’ve gotta understand how to build that iceberg. But this requires a different way of framing your priorities. Teams that struggle to ship big features are often lacking the right strategy to construct their iceberg, rather than lacking the right skills

When you have a complex project, you’ve got to change the way that you look at it.

A mindset shift: reducing uncertainty

When you’re staring a huge, challenging project in the face, don’t align your team around just getting it done. Instead, align your team around continually reducing uncertainty

You reduce uncertainty until the software exists. You reduce uncertainty by doing: prototyping, designing, writing code, and shipping. Each of these actions serve to reduce the uncertainty about what is left to build.

When you have zero uncertainty, the feature has shipped. Until then, when you have uncertainty, you aggressively work on reducing it by taking positive action. By doing.

You prioritise the most uncertain parts of your project and focus your efforts on getting answers. Answers fall into two broad categories: that it is possible, as proved by code, or that it’s not possible, but yields another avenue to try. You repeat this process until you’re done, or until you think it’s best to stop.

Focussing on reducing uncertainty builds momentum and trust both inside and outside of the team.

This is because the more uncertainty that you reduce, the more predictability that you gain, and the closer that you get to your goal.

And the more predictability that you have within your team, the more possible that it is to commit to your milestones. Stripping away uncertainty backs the contracts that you have with each other and with the rest of the company. 

It says “we’ve got this.”

The worst project crunch happens when uncertainty hasn’t been stripped away up front. If you leave the most uncertain parts until last, you’ll be dealing with them right before the deadline, and the most uncertain parts always have the biggest probability of blowing up in scope and complexity.

Yep, we’ve all been there.

But how do you reduce uncertainty? Let’s look at some examples that you can use as you piece together your own icebergs.

The early stages: prototyping

Your best tool for removing uncertainty at the start of a project is prototyping. And that isn’t just something that designers do. It covers all parts of the stack.

Getting your metrics defined

At the beginning of every project, everything is uncertain. And I’m not just talking about the way in which it needs to be built: I’m talking about whether something even needs to be built at all. So many projects fail because they don’t come up with success metrics that show how that particular project is going to move the needle and be valuable to the company.

If your project doesn’t have success metrics, you probably shouldn’t even start it until it does.

So define them:

  • What is this new piece of engineering meant to achieve?
  • Is it meant to drive up daily active users by offering a compelling reason for users to log back in?
  • Is it meant to increase the average session time by providing more content?
  • Can you be specific, with numbers and percentages? With revenue?

I’ve written before about leading and lagging indicators. Defining them up front is one of the most impactful things you can do to reduce uncertainty down the line. You can’t set sail unless you know where you’re going.

Don’t wait to iterate on UX

If you wait until you build something before you start testing it with others, then you’re carrying a huge weight of uncertainty. We all have access to tools that allow us to prototype our UX ideas without writing a single line of code. 

Figma prototypes, for example, can look just as real as the final product, but can be built in one hundredth of the time. The more time that something takes to assemble, the more expensive and challenging that it is to throw away. So if you need to rework again and again, do it up front.

Getting your prototypes in front of users—even if they are just internal—can result in significant iterations of the UX before a single line of code is written, driving down uncertainty immediately at the beginning of the project. You might have hundreds of fresh pairs of eyes in your company: use them. They might save you months of work.

Prototyping architecture

Often folks think that prototyping is something that only applies to the user-facing parts of the application, but that is absolutely not true. You want to make sure that the way that you’re storing and accessing your data is right: remember that your job is to reduce uncertainty.

Even if you’re sure about your database schema, don’t let its first real test be with actual users. 

Do some back of the envelope calculations:

  • Think about the scale that you need to hit now and in the future.
  • Create those tables and load them up by generating dummy data, and see how those joins hold out when there are millions or billions of records.
  • Make certain that any obvious future additions to your data models have clear paths to follow and that you aren’t painting yourself into a corner.

And if you’ve prototyped your storage, you’re well on the way to defining contracts.

Build: contracts and meeting in the middle

So, you’ve finished your prototyping and you’re feeling good. It’s time to build, and that involves willing that 90% of the iceberg into existence.

However, it shouldn’t be a long march until it’s done. There are still strategies that you should use to keep reducing uncertainty along the way. That typically involves establishing clear interfaces and contracts.

Make contracts if you can’t move in lockstep

Big icebergs need big work. And sometimes, no matter how you cut it, it’s too hard to get everyone across the stack building concurrently in perfect unison. In order to keep things moving use contracts to bridge the gaps.

But what do I mean by contracts? Well, it’s simple really: just make it really clear what is expected from various parts of the system up front, so you can build against those specifications. 

A canonical example is defining what the API responses and payloads are going to look like before you actually build them. It reduces the uncertainty at the boundary between the API and the frontend, and it breaks the dependency between engineers, allowing them to focus on building their component pieces against the pre-agreed contract, rather than waiting for each other to be ready.

Contracts go beyond this though. They can be contracts about the desired performance and trade-offs of the system. For example, which interactions need to be synchronous and which can be asynchronous? What’s an acceptable P99 for a certain action? What sort of size of response are we expecting in each scenario? 

Defining these contracts up front not only decreases uncertainty, but it also opens up possibilities for building better user experiences by understanding how the machinery is working underneath and working with it, rather than against it.

Static data can replace whole backends

If your team is building a particularly complex backend system that may take some time, then you might even want to build an API with static data so that frontend engineers can work against something that feels real, even if it isn’t. You could create database tables containing dummy data with the real API in front of them, or you could even just serve up some static JSON files.

Not only does this reduce uncertainty by getting to something that feels like a real working system more quickly, it allows all of the inevitable snags and omissions to come to the surface much, much quicker. That missing sort option you forgot about? That additional column that would be useful to store? Yep, you’ve experienced that snag before the system is built.

If you’re a backend engineer on a complex feature, never let the first time that your API or data is used be when you’ve finished building it. Let static data take the initial contact with the rest of the system so you can build the real thing with far less uncertainty.

High fidelity mock-ups leave nothing to guesswork

Moving up the stack a little, there’s nothing worse than deriving a UI build from intuition and then realising that it just doesn’t feel right when you use it. Whenever you can, use high fidelity prototypes that look and feel like the real thing in order to feel the product—and whenever possible, run it by users—before it is built.

In addition to uncovering all of the snags that exist in your workflow designs, your high fidelity mockups make frontend work so much easier. It acts as another form of contract, allowing engineers to work from them side-by-side with their code, asynchronously, and there is no doubt what pixel perfect looks like: it’s right there in the mockup.

This frees up designers to focus on future iterations, laying out the path for the team to follow.

Launch: the tip of the iceberg

Once you’re getting ready to ship to users, you still need to focus on reducing uncertainty. And there’s a lot of it when it comes to launching and scaling a product.

Feature flags are your friend

If you’re not already deploying continually behind feature flags, you should be. Feature flags allow you to deploy code into production early but only toggle that functionality on for specified users or cohorts.

As well as allowing you to do A/B testing or to release early to beta groups, feature flags allow you to reduce uncertainty by merging your code into the main branch far, far earlier. This prevents chains of pull requests, stale branches, and most importantly, prevents big bang launches where you need to ship to production and launch a product at the same time.

Feature flags allow you to use your new features for real, even if you’re the only ones that can see them. And then the best part is, when you’re ready to launch, you just turn on the feature toggle for all users and clean up the toggle in the code. 

You just lift the curtain and show what was already there. Your marketers can run the go-to-market campaign when they want and with confidence.

Shadow deployments

For many engineers, the difference between development environments and production environments can be vast. It’s like crossing a chasm. 

Users, traffic, and increase in data means that any new backend system that you are deploying is going to be under significantly more load in production when compared to development, and the shape of real usage and traffic is always different to any synthetic load testing.

You can greatly reduce uncertainty by using a technique called shadow deployments, which is where you deploy your new backend system into production and route traffic towards it even though it’s not being used. If you can measure both ingress and egress paths, you can observe your new system under real load without the risk of users experiencing any performance problems. They don’t even know that it’s there.

In the past, I’ve been part of projects that had shadow deployments of infrastructure happening at the multi terabyte scale. And if it went wrong, nobody knew. And what’s great is that the longer you have a shadow deployment running collecting real data, the less you have to migrate when it’s time to switch it on for real.

Standing on the shoulders of giants

Big projects are all about reducing uncertainty while you build the 90% of the iceberg that nobody else is going to see. We’ve touched upon a number of strategies that you can use to do so.

However, here’s a closing thought: given that a zero to one project build involves investing significant amounts of time constructing our iceberg underwater, why don’t we spend more time capitalising on it when it’s done?

I’ve noticed that the most productive time for shipping additions and improvements to a feature is in the 6-12 months after it has been initially launched. The code is new, the context and kinetic energy within the team is high, and there is often a rich backlog full of ideas that didn’t quite make the cut for the first version.

Making sure that a team is able to continue iterating on what they’ve shipped allows for low uncertainty improvements that are all visible at the top of the iceberg.

But here’s the catch: some organisations are so desperate to move on to the next feature (see feature factories) that they completely miss this magic window where the team is on fire.

So if you’re a leader, don’t be so quick to move on: your most predictable and impactful work can come in the period right after shipping. The iceberg is there. You can keep building upwards.

Software development can spend a lot of time underwater. But if you think about projects as a process of aggressively reducing uncertainty, then you can maximise the opportunities for the tip of your iceberg to be visible to yourselves and others as you progress. And once it’s built, don’t be so fast to move on.

Know your metrics, prototype and iterate while it is cheap to discard work, create contracts to break apart dependencies within the team, and have code in production under load way, way before launch. 

Then, when it’s done, keep building.