Why Good Enough Is Often Better Than Perfect
Engineering Philosophy

Why Good Enough Is Often Better Than Perfect

The counterintuitive case for strategic imperfection in software and life

The Feature That Never Shipped

I once spent six months perfecting a feature that never reached users. Six months of code reviews, refactoring, edge case handling, and architectural elegance. The codebase was beautiful. The test coverage was complete. The documentation was pristine.

Then the product pivot made the entire feature irrelevant.

Six months of perfect work, zero value delivered. Meanwhile, a competitor shipped a janky version of the same feature in three weeks. Their code was ugly. Their edge cases crashed. Their documentation was a README with three bullet points. But they learned what users actually wanted, iterated based on feedback, and now dominate the market.

My British lilac cat understands “good enough” instinctively. Her hunting technique isn’t elegant—she misses frequently, misjudges distances, and sometimes falls off furniture mid-pounce. But she catches enough toys to stay satisfied. She ships imperfect pounces constantly rather than perfecting her technique in isolation. The iteration happens in reality, not in theory.

That failed feature taught me something I wish I’d learned earlier: perfection is often a trap disguised as virtue. We celebrate perfectionism as dedication. We should often recognize it as fear—fear of shipping, fear of judgment, fear of discovering our work isn’t as valuable as we hoped.

This article explores when “good enough” beats perfect, how to recognize the difference, and how to develop the judgment that knows which situations call for which approach.

The Economics of Perfection

Perfection has costs. Understanding these costs transforms the decision from moral (“be excellent”) to economic (“maximize value”).

The first cost is time. Perfection takes longer than good enough. The gap isn’t linear—it’s exponential. Getting from 80% to 90% quality might take the same time as getting from 0% to 80%. Getting from 90% to 95% might double that again. Getting from 95% to 99% might double it yet again. The final 1% toward perfection can consume more resources than everything before it.

The second cost is opportunity. Time spent perfecting one thing is time not spent on other things. Every hour polishing an already-functional feature is an hour not building the next feature, not fixing that bug, not learning that skill. Opportunity cost is invisible but real.

The third cost is information. Perfecting in isolation means perfecting without feedback. You’re optimizing based on assumptions about what users want. Those assumptions are often wrong. Shipping early provides information that improves subsequent decisions. Perfectionism delays that information, sometimes until it’s too late.

The fourth cost is motivation. Perfectionism is exhausting. The endless pursuit of an unreachable standard drains energy and enthusiasm. Teams that never ship become demoralized. Individuals who never complete become paralyzed. “Good enough” enables momentum. Momentum enables progress.

The math is simple: if perfection takes three times longer and provides only 10% more value, the return on investment is negative. You’d deliver more total value by shipping three “good enough” solutions in the same time.

When Perfect Is Actually Required

Before advocating universally for “good enough,” we must acknowledge exceptions. Some contexts genuinely require perfection.

Safety-critical systems demand perfection. Medical devices, aviation software, nuclear plant controls—these cannot be “good enough.” Failure costs lives. The additional investment in perfection is justified by the consequences of imperfection.

Security-critical components require similar rigor. Authentication systems, encryption implementations, access controls—flaws here enable catastrophic breaches. “Good enough” security is often equivalent to no security.

Foundational infrastructure needs higher standards than applications built on it. A bug in React affects millions of applications. A bug in your internal admin tool affects dozens of users. The scope of impact justifies different quality thresholds.

Irreversible decisions deserve more perfection than reversible ones. A database schema that’s hard to change later deserves more thought than a UI that can be redesigned weekly. The cost of correction scales with the difficulty of correction.

Public commitments warrant higher polish. The demo you show investors, the API you publish for external developers, the design you present to the board—these moments merit extra effort because they shape perceptions that are hard to update.

The key insight: perfection requirements are contextual. Most software isn’t safety-critical, security-critical, or foundational. Most decisions are reversible. Most work is internal. Defaulting to perfection everywhere ignores these distinctions.

flowchart TD
    A[Task/Feature] --> B{Safety Critical?}
    B -->|Yes| C[Pursue Perfection]
    B -->|No| D{Security Critical?}
    D -->|Yes| C
    D -->|No| E{Foundational/Hard to Change?}
    E -->|Yes| F[Higher Quality Threshold]
    E -->|No| G{High Visibility/Public?}
    G -->|Yes| F
    G -->|No| H[Good Enough is Sufficient]

How We Evaluated This Philosophy

