Nobody hands you a manual when you become a team lead. One week you’re a developer who ships features, and the next you’re responsible for whether other people ship features. Meanwhile your own code has quietly dropped to the bottom of your priority list.

A small team, somewhere between three and six developers, is a particular situation. You’re too small for heavy process, but too big to run on instinct alone. The habits you establish early become the culture, for better or worse.

This article covers the three areas that shaped my team the most: how we do code reviews, how we use Git, and how we keep shipping without grinding people down. These are lessons I’m still learning, not commandments. Take what fits your team and leave the rest.

Part 1: Code reviews

What reviews are actually for

Most teams say reviews exist to catch bugs. They do catch some, but that’s the weakest reason to have them. Reviews are far better at four other things.

They spread knowledge: after a review, at least two people understand that part of the system. They keep the codebase consistent, so naming and patterns stay coherent. They teach, because juniors learn from seniors and seniors get challenged by fresh eyes. And they catch design problems early, while fixing them is still cheap.

If you treat reviews as pure bug hunting, they become adversarial: the reviewer is a gatekeeper and the author is a suspect. If you treat them as knowledge sharing, they become collaborative. That one change in framing quietly changes the tone of every comment on the team.

Keep pull requests small

The single most effective rule I’ve enforced is small pull requests.

A twelve-hundred-line change gets approved in four minutes, because nobody can hold that much in their head. A hundred-line change gets real attention. Review quality drops sharply as size grows, and no amount of discipline fully fixes that. It’s a human limit.

Some guidelines that work. One pull request should have one purpose, so a refactor and a feature are two separate changes. If a change needs a long explanation, it’s probably too big. Break features into steps that can each be merged safely, such as the data layer first, then the endpoint, then the interface. And use feature flags so that unfinished work can be merged without being visible to users.

Small changes also make reverting painless. Rolling back a small change is routine. Rolling back a huge one is a project.

Give every pull request a template

A short template removes friction and ensures reviewers get the context they need. Mine asks for four things: what the change does, why it’s needed, how to test it, and a checklist covering tests, leftover debug code, and documentation.

The “why” matters most. Code shows what changed, but only the author knows the reasoning, and a reviewer can’t judge a decision without understanding the problem it solves.

Review in the right order

When I review, I work from the top down rather than line by line.

First I read the description and make sure I understand the goal. Then I look at the overall shape and ask whether the approach is sensible, because if the design is wrong, detailed comments are wasted effort. Next I check correctness and edge cases: what happens with empty input, with failures, with two things happening at once. Then I check the tests and ask whether they verify behaviour or merely chase a coverage number. Only after all that do I look at readability and style.

Style deserves one more note: let tools settle it. Automated formatters and linters should end every debate about spacing and naming conventions, so human attention is reserved for logic and design. Nothing drains a team’s goodwill like two people arguing about formatting in a review.

Write comments people can act on

Tone is fragile in text. A comment that sounds neutral to you can sound hostile to the person reading it. A few habits help enormously.

Signal how serious each comment is. We use simple labels. A “nit” is minor and optional. A “question” means I genuinely want to understand. A “suggestion” means I’d do it differently but it’s the author’s call. And “blocking” means this has to change before merging, with a reason attached. When everything looks equally urgent, authors can’t prioritise, and they start to feel buried.

Explain the why. Compare “don’t do this” with “this holds a lock while waiting on a network call, so one slow upstream would block every other request; could we release it first?” The second takes ten extra seconds and teaches something the author will remember for years.

Ask when you’re unsure. “Would it be simpler to do it this way?” invites a conversation. A flat command assumes you understand everything the author knows, which you usually don’t.

Praise out loud. If something is clever, clean, or well tested, say so. A review that only contains criticism teaches people to dread reviews. A sincere compliment costs nothing and builds the trust that makes hard feedback easier to accept later.

The author’s responsibilities

Review culture runs both ways, and authors have a part to play.

Read your own change before anyone else does. Looking at your diff as though you were the reviewer catches stray debug output, unclear names, and forgotten files before they waste someone else’s time. Don’t take comments personally, because the review is of the code, not of you. Respond to every comment, even if only to say it’s done or to explain why you disagree. And if a thread goes beyond two or three exchanges, switch to a quick call. Long comment wars help nobody and almost always end faster face to face.

Turnaround time is a leadership problem

Here’s the uncomfortable truth: slow reviews are usually the lead’s fault.

If changes sit for two days, developers start batching work into bigger changes to avoid waiting. Bigger changes are harder to review, so reviews get slower, and the cycle feeds itself. It starts with review latency, and review latency starts with whoever sets the example.

