Writing

The scariest bugs quietly do nothing

8 Oct 2026, 2 min read

The scariest bugs aren’t the ones that crash. A crash gives you a stack trace and a line number. The scary ones just quietly do nothing: the button you tap, the save that never saves, the reminder that never fires.

Swift is a safe language, and some of that safety is exactly what makes failures quiet. Here’s where I’ve learned to look.

1. try? that swallows the error

try? context.save()   // if this fails, nobody will ever know

try? turns an error into nil and moves on. That’s fine when failure truly doesn’t matter. For saves, network calls and file writes, it hides the one piece of information you need.

do {
    try context.save()
} catch {
    logger.error("Save failed: \(error.localizedDescription)")
}

2. Optional chaining on something that’s nil

viewModel?.markComplete(event)

If viewModel is nil, this line does nothing. No warning, no crash. When an action “doesn’t work,” the first thing I check is whether the object I’m calling is actually there.

3. A guard that returns without a word

guard let event = events.first(where: { $0.id == id }) else { return }

Every early return is a path where the feature silently stops. I add a log line to the ones that should never happen.

4. Permissions that were never granted

Notifications, location and the camera all fail politely when permission is denied. Your code runs and nothing happens. Check the authorization status and handle the “denied” case in the UI.

5. Work that never reaches the main actor

UI state updated off the main actor, or a Task that was cancelled before it finished, can leave the screen showing old data with no error anywhere. Swift 6’s strict concurrency checking catches many of these at compile time, which is one reason I turn it on.

How I make failures loud

A crash is the app telling you something went wrong. A silent failure is the app keeping a secret.