ipairs
ipairs() is a stateless iterator for traversing Lua tables that contain sequential, array-style data. It iterates over integer keys in ascending order, starting from 1, and stops at the first nil value. This makes it the right choice when you know your table has no gaps.
Basic Usage
t = {"apple", "banana", "cherry"}
for i, v in ipairs(t) do
print(i, v)
end
-- 1 apple
-- 2 banana
-- 3 cherry
ipairs vs pairs
The core difference is that ipairs only touches integer keys starting at 1, in order, while pairs iterates all keys (including strings and non-sequential integers) with no guaranteed order.
| Behavior | ipairs | pairs |
|---|---|---|
| Iterates string keys | No | Yes |
| Iterates non-sequential integer keys | No | Yes |
| Preserves insertion order | Yes | No |
| Stops at first nil | Yes | No |
| Skips nil gaps | Yes | No |
t = {1, nil, 3, "hello", [10] = "late"}
-- ipairs stops at the nil gap — never reaches index 3 or beyond
for i, v in ipairs(t) do
print(i, v)
end
-- 1 1
-- pairs visits everything including the string key and non-sequential integer
for k, v in pairs(t) do
print(k, v)
end
-- 1 1
-- 3 3
-- hello world
-- 10 late
Sequential Tables Only
ipairs is designed for tables that represent arrays — consecutive integer keys starting at 1 with no nil gaps. If your table has holes, use pairs instead or fill the gaps.
-- Clean array: works perfectly with ipairs
fruits = {"apple", "banana", "cherry"}
-- Sparse array: ipairs stops early
sparse = {}
sparse[1] = "first"
sparse[3] = "third" -- index 2 is nil
sparse[5] = "fifth" -- index 4 is nil
for i, v in ipairs(sparse) do
print(i, v)
end
-- 1 first
-- 3 third
-- ipairs stops here because index 4 is nil
Index Starting at 1
Lua arrays are 1-indexed. ipairs enforces this by starting at index 1 — if your table starts at index 0 or some other number, ipairs will visit nothing.
-- Zero-indexed table — ipairs visits nothing
zero = {[0] = "zero", [1] = "one", [2] = "two"}
for i, v in ipairs(zero) do
print(i, v)
end
-- (no output)
-- This is why Lua arrays conventionally start at 1
Practical Patterns
Iterating With Default Values
A common pattern fills nil slots with a default so ipairs works reliably:
t = {1, nil, 3, nil, 5}
-- Fill holes with a default value
for i, v in ipairs(t) do
print(i, v)
end
-- 1 1
-- (stops at index 2 because it's nil)
-- Safer: pre-populate
safe = {1, 0, 3, 0, 5}
for i, v in ipairs(safe) do
print(i, v)
end
-- 1 1
-- 2 0
-- 3 3
-- 4 0
-- 5 5
Building an Array
ipairs naturally handles tables built incrementally:
local result = {}
for i = 1, 10 do
result[i] = i * i
end
for i, v in ipairs(result) do
print(i, v)
end
-- 1 1
-- 2 4
-- ...
-- 10 100
Skipping the Index
If you only need values, ignore the index:
for _, v in ipairs({"a", "b", "c"}) do
print(v)
end
-- a
-- b
-- c
Common Mistakes
Assuming ipairs Visits All Integer Keys
ipairs only visits keys 1, 2, 3… in order. It never touches index 0 or any non-sequential integer keys.
t = {[1] = "first", [3] = "third", [5] = "fifth"}
for i, v in ipairs(t) do
print(i, v)
end
-- 1 first
-- (stops because 2 is nil)
Using ipairs on Hash Tables
If your table uses string keys or has non-sequential integer keys, pairs is the correct choice:
-- Configuration-style table (hash map)
config = {host = "localhost", port = 8080, max = 100}
-- ipairs visits nothing on a hash-only table
for i, v in ipairs(config) do
print(i, v)
end
-- (no output)
-- Use pairs instead
for k, v in pairs(config) do
print(k, v)
end
-- host localhost
-- port 8080
-- max 100
See Also
- /reference/core-functions/ref-pairs/ — iterate over all key-value pairs in any table order
- /reference/core-functions/ref-select/ — select individual elements from a vararg list
- /reference/core-functions/ref-error/ — raise errors explicitly with a message