Our expectation is a first response within one working day. I try to do my reviews first thing in the morning, before my own coding, so nobody is blocked waiting on me. Your speed sets the pace for the whole team, whether you intend it to or not.

On approvals, one reviewer is plenty for a small team. Requiring more creates bottlenecks without much extra safety. Save the extra scrutiny for genuinely risky areas, like database changes, authentication, payments, and infrastructure, and trust your teammates everywhere else.

Part 2: Git workflow

Keep it boring

You don’t need a complicated branching model. Strategies with long-lived development, release, and hotfix branches were designed for software shipped on a fixed schedule. For a small team deploying web services, they mostly add merge pain.

What works for us is close to trunk-based development. The main branch is always deployable. Everyone works on short-lived branches taken from main, and those branches live for a day or two, not weeks. Work merges back through a reviewed pull request, and deployments come from main.

The important phrase is “short-lived.” The longer a branch exists, the further it drifts from reality, and the more painful the eventual merge. Frequent small merges keep everyone looking at roughly the same picture of the code.

Protect the main branch

Set rules on the main branch so mistakes are hard to make. Require a pull request before merging, at least one approval, and passing automated checks. Block force pushes. Optionally require that branches be up to date before they can merge.

This isn’t about distrust. It’s about making the safe path the default path, so that nobody has to rely on their memory at six o’clock on a Friday.

Names and messages

Consistent branch names make a branch list scannable. We use a short type prefix, such as feature, fix, chore, or docs, followed by a brief description. If you use a tracker, including the ticket number makes work easy to trace.

Commit messages deserve more care than most developers give them, because they’re documentation for your future self. “Fix stuff” tells you nothing a year later. A good message has a short summary line in the imperative mood, a blank line, and then a few sentences explaining why the change was needed. The diff already shows what changed. The message exists to explain the reasoning.

You don’t have to be perfect on every commit inside a feature branch, because we squash them on merge. But the final squashed message should be a good one.

Squash, rebase, or merge?

There’s no universal answer, but for a small team I’d recommend this combination.

Use squash-and-merge for feature branches. It gives you one clean commit per pull request on main, easy to read and easy to revert. Keep your own branch current by rebasing it onto main rather than repeatedly merging main into it, which litters history with noise commits. And never rewrite shared history. Rebasing your own personal branch is fine, but force-pushing over a branch other people are using is how work gets lost.

If you do need to force-push your own branch after a rebase, use the safer variant that refuses to overwrite commits you haven’t seen. It protects you from silently erasing a teammate’s push.

A few skills worth teaching everyone

Some Git capabilities save so much time that I make sure everyone on the team knows them:

  • Stashing, for setting work aside when you need to switch tasks without committing half-finished code.
  • Soft resets and amending, for fixing the last commit or its message before anyone sees it.
  • Bisecting, which lets Git find the exact commit that introduced a bug by walking you through a binary search of your history. It feels like cheating the first time you use it.
  • The reflog, which records where your branch tips have been. Nearly anything you ever committed is recoverable through it, and it has rescued more panicked afternoons than any other feature.

Knowing that mistakes are recoverable also reduces fear, and fear makes people cautious in unproductive ways.

Tag releases and plan for hotfixes

Tag each release so you can always answer the question “what exactly is in production right now?” When an urgent fix is needed, branch from main or from the production tag, fix it, review it, and deploy it through the same pipeline as everything else.

Having a fast, normal path for urgent fixes means nobody is tempted to bypass your process in an emergency. And emergencies are precisely when process matters most.

Let automation do the nagging

Any rule enforced through human reminders will eventually be forgotten. Put the rules in your continuous integration pipeline instead: run the tests, check formatting and linting, make sure the project builds, and optionally scan dependencies for known vulnerabilities.

When the pipeline is red, merging is blocked. Nobody has to play the bad guy. The pipeline says no, so you don’t have to, and the conversation can stay focused on the work instead of on policing.

Part 3: Shipping without burning out

This is the section I wish someone had talked to me about earlier. Process is easy to copy. Sustainability is harder, because the pressure to go faster never stops arriving.

Burnout is usually a system problem

When a teammate is exhausted, it’s tempting to see it as their personal failing: they need better time management or more resilience. In my experience, burnout is far more often a symptom of the system around them. Unrealistic deadlines, constant interruptions, unclear priorities, and a hero culture where staying late is quietly rewarded all produce it reliably, whoever is on the team.

As a lead, you control much of that system. That makes burnout partly your responsibility, and also your opportunity.

Cut scope, not corners