Testing “good enough versus perfect” required examining real outcomes, not theoretical arguments.

Step one: we identified projects that shipped imperfect and projects that pursued perfection. Same teams, same domains, different approaches at different times. This controlled for skill differences.

Step two: we tracked outcomes over 12 months. User adoption, revenue impact, iteration velocity, team morale, technical debt accumulation. Both short-term and long-term measures mattered.

Step three: we interviewed engineers about their experience. Perfectionism often felt virtuous in the moment. How did it feel in retrospect? Satisfaction? Regret? Depends on context?

Step four: we analyzed the features that succeeded. Were they the polished ones or the scrappy ones? What percentage of polish contributed to success? What percentage was invisible to users?

Step five: we examined failures. Did “good enough” features fail more often? When they failed, was quality the cause? Or did perfect features fail too, for different reasons?

The findings were nuanced. “Good enough” won on average, but the average masked important exceptions. The skill isn’t adopting “good enough” universally—it’s developing judgment about which approach fits which situation.

The Perfectionism Trap

Perfectionism disguises itself as quality. Recognizing the disguise is the first step toward escape.

Perfectionism is often fear. Fear that imperfect work reveals imperfect ability. Fear that shipping invites criticism. Fear that “good enough” means “not good enough as a person.” These fears are real but misleading. Shipping imperfect work and learning from feedback demonstrates more professional maturity than hiding behind endless polish.

Perfectionism is often procrastination. Working on details feels productive while avoiding the scary moment of exposure. The deadline approaches, and suddenly there’s urgent need to refactor this function, clean up that naming, add one more test case. These activities aren’t worthless, but they’re suspiciously convenient when shipping feels uncomfortable.

Perfectionism is often ego. The code represents the coder. Imperfect code feels like imperfect self. This identification is false and harmful. Code is product, not identity. Shipping imperfect code that helps users is more valuable than perfect code that helps no one.

My cat has no ego attachment to her hunting technique. A missed pounce doesn’t trigger existential crisis. She just pounces again, slightly adjusted. The feedback loop is fast because identity isn’t at stake. Our feedback loops are slow because we make every output a referendum on our worth.

Perfectionism is often habit. If you’ve always polished until perfect, stopping feels wrong. The discomfort isn’t evidence that stopping is wrong—it’s evidence that your comfort zone doesn’t align with optimal behavior. Growth requires discomfort.

Recognizing these patterns in yourself is difficult because they feel like legitimate quality concerns. The question that cuts through: “If I ship this now, what specifically will go wrong?” If the answer is vague (“it’s not ready,” “it could be better”), you’re probably trapped. If the answer is specific (“users will lose data,” “the API will break clients”), you have a real quality concern.

The Art of “Good Enough” Standards

Accepting “good enough” doesn’t mean accepting garbage. It means deliberately choosing quality standards appropriate to context.

Define explicit criteria for “good enough.” Before starting, specify what must be true for the work to ship. Tests pass. Core functionality works. Known issues documented. Meeting these criteria means done, regardless of what could theoretically be improved.

Distinguish must-have from nice-to-have. Every feature has core requirements and peripheral enhancements. Ship when must-haves are complete. Nice-to-haves can come in subsequent iterations—or never, if they prove unnecessary.

Set time boxes. “I will spend two hours on this, then ship whatever exists.” Time constraints force prioritization. Without constraints, work expands to fill available time. Perfectionism thrives in open-ended timelines.

Embrace iteration. Shipping “good enough” now doesn’t preclude improvement later. Most successful products started rough and refined through user feedback. The rough version gathered the information that made refinement effective.

Accept that some work won’t improve. Not every shipped feature gets revisited. Some “good enough” work stays that way forever. This is fine. Not everything deserves perfection. Resources are finite. Allocation requires prioritization.

Here’s a practical framework:

Before starting:
- What are the must-have requirements?
- What quality threshold triggers "done"?
- What time budget is appropriate?

During work:
- Am I addressing must-haves or nice-to-haves?
- Am I within time budget?
- Would a user notice this improvement?

Before shipping:
- Do must-haves work?
- Is the time budget exhausted?
- What specifically would break if I ship now?

Perfectionism in Code

Software development has particular perfectionism patterns worth examining.

The refactoring spiral. Code works, but it could be cleaner. You refactor. Now this other part looks inconsistent. You refactor that too. Now the tests don’t match the new structure. Hours later, you’ve rewritten working code into differently-working code. The code isn’t better for users—it’s better for your aesthetic sensibilities.

