Question 6
Error handling: throws and Result
Compare throws/try/catch with Result for modeling failures.
Answer outline
throws, try, and catch are the natural fit for linear flows. Failure interrupts control flow, and the happy path reads top to bottom with no Result to unwrap at each step. Throwing also composes cleanly with async/await, which makes it the default for most Swift error handling.
Result is more useful when failure needs to be treated as a value. Use it when you store an outcome, pass it across a boundary, combine several results by hand, or defer handling to a later point.
Principles
- Use
throwsfor linear flows, because it keeps the happy path front and center. - Use
Resultwhen the error needs to be stored or passed as data rather than thrown immediately. - Model error types around what the caller needs to handle, and keep implementation details out of them.
throwscomposes naturally with async/await, so prefer it overResultin async code.
Use throws when the caller handles failure as part of normal control flow:
func loadProfile() throws -> Profile {
let data = try Data(contentsOf: profileURL)
return try JSONDecoder().decode(Profile.self, from: data)
}
do {
let profile = try loadProfile()
show(profile)
} catch {
showError(error)
}
Use Result when you want to store, return, or pass around an outcome without throwing immediately:
func loadProfileResult() -> Result<Profile, Error> {
do {
let profile = try loadProfile()
return .success(profile)
} catch {
return .failure(error)
}
}
let result = loadProfileResult()
switch result {
case .success(let profile):
show(profile)
case .failure(let error):
showError(error)
}



