Understanding 'indirect' keyword in Swift - Recursive Enums Explained Clearly

May 2, 2026

Swift is a language that rewards clarity and safety. Most of the time, its features feel obvious once you see them in action.

The indirect keyword is not one of those features.

At first glance, it feels obscure. You rarely see it in day-to-day app development. But when you do need it, nothing else quite replaces it.

This article breaks down:

  • What is indirect
  • Why it exists
  • Where it is used
  • How it works under the hood
  • When you should (and should not) use it

What is indirect?

The indirect keyword in Swift is used with enumerations to enable recursive data structures.

It tells the compiler:

“Store this case indirectly (via a reference), not inline.”

This matters when an enum case refers to itself, either directly or indirectly.

It’s not just a keyword.

It’s a design escape hatch for a very real limitation of value types.


The Problem indirect Solves

Let’s start with a simple attempt:

enum Node {
    case value(Int)
    case branch(Node, Node)
}

This will not compile.

Why?

Because Swift enums are value types, and value types require a known, finite size at compile time.

But here:

case branch(Node, Node)

Node contains Node, which contains Node, which contains Node

This creates an infinite size problem.

The compiler cannot determine how much memory to allocate.


Enter indirect

We fix this by marking the recursive case as indirect:

enum Node {
    case value(Int)
    indirect case branch(Node, Node)
}

Now it compiles.

What changed?

The branch case is no longer stored inline. Instead, Swift stores it indirectly, typically using a heap allocation and reference.

This breaks the infinite recursion problem.

In other words:

  • Without indirect → everything is stack-like, inline, predictable
  • With indirect → you introduce heap allocation and indirection

You are trading:

✅ Infinite structure support
❌ For absolute memory predictability

That trade is intentional.


Case-Level vs Enum-Level indirect

You have two options:

Targeted (Better Default)

enum Node {
    case value(Int)
    indirect case branch(Node, Node)
}

This is precise. You only pay the cost where needed.

Blanket (Convenient, but Lazy)

indirect enum Node {
    case value(Int)
    case branch(Node, Node)
}

This works but it’s often overkill.

Opinion:

If you mark the entire enum as indirect, you’re probably not thinking hard enough about your data model.


A Real Example: Expression Trees

This is where indirect becomes genuinely useful.

Let’s model mathematical expressions:

indirect enum Expression {
    case number(Int)
    case addition(Expression, Expression)
    case multiplication(Expression, Expression)
}

This is not just an enum.

This is a tree.

Building a Real Expression

let expr = Expression.multiplication(
    .addition(.number(2), .number(3)),
    .number(4)
)

This represents:

(2 + 3) * 4

Evaluating the Expression

Now let’s evaluate it:

func evaluate(_ expression: Expression) -> Int {
    switch expression {
    case .number(let value):
        return value
    case .addition(let left, let right):
        return evaluate(left) + evaluate(right)
    case .multiplication(let left, let right):
        return evaluate(left) * evaluate(right)
    }
}

This is a recursive function operating on a recursive data structure:

  • .number → base case
  • .addition → evaluate both sides, then add
  • .multiplication → evaluate both sides, then multiply

Result

let result = evaluate(expr)
print(result) // 20

This is clean, expressive, and type-safe.

No string parsing. No runtime ambiguity.


Why indirect Matters

1. Enables Recursive Models

Without indirect, you cannot model:

  • Trees
  • Graphs (with care)
  • Expression evaluators
  • Abstract syntax trees (ASTs)

2. Keeps Value Semantics

Even though indirect uses references internally, the enum still behaves like a value type externally.

This means:

  • Copying still works as expected
  • No shared mutable state by default
  • Safer than class-based alternatives

3. Avoids Manual Boxing

Without indirect, you'd need something like:

final class Box<T> {
    let value: T
    init(_ value: T) {
        self.value = value
    }
}

And then:

enum Node {
    case value(Int)
    case branch(Box<Node>, Box<Node>)
}

This is:

  • Noisy
  • Less expressive
  • Easy to misuse

indirect removes this boilerplate entirely.


Under the Hood

When you mark a case as indirect, Swift:

  • Stores the associated value on the heap
  • Keeps a reference in the enum

So instead of:

Enum contains full nested structure inline

You get:

Enum → pointer → actual data

This is what breaks the infinite size recursion.


The Trade-offs

1. Heap Allocation

You lose stack-only guarantees.

2. Indirection Cost

Extra pointer lookups. Usually negligible but real.

3. Debugging Complexity

Memory graphs become less obvious.

But Here’s the Reality

If you’re building:

  • Expression evaluators
  • Syntax trees
  • Nested UI/state models
  • File hierarchies

You don’t have a better alternative.


Common Mistakes

❌ Forgetting indirect in recursive enums

enum Tree {
    case leaf(Int)
    case node(Tree, Tree) // ❌ compiler error
}

Fix:

enum Tree {
    case leaf(Int)
    indirect case node(Tree, Tree)
}

❌ Overusing indirect

Not every enum needs it.

enum Result {
    case success(Int)
    case failure(Error)
}

Adding indirect here is unnecessary and harmful.

❌ Assuming reference semantics

Even with indirect, this is still a value type:

var a = expr
var b = a

Mutating b does not affect a.


A Practical Example: File System

indirect enum FileSystemItem {
    case file(name: String)
    case folder(name: String, contents: [FileSystemItem])
}
let system = FileSystemItem.folder(
    name: "root",
    contents: [
        .file(name: "README.md"),
        .folder(
            name: "src",
            contents: [
                .file(name: "main.swift")
            ]
        )
    ]
)

This is exactly how you should model hierarchical data.

Not with dictionaries.
Not with loosely typed arrays.
Not with classes by default.


When Should You Use indirect?

Use it when:

  • Your data is inherently recursive
  • You need tree-like structures
  • You want value semantics with structure

When You Should Not

Avoid it when:

  • Your model is flat
  • You’re guessing
  • You’re trying to “future-proof” unnecessarily

Final Thoughts

indirect is not a beginner feature.
It’s a modeling tool.

Most developers don’t need it because most developers don’t model complex data structures.

But if you’re building:

  • Compilers
  • DSLs
  • Advanced state systems
  • Or anything tree-shaped

Then indirect stops being optional.

The key is simple:

Reach for indirect only when your data structure demands recursion.

If you have suggestions or opinion, feel free to connect with me on X and send me a DM. If this article helped you, Buy me a coffee.