Skip to main content
A dreamy and soft palette paints a developer's desk scene. Scattered tools, a glowing computer screen, and a whimsical yak with patches of shaved fur take center stage. Above, in flowing watercolor script, the title reads 'To Yak Shave or Not'.

To Yak Shave or Not: The Art of Procrastination in Software Development

You sit down to ship a chat component. Four hours later you’ve upgraded TypeScript, fixed three unrelated type errors, bumped your test runner, and rewritten your ESLint config. The chat feature has zero lines of code. Welcome to yak shaving.

The interesting question isn’t “how do I stop?” It’s “which yaks are worth shaving, and which should I leave alone?”

Yak shaving vs. bike shedding

I conflated these for years, so let’s nail the distinction first.

Quick test: if you’re alone going deeper, it’s a yak. If you’re in a meeting going wider on something small, it’s a shed.

They share a root (losing the plot), but the fixes differ. Yak shaving is fixed by scoping and stashing. Bike shedding is fixed by forcing a decision (“rename in a follow-up PR; merging now”) or a default (“formatter wins, move on”). The rest of this post is about yaks.

A decision framework: shave, stash, or skip

When you hit a yak, run three checks before touching it:

  1. Is it actually blocking? Not “would be nicer,” but “I cannot ship without this.” If the answer is no, it’s not on the critical path.
  2. Is the fix bounded? Can you size it in minutes, or does it dissolve into “let’s see”? Unbounded yaks eat days.
  3. Does it belong in this PR? A reviewer should be able to describe your diff in one sentence. If the yak breaks that sentence, it belongs in its own PR.

Three outcomes:

Most yaks I regret were category-three pretending to be category-one.

The chat example, replayed

Same scenario, run through the framework:

  1. Outdated TypeScript. Blocking? No. The new utility types are nice, not required. Skip. File an issue: “Bump TS, evaluate satisfies for X.”
  2. TS upgrade breaks types. Wouldn’t have happened; we skipped step 1. This is the cascade you’re avoiding.
  3. Failing tests. Blocking if they’re tests for chat. Skip if they’re pre-existing failures on main. Don’t fix unrelated red tests in a feature PR; that PR can never be reverted cleanly.
  4. ESLint misconfig. Skip. File it. Linter setup is its own PR with its own review.

The chat ships in an afternoon. The yaks become a backlog you can prioritize on their own merits, instead of a pile you discovered at 11pm.

Tactics that actually work

Keep a yak file open. A scratch buffer, a TODO at the bottom of the PR description, anywhere you can flush a distraction in 10 seconds and return to the task. Most yaks die quietly once written down; they only metastasize when you’re afraid you’ll forget them.

git stash and git worktree are yak insurance. When a “quick fix” starts touching unrelated files, stash, branch, or open a worktree. Cheap to do, expensive to skip. A clean diff is a forcing function: if your fix doesn’t fit, you’ll feel it.

Time-box with a real timer. “I’ll spend 20 minutes on this” without a timer means an hour. Set one. When it goes off, the question isn’t “am I close?” but “do I stash or commit another 20?”

Notice the second hop. Yak 1 is usually fine. Yak 2 is the warning. By Yak 3 you are no longer doing the original task; you are doing archaeology. The pattern to recognize is the hop, not the depth.

Revert is your escape hatch. If you’ve gone three hops in and the original feature is untouched, git reset to the start of the session and try again with the framework. The hour is gone either way; don’t compound it.

When to shave anyway

The framework says skip; sometimes you should still shave. Two cases:

Outside those, the boring answer wins: file the issue, ship the feature, let the yak grow its coat back.

Stay in touch

Don't miss out on new posts or project updates. Hit me up on X for updates, queries, or some good ol' tech talk.

Follow @zkMake
Zubin's Profile Written by Zubin