DEV Community

Cover image for The Boy Scout Rule: Leave Code Better Than You Found It
Maxime Sahroui
Maxime Sahroui

Posted on

The Boy Scout Rule: Leave Code Better Than You Found It

"Always leave the campground cleaner than you found it." — The Boy Scouts

Robert C. Martin adapted this principle for software: every time you touch a file, leave it a little better than you found it. Not a rewrite. Not a refactoring sprint. Just a small, deliberate improvement — on every commit, every PR, every day.

This is the most underrated practice in software engineering. It requires no planning, no tickets, no approval. It compounds silently until one day you realize the codebase doesn't hurt anymore.

Why It Works

Codebases don't rot overnight. They decay one shortcut at a time — a vague variable name here, a duplicated condition there, an unused import nobody cleaned up. Each one is trivial. Together, they make the code hostile to change.

The Boy Scout Rule reverses that entropy at the same rate it accumulates. You're not trying to fix everything. You're making sure the trend line points up instead of down.

The math is simple: if 5 engineers each make one micro-improvement per commit, and each pushes 3 commits a day, that's 75 improvements per week. In a quarter, you've made nearly a thousand small things better — without ever scheduling a "tech debt sprint."

What Counts as a Micro-Refactor

The key constraint: the improvement must not change behavior. If your PR is about adding a new endpoint, your Boy Scout cleanup should be invisible to the reviewer's eye — obvious, safe, and small.

Here's what qualifies:

Rename a vague variable

You're debugging a function and see d. You now know it's a duration in milliseconds. Rename it before you move on.

// before — what is d?
function isExpired(token: Token): boolean {
  const d = Date.now() - token.issuedAt;
  return d > 3600000;
}

// after — future you says thanks
const ONE_HOUR_MS = 3_600_000;

function isExpired(token: Token): boolean {
  const elapsed = Date.now() - token.issuedAt;
  return elapsed > ONE_HOUR_MS;
}
Enter fullscreen mode Exit fullscreen mode

Three changes, zero risk: a meaningful name, an extracted constant, and a numeric separator for readability. The function does exactly the same thing.

Replace a magic string with a union type

You're adding a new case to a status check and notice the existing code uses raw strings everywhere. Lock it down.

// before — "active", "paused", "cancelled" scattered across the codebase
function canRenew(subscription: { status: string }): boolean {
  return subscription.status === "active" || subscription.status === "paused";
}

// after — the compiler catches typos for you
type SubscriptionStatus = "active" | "paused" | "cancelled";

type Subscription = {
  status: SubscriptionStatus;
  // ...other fields
};

function canRenew(subscription: Subscription): boolean {
  return subscription.status === "active" || subscription.status === "paused";
}
Enter fullscreen mode Exit fullscreen mode

Now if someone writes "actve" anywhere, TypeScript catches it at compile time. You just eliminated an entire category of bugs in 30 seconds.

Extract a repeated condition into a named function

You notice the same check in two places while working on a feature. Don't copy-paste it a third time — extract it.

// before — this check appears in 3 different files
if (user.role === "admin" || user.role === "owner") {
  // ...
}

// after — one source of truth, readable everywhere
function hasElevatedAccess(user: User): boolean {
  return user.role === "admin" || user.role === "owner";
}

// now the call sites read like plain English
if (hasElevatedAccess(user)) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

When the definition of "elevated access" changes — and it will — you change it in one place.

Clean up dead code

You're reading a module and spot an exported function that nothing imports. Check with a quick search and remove it.

# bun makes this fast — search the entire project in milliseconds
bun x grep-cli "calculateLegacyDiscount" src/
# no results? delete it.
Enter fullscreen mode Exit fullscreen mode

Dead code is worse than no code. It misleads readers into thinking it matters, and it creates merge conflicts for no reason.

Add a return type

You're calling a function and have to read through the implementation to understand what it returns. Add the type so the next person doesn't have to.

// before — what does this return? you have to read every branch
function resolvePrice(item: CartItem, coupon?: Coupon) {
  if (!coupon) return item.basePrice;
  if (coupon.type === "percentage") return item.basePrice * (1 - coupon.value);
  return Math.max(0, item.basePrice - coupon.value);
}

// after — the contract is explicit
function resolvePrice(item: CartItem, coupon?: Coupon): number {
  if (!coupon) return item.basePrice;
  if (coupon.type === "percentage") return item.basePrice * (1 - coupon.value);
  return Math.max(0, item.basePrice - coupon.value);
}
Enter fullscreen mode Exit fullscreen mode

One word added (: number). Now the function communicates its contract without requiring the reader to trace through three branches.

Replace an imperative loop with a declarative expression

You're reading through a data transformation and it takes you 30 seconds to understand a for loop that's really just a filter + map.

// before — imperative, you have to simulate the loop mentally
function getActiveEmails(users: User[]): string[] {
  const result: string[] = [];
  for (let i = 0; i < users.length; i++) {
    if (users[i].isActive && users[i].email) {
      result.push(users[i].email);
    }
  }
  return result;
}

