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. The key insight is that ipairs uses the internal array part of the table, which only stores elements with consecutive positive integer keys. Any break in the sequence signals the end of the array portion.
-- 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. This behavior reflects the design choice Lua made to target non-programmers, making indexing feel natural to people who count starting from one. Most Lua standard library functions also assume 1-based indexing.
-- 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. This is especially useful when you receive a table from an external source where entries might be missing, or when you’ve selectively deleted elements using nil assignment and need to iterate the entire range without gaps causing early termination.
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. The key requirement is that you assign integer keys in ascending order without skipping any numbers. When you use table.insert or assign to sequential indices, ipairs visits every element in the order they were placed, making it the go-to iterator for any list-like structure where order matters.
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 by using the conventional _ placeholder. This is the idiomatic Lua pattern for discarding a return value that the for-loop syntax requires but your logic doesn’t need. It works with any iterator, not just ipairs, and helps keep code self-documenting by signaling intent.
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. This trips up developers coming from languages where arrays are sparse data structures with arbitrary integer indexing. ipairs treats a Lua table like a C array internally, so the first nil is a sentinel that stops iteration cold.
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. Lua tables serve double duty as both arrays and dictionaries, but ipairs only understands the array portion. If all your keys are strings or arbitrary integers, ipairs iterates nothing at all, producing zero iterations without raising an error — a silent failure that can be hard to spot in larger codebases.
-- 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