Post

Understanding Eager vs. Lazy Evaluation in Swift

Clearly explained differences between eager and lazy evaluation in Swift with practical code examples.

Understanding Eager vs. Lazy Evaluation in Swift

When working with Swift, understanding the evaluation strategies—eager (strict) and lazy evaluation—is crucial. Let’s demystify these concepts clearly with practical examples.

Eager Evaluation (Strict Evaluation)

In eager evaluation, the closure or expression executes immediately at the point of declaration. Swift’s built-in Result(catching:) initializer follows eager evaluation, running your closure immediately.

Consider this example:

1
2
3
4
5
6
7
8
9
10
11
let result = Result<Int, Error> {
    print("🔧 running the work now…")
    return 123
}
print("⏸ result init is done")
do {
    let value = try result.get()
    print("✅ got value:", value)
} catch {
    print("❌ got error:", error)
}

Console output:

1
2
3
🔧 running the work now
 result init is done
 got value: 123

Notice the closure runs immediately (“🔧 running the work now…”) before even reaching the print statement that follows. Calling get() later doesn’t run the closure again—it merely returns the already computed result.

Why Swift’s Result is Eager

Here’s why Result behaves eagerly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum Result<Success, Failure: Error> {
  case success(Success)
  case failure(Failure)

  init(catching body: () throws -> Success) {
    do {
      let value = try body()
      self = .success(value)
    } catch {
      self = .failure(error as! Failure)
    }
  }

  func get() throws -> Success {
    switch self {
      case .success(let v): return v
      case .failure(let e): throw e
    }
  }
}

The initializer explicitly executes the closure (body()) at initialization, capturing the result immediately.

Lazy Evaluation

Lazy evaluation defers computation until the value is actually required. Swift’s standard library does not include a lazy variant of Result, but here’s how you could implement one:

1
2
3
4
5
6
7
8
9
10
11
struct LazyResult<Success, Failure: Error> {
  private let thunk: () throws -> Success

  init(_ work: @escaping () throws -> Success) {
    self.thunk = work
  }

  func get() throws -> Success {
    return try thunk()
  }
}

Usage:

1
2
3
4
5
6
7
8
9
10
11
let lazyResult = LazyResult<Int, Error> {
    print("🔧 lazily running the work now…")
    return 456
}
print("⏸ lazy result init is done")
do {
    let value = try lazyResult.get()
    print("✅ got lazy value:", value)
} catch {
    print("❌ got lazy error:", error)
}

Output:

1
2
3
 lazy result init is done
🔧 lazily running the work now
 got lazy value: 456

Notice how the computation (print) happens only when calling get(), clearly showcasing lazy evaluation.

Summary

  • Eager (strict) evaluation: executes immediately upon initialization.
  • Lazy evaluation: execution defers until the result is explicitly requested.

Understanding these concepts allows you to choose the right approach for your Swift apps, enhancing performance and predictability.

☕ Support My Work

If you found this post helpful and want to support more content like this, you can buy me a coffee!

Your support helps me continue creating useful articles and tips for fellow developers. Thank you! 🙏

This post is licensed under CC BY 4.0 by the author.