I ship broken code. Intentionally. Regularly. And it made me the most productive developer on my team.
Before you close this tab in disgust, let me explain.
The Old Me
I used to be a perfectionist. Every feature had to be bulletproof before it touched production:
- 100% test coverage
- Every edge case handled
- Performance optimized
- Documentation complete
- Code reviewed three times
The result? I shipped maybe one feature per month. My teammates shipped four. My code was "better" in isolation, but the product barely moved.
My manager pulled me aside: "Your code quality is great. But we need features, not perfection."
That conversation broke something in my brain. In a good way.
What "Shipping Broken Code" Actually Means
Let me be clear: I don't ship code that crashes the app, loses data, or creates security holes.
What I DO ship:
1. Features With Known Limitations
// V1: Ships today
function searchUsers(query) {
return users.filter(u =>
u.name.toLowerCase().includes(query.toLowerCase())
);
}
// Known limitation: no fuzzy matching, no pagination
// Works for our current 200 users
// TODO: optimize when we hit 10,000 users
Is this "broken"? By perfectionist standards, yes. It doesn't handle typos. It won't scale to a million users. It doesn't support regex.
But we have 200 users. It works perfectly for 200 users. I shipped it in 20 minutes instead of 2 days.
2. UI That's "Good Enough"
My first version of any UI looks rough. Spacing is off. Colors aren't perfect. The loading state is a plain "Loading..." text instead of a skeleton screen.
But users can USE it. They can give feedback. And that feedback is worth 100x more than my guess about what the UI should look like.
3. Error Handling That's Honest
// Perfectionist approach
try {
await submitForm(data);
} catch (error) {
if (error instanceof ValidationError) {
showFieldErrors(error.fields);
} else if (error instanceof NetworkError) {
showRetryDialog();
} else if (error instanceof AuthError) {
redirectToLogin();
} else if (error instanceof RateLimitError) {
showCooldownTimer(error.retryAfter);
} else {
showGenericError();
logToSentry(error);
}
}
// My V1 approach
try {
await submitForm(data);
} catch (error) {
alert('Something went wrong. Please try again.');
console.error(error);
}
The V1 handles the error. The user knows something failed. I can see what happened in the console. Done.
Will I add granular error handling later? Maybe. If users actually hit these errors frequently enough to justify it.
The Framework: Ship → Measure → Fix
Step 1: Ship The Minimum
Ask: "What's the smallest thing I can ship that provides value?"
Not "What's the smallest thing that's technically correct." VALUE. If users can accomplish their goal, ship it.
Step 2: Watch What Breaks
Most of the edge cases you imagine never happen. I used to spend days handling scenarios that zero users ever triggered.
Now I ship and watch. Real usage data tells me what actually needs fixing.
Step 3: Fix What Matters
A bug that 50% of users hit? Fix immediately.
A bug that 1 user hit once? Log it, move on.
A theoretical bug that nobody has hit? Delete the TODO comment and forget about it.
The Math That Changed My Mind
Perfectionist approach:
- 1 feature per month
- 0 bugs at launch
- 12 features per year
Ship-and-iterate approach:
- 4 features per month
- 2-3 minor bugs per feature at launch
- 48 features per year
- Bugs fixed within 1-2 days based on real feedback
Which product do users prefer? The one with 48 features and occasional bugs, or the one with 12 perfect features they've been waiting months for?
What I'm NOT Saying
I'm NOT saying:
- Skip security. Never ship auth bugs, data leaks, or injection vulnerabilities.
- Skip correctness for critical paths. Payment processing, medical data, legal documents — be thorough.
- Never write tests. I write tests for critical business logic. I just don't chase 100% coverage.
- Don't care about quality. I care deeply about quality. I just define quality differently now.
Old definition: Code that handles every possible scenario.
New definition: Code that delivers value to users as fast as possible, while being safe and maintainable.
The Career Impact
Since adopting this approach:
- I ship 3-4x more features
- My bug count is roughly the same (because most theoretical bugs never happened anyway)
- I get more user feedback (because features exist sooner)
- I'm less stressed (no more anxiety about "what if someone enters 🤪 in the email field?")
- I got promoted (because visible output > invisible perfection)
The developers who get promoted aren't the ones who write the most elegant code. They're the ones who deliver the most value.
The One Question That Changed Everything
Before I write any code, I ask:
"If this feature has a bug, will anyone die, lose money, or lose data?"
If the answer is no, I ship fast and fix later.
If the answer is yes, I take my time.
99% of features fall into the first category. That realization alone made me 3x more productive.
Shipping fast without shipping garbage is a skill. I write about practical development approaches that actually work in the real world at boosty.to/swiftuidev.
Top comments (0)