High velocity outputfor product engineering teams


The following is an adaptation of a recent talk I presented to the Growth organization at Squarespace. I wanted to share it here as well, since I think it's a topic that's relevant to many teams and organizations in the industry.

I've been in product engineering for a while now and have seen various teams and approaches to improving engineering velocity. Some methods work; others don't. But one thing that's always struck me is how much time is wasted on things that don't matter. I'm not talking about the big stuff—the big decisions, features, or bugs. I'm talking about the little things that add up over time and slow you and your team down.

Most product engineering teams eventually ask themselves some variation of the following questions:

  • Why is it taking so long to get this feature out the door?
  • How can we get this in front of users sooner?
  • How do we move up our test launch to start collecting learnings faster?

These are all good questions, and they can be answered by examining how you're spending your time. Are you spending it on the right things? Are you focusing on things that matter and will move the needle in getting the project out the door?

Broadly speaking, these questions boil down into a single one: How can we ship faster? What changes, big or small, will help accelerate our timeline?

How Can We Ship Faster?

Fortunately, there is some low-hanging fruit with extremely high ROI that many teams are not taking advantage of.

The answer is simple: Code reviews.

But We Already Do Code Reviews...

Sure, most organizations have some code review practice in place, and that's great. But the question is: are you doing them right? Are you maximizing their value to the team and the business? And more specifically: are you doing them in a timely fashion?

Is your team breaking down their work to optimize for code review? Are your PRs a joy to review? If not, you're leaving a lot of value on the table and slowing down your team.

TL;DR for Shipping Faster

It's simple but worth repeating. If you want to ship faster, you need to:

  1. Make code review your top priority, every single day.
  2. Make your PRs a joy to review.

Honestly, that's it! For many (if not most) product teams, it's truly that simple. And I'll tell you why this is true and how you can make both these goals easier to achieve for you and your team.

At this point, some of you might be shaking your heads, wondering aloud: "But Tom, I'm a software engineer! Isn't my top priority writing code, not reading it? I have this huge backlog of work to do, and it keeps growing and growing… Shouldn't I focus on writing code as my top priority and reviewing code second?"

My answer to that is a resounding: No. Reviewing PRs is more important to the team and the business.

Pending Code Reviews Represent Blocked Threads of Execution

As engineers, we know that blocked threads are bad. A blocked thread is a task that is pending but not complete. When a thread is blocked, things lock up, stop moving, and don't get done.

Applying this metaphor to engineering teams: often, a team's "thread" of feature delivery is "blocked" by a PR waiting for review. Features are not released if they are not done, and they are not done if they have not been carefully reviewed. Pending reviews impact not only the engineer waiting for reviews but the entire team. Sometimes work waiting for review blocks another engineer from starting. In the worst case, a single review bottleneck can prevent any other task from being started. Yikes!

The Two Options for Blocked Engineers

When an engineer is blocked waiting for reviews, they have two options: the best case and worst case scenarios.

Best Case Scenario

In the best case scenario, the blocked engineer can put their blocked work on pause and switch to an unrelated task. But this is not a good outcome, as it incurs the expensive cost of memory reallocation and context switching. We'll touch on that in more detail in a minute.

Worst Case Scenario

In the worst case scenario, there are no other pending threads to pull on. If the remaining work is entirely serialized on the blocked review, the engineer can't make any progress at all. They're entirely blocked, waiting.

Best Case Scenario: Incurring the Cost of Context Switching

"Multitasking is a myth. You cannot do two things at once." - Earl Miller, MIT neuroscientist

The cost of context switching is well-known in software engineering. It's the cost of moving your brain from one task to another, remembering where you left off, and getting back into the flow of things. Many of us think, "Not me! I know other people struggle with context switching, but I handle it with ease!" But the truth is, context switching is a real cost we all pay. Science has proved that humans are terrible at multitasking. Don't believe me? Here are some quotes from studies:

“Our data suggests that people compensate for interruptions by working faster, but this comes at a price: experiencing more stress, higher frustration, time pressure and effort.” - The Cost of Interrupted Work: More Speed and Stress"

