The `Result` Type in Swift: Handling Success and Failure Elegantly
When WALL-E sends a signal back to the Axiom, one of two things happens: the message gets through (success!) or it fails
(maybe space debris blocked the transmission). Swift’s Result type models exactly this — every operation either
succeeds with a value or fails with an error. No ambiguity.
You’ll learn how Result works, how to create and unwrap results, and how to transform them with map and flatMap.
We won’t cover async/await or networking — those build on Result in later posts.
What You’ll Learn
- What Is the Result Type?
- Creating a Result
- Unwrapping a Result
- Transforming Results with
mapandflatMap - Common Mistakes
- What’s Next?
What Is the Result Type?
Result is a built-in enum with two cases: .success and .failure. It uses
generics to let you specify the type of the success value and the type of the
error:
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
Apple Docs: Result — Swift Standard Library
Think of it like a Pixar movie review: the review is either a rave (success with a rating) or a complaint (failure with
a reason). The Result type forces you to handle both possibilities — you can’t accidentally ignore the failure case.
This is different from error handling with throws, where errors fly through the
air and you catch them. With Result, the success or failure is wrapped neatly in a value you can store, pass around,
and transform.
Creating a Result
First, define an error type that conforms to Error:
enum MovieError: Error {
case notFound
case invalidRating
}
Now write a function that returns a Result instead of throwing:
func findMovie(named title: String) -> Result<String, MovieError> {
let pixarMovies = ["Toy Story", "Up", "Coco", "WALL-E"]
if pixarMovies.contains(title) {
return .success(title)
} else {
return .failure(.notFound)
}
}
The return type Result<String, MovieError> makes the contract explicit: this function either succeeds with a String
or fails with a MovieError. No surprises.
let found = findMovie(named: "Coco")
let missing = findMovie(named: "Shrek")
print(found)
print(missing)
success("Coco")
failure(MovieError.notFound)
Unwrapping a Result
You have several ways to extract the value from a Result.
Using switch
The most complete approach handles both cases explicitly:
let result = findMovie(named: "Up")
switch result {
case .success(let title):
print("Found: \(title)")
case .failure(let error):
print("Error: \(error)")
}
Found: Up
Using get() with try
The get() method extracts the success value or throws the error. This bridges Result back into the do-catch
world:
do {
let title = try findMovie(named: "WALL-E").get()
print("Playing: \(title)")
} catch {
print("Something went wrong: \(error)")
}
Playing: WALL-E
Creating a Result from a Throwing Function
You can also go the other direction — wrap a throwing function in a Result:
func loadRating(for title: String) throws -> Double {
guard title == "Toy Story" else {
throw MovieError.notFound
}
return 9.8
}
let result = Result { try loadRating(for: "Toy Story") }
print(result)
success(9.8)
The Result { try ... } initializer catches any thrown error and wraps it as .failure automatically.
Transforming Results with map and flatMap
One of Result’s superpowers is chaining operations without unwrapping at every step.
map — Transform the Success Value
map applies a function to the success value and leaves failures untouched:
let result = findMovie(named: "Coco")
let uppercased = result.map { title in
title.uppercased()
}
print(uppercased)
success("COCO")
If the original result was a .failure, map skips the transformation and passes the failure through unchanged. No
if statements needed.
flatMap — Chain Operations That Can Also Fail
When your transformation itself returns a Result, use flatMap to avoid nesting results inside results:
func fetchRating(for title: String) -> Result<Double, MovieError> {
let ratings = ["Coco": 9.5, "Up": 9.2]
guard let rating = ratings[title] else {
return .failure(.invalidRating)
}
return .success(rating)
}
let finalResult = findMovie(named: "Coco").flatMap { title in
fetchRating(for: title)
}
print(finalResult)
success(9.5)
With flatMap, you chain multiple operations that can each fail, and you only handle the error once at the end. Think
of it like a Pixar production pipeline: each stage (story, animation, rendering) can fail, and flatMap passes work
forward only when each stage succeeds.
Common Mistakes
Ignoring the Failure Case
It’s tempting to only handle success, but that defeats the purpose of Result:
// ❌ Only handling success — failures are silently ignored
let result = findMovie(named: "Shrek")
if case .success(let title) = result {
print("Found: \(title)")
}
// Nothing happens if it's a failure!
// ✅ Handle both cases
let result = findMovie(named: "Shrek")
switch result {
case .success(let title):
print("Found: \(title)")
case .failure(let error):
print("Couldn't find movie: \(error)")
}
Couldn't find movie: notFound
Always handle both .success and .failure. The whole point of Result is making both outcomes explicit.
Using Result When throws Is Simpler
Result shines when you need to store or pass around a result value. If you’re just calling a function and handling the
error immediately, throws with do-catch is simpler:
// ❌ Unnecessary Result — just use throws
func loadMovie() -> Result<String, MovieError> {
return .success("Toy Story")
}
let result = loadMovie()
// Then immediately switch on result...
// ✅ Simpler with throws when you handle errors right away
func loadMovie() throws -> String {
return "Toy Story"
}
do {
let movie = try loadMovie()
print(movie)
} catch {
print(error)
}
Use Result when you need to store the outcome for later, pass it to a callback, or chain transformations with
map/flatMap.
What’s Next?
Result<Success, Failure>wraps an operation’s outcome as either.successor.failure- Use
switch,get(), ormap/flatMapto extract and transform values Result { try ... }bridges throwing functions into theResultworldmaptransforms the success value;flatMapchains failable operations- Prefer
throwsfor simple cases; useResultwhen you need to store or chain outcomes
Ready to transform entire collections in one go? Check out map, filter, reduce, and compactMap to see these same ideas applied to arrays.