The abstraction obsession. This function could be more general. This pattern could be extracted. This module could support use cases we don’t have. Over-abstraction creates complexity that serves theoretical flexibility rather than actual needs.

The edge case excavation. What if the input is empty? What if it’s negative? What if it’s larger than memory? Some edge cases matter. Most don’t. Handling edges that will never occur is perfectionism disguised as thoroughness.

The test coverage fetish. 97% coverage? Must get to 100%. But the remaining 3% is error handling paths that require elaborate mocking and test dozens of lines of code to verify behavior that’s obvious from inspection. The tests don’t prevent bugs—they prevent perceived incompleteness.

The performance optimization trap. This function takes 50ms. Could optimize to 5ms. But the function runs once per day in a batch job. Nobody notices. Nobody cares. The optimization serves the optimizer’s satisfaction, not users’ needs.

Each pattern feels like quality improvement. Each is often perfectionism in disguise. The distinguishing question: “Does this change provide value proportional to its cost?” If the value is purely aesthetic or theoretical, the change is probably perfectionism.

The Shipping Muscle

Shipping is a skill. Like any skill, it atrophies without practice. Teams that rarely ship lose the ability to ship. Individuals who rarely complete lose the ability to complete.

The shipping muscle needs regular exercise. Small, frequent releases build the habit of completion. Large, infrequent releases atrophy it. Teams that deploy daily find shipping natural. Teams that deploy quarterly find it terrifying.

Each ship builds tolerance for imperfection. The first time you release something rough, it feels wrong. The tenth time, it feels normal. The hundredth time, you barely notice. The discomfort diminishes with exposure.

Shipping also builds feedback loops. Each release teaches something. Small frequent releases mean small frequent lessons. Large infrequent releases mean learning opportunities months apart. The team that ships more learns more.

My cat ships constantly. Every pounce is a release. Every hunt is a deployment. The feedback is immediate: caught the toy or missed. This rapid iteration developed her skills faster than careful planning ever could. She’s not a perfect hunter, but she’s an effective one.

Build shipping into your rhythm. Daily commits. Weekly deployments. Monthly releases. Whatever cadence fits your context, make it regular. The rhythm creates expectation, and expectation creates completion.

Generative Engine Optimization

The “good enough” philosophy connects to an emerging concern: Generative Engine Optimization. As AI systems help create content and code, the question of “how good is good enough” becomes more pressing.

AI generates “good enough” output rapidly. Perfect output requires human refinement. The efficient workflow accepts AI’s “good enough” as a starting point rather than demanding perfection from either AI or human.

This shifts where perfectionism applies. Instead of perfecting first drafts, perfect the prompts that generate drafts. Instead of polishing every output, polish the selection criteria that choose which outputs matter. The leverage point moves from production to direction.

AI also changes the iteration speed. When generating variations is cheap, “good enough” becomes even more viable. Generate five “good enough” options, pick the best, refine minimally. This beats trying to perfect a single attempt.

The subtle skill is recognizing that AI amplifies the “good enough” strategy. Human effort should focus on judgment and direction rather than execution and polish. The human role becomes curator and editor rather than sole creator.

This doesn’t eliminate the need for quality judgment. It intensifies it. With AI producing volume, the human must distinguish good from good enough from not good enough. That judgment is the valuable skill. Polishing ability becomes less scarce. Quality judgment becomes more valuable.

The Paradox of Lowering Standards

Here’s the counterintuitive truth: lowering your shipping standards often raises your eventual quality.

When you ship early, you learn faster. Faster learning means more iterations. More iterations mean better outcomes. The rough first version, improved five times based on feedback, beats the polished first version that never got feedback.

When you ship more, you experiment more. Experiments require accepting that many attempts will fail. Perfectionism prevents experimentation because each attempt must succeed. Lower standards enable the exploration that discovers better approaches.

When you ship quickly, you maintain momentum. Momentum sustains effort over time. Perfectionism kills momentum through endless pre-release cycles. The long-term productivity of a team that ships regularly exceeds a team that rarely ships, even if each individual release is less polished.

The paradox resolves when you measure quality over time rather than per release. A “good enough” team that ships weekly accumulates more quality over a year than a perfectionist team that ships quarterly. The denominator matters as much as the numerator.

This doesn’t justify genuinely low standards. It justifies appropriate standards—standards calibrated to context, learning goals, and cumulative impact rather than standards derived from abstract ideals of perfection.

