The logs were clean.
The feature mostly worked.
So we did what engineers do best.
We started reading code.
π The Investigation Begins
The file was massive.
- Helpers
- Extensions
- Business rules
- Edge-case handling
- Defensive checks everywhere
Over 1,000 lines of code.
We zoomed into the complex parts first β
surely the bug lived where the logic was hard.
It didn't.
π The Bug That Humbled Everyone
Here were the first few lines:
class PaymentManager {
var delegate: PaymentDelegate?
init(delegate: PaymentDelegate) {
self.delegate = delegate
}
}Looks harmless.
No crashes.
No warnings.
No compiler complaints.
Until you notice what's missing.
π₯ The Real Problem
The delegate wasn't weak.
weak var delegate: PaymentDelegate?That single keyword.
Missing.
As a result:
- PaymentManager retained the delegate
- The delegate retained PaymentManager
- ARC couldn't break the cycle
π Memory leak. Silent. Persistent. Deadly.
π€― Why This Hurt So Much
Because everything else was⦠beautiful.
- Perfect abstractions
- Clean architecture
- Defensive coding
- Edge cases handled
But all of that effort couldn't save us from line 3.
π§ The Psychological Trap
Here's what really happened:
π§ "The bug must be in the complex logic."
π§ "Senior code doesn't fail in simple places."
π§ "Let's skip the boilerplate."
And that bias cost us hours.
Complexity hides bugs β but simplicity hides them better.
π§ͺ How We Found It (Eventually)
Not by reading deeper.
But by stepping back.
- Memory Graph Debugger
- Watching objects refuse to deallocate
- deinit never firing
deinit {
print("PaymentManager deallocated")
}Silence.
That silence pointed us to the top.
β οΈ The Real Lesson
Most bugs aren't:
- In algorithms
- In edge cases
- In clever abstractions
They live in:
- Initialization
- Ownership
- Lifecycles
- Defaults
Places we stop paying attention to.
π οΈ My New Review Rules
After this incident, I changed how I review code:
β Start from the top
β Scan ownership first
β Look for weak, unowned, lifetimes
β Verify deinit
β Never skip "boring" lines
π― Final Thought
We love complex bugs.
They make us feel smart.
But real-world bugs are boring.
And that's what makes them dangerous.
Next time you review code β
don't rush to line 500.
Start at line 3.