Error handling is a crucial part of programming, and Swift provides multiple ways to deal with potential failures. Many beginners struggle to understand why do-try-catch
exists when we already have other mechanisms like Optionals, Tuples, and Result enums.
In this blog, we’ll go step by step, analyzing different error-handling approaches and showing why do-try-catch
is the cleanest and most structured way to handle errors in Swift.
Step 1: Understanding the Problem — isActive
Flag
We will use a class called DoTryCatchDataManager
, which has a flag isActive
.
- If
isActive = true
, it returns valid data. - If
isActive = false
, it simulates a failure, returning eithernil
, an error, or throwing an error.
Function 1: Using Optionals (getTitle1()
)
func getTitle1() -> String? {
if isActive {
return "Title 1"
} else {
return nil // No title if isActive is false
}
}
Problem with Optionals
This function returns an Optional String (String?
), which means:
- If
isActive = true
, it returns"Title 1"
. - If
isActive = false
, it returnsnil
.
How Do We Handle This?
To use this function safely, we need optional unwrapping (if let
, guard let
, or ??
operator).
Example:
let title = getTitle1() ?? "Error: No Title"
What’s Wrong with This Approach?
❌ It doesn’t tell us why it failed. We just get nil
, but we don't know what went wrong.
❌ Forces developers to unwrap the optional. Beginners often forget to handle nil
, which leads to unexpected crashes.
Function 2: Using Tuples (getTitleTitle2()
)
func getTitleTitle2() -> (title: String?, error: Error?) {
if isActive {
return ("Title 2", nil)
} else {
return (nil, URLError(.badURL)) // Simulating error
}
}
What Changed?
Now, the function returns a tuple:
title:
→ The actual value (if successful).error: Error?
→ The error (if something goes wrong).
How Do We Handle This?
We need to manually check both values:
let result = getTitleTitle2()
if let title = result.title {
print("Success: \(title)")
} else {
print("Error: \(result.error?.localizedDescription ?? "Unknown Error")")
}
What’s Wrong with This Approach?
❌ Still requires manual error checking. We need to handle both the title
and error manually.
❌ Not Swift-like. It feels like we are simulating an error-handling system instead of using Swift’s built-in error-handling mechanism.
Function 3: Using Result
Enum (getTitle3()
)
func getTitle3() -> Result<String, Error> {
if isActive {
return .success("Title 3")
} else {
return .failure(URLError(.badURL)) // Simulating failure
}
}
What Changed?
This function now returns Swift’s built-in Result
type, which can either be:
.success(value)
→ Contains the title..failure(error)
→ Contains the error.
How Do We Handle This?
We use a switch case:
switch getTitle3() {
case .success(let title):
print("Success: \(title)")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
What’s Wrong with This Approach?
✅ Better than Optionals & Tuples because it forces us to handle both success and failure.
❌ Still requires a switch case. The error-handling logic is separated from where the function is called.
❌ Becomes complex when dealing with multiple function calls. Imagine handling five functions this way — our code would become messy with switch
cases everywhere.
Function 4: Using do-try-catch
(getTitle4()
)
func getTitle4() throws -> String {
if isActive {
return "Title 4"
} else {
throw URLError(.badURL)
}
}
What Changed?
- Instead of returning
nil
, a tuple, or aResult
, this function throws an error if something goes wrong. - This means Swift enforces error handling.
How Do We Handle This?
We must use do-try-catch
:
do {
let title = try getTitle4()
print("Success: \(title)")
} catch {
print("Error: \(error.localizedDescription)")
}
Why do-try-catch
is the Best Approach?
✅ Forces error handling. Unlike Optionals, we cannot ignore the error.
✅ No unnecessary switch case
. We just try
the function and handle the error in catch
.
✅ Easier to read & maintain. Swift’s built-in error handling mechanism keeps the code structured and clear.
Comparing All Four Approaches

Final Thoughts: When to Use What?

Conclusion: Why do-try-catch
is Beautiful
- It forces error handling, so no errors are ignored.
- It keeps code clean, without messy
if let
,switch,
or tuple checks. - It uses Swift’s built-in error system, making it easy to integrate with other libraries.
By understanding why do-try-catch
exists, beginners can appreciate how Swift encourages safer and more structured error handling.
Feel free to drop your thoughts and comments on my Medium blog here and for any project related inquiries, contact us below.