DEV Community

Cover image for When Your App Freezes in Front of Your Testers: What I Learned About Ego & iOS Development
Petr Jahoda
Petr Jahoda

Posted on

When Your App Freezes in Front of Your Testers: What I Learned About Ego & iOS Development

Development of justRead app in six parts; part five.

I’ve been building software for almost 15 years. Manufacturing systems, backend services, databases that handle millions of transactions. Things that actually matter, production lines depend on them. I thought I knew what I was doing.

Then I shipped my first TestFlight, and my app crashed for 50% of my testers.

The confidence trap

When you’ve spent a decade and a half solving hard problems, you develop a certain… confidence. Not arrogance, exactly. Just the quiet assumption that you understand your domain. You’ve debugged memory leaks in enterprise systems. You’ve optimized database queries for millions of records. You’ve shipped products to paying customers who depended on your code.

Building a native iOS app in SwiftUI felt manageable by comparison. Smaller scope. Single platform. Modern tooling. Surely the hard parts were the product design and the business strategy, not the code.

So when I invited 157 testers to try justRead, my e-book reader app, I was genuinely unprepared for what came next.

The day everything broke

The numbers seemed reasonable at first:

  • 157 people applied for TestFlight
  • 88 actually run the TestFlight
  • 4 unsubscribed immediately (missing features they wanted)
  • 84 stayed and used TestFlight
  • 10–15 engaged deeply with feedback

What I wasn’t ready for: approximately 50% of those users experienced crashes and freezes. Not occasionally. Repeatedly.

The feedback came in waves on the first day. “App froze.” “Crashed when I opened a book.” “Keeps crashing during search.” “Freezes and won’t respond.”

I felt it immediately: shame & embarrassment. That specific kind of ego hit you get when you assumed you were competent and the evidence suggests otherwise.

My first instinct was to fix it immediately. I pushed four quick builds in rapid succession, each targeting what I thought were the culprits. Each time I was convinced I’d found it. Each time, I learned I hadn’t.

progress in informing user

The problem I didn’t expect

I totally not anticipated: the app was trying to download data from cloud storage to local iPhone storage in the background while users were trying to interact with it. The process wasn’t properly managed. It wasn’t being cancelled. It wasn’t communicating with the UI that it was happening.

The result? The app would freeze while syncing. Then crash when users tried to do anything else.

This is a foundational problem. Not a bug. A design oversight. And it required rethinking how the entire cloud-sync system works.

That’s not fixed in a quick build. That’s fixed in the next major revision: the one I’m working on now, scheduled for next week.

splash screen

Why this mattered more than I expected

I wrote the post-TestFlight email to my testers laying out exactly what we found, exactly what we’re fixing. It’s detailed. 27 separate items across stability fixes, functional improvements, experience enhancements, and new features.

When I look at that list, I feel two things simultaneously:
(1) genuine gratitude that TestFlight caught these problems before actual users did, and …
(2) the uncomfortable knowledge that I shipped something to real people that wasn’t ready.

But this thing hit harder than the crashes themselves: I wasn’t expecting problems in the first place.

Not intellectually. Obviously, you expect problems when you’re building in public and testing. That’s the point of TestFlight.

But emotionally? I thought I knew what I was doing. My experience did translate — to the architectural thinking, the debugging methodology, the understanding that stability matters. But it didn’t translate to the specifics of how iOS manages background tasks, memory pressure, and network state. That requires learning deeply iOS, not just applying general software engineering. I thought the hard parts would be the things I expected them to be, not the foundational infrastructure that I’d glossed over.

settings

The humility reboot

Every developer who’s shipped something meaningful knows this feeling: the moment your assumptions collide with reality. It’s not special. It’s not even novel. Thousands of indie developers ship broken first versions to TestFlight every week.
What is interesting is that I didn’t see it coming, even though I should have.

I think it’s because there’s a category error in how we talk about indie app development. The content I read before shipping was almost entirely about growth metrics, monetization strategy, App Store Optimization, and how to build profitable businesses. Very little about the actual problem-solving journey. Very little about the moment when your code meets real devices, real networks, real edge cases, and fails spectacularly.

Everyone talks about MRR and unit economics and customer acquisition cost. Almost nobody talks about the humbling moment when you realize your search implementation is leaking memory, or your cloud-sync strategy is fundamentally broken, or your assumptions about how iOS handles background tasks were incomplete.

