Result Builders

Reviewed & published by Brayan K

Result builders power SwiftUI's declarative syntax. Learn @resultBuilder, the buildBlock / buildOptional / buildEither / buildArray methods, how @ViewBuilder and the body DSL work, and write a simple builder yourself.

Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Your First Builder

Mark the type @resultBuilder and implement buildBlock. Applying the attribute to a function turns its body into a declarative DSL.

// A result builder turns a block of statements into a single value.
// Mark the type @resultBuilder and provide buildBlock.

@resultBuilder
struct StringBuilder {
    // Combine the lines written in the block into one String.
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }
}

// Apply the builder to a function: its body is now a DSL.
@StringBuilder
func makePoem() -> String {
    "Roses are red"
    "Violets are blue"
    "Swift builds results"
    "Declaratively too"
}

print(makePoem())

2️⃣ Control Flow in a Builder

Add more methods to unlock control flow: buildOptional for a bare if, buildEither for if/else, and buildArray for for loops.

// Extra build methods unlock if/else and for inside the block.
@resultBuilder
struct ListBuilder {
    static func buildBlock(_ items: [String]...) -> [String] { items.flatMap { $0 } }
    static func buildExpression(_ s: String) -> [String] { [s] }
    static func buildOptional(_ item: [String]?) -> [String] { item ?? [] }
    static func buildEither(first: [String]) -> [String] { first }
    static func buildEither(second: [String]) -> [String] { second }
    static func buildArray(_ items: [[String]]) -> [String] { items.flatMap { $0 } }
}

@ListBuilder
func shoppingList(includeSnacks: Bool) -> [String] {
    "Bread"
    if includeSnacks { "Chips" }     // needs buildOptional
    for fruit in ["Apple", "Pear"] { fruit }   // needs buildArray
}

print(shoppingList(includeSnacks: true))

3️⃣ SwiftUI's @ViewBuilder

SwiftUI's body is built by @ViewBuilder. That is why you list views with no commas or return, and why if/else works inside a body.

// SwiftUI's body uses the @ViewBuilder result builder. That is why you
// list views with no commas and no explicit return.
import SwiftUI

struct ProfileView: View {
    let loggedIn: Bool

    var body: some View {      // @ViewBuilder is implied here
        VStack {
            Text("Welcome")
            if loggedIn {       // works because of buildOptional/buildEither
                Text("Dashboard")
            } else {
                Text("Please sign in")
            }
        }
    }
}

// You can apply @ViewBuilder yourself to a helper:
@ViewBuilder
func banner(_ show: Bool) -> some View {
    if show { Text("New!") }
}

Your turn. Fill in the defining attribute and the required method.

// 🎯 YOUR TURN — fill in each ___ then run it on SwiftFiddle.

___                                   // 👉 the attribute that defines a builder
struct Joiner {
    static func ___(_ parts: String...) -> String {   // 👉 the required method
        parts.joined(separator: " ")
    }
}

@Joiner
func greeting() -> String {
    "Hello"
    "there"
    "world"
}

print(greeting())                     // Hello there world

// ✅ Expected output:
//    Hello there world

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

MethodEnablesRequired?
buildBlockcombine a blockyes
buildOptionalif (no else)optional
buildEitherif/else, switchoptional
buildArrayfor loopsoptional
buildExpressionlift expressionsoptional
buildFinalResultpost-processoptional

Mini-Challenge: A Number Builder

Write a builder whose buildBlock sums the numbers you list, then total three values.

// 🎯 MINI-CHALLENGE: a number builder
// 1. Define @resultBuilder struct SumBuilder.
// 2. buildBlock(_ numbers: Int...) -> Int returns numbers.reduce(0, +).
// 3. Apply @SumBuilder to a function total() that lists 10, 20, 30.
// 4. print(total())  ->  60
//
// ✅ Expected output:
//    60

// your code here

Practice quiz

Which attribute defines a result builder?

  • @resultBuilder
  • @dsl
  • @buildable
  • @builder

Answer: @resultBuilder. A type becomes a result builder when marked with the @resultBuilder attribute.