When a deadline looms and the work won’t fit, you only have four levers: time, people, quality, and scope. Adding people to a late project usually makes it later. Cutting quality creates debt that slows you down next month. Moving the date is sometimes impossible.

That leaves scope, and it’s the lever teams use least. Ask early and often: what is the smallest version of this that still delivers the value? Ship that. A cake shop that takes orders without a loyalty programme is still a cake shop that’s open. Keep a visible “later” list so that cut ideas feel deferred rather than rejected, and people stay willing to propose them.

Ship small and ship often

Big releases are stressful because the stakes are big. Everything lands at once, anything can break, and rolling back is hard. Small, frequent releases flip this: each one is low risk, easy to understand, and easy to undo.

Frequent shipping is also a morale tool. People enjoy seeing their work go live. A team that deploys several times a week feels constant progress, while a team that deploys quarterly lives in a long tunnel, with all the dread that comes with an end-of-tunnel release.

Limit work in progress

Starting ten things and finishing none is the fastest way to feel busy and accomplish nothing. We cap work in progress: each person has at most one or two active tasks, and finishing something takes priority over starting something new.

It sounds like this would slow you down. It does the opposite, because context switching is expensive and half-finished work blocks other people, especially reviewers. The rule of thumb is simple: stop starting, start finishing.

Protect focus time

Developers do their best work in long, uninterrupted stretches. A meeting in the middle of the afternoon doesn’t cost thirty minutes. It can cost half a day of useful focus.

Several things helped us. We cluster meetings into certain blocks and leave other blocks meeting-free. We default to written, asynchronous updates, which often replace status meetings entirely. We agreed that chat is not a pager: if something truly needs an answer within ten minutes, there’s a clear escalation path, and everything else can wait a few hours. And we keep standups short, or replace them with a written check-in once they’ve turned into status theatre.

Protect your own focus too. Predictable windows when you’re available for questions, and windows when you’re not, are easier on everyone than constant, unpredictable availability.

Estimate honestly and add buffer

Software estimates are wrong, and they’re wrong in a consistent direction: too optimistic. Instead of pretending otherwise, build that reality into your process.

Break work into small tasks before estimating, because estimates for small tasks are far more accurate. Give ranges rather than single numbers. Add buffer for review, testing, deployment, and the unexpected, because if you plan to full capacity, any surprise becomes overtime. And when a deadline is imposed from outside, translate it back honestly: “To hit that date, here is what we would need to cut.”

Telling stakeholders the truth early is uncomfortable. Telling them late is much worse.

Make incidents and on-call humane

If your team supports production, plan for the human cost. Rotate on-call duty fairly and give people time off after a rough night. Write runbooks so the person responding at two in the morning isn’t guessing. After incidents, hold blameless retrospectives that ask what in the system allowed the failure, not who caused it. People who fear blame hide problems, and hidden problems grow.

Also treat recurring alerts as engineering work. If the same alert fires every week, fixing it is worth more than most features, because it’s quietly taxing everyone’s sleep and attention.

Lead by example

Teams copy what leaders do, not what they say. If I tell people to take breaks but send messages at eleven at night, the real message is that late replies are expected.

So I try to model the behaviour I want. I take my leave. I log off at a reasonable hour. I schedule messages to send during working hours. I say “I’m not available for this until Monday” without apologising. When the lead takes a real vacation and the team copes fine, everyone learns that it’s allowed, and that the team doesn’t depend on one person never resting.

Notice the signs early

Burnout rarely arrives suddenly. It shows up as changes in a person: withdrawing from discussions they used to join, growing cynicism, missed details that were never missed before, irritability over small things, or work that’s technically completed but joyless.

When you notice, don’t wait for a performance conversation. Check in privately and ask open questions. Then listen more than you talk, and be ready to act: reduce their load, adjust a deadline, remove a source of friction. A person who feels heard early usually recovers quickly. A person who is ignored until they break often leaves.

Bringing it together

Reviews, Git habits, and sustainable pace look like three separate topics, but they share a common thread. Each is about making the right behaviour the easy behaviour, and about protecting the team’s ability to do good work over a long period of time, not just this sprint.

Small reviews, short-lived branches, automated checks, limited work in progress, and protected focus time all reduce friction and fear. Less fear means more honest communication, faster feedback, and better decisions. And a team that communicates honestly can handle almost any technical challenge.

If you’ve just stepped into a lead role, the reframe that helped me most was this: your job is no longer to write the best code on the team. It’s to build the environment in which good code gets written reliably, by everyone, without anyone paying for it with their health.

I’d love to hear what’s worked for your team. Share your own lessons in the comments, especially the ones you learned the hard way.