// after — declarative, reads like a sentence
function getActiveEmails(users: User[]): string[] {
  return users
    .filter((user) => user.isActive && user.email)
    .map((user) => user.email);
}
Enter fullscreen mode Exit fullscreen mode

Same output. But now the shape of the transformation — filter then project — is immediately visible.

The Rules of Engagement

The Boy Scout Rule only works if you respect its boundaries. Here's how to keep it safe and sustainable:

Keep it under 5 minutes. If a cleanup takes longer, it's not a Boy Scout improvement — it's a task. Write it down, create a ticket, and move on.

Never mix behavior changes with cleanups. Your PR should tell a clear story. Reviewers should be able to look at the diff and say "this part is the feature, this part is cleanup" — or better yet, put the cleanup in a separate commit.

Run the tests. Even a rename can break something if there's reflection, string-based lookups, or dynamic imports involved. With Bun, this takes seconds:

# run tests after every micro-refactor to make sure nothing broke
bun test

# or scope it to the files you touched
bun test src/pricing/
Enter fullscreen mode Exit fullscreen mode

Don't refactor what you don't understand. If you're in unfamiliar code and you're not sure why something is written a certain way, don't touch it. The Boy Scout Rule is for improvements you're confident about — not for guessing.

Don't gold-plate. The goal is not to make every file perfect. It's to make it slightly better. Resist the urge to "while I'm here, let me also..." — that's how a 10-minute task becomes a 3-hour rabbit hole.

Making It a Team Habit

The Boy Scout Rule scales best when it's a shared norm, not a solo practice.

Name it in code reviews. When you see a small improvement in a PR, call it out positively: "Nice Boy Scout cleanup on the naming here." Reinforcement works better than mandates.

Lead by example. As a team lead, include a small cleanup in your own PRs. When people see it's normal and expected, they'll start doing it too.

Add it to your PR template. A simple checkbox — [ ] Boy Scout: left the code a little better — turns it from an abstract principle into a visible practice. It's a gentle nudge, not a gate.

Track it informally. You don't need metrics. But once a month, ask the team: "Does the codebase feel better or worse than last month?" If the answer is consistently "better" — the rule is working.

The AI-Powered Boy Scout

AI coding assistants are natural Boy Scouts — they can spot cleanup opportunities you'd miss when focused on your feature. The trick is to ask them explicitly and at the right moment.

Prompt your assistant before committing

Before you push, paste the diff or the files you touched and ask a focused question. Not "review my code" — that's too broad. Instead:

Here are the files I changed for the new billing endpoint.
Without changing any behavior, suggest small Boy Scout improvements:
renames, type tightening, dead code, extracted constants.
Only suggest changes you're confident are safe.
Enter fullscreen mode Exit fullscreen mode

The constraint — "without changing behavior" and "confident are safe" — keeps the AI from going on a refactoring adventure. You get 2-3 surgical suggestions you can apply in a minute.

Add a Boy Scout pass to your AI rules

If you use project-level instructions (.claude/, .cursorrules, or a system prompt), you can make the Boy Scout Rule automatic:

# Boy Scout Rule

After completing any task, scan the files you touched for safe micro-refactors:
- Vague names that can be made specific
- Magic numbers or strings that should be named constants
- Missing return types on exported functions
- Unused imports or dead code
- Imperative loops that could be declarative
- Raw strings that should be union types

Apply these improvements in a SEPARATE commit with the message:
"chore: boy scout cleanup"

Constraints:
- Never change behavior. If unsure, skip it.
- Keep each cleanup under 5 lines changed.
- Do not refactor code you did not touch in this task.
Enter fullscreen mode Exit fullscreen mode

This turns every AI-assisted task into a two-step process: ship the feature, then clean the campsite. The separate commit keeps the git history clean and makes review easy.

Use it in code review

When reviewing a PR (yours or someone else's), feed the diff to your AI assistant with a targeted prompt:

Review this PR diff. Ignore the feature logic — only flag Boy Scout
opportunities: naming, types, dead code, readability.
No behavior changes. Keep suggestions minimal and safe.
Enter fullscreen mode Exit fullscreen mode

This is especially powerful for team leads reviewing junior developers' PRs. Instead of nitpicking names yourself, let the AI flag them — it feels less personal and more systematic.

A word of caution

AI assistants love to over-refactor. They'll suggest extracting a helper for a two-line function, or creating a generic utility for something used once. Remember Rule 4 of Simple Design: fewest elements. If the AI suggests an improvement that adds more complexity than it removes, skip it. The Boy Scout Rule is about making things simpler, not about maximizing the number of changes.

The Compound Effect

No single micro-refactor will transform your codebase. That's the point. You're not looking for a dramatic before-and-after. You're investing in a trend.

After a month, variable names are clearer. After a quarter, the module boundaries make more sense. After a year, new team members onboard faster because the code explains itself.

The Boy Scout Rule works because it removes the false choice between "shipping features" and "improving code quality." You do both, in every commit, forever.

Leave it better than you found it. Every time.

Top comments (0)