So I wasn’t prepared for the specific ways iOS exposes stability problems: background task lifecycle, memory pressure, network interruption, concurrent file access. I knew stability mattered. I didn’t anticipate how differently it manifests.

what is coming

What I know now (that I didn’t know last week)

Confidence and competence are not the same thing.

I’ve built complex systems. I understand databases, performance optimization, distributed systems. But iOS development operates by different rules. Memory management is different. Concurrency is different. The operating system’s lifecycle management is different. And my 15 years of experience didn’t automagically transfer to these domains.
The apps that freeze on 50% of devices aren’t built by incompetent people. They’re built by people who didn’t anticipate the specific, non-obvious ways that iOS handles background tasks, network requests, and memory pressure.

Testing on the simulator is not the same as testing on real devices.

I tested extensively. On the simulator. On my own devices. But real users with different network speeds, device generations, cloud storage providers, and actual reading patterns found problems in 48 hours that I didn’t find in months.
The testers weren’t doing anything wrong. They were using the app the way it’s supposed to be used. And that’s exactly when the problems appeared.

The gap between “it works for me” and “it works” is massive.

This is obvious when you say it out loud. But it was a genuine surprise living through it. My devices are development machines. They have good connectivity, fresh iOS versions, ample memory. Real users have older iPhones running older iOS versions on spotty WiFi networks while the app is trying to download a 500MB library from cloud storage.
The problem wasn’t the code. The problem was that the code had never met these conditions before.

Freezes and crashes are not small bugs. They’re design problems.

I initially approached the crashes as debugging exercises. Find the null pointer. Find the memory leak. Find the race condition. But the core issue was architectural: my app’s approach to cloud sync didn’t account for the reality that syncing might take a long time, might fail, might be interrupted, and the UI should probably tell the user what’s happening instead of just freezing.

That’s not a bug fix. That’s a redesign.

The week ahead

I’m rebuilding the cloud-sync system from the ground. I’m adding proper error handling, user notifications, timeout mechanisms, and background task management. I’m making sure the app doesn’t try to open files that haven’t finished downloading. I’m creating feedback loops so users know what’s happening instead of wondering why their app is frozen.

The new build goes to TestFlight next week.

And I have to admit that I’m nervous about it. Not because I think there will still be crashes (I’m 100% confident there won’t be, and yes, I know what that overconfidence sounds like now). But because I’ve learned that my confidence is not a reliable predictor of reality.

The 84 people who stuck with the app deserve a product that works. The 10–15 who gave detailed feedback deserve to know that their investment of time was the thing that made the difference between shipping something broken and shipping something real.

The thing nobody writes about

This is why I’m writing this. Not to tell you that my app is great (it’s not, yet). Not to tell you about my growth metrics or my monetization strategy (I have neither, we’re literally in TestFlight week one). And not to tell you that building an indie app is easy if you follow these ten steps.

I’m writing this because the indie dev content I read before shipping was almost entirely about the business side, the metrics, the strategy and the market opportunity. Almost none of it was about the moment when you realize that iOS stability requires entirely different thinking than backend systems. That managing cloud sync while the OS kills background tasks while the user is on spotty WiFi requires architectural decisions, not just debugging. Stability as a principle? Everyone knows that. Stability as iOS execution? That’s what nobody talks about.

Almost none of it was about the feeling of shipping something to real people and having it fail for half of them, even though you thought you knew what you were doing.

Almost none of it was about the humility that comes from learning, in real time, why your confidence was misplaced.

And maybe that’s because those stories are harder to write. They require admitting that despite 15 years of experience, despite months of development, despite testing that felt comprehensive, you still got something fundamentally wrong.

But they’re also the stories that matter most, because they’re the ones that prepare you for what actually happens when you ship.

What comes next

For justRead: a rebuilt cloud-sync system, more thorough testing with real devices, and a deep respect for what I still have to learn about iOS development.

For this journey: continued transparency about what works and what doesn’t. Continued gratitude for the 84 testers who are willing to help us figure this out. And continued proof that the hard part of building an app isn’t the idea or the design, it’s the relentless, humbling work of making it actually work.

The next TestFlight build will be better. And I’ll be more honest about what “better” actually means: not that we fixed everything, but that we learned something.

That’s progress, even if it doesn’t feel like it when your app is freezing in front of real people.
justRead is an EPUB reader for iPhone, currently in TestFlight. If you’re interested in early access or want to share your own story of shipped-too-early products, I’d love to hear from you. The next build ships next week.

Peter
Reader, Developer, justRead Creator

Top comments (0)