AI, ML & Dev Productivity

Go Fast: Write Minimal Software

May 18, 2026

The highest quality, fastest software is no software at all. Complexity is the speed killer - the parachute behind the drag racer. Here is how I think about writing less code to ship faster.

The goal is to deliver features customers use and love, fast. To maximize impact per hour spent.

The highest quality, fastest software possible is no software at all.

We don’t always know which features customers will use. When shipping new features, we must focus on validating a bet’s core premise in the lightest way possible.

Writing more software than is required to validate a bet’s core premise takes time, but much worse, it adds complexity. Complexity is the speed killer, the parachute behind the drag racer. Complexity increases the cognitive load of working in a codebase, slowing down the current project and every future project.

COMPLEXITY COMPOUNDSFEATURES ADDEDTIME PER FEATURESimple discount4 lines, 1 condition+ VIP + cap12 lines, 3 conditions+ holiday + no stack18 lines, 5 conditions+ region + exclusions28 lines, 8 conditionsEach requirement issimple in isolation.Complexity compounds.

How complexity manifests

Imagine calculating a discount. It starts simply: orders over $100 get a $10 discount.

function calculateDiscount(orderTotal: number): number {
  if (orderTotal > 100) {
    return 10;
  }
  return 0;
}

Four lines. One condition. Easy to understand, easy to change.

Then we add two more rules: VIP customers get an additional 5%, and discounts cannot exceed 20% of the order total.

function calculateDiscount(orderTotal: number, isVIP: boolean): number {
  let discount = orderTotal > 100 ? 10 : 0;
  if (isVIP) {
    discount += orderTotal * 0.05;
  }
  if (discount > orderTotal * 0.2) {
    discount = orderTotal * 0.2;
  }
  return discount;
}

Then a holiday promotion: an additional $15 for orders over $200. The VIP discount doesn’t stack with it.

function calculateDiscount(
  orderTotal: number, isVIP: boolean, isHoliday: boolean
): number {
  let discount = 0;
  if (isHoliday && orderTotal > 200) {
    discount = 15;
  } else if (orderTotal > 100) {
    discount = 10;
    if (isVIP) {
      discount += orderTotal * 0.05;
    }
  }
  if (discount > orderTotal * 0.2) {
    discount = orderTotal * 0.2;
  }
  return discount;
}

Then regional discounts and product category exclusions.

function calculateDiscount(
  orderTotal: number, isVIP: boolean, isHoliday: boolean,
  state: string, excludedCategories: string[],
  items: { category: string; price: number }[]
): number {
  let discount = 0;
  const eligibleTotal = items
    .filter((item) => !excludedCategories.includes(item.category))
    .reduce((sum, item) => sum + item.price, 0);

  if (isHoliday && eligibleTotal > 200) {
    discount = 15;
  } else if (eligibleTotal > 100) {
    discount = 10;
    if (isVIP) { discount += eligibleTotal * 0.05; }
  }
  if (state === "CA") { discount += eligibleTotal * 0.1; }
  if (discount > eligibleTotal * 0.2) { discount = eligibleTotal * 0.2; }
  return discount;
}

Each new requirement is simple in isolation. But complexity compounds. Each reader needs to understand the interplay between all the conditions, branches, rules, and inputs. Each change requires verifying it doesn’t break everything else.

Some complexity is unavoidable. But a lot of it - maybe most of it - is added through scope creep and over-engineering. That complexity is avoidable.

The questions that fight complexity

EIGHT QUESTIONS BEFORE YOU BUILD01What’s the smallest scope that still validates our hypothesis?02Do we need feature X now, or can we defer it to a later iteration?03Does feature X, however simple from the outside, add significantengineering complexity? Say so.04Can we pay down tech debt while adding this feature? Can we deleteunused features or stale feature flags?05Can we build a proof of concept or spike before investing heavily?06Are we building for a speculative future instead of what’s required now?(KISS, YAGNI)07Can we use an existing SaaS tool or library instead of building it?08What’s the 20% of work that gets us 80% of the benefit? (Pareto)

Product and engineering must work together to ask these questions. Every one of them is a chance to cut scope, reduce complexity, and ship faster.

The hardest one is number 3. Engineers need to speak up when a feature that looks simple from the product side carries hidden engineering cost. A configurable setting per qualification per workplace sounds like a checkbox. It’s actually a new data model, a settings inheritance chain, migration scripts, and edge cases for every downstream consumer.

Real examples of avoidable complexity

Here are patterns I’ve seen repeatedly:

Premature abstractions. Building a generic NotificationProvider framework when you only have one notification channel (email). Start with the concrete implementation. Refactor into an abstraction later if you actually build a second channel. Most of the time, you won’t.

Unnecessary configurability. Allowing configurable policy rules per entity type per country per department, when a single global rule would validate the same hypothesis. Every configurable dimension is a new axis of complexity. A configurable setting per role per region sounds like a checkbox. It’s actually a new data model, a settings inheritance chain, migration scripts, and edge cases for every downstream consumer.

Speculative features. Building a full dependency graph visualization when you only need to know “does this field have dependents, yes or no.” You could ship a boolean check instead of a DAG renderer.

Over-detailed audit trails. Building a complete audit trail system for MVP when a simple updatedAt timestamp would suffice for validating the core premise.

Each of these decisions, in isolation, seems reasonable. “It’s just one more parameter.” “It’s just one more table.” But each one slows down the current project and every future project that touches the same code.

COMPLEXITY IS THE PARACHUTEYOUR TEAM →configscopecreepThe car wants to go fast.The parachute is every feature you didn’t need to build:configaudit trailsabstractionsedge casesscope creeprankingsEvery unnecessary feature is drag you carry forever.The parachute is permanent. It never comes off.

Using AI to fight complexity

With agents writing most of our code, the risk of complexity explosion is higher than ever. Anyone in the organization can ask agents to add requirements. Each new requirement is simple in isolation, but complexity compounds.

There is surely nothing quite so useless as doing with great efficiency what should not be done at all. - Peter Drucker

I built a reviewer tool that takes a design document and reviews it against engineering best practices, checking for flaws, oversights, over-engineering, and opportunities for simplification. The results are consistently useful: it catches premature abstractions, unnecessary configurability, and scope that doesn’t serve the core hypothesis.

This is one of the highest-leverage uses of AI in engineering: not writing code, but preventing unnecessary code from being written in the first place.

How to actually go fast

Some practical reminders:

Create focus blocks. 90% of participants in the Clockwise study said focus time makes them more productive. Block it on your calendar. Protect it.

Mute notifications. It’s okay. People can bypass your Do Not Disturb if they really need you.

Limit work in progress. Context switching is the productivity killer. Finish one thing before starting the next.

Delete code. Every unused feature, stale feature flag, and dead code path is complexity you’re paying for. Delete it.

We go fast by keeping complexity in check. We go fast by writing minimal software.

← All chapters