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.
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
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.
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.