← All topics/Swift language features

Practical interview questions

Scenario-style prompts with sample answer outlines. Focus is on how you would design and reason in real codebases.

Question 6

Error handling — throws and Result

Compare throws/try/catch with Result for modeling failures.

Answer outline

throws / try / catch is the natural fit for linear flows — failure interrupts control flow and the happy path stays readable.

It also composes cleanly with async/await, making it the default choice for most Swift error handling.

Result is more useful when failure needs to be treated as a value.

Use it when storing an outcome, passing it across boundaries, combining results manually, or deferring error handling to a later point.

Principles

  • Use throws for linear flows — it keeps the happy path front and center.
  • Use Result when error state needs to be stored or passed as data rather than thrown immediately.
  • Model error types to reflect what the caller needs to handle, not internal implementation details.
  • throws composes naturally with async/await; prefer it over Result in async contexts.

Use throws when the caller should handle failure as part of the normal control flow.

`do` / `try` / `catch`
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 success/failure without throwing immediately.

`Result` as a value
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)
}