Which method is required and combines the statements in a block?

  • buildList
  • buildAll
  • buildBlock
  • buildFinal

Answer: buildBlock. buildBlock(_:) is the core method that combines the components written in a block.

Which method enables an if without else inside a builder?

  • buildIf
  • buildOptional
  • buildMaybe
  • buildEither

Answer: buildOptional. buildOptional(_:) handles an if statement that may produce no value.

Which method handles the two branches of an if/else?

  • buildBranch
  • buildSwitch
  • buildChoice
  • buildEither(first:)/buildEither(second:)

Answer: buildEither(first:)/buildEither(second:). buildEither(first:) and buildEither(second:) handle the two branches of if/else and switch.

Which method supports a for loop inside the builder?

  • buildArray
  • buildEach
  • buildLoop
  • buildFor

Answer: buildArray. buildArray(_:) combines the results produced by iterating a for loop.

What is SwiftUI's @ViewBuilder an example of?

  • A protocol
  • A result builder
  • A macro
  • A property wrapper

Answer: A result builder. @ViewBuilder is a result builder that assembles the views listed in a body into one view.

Why doesn't a SwiftUI body need commas or return between views?

  • They are stored in an array literal
  • Macros remove them
  • The compiler ignores them
  • A result builder transforms the statements into one combined value

Answer: A result builder transforms the statements into one combined value. The result builder rewrites the listed statements into buildBlock calls, so no commas or return are needed.

Where do you attach a result builder so a closure uses it?

  • Only on the type
  • In a protocol only
  • On a parameter, property, or function as an attribute
  • Nowhere; it is automatic

Answer: On a parameter, property, or function as an attribute. You apply the builder attribute to a function, computed property, or a closure parameter to transform that block.

Which optional method post-processes the final combined result?

  • buildResult
  • buildFinalResult
  • buildExpression
  • buildLimited

Answer: buildFinalResult. buildFinalResult(_:) lets you transform the assembled value one last time before it is returned.

What does buildExpression let you do?

  • Convert each individual expression into the component type
  • Nothing useful
  • Run the block twice
  • Add commas

Answer: Convert each individual expression into the component type. buildExpression(_:) lets the builder accept raw expressions and lift them into its component type.

Continue this course

Frequently asked questions

What is a result builder in Swift?

A result builder is a type marked with the @resultBuilder attribute that lets you write a block of statements and have the compiler combine them into a single value. Instead of building a collection by hand with commas, appends, and returns, you list items declaratively and the builder's static methods assemble them. SwiftUI's view bodies are the most famous example, but you can write your own builders for HTML, attributed strings, test plans, or any nested, tree-shaped data.

Which build methods does a result builder need?

The only required method is buildBlock, which combines the components written in a block into one result. Beyond that you opt into features: buildOptional supports an if with no else, buildEither(first:) and buildEither(second:) support the two branches of if/else and switch, buildArray supports for loops, buildExpression lets the builder accept raw expressions, and buildFinalResult post-processes the assembled value. You implement only the ones your DSL needs.

How does SwiftUI's ViewBuilder use this feature?

@ViewBuilder is a result builder provided by SwiftUI. When you write a view's body and list several views with no commas and no explicit return, the compiler is actually feeding those statements through ViewBuilder's build methods, which combine them into a single opaque some View value. The same mechanism is why you can use if/else and limited for loops inside a body: ViewBuilder implements buildOptional, buildEither, and buildArray under the hood.

How do I apply a result builder to my own code?

You attach the builder as an attribute. Putting @MyBuilder on a function makes its body a DSL transformed by that builder. You can also mark a computed property or a closure parameter with the attribute so any caller's trailing closure is built the same way. SwiftUI marks the body requirement and many container initializers with @ViewBuilder, which is why your view code automatically gets the declarative syntax without you writing the attribute yourself.

When should I write my own result builder?

Most developers consume result builders far more than they write them. Reach for a custom one when you have a nested, declarative structure that would otherwise require verbose, repetitive construction code, such as generating HTML, composing query predicates, assembling navigation routes, or building styled text. If your data is flat or you only build it once, a normal array or initializer is simpler. Builders pay off when readability of nested, conditional structure matters.