# Swift Result Type (Result<Success, Failure>): Complete Guide

Source: https://devdocs10.com/articles/swift/swift-result-type  
By Murat Bo · Published 2026-05-29 · DevDocs10 (https://devdocs10.com)

The `Result` type is Swift’s standard way to represent an operation that either succeeds with a value or fails with an error. It is especially useful when you want to return success and failure information from a function, callback, or asynchronous API in a form that is easy to switch over and compose.

> **Quick answer:** `Result<Success, Failure>` is an enum with two cases: `.success(Success)` and `.failure(Failure)`. Use it when you want a typed, explicit way to return either a value or an error instead of throwing or using optional values.

> **Difficulty:** Beginner to Intermediate
>
> **You'll understand this better if you know:** basic Swift enums, error handling with `throw` and `catch`, and how functions return values.

## 1. What Is Result<Success, Failure>?

`Result` is a generic enum in the Swift standard library designed to model the outcome of an operation. It wraps either a successful value or a failure value, and both sides are part of the type signature.

- `.success` stores the successful output.
- `.failure` stores an error.
- The `Success` type can be almost anything.
- The `Failure` type must conform to `Error`.

This makes `Result` a safer alternative to “return a value or hope the caller notices the error somehow.”

## 2. Why Result Matters

`Result` matters because many Swift APIs need to communicate success and failure without immediately stopping execution. It is common in networking, file loading, parsing, and asynchronous callbacks.

Compared with using an optional, `Result` tells you why something failed. Compared with a thrown error, it can be easier to pass through closures, store, or transform later.

Use `Result` when you want:

- Explicit success and failure handling in one value.
- A clean way to pass results through completion handlers.
- Better composition with helper methods like `map` and `flatMap`.
- A single return type that documents what can happen.

## 3. Basic Syntax

The generic form is `Result<Success, Failure>`. Swift often infers the generic types, so you may also see just `Result` in context.

### The two cases

Here is the minimal shape of a result value:

```swift
enum Result<Success, Failure : Error> {
    case success(Success)
    case failure(Failure)
}
```

In real Swift code, you do not define this yourself; it already exists in the standard library.

### Creating a result value

You create a successful result with `.success` and a failing result with `.failure`.

```swift
let successResult = Result<String, Error>.success("Downloaded")
let failureResult = Result<String, Error>.failure(URLError(.notConnectedToInternet))
```

This gives you a value you can return, store, inspect, or transform later.

## 4. Step-by-Step Examples

### Example 1: Returning a result from a function

Here is a simple parser that either returns a number or a custom error.

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

func parseScore(_ text: String) -> Result<Int, ParseError> {
    guard !text.isEmpty else {
        return .failure(.emptyInput)
    }

    guard let score = Int(text) else {
        return .failure(.invalidNumber)
    }

    return .success(score)
}
```

This pattern makes the caller handle both the parsed value and the failure reason.

### Example 2: Switching over the result

Callers usually use `switch` to handle both cases explicitly.

```swift
let result = parseScore("42")

switch result {
case .success(let value):
    print("Parsed score:", value)
case .failure(let error):
    print("Could not parse score:", error)
}
```

Output (tested with Swift 6.4):

```text
Parsed score: 42
```

The compiler forces you to account for both paths, which is one of the main advantages of `Result`.

### Example 3: Using map to transform success values

If the operation succeeds, you can transform the wrapped value without changing the failure case.

```swift
let parsed = parseScore("7")
let doubled = parsed.map { value in
    value * 2
}
```

`map` only changes the success value. If the result is already a failure, it stays a failure.

### Example 4: Converting a result into a thrown error

Sometimes you receive a result but want to integrate with throwing code.

```swift
func scoreValue(from text: String) throws -> Int {
    switch parseScore(text) {
    case .success(let value):
        return value
    case .failure(let error):
        throw error
    }
}
```

This is a common bridge when moving between callback-based APIs and throwing APIs.

## 5. Practical Use Cases

`Result` is most useful when you want to keep error information attached to the operation outcome.

- Networking callbacks that return downloaded data or an error.
- File loading utilities that may succeed with parsed content or fail with a readable error.
- Validation code that returns either a clean value or a reason it was rejected.
- Background work where you want to store the outcome and inspect it later.
- APIs that must avoid `throws` because the failure needs to travel through escaping closures.

It is less useful when failure is impossible, when a simple optional is enough, or when the caller should not proceed at all after an error.

## 6. Common Mistakes

### Mistake 1: Using Result when an optional is enough

Sometimes developers use `Result` just to say “maybe there is a value.” That adds unnecessary complexity when there is no meaningful error to report.

**Problem:** If the only thing you can say is that the value is missing, wrapping it in `Result` makes the API harder to use without adding useful information.

```swift
func nickname(for name: String) -> Result<String, Error> {
    if name.isEmpty {
        return .failure(NSError(domain: "App", code: 1))
    }
    return .success(name)
}
```

**Fix:** Use an optional when you only need to represent presence or absence.

```swift
func nickname(for name: String) -> String? {
    guard !name.isEmpty else {
        return nil
    }
    return name
}
```

The corrected version is easier to read because it uses the simplest type that matches the problem.

### Mistake 2: Ignoring the failure case

Some code only looks at the success path and forgets that a result can fail. This usually happens when developers try to extract the value too early.

**Problem:** Accessing only the success side without handling the failure leads to incomplete logic and often to compiler complaints when the result is not fully covered.

```swift
let result = parseScore("abc")

if case .success(let value) = result {
    print(value)
}
```

**Fix:** Handle both cases with `switch` or provide an `else` branch.

```swift
switch parseScore("abc") {
case .success(let value):
    print("Value:", value)
case .failure(let error):
    print("Error:", error)
}
```

Output (tested with Swift 6.4):

```text
Error: invalidNumber
```

The corrected version works because it does not assume success is the only possible outcome.

### Mistake 3: Using a non-Error failure type

The failure type must conform to `Error`. If it does not, Swift will reject the declaration.

**Problem:** A failure type like `String` does not satisfy the generic constraint, so the compiler cannot treat it as a valid `Result` failure.

```swift
let badResult: Result<Int, String> = .failure("Not allowed")
```

**Fix:** Define an error type that conforms to `Error`, or use an existing error type such as `Error` or `URLError`.

```swift
enum LoadError: Error {
    case message(String)
}

let goodResult: Result<Int, LoadError> = .failure(.message("Not allowed"))
```

The corrected version compiles because the failure type now meets Swift’s requirement.

## 7. Best Practices

### Practice 1: Use precise failure types

A specific error type makes your API clearer than a generic `Error` when you know the likely failure cases.

```swift
enum LoginError: Error {
    case emptyUsername
    case invalidPassword
}
```

This helps callers understand and handle failures more accurately.

### Practice 2: Prefer `switch` when both outcomes matter

`switch` makes it obvious that both cases are handled.

```swift
switch parseScore("8") {
case .success(let value):
    print(value)
case .failure(let error):
    print(error)
}
```

Output (tested with Swift 6.4):

```text
8
```

This reads better than forcing the value out of the result too early.

### Practice 3: Use result helpers for transformation

Methods like `map`, `mapError`, and `flatMap` keep pipelines clean.

```swift
let message = parseScore("12")
    .map { value in "Score: \(value)" }
    .mapError { error in "Parse error: \(error)" }
```

This keeps success and failure handling in one chain instead of branching repeatedly.

## 8. Limitations and Edge Cases

- `Result` is not a replacement for every optional return value.
- It can become verbose if you use it for very small, local operations.
- When bridging from older closure-based APIs, you may still see legacy callback styles that use separate value and error parameters.
- `Result` is great for one outcome and one failure, but less ideal for operations that need multiple partial errors.
- Some APIs prefer throwing because it fits the call site better, especially for synchronous work.
- A `Result` value does not automatically log, show, or recover from the error; you still need explicit handling.

A common “not working” scenario is assuming that `Result` itself prints a readable message. It only stores the outcome; formatting is up to your code.

## 9. Practical Mini Project

Let’s build a tiny account lookup service that returns a result instead of throwing. This example shows how `Result` keeps success and failure handling explicit.

```swift
struct User {
    let id: Int
    let name: String
}

enum LookupError: Error {
    case notFound
    case invalidID
}

func lookupUser(id text: String) -> Result<User, LookupError> {
    guard let id = Int(text) else {
        return .failure(.invalidID)
    }

    if id == 1 {
        return .success(User(id: 1, name: "Ava"))
    } else {
        return .failure(.notFound)
    }
}

let lookup = lookupUser(id: "1")

switch lookup {
case .success(let user):
    print("Welcome, \(user.name)")
case .failure(let error):
    switch error {
    case .notFound:
        print("User not found")
    case .invalidID:
        print("Please enter a numeric ID")
    }
}
```

Output (tested with Swift 6.4):

```text
Welcome, Ava
```

This mini project shows a typical pattern: parse input, return a typed success or failure, and handle both outcomes clearly at the call site.

## 10. Key Points

- `Result<Success, Failure>` stores either a success value or an error.
- The failure type must conform to `Error`.
- `switch` is the clearest way to handle both cases.
- `map` and related helpers make success transformations easier.
- Use `Result` when explicit outcome reporting is more useful than an optional or a thrown error.

## 11. Practice Exercise

- Write a function named `divide` that accepts two `Double` values.
- Return `Result<Double, DivisionError>`.
- Use one error case for division by zero.
- Print the quotient on success or the error on failure.

**Expected output:** If the denominator is zero, the program should report a division error. Otherwise, it should print the calculated quotient.

**Hint:** Check the denominator before dividing and return `.failure` early when it is zero.

```swift
enum DivisionError: Error {
    case divisionByZero
}

func divide(_ lhs: Double, _ rhs: Double) -> Result<Double, DivisionError> {
    guard rhs != 0 else {
        return .failure(.divisionByZero)
    }

    return .success(lhs / rhs)
}

switch divide(10, 2) {
case .success(let value):
    print("Quotient:", value)
case .failure(let error):
    print("Division failed:", error)
}
```

Output (tested with Swift 6.4):

```text
Quotient: 5.0
```

## 12. Final Summary

`Result<Success, Failure>` is Swift’s built-in way to represent an operation that either produced a value or failed with an error. It is especially helpful when you want the outcome to stay explicit, type-safe, and easy to handle in callbacks or transformation pipelines.

Use it when the caller needs to know both the success value and the reason for failure. Prefer a simple optional when failure is not meaningful, and prefer throwing when synchronous code reads more naturally with `try` and `catch`.

Once you are comfortable switching over `Result`, the next step is to learn how to combine it with `map`, `flatMap`, and custom error types in real APIs.