Knowing When to Polish

Despite advocating for “good enough,” polish sometimes matters. The skill is knowing when.

Polish when users will see it. User-facing surfaces deserve more attention than internal implementations. The checkout flow matters more than the admin dashboard. The API response format matters more than the internal data representation.

Polish when first impressions matter. Initial user experience shapes expectations and retention. The onboarding flow, the landing page, the first interaction—these deserve extra attention because they disproportionately affect outcomes.

Polish when differentiation depends on quality. If competitors offer similar functionality, execution quality becomes the differentiator. Polish is a competitive advantage when everything else is equal.

Polish when the cost is low. Some polish is cheap—fixing typos, aligning elements, improving variable names. Cheap polish has high ROI. Expensive polish requires justification.

Polish when you’re genuinely done with functionality. Once features are stable and feedback is incorporated, polish adds the final layer. But polish before stability is waste—you’re polishing something that will change.

The judgment required: “Would polish here provide value proportional to its cost?” This question has different answers in different contexts. Developing accurate intuition for these answers is a career-long practice.

Practical Exercises

Building “good enough” instincts requires deliberate practice. These exercises develop the judgment.

Exercise one: Ship something today. Whatever you’re working on, find a subset that can ship today. Define “good enough” criteria, meet them, ship. Notice the discomfort. Ship anyway.

Exercise two: Time-box a task. Choose something you’d normally spend days on. Give yourself two hours. Ship whatever exists when time expires. Compare outcomes to your usual approach.

Exercise three: List your polish patterns. What perfectionist behaviors do you default to? Identify three patterns. For each, ask: “What would happen if I skipped this?” Often, nothing.

Exercise four: Review shipped work. Examine something you shipped months ago. What polish would you add now? Is that polish worth adding, or has the moment passed? Most polish opportunities expire with context.

Exercise five: Ship without review. For low-stakes work, skip the self-review that catches imperfections. Notice what breaks. Usually, less than you feared.

These exercises build tolerance for imperfection and evidence that “good enough” actually works. The evidence overcomes the anxiety better than abstract arguments.

The Cultural Dimension

“Good enough” isn’t just individual philosophy—it’s team culture. Individual adoption in a perfectionist culture creates friction. Cultural shift enables individual change.

Leaders set standards by example. If leaders ship rough early versions, teams feel permission to do the same. If leaders demand perfection, teams pursue it regardless of efficiency.

Definition of done matters enormously. Explicit, documented criteria for “done” prevent endless pursuit of implicit perfection. When “done” is clear, completion is achievable.

Celebrate shipping, not just quality. Recognition systems that reward deployment frequency encourage shipping. Systems that only reward polish encourage perfectionism.

Blameless post-mortems enable “good enough.” If shipping imperfect work leads to blame, people won’t ship imperfect work. If imperfect work is expected and learning from it is celebrated, “good enough” becomes safe.

My cat doesn’t need cultural permission to ship imperfect pounces. She has no cultural layer adding anxiety to her execution. We humans operate in social contexts that shape our behavior. Changing individual perfectionism often requires changing cultural perfectionism.

The Balance Point

This article advocates for “good enough,” but the goal is balance, not elimination of quality standards.

Perfect has its place. “Good enough” has its place. Wisdom is knowing which is which.

When stakes are high and reversibility is low, lean toward perfection. When stakes are moderate and learning is valuable, lean toward shipping. When you’re uncertain, probably ship—uncertainty usually indicates you need feedback.

The perfectionist voice serves a purpose: it protects against genuine mistakes. The problem is when it speaks constantly, blocking all shipping with fears of imperfection. The goal is calibrating that voice to speak when appropriate and stay quiet otherwise.

My cat balances instinctively. She’s careful near edges (high stakes) and casual on stable surfaces (low stakes). She doesn’t bring perfectionist intensity to every situation. Neither should we.

The shift from perfectionism to “good enough” isn’t lowering standards. It’s contextualizing them. High standards where they matter. Appropriate standards everywhere else. This distinction creates more value than universal perfectionism ever could.

Ship the imperfect thing. Learn from the feedback. Iterate toward better. This cycle produces more quality over time than polishing in isolation.

The feature that never shipped taught me this lesson expensively. I hope this article transmits the lesson more cheaply.

Your work doesn’t need to be perfect. It needs to be useful. Those are different things.

Ship something today.