# Swift try, try?, and try! Explained with Examples

Source: https://devdocs10.com/articles/swift/swift-try-variants  
By Murat Bo · Published 2026-06-01 · DevDocs10 (https://devdocs10.com)

Swift uses `try`, `try?`, and `try!` when you call a throwing function. These three forms look similar, but they behave very differently: one requires you to handle errors, one converts errors into `nil`, and one crashes if an error happens. Understanding the difference is essential for writing safe, predictable Swift code.

> **Quick answer:** Use `try` when you want to handle or propagate errors normally, `try?` when a failure should simply become `nil`, and `try!` only when you are absolutely sure the call cannot fail at runtime.

> **Difficulty:** Beginner
>
> **Helpful to know first:** You will understand this better if you know basic Swift functions, optionals, and how `if` statements and constants work.

## 1. What Is Swift try, try?, and try!?

In Swift, some functions are marked with `throws`. That means the function can either return a normal value or stop by throwing an error. When you call one of these functions, Swift makes you be explicit about how you want to deal with the possible failure.

- `try` calls a throwing function and requires real error handling.
- `try?` calls a throwing function and turns any thrown error into an optional result.
- `try!` calls a throwing function and assumes it will never fail; if it does fail, your program crashes.
- These forms only apply to functions or expressions that can throw.
- They are part of Swift's compile-time safety system, so Swift prevents you from ignoring possible errors accidentally.

This topic is often confused with optionals and force unwrapping. The key difference is that `try?` handles a thrown error by returning an optional, while `try!` does not return an optional just because of the `!`; instead, it force-assumes success and crashes on failure.

## 2. Why try Variants Matter

These three forms matter because different failures should be handled in different ways.

For example, if you load a configuration file that must exist for the app to work, you might use normal `try` and stop with a clear message if it fails. If you are attempting an optional operation, such as parsing a value that may or may not be present, `try?` may be a good fit. If you are calling something that you know cannot fail in the current context, `try!` can make code shorter, but it should be rare.

Choosing the wrong form can lead to several problems:

- You may hide an important error by using `try?` too casually.
- You may crash your app by using `try!` when failure is possible.
- You may write awkward code if you use plain `try` when a simple optional result would be clearer.

## 3. Basic Syntax

### Defining a throwing function

First, here is a simple throwing function. It returns a username unless the input is empty.

```swift
enum ValidationError: Error {
    case emptyName
}

func validatedName(_ name: String) throws -> String {
    if name.isEmpty {
        throw ValidationError.emptyName
    }

    return name
}
```

This function is marked with `throws`, so callers must use one of the Swift `try` forms.

### Using plain try

Use plain `try` when you want to handle the error properly.

```swift
do {
    let name = try validatedName("Taylor")
    print(name)
} catch {
    print("Validation failed: \(error)")
}
```

Output (tested with Swift 6.4):

```text
Taylor
```

The `do-catch` block catches any error thrown by the function.

### Using try?

Use `try?` when you want failure to become an optional value.

```swift
let name = try? validatedName("")
print(name as Any)
```

Output (tested with Swift 6.4):

```text
nil
```

If the function throws, `name` becomes `nil` instead of stopping with an error.

### Using try!

Use `try!` only when failure is impossible or already guaranteed not to happen.

```swift
let name = try! validatedName("Morgan")
print(name)
```

Output (tested with Swift 6.4):

```text
Morgan
```

This works if the function succeeds. If it throws, the program stops immediately with a runtime crash.

## 4. Step-by-Step Examples

### Example 1: Handling a thrown error with do-catch

This example shows the standard way to call a throwing function and respond to failure.

```swift
enum PasswordError: Error {
    case tooShort
}

func checkPassword(_ password: String) throws -> String {
    if password.count < 8 {
        throw PasswordError.tooShort
    }

    return "Accepted"
}

do {
    let result = try checkPassword("secret123")
    print(result)
} catch {
    print("Password check failed: \(error)")
}
```

Output (tested with Swift 6.4):

```text
Accepted
```

Use this style when the reason for failure matters and you want to keep that information.

### Example 2: Using try? for optional parsing

Sometimes a failure is not exceptional. In that case, turning a thrown error into `nil` makes the code simpler.

```swift
enum ParseError: Error {
    case invalidNumber
}

func parseWholeNumber(_ text: String) throws -> Int {
    guard let number = Int(text) else {
        throw ParseError.invalidNumber
    }

    return number
}

let age = try? parseWholeNumber("42")
let badAge = try? parseWholeNumber("forty-two")

print(age as Any)
print(badAge as Any)
```

Output (tested with Swift 6.4):

```text
Optional(42)
nil
```

The first result is an optional containing `42`. The second result is `nil`. This is useful when you only care whether the operation succeeded.

### Example 3: Using try! when success is guaranteed

This example shows the narrow case where `try!` can be acceptable.

```swift
enum TextError: Error {
    case empty
}

func firstCharacter(of text: String) throws -> Character {
    guard let character = text.first else {
        throw TextError.empty
    }

    return character
}

let letter = try! firstCharacter(of: "Swift")
print(letter)
```

Output (tested with Swift 6.4):

```text
S
```

This works because the string literal is clearly non-empty. The risk is low here, but in general `try!` should be used sparingly.

### Example 4: Propagating errors from another throwing function

You do not always handle errors immediately. Sometimes your function should pass them upward.

```swift
enum LoginError: Error {
    case missingUsername
}

func validateUsername(_ username: String) throws -> String {
    if username.isEmpty {
        throw LoginError.missingUsername
    }

    return username
}

func normalizedUsername(from input: String) throws -> String {
    let username = try validateUsername(input)
    return username.lowercased()
}

do {
    let name = try normalizedUsername(from: "Admin")
    print(name)
} catch {
    print("Login problem: \(error)")
}
```

Output (tested with Swift 6.4):

```text
admin
```

Here, the inner function uses plain `try` because it wants to pass the error upward rather than converting or force-assuming success.

## 5. Practical Use Cases

- Use `try` when validating user input and you need to tell the user exactly what went wrong.
- Use `try` when loading important app data where failure must be handled explicitly.
- Use `try?` when a failed conversion should simply produce no result, such as optional parsing or best-effort reads.
- Use `try?` when you want concise code in an `if let` or `guard let` chain.
- Use `try!` in tests, prototypes, or cases where the input is fully controlled and failure would indicate a programming mistake.
- Use plain `try` inside another throwing function when you want to propagate the error to the caller.

## 6. Common Mistakes

### Mistake 1: Calling a throwing function without try

A throwing function must be called with one of Swift's `try` forms. Beginners often forget this when they first see `throws`.

**Problem:** Swift will report an error like `Call can throw, but it is not marked with 'try' and the error is not handled` because the code ignores the function's failure path.

```swift
enum FileError: Error {
    case missing
}

func loadFileName() throws -> String {
    throw FileError.missing
}

let name = loadFileName()
```

**Fix:** Add `try` and either catch the error or propagate it.

```swift
do {
    let name = try loadFileName()
    print(name)
} catch {
    print("Could not load file name: \(error)")
}
```

The corrected version works because it explicitly acknowledges that the function can fail.

### Mistake 2: Using try? when the error information matters

`try?` is convenient, but it silently discards the actual error. That can make debugging and user feedback much harder.

**Problem:** This code turns every failure into `nil`, so you cannot tell whether the problem was missing data, invalid input, or something else.

```swift
enum AccountError: Error {
    case locked
    case missingUser
}

func fetchAccountName(for id: Int) throws -> String {
    throw AccountError.locked
}

let name = try? fetchAccountName(for: 42)
print(name as Any)
```

Output (tested with Swift 6.4):

```text
nil
```

**Fix:** Use plain `try` with `do-catch` when the cause of failure should be preserved.

```swift
do {
    let name = try fetchAccountName(for: 42)
    print(name)
} catch AccountError.locked {
    print("The account is locked.")
} catch AccountError.missingUser {
    print("No user was found.")
} catch {
    print("Unexpected error: \(error)")
}
```

The corrected version works because it keeps the error information available for specific handling.

### Mistake 3: Using try! when failure is possible

`try!` is the riskiest form. It looks short and convenient, but it will crash if the function throws.

**Problem:** If the input is invalid, this code causes a runtime trap instead of handling the failure safely.

```swift
enum NumberError: Error {
    case negative
}

func squareRoot(of value: Int) throws -> Double {
    if value < 0 {
        throw NumberError.negative
    }

    return Double(value).squareRoot()
}

let result = try! squareRoot(of: -9)
print(result)
```

Output (tested with Swift 6.4):

```text
main/main.swift:13: Fatal error: 'try!' expression unexpectedly raised an error: main.NumberError.negative
```

**Fix:** Use plain `try` and catch the error, or use `try?` if losing the error details is acceptable.

```swift
do {
    let result = try squareRoot(of: -9)
    print(result)
} catch {
    print("Cannot calculate square root: \(error)")
}
```

The corrected version works because it handles the error path instead of force-assuming success.

## 7. Best Practices

### Prefer try as the default choice

In most real applications, errors are meaningful. Starting with plain `try` keeps the code honest and preserves error details.

```swift
do {
    let username = try validatedName("Avery")
    print(username)
} catch {
    print("Validation error: \(error)")
}
```

Output (tested with Swift 6.4):

```text
Avery
```

This approach is easier to maintain because future failures remain visible and debuggable.

### Use try? only for truly optional results

If failure is expected and should simply mean “no value,” `try?` is a clean option.

```swift
let number = try? parseWholeNumber("100")

if let number = number {
    print("Parsed: \(number)")
} else {
    print("No valid number provided.")
}
```

This is a good fit because the code only needs success or absence, not detailed error reasons.

### Avoid try! in production paths unless failure is impossible

Even if `try!` looks safe today, later code changes can make it unsafe. Keep it for tightly controlled situations.

```swift
// Acceptable only because the input is fully known here.
let letter = try! firstCharacter(of: "Hello")
print(letter)
```

Use this style only when the failure condition is impossible in practice and obvious to future readers.

## 8. Limitations and Edge Cases

- `try?` discards the original error, so you cannot inspect what failed later.
- `try!` crashes at runtime if the function throws, which makes it dangerous for user-controlled input.
- If a function is not marked `throws`, you do not use any `try` form with it.
- Inside a throwing function, plain `try` can propagate errors upward without `do-catch`.
- Using `try?` changes the result type into an optional, which may require additional unwrapping.
- A common “not working” scenario is forgetting that `try?` can hide why a value became `nil`.

## 9. Swift try vs try? vs try!

Because these three forms are commonly confused, it helps to compare them directly.

| Form | What happens on success | What happens on failure | Typical use |
| --- | --- | --- | --- |
| `try` | Returns the normal value | Throws an error that must be caught or propagated | Default choice when error details matter |
| `try?` | Returns an optional containing the value | Returns `nil` | Optional or best-effort operations |
| `try!` | Returns the normal value | Crashes the program | Rare cases where failure is impossible |

> A simple rule is: start with `try`, switch to `try?` only when `nil` is the right failure result, and use `try!` only when you can prove the call will never throw.

## 10. Practical Mini Project

This mini project validates a promo code entered by the user. It shows all three forms in a small, complete example.

```swift
enum PromoCodeError: Error {
    case emptyCode
    case invalidCode
}

func validatePromoCode(_ code: String) throws -> String {
    if code.isEmpty {
        throw PromoCodeError.emptyCode
    }

    let allowedCodes = ["SAVE10", "FREESHIP"]

    guard allowedCodes.contains(code) else {
        throw PromoCodeError.invalidCode
    }

    return code
}

// 1. Standard error handling with try
do {
    let validCode = try validatePromoCode("SAVE10")
    print("Accepted code: \(validCode)")
} catch {
    print("Promo code error: \(error)")
}

// 2. Optional handling with try?
let optionalCode = try? validatePromoCode("BADCODE")
print("Optional result: \(optionalCode as Any)")

// 3. Forced success with try!
let knownCode = try! validatePromoCode("FREESHIP")
print("Known valid code: \(knownCode)")
```

Output (tested with Swift 6.4):

```text
Accepted code: SAVE10
Optional result: nil
Known valid code: FREESHIP
```

This example shows the three variants side by side. In a real app, the first version is usually the safest, the second is useful for optional flows, and the third should be limited to guaranteed-success inputs.

## 11. Key Points

- `try` is the standard form for calling a throwing function.
- `try?` converts a thrown error into `nil`.
- `try!` force-assumes success and crashes if an error is thrown.
- Use plain `try` when the reason for failure matters.
- Use `try?` when failure should simply mean “no value.”
- Use `try!` only in tightly controlled situations where failure is impossible.
- A common compiler error is forgetting to mark a throwing call with `try`.
- `try?` changes the result into an optional, so you may need to unwrap it.

## 12. Practice Exercise

Build a throwing function called `positiveNumber(from:)` that:

- Takes a `String` input.
- Throws an error if the string cannot be converted to an `Int`.
- Throws an error if the number is less than or equal to zero.
- Returns the valid positive number otherwise.
- Call it once with plain `try` inside `do-catch`.
- Call it once with `try?`.

**Expected output:** One successful result and one optional `nil` for invalid input.

**Hint:** Create an `enum` that conforms to `Error` and use `guard` statements.

```swift
enum PositiveNumberError: Error {
    case notANumber
    case notPositive
}

func positiveNumber(from text: String) throws -> Int {
    guard let number = Int(text) else {
        throw PositiveNumberError.notANumber
    }

    guard number > 0 else {
        throw PositiveNumberError.notPositive
    }

    return number
}

do {
    let value = try positiveNumber(from: "25")
    print("Valid number: \(value)")
} catch {
    print("Error: \(error)")
}

let optionalValue = try? positiveNumber(from: "-5")
print("Optional result: \(optionalValue as Any)")
```

Output (tested with Swift 6.4):

```text
Valid number: 25
Optional result: nil
```

## 13. Final Summary

Swift's `try` variants let you choose how a throwing function should behave at the call site. Plain `try` is the normal and safest default because it keeps errors visible. `try?` is useful when failure should quietly become `nil`. `try!` is the most dangerous option because it crashes if anything goes wrong.

If you remember one rule, make it this: use `try` first, use `try?` when an optional result is the right model, and use `try!` only when failure is truly impossible. A good next step is to learn deeper Swift error handling patterns such as custom error types, `do-catch` matching, and rethrowing functions.