“Task switching results in increased error rates.” - "Task Switching" by Monsell (2003)

“Multitasking impairs executive functions in the brain, which are responsible for managing cognitive processes. [...] leading to slower overall performance” - "Multitasking Slows You Down" by Verywell Mind (2023)

These studies conclude the same things: multitasking and context switching lead to more stress, slower work, and ultimately lower quality of work than focusing on a single task to completion.

Switching eats our finite memory bandwidth. You have PR A open, and now you're switching to B. You need to keep context about both in your memory in case you need to bounce back to A. The more tasks you have in the air simultaneously, the more precious bandwidth is required. Humans have very finite amounts of memory, unlike computers. That memory can get paged out, forcing us to “ramp up” on the new thing, which is slow. Thus the expensive cost of context switching.

It's better for the business to minimize context switching and better for your personal health and well-being. So avoid it when you can!

Isn't Going from Code Writing to Code Reviewing a Form of Context Switching?

Yes! It is! You caught me. Switching from code writing to code reviewing is context switching and can be expensive. However, this is good context switching: we're using that expensive cost to buy something more valuable: team velocity.

The team as a whole is better off by prioritizing reviews. Reviews represent tracks of work closer to the finish line than your work in progress. Moving work from the “in review” to “done” column is far more valuable for the team’s net velocity than moving a single task from your todo to in progress. The team's velocity is the aggregate of everyone on the team, not your individual velocity.

Said another way: it is far more valuable to focus the team's energy on moving items from "IN PROGRESS" to "DONE" than it is to move items from "TODO" to "IN PROGRESS".

Some agile methods represent this by reviewing the sprint board right-to-left, from closest-to-done to not yet started. This puts the team's focus on “how do we get this over the finish line,” which is the right attitude for high-velocity teams.

Worst Case Scenario: Serialized Work Blocks All Progress

Serialized work is the idea that we need to finish Task A before starting Task B. When work is overly serialized, pending reviews can block all progress. We try to ensure everyone on the team has a few threads available to avoid this, but sometimes that's not possible or realistic. In this scenario, code review becomes a synchronous barrier preventing other work from getting done.

As a team, we depend on one another to meet our goals. Unfortunately, this work often cannot be completely parallelized. Maybe we're blocked on infrastructure issues or waiting on work from other teams to wrap before we can integrate. Working in a serialized way means the entire team moves at the speed of the slowest common denominator, which is not good if you're trying to ship quickly.

Often, when we are blocked from progressing, it's not due to external factors but internal ones: someone writing code or someone reviewing it. So, the best way to unblock the team and yourself is to ensure everyone is reviewing and getting reviewed as quickly as possible.

Tips for Code Reviewers and Code Authors

Assuming I've sold you on the premise of prioritizing code review above other work, let's discuss how to make this happen. Here are a few low-hanging things we can do as reviewers and authors to optimize for efficiency.

Tips for Reviewers

Here are five ways to become a rock-star code reviewer and help your team close work out faster:

Reviewer Tip 1: Always Know What Reviews You're on the Hook For

Always know the tracks of work your coworkers are working on and which need your input. Do you know who is waiting for your feedback right now? Prioritize having a quick way to see all the pending review requests attached to your name.

Some code review systems do this better than others. GitHub offers great controls for monitoring notifications. I like using the GitHub Slack integration, which pushes notifications in Slack whenever I'm added to a PR as a reviewer or someone replies to my comment.

Reviewer Tip 2: Shorten Your Event Loop; Be

Interrupt Driven

Integrate your review monitoring method into your event loop. The platonic ideal is handling code review requests in real-time. Some teams have a sub-15-minute time-to-review loop and are some of the highest output teams in the industry.

Heads-down focus time is essential, but weigh the cost of context switches against the larger switch when blocked on others for code review. It's OK to put off a review for a few minutes to finish a thought. Get to a safe checkpoint, then turn your focus to reviewing.

Don't put reviewing off for a day or even more than a few hours. If you have time for a break, you probably have time to run through your code review requests before pushing up your diff.

Reviewer Tip 3: Prioritize Reviews Over All Other Work (Within Reason)

