luaguides

Writing Custom Iterators in Lua

Iterators let you step through data structures one element at a time. Lua’s generic for loop has a built-in protocol for them, and once you understand that protocol, you can build iterators for anything: trees, graphs, file formats, API responses.

How the Generic for Loop Works

The generic for loop calls your iterator on every iteration. It passes two values each time: the invariant state (the collection, which never changes) and the control variable (the current position).

for <var1>, <var2>, ... in <iter>, <state>, <init> do
  -- loop body
end

On the first iteration, Lua calls <iter>(<state>, <init>). On subsequent iterations, it calls <iter>(<state>, <var1>) — the first returned value from the previous call becomes the new control variable.

The loop stops when the iterator returns nil. If it returns multiple values, the first becomes the next control variable and the rest are assigned to the loop variables.

This is the protocol. Everything in this guide builds on it.

Built-in Iterators: pairs and ipairs

Before writing your own, understand what Lua gives you.

ipairs

Iterates integer-keyed tables sequentially from index 1. Stops at the first nil.

for i, v in ipairs({10, 20, 30}) do
  print(i, v)  -- 1 10, 2 20, 3 30
end

pairs

Iterates all key-value pairs. Order is not guaranteed for non-integer keys.

for k, v in pairs({a = 1, b = 2, [3] = 3}) do
  print(k, v)
end

pairs is implemented in C and uses the next function internally. You can call next directly:

for k, v in next, t, nil do
  print(k, v)
end

This is equivalent to pairs(t).

Stateless Iterator with a Factory Function

A stateless iterator only uses its two arguments (state and control). The factory function returns three values: the iterator function, the invariant state, and the initial control value.

local function squares(max)
  local function iter(state, i)
    i = i + 1
    if i <= state.max then
      return i, i * i
    end
  end
  return iter, {max = max}, 0
end

for i, sq in squares(5) do
  print(i, sq)  -- 1 1, 2 4, 3 9, 4 16, 5 25
end

The iterator function iter is pure — it doesn’t store any state itself. All the state lives in the table {max = max} passed as the invariant state. The control variable i tracks position across calls.

This pattern works well when the traversal logic is straightforward. For more complex state, it gets awkward fast.

Closure-Based Stateful Iterator

If stateless iteration feels forced, use a closure. The iterator function carries state in its upvalues, so you don’t need to pass everything through the protocol.

local function list_iter(t)
  local i = 0
  local n = #t
  return function()
    i = i + 1
    if i <= n then
      return t[i]
    end
  end
end

for v in list_iter({"one", "two", "three"}) do
  print(v)  -- one, two, three
end

Here list_iter returns a closure that reads i, n, and t from its upvalues. The generic for loop just keeps calling that function until it returns nil.

The tradeoff: this iterator can’t be paused and resumed from the same position because the state lives inside the closure, not in the protocol arguments.

Coroutine-Based Iterator

Coroutines shine when the iteration logic is complex or naturally recursive. Each coroutine.yield produces a value for the loop.

local function permutations(t)
  return coroutine.wrap(function()
    local function permgen(a, n)
      if n == 0 then
        coroutine.yield(a)
      else
        for i = 1, n do
          a[n], a[i] = a[i], a[n]
          permgen(a, n - 1)
          a[n], a[i] = a[i], a[n]  -- backtrack
        end
      end
    end
    local a = {}
    for _, v in ipairs(t) do table.insert(a, v) end
    permgen(a, #a)
  end)
end

for perm in permutations({"A", "B", "C"}) do
  print(table.concat(perm, ","))  -- prints all 6 permutations
end

coroutine.wrap returns an iterator function. When that function calls coroutine.yield(value), the value bubbles up to the for loop’s loop variable. When the coroutine finishes, coroutine.wrap returns nil and the loop ends.

This is cleaner than trying to cram a recursive traversal into a stateless iterator. You write the logic naturally, and yield handles the communication.

Iterator with Complex State

When an iterator needs multiple pieces of state, pack them into a table used as the invariant state.

local function words(s)
  local state = {s = s, pos = 1}
  return function(st)
    local start = st.s:find("%S", st.pos)
    if not start then return nil end
    local finish = st.s:find("%s", start + 1)
    local word
    if finish then
      word = st.s:sub(start, finish - 1)
      st.pos = finish + 1
    else
      word = st.s:sub(start)
      st.pos = #st.s + 1
    end
    return word
  end, state
end

for w in words("hello world from lua") do
  print(w)  -- hello, world, from, lua
end

The state table holds both the string and the current position. The iterator function reads and writes these fields on every call.

Common Mistakes

Forgetting that nil ends the loop

The loop only stops when the iterator returns exactly nil. Returning false, 0, or an empty table does not end it.

for v in function() return false end do  -- infinite loop
  print("oops")
end

Always return nil to signal done.

ipairs stops at nil

If your array has gaps, ipairs stops at the first nil:

local t = {1, nil, 3}
for i, v in ipairs(t) do print(i, v) end  -- only prints 1 1

Use pairs for sparse arrays.

Modifying the table you’re iterating over

Removing keys during pairs iteration can cause skipped or duplicate elements:

for k, v in pairs(t) do
  t[k] = nil  -- may skip some keys
end

Copy the keys first if you need to delete while iterating.

Confusing numeric for with generic for

Numeric for (for i = 1, 10) and generic for (for k, v in pairs(t)) are separate constructs. Numeric for manages its own counter. Generic for calls an iterator function. Don’t mix them up.

When to Use Each Pattern

Use a stateless iterator when the traversal is simple and sequential. The factory pattern keeps things clean and lets you reuse iterator functions.

Use a closure iterator when you want to hide state from the protocol entirely. It’s easier to write but can’t be reset mid-iteration.

Use a coroutine when the traversal is complex, recursive, or involves branching. You get natural control flow at the cost of some overhead.

Conclusion

Lua’s iterator protocol is simple: an iterator function, an invariant state, and a control variable. Once that clicks, you can build iterators for any data structure. Start with the stateless pattern for sequential traversals, reach for coroutines when the logic gets tangled, and use closures for quick cases where reusability doesn’t matter.

See Also: Lua Metatables | Coroutine Basics | Tables in Lua