Reviewing code is more important than writing new code! If you let code go unreviewed, you're prioritizing your productivity over the team's. Everyone operating at 75% efficiency because they spend 25% of their time in code review is better than some members operating at 95% and others idling.

If you have nothing to say on the review, at least acknowledge it. Minimize the time between outgoing and incoming activity on the review to avoid blocking anyone longer than necessary.

Reviewer Tip 4: Expect to Spend Time Reviewing Code Every Day, Multiple Times a Day

Review code every day, ideally multiple times. Your day should begin and end with clearing your code-review backlog. Try to clear the backlog at least once or twice in between.

Inbox zero is the goal here, and spending one or two total hours a day on this isn't unreasonable. Don't do it all at once, though! Reviewing too much code or too many PRs in one go can reduce your effectiveness.

Reviewer Tip 5: Be Kind, Be Specific, Be Thorough, and Be Actionable

The only things worse than no code review at all are rubber-stamp “LGTM” PRs or a single good review broken into many smaller, less-thorough reviews over multiple days.

It's the code author's job to open PRs that are well-formed and carefully crafted. It's the reviewer's job to be as thorough as possible in each pass rather than metering out feedback in a series of partial reviews over multiple revisions. Try your best to be exhaustive in your first pass. If you catch something late in a review cycle, own up to it and apologize. We're only human.

Tips for Authors

Here's how to author code that your coworkers will love:

Author Tip 1: Dot Your I's and Cross Your T's

Make your PR as good as you can. Take the time to dot your i's and cross your t's before anyone sees your code. Be thorough and put your best foot forward. Your teammates prioritize reviewing your code over their important work, so don't waste their time.

Push your code to GitHub as a Draft PR. Then, self-review your PR as if it belongs to someone else. Make sure nothing is missing or unnecessary. Review your own code in one sitting and ensure you only make necessary changes. Put unrelated improvements in the backlog and cherry-pick them to a new branch.

Make sure your code conforms to the style guidelines (or local examples) of the project you're working on. Don't submit a review request and immediately push multiple revisions as you find problems. If you don't like what you see, discard the draft review, fix your branch, push it, and start over. Make sure your code is ready to be looked at once you ask others to review it.

Author Tip 2: Keep Review Requests as Small as Humanly Possible

Bias heavily towards small, digestible, bite-sized pull requests. Large PRs should be broken down into smaller, easier-to-reason-about changes, reviewed in sequence or parallel if possible.

Avoid PRs over 400-500 lines of code. Studies show a correlation between lines of code and bug detection density. As lines of code exceed 400, the detection rate plummets. Don't make it easy to ship bugs. Open small pull requests.

Small PRs get reviewed faster, bugs are more likely to be caught, and approvals are more likely to land sooner. So do it!

Author Tip 3: Write Good Titles and Descriptions

Write good titles and descriptions in your PRs. The title should distinguish it clearly in a list of other review requests in someone's inbox. If the title is just the most recent commit message or a copy/paste of the JIRA ticket, that's not providing additional value.

Your description should tell the story of your change. Explain why you're making the change, what problem you're solving, what code you changed, what classes you introduced, and how you tested it. Reviewers should be able to tell which parts to focus on more and which are less important by reading the description.

Consider leaving inline comments on your own PR to help guide the reviewer and provide additional context.

Author Tip 4: Acknowledge Comments Quickly

A tight event loop is crucial when your code is out for review. Acknowledge comments quickly, ask for clarification if needed, and push back on comments if necessary. The ball is back in your court once a reviewer submits their comments. If the changes requested are straightforward and small, push a new version quickly and flag the PR for re-review to keep the ball rolling. The faster you respond to comments and push fixes, the more likely the reviewer will remember your PR and the quicker you'll reach a consensus.

Wrapping Up

If you take anything away from this article, remember these points:

  • Code review is a common bottleneck for teams to deliver work.
  • Prioritizing code review is a force multiplier for you and your team.
  • Context switching is expensive, causes stress, and damages work quality. Avoid it!
  • Be a good teammate: Make code review your top priority and make your PRs a joy to review.

Happy hacking!