luaguides

next

next(table, index)

next() is the low-level function that underlies Lua’s table iteration. It returns the next key-value pair after a given position, or nil when there are no more entries. Understanding next directly helps you grasp how pairs() works and when you need manual control over traversal.

Signature

next(table, index)
ParameterTypeDefaultDescription
tabletableThe table to traverse.
indexanynilThe current key position. Pass nil to start traversal from the beginning.

Return values:

  • If more elements exist: (next_key, next_value)
  • If at the end or table is empty: nil

Basic Traversal

When you call next with nil as the index, it returns the first key-value pair. The order of that first pair is implementation-dependent and may vary between Lua versions or even between runs in some embedded contexts.

local t = {name = "Alice", age = 30, city = "London"}
local k, v = next(t, nil)
print(k, v)  -- e.g., "name\tAlice" (order varies)

To continue traversal, pass the key returned by the previous call back into next. Each call advances the internal position to the next entry in the hash table’s bucket chain. The while-loop pattern below manually replicates what the generic for loop does with pairs() behind the scenes.

local t = {a = 1, b = 2, c = 3}
local k, v = next(t, nil)
while k ~= nil do
    print(k, v)
    k, v = next(t, k)
end

This manual iteration pattern is equivalent to what pairs() does internally. In fact, pairs(t) is defined as the expression next, t, nil — an iterator function, table, and starting state bundled together.

Checking for empty tables

next(t) returns nil when called on an empty table, making it a clean way to test for emptiness:

local empty = {}
local full = {a = 1}

if next(empty) == nil then
    print("table is empty")       -- prints
end

if next(full) ~= nil then
    print("table has entries")   -- prints
end

This is the idiomatic Lua pattern for testing emptiness because it avoids calling #t, which only counts the array portion of a table — a table with only string keys would have #t == 0 even though it’s non-empty. A common helper wraps this logic:

function is_empty(t)
    return next(t) == nil
end

Why traversal order is unspecified

next walks the hash part of a table, and the order depends on how keys are arranged in hash buckets. This order has no relation to insertion order or numeric magnitude. Lua’s hash table implementation uses open addressing with a probing strategy that scatters entries based on their hash values, so even adding keys in sorted order does not guarantee sorted traversal.

local t = {z = 1, a = 2, m = 3}
for k, v in next, t, nil do
    print(k, v)
end
-- Output order is unpredictable — not alphabetical, not insertion order

If you need numeric keys in sorted order, use ipairs() with table.sort() on the keys, or build an explicit numeric loop.

Sparse Tables

next visits every key present in a sparse table, regardless of gaps. Unlike ipairs, which treats nil values in the integer-key sequence as termination points, next simply enumerates whatever hash-table entries exist:

local sparse = {}
sparse[1] = "first"
sparse[100] = "hundredth"
sparse[50] = "fiftieth"

local k, v = next(sparse, nil)
while k ~= nil do
    print(k, v)
    k, v = next(sparse, k)
end
-- Visits all three entries (order undefined)
-- 1     first
-- 50    fiftieth
-- 100   hundredth

ipairs() would stop at the first gap in numeric keys. next handles sparse tables faithfully.

Modifying a table during iteration

This is the most important gotcha: never assign to a non-existent key while iterating. Doing so invalidates the traversal state and can cause infinite loops or skipped entries. Lua’s internal table structure may rehash when a new key is added, shifting the positions of existing entries and confusing the iterator’s internal cursor.

You can safely modify or delete existing keys during iteration. Deleting an existing key removes it from the table without triggering a rehash, so the other entries stay where they are and the iterator continues normally:

local t = {a = 1, b = 2, c = 3}

for k, v in next, t, nil do
    if k == "b" then
        t[k] = nil  -- safe: deleting existing key
    end
end
-- t is now {a = 1, c = 3}

But adding a new key mid-iteration is dangerous. The reason is subtle: when you insert a previously non-existent key, the hash table may grow and rehash all entries. This changes the internal order that the iterator is following, potentially causing it to revisit entries, skip over others, or loop forever on the same key:

local t = {a = 1, b = 2}

for k, v in next, t, nil do
    t.z = 99  -- unsafe: modifies table during traversal
    print(k, v)
end
-- May print extra entries, loop infinitely, or behave unpredictably

For safe bulk modification, collect the keys into a separate list first, then iterate that list. This two-pass approach works because you’re iterating a snapshot of the keys while modifying the original table — the iterator and the modified table never interact directly:

local keys = {}
for k in pairs(t) do keys[#keys + 1] = k end

for _, k in ipairs(keys) do
    if condition then t[k] = nil end
end

Relationship to pairs()

pairs(t) is syntactic sugar for next, t, nil. This means the generic for loop unpacks these three values — the iterator function, the invariant state table, and the initial control variable — and calls next repeatedly until it returns nil. The two forms below produce identical behavior in every Lua version since 5.0:

for k, v in pairs(t) do ... end

is exactly equivalent to the explicit form using next directly. The generic for loop calls next(t, nil) to start, captures the returned key and value, and passes the key back to next for each subsequent call until nil signals the end:

for k, v in next, t, nil do ... end

Understanding this equivalence lets you write custom iteration patterns by calling next manually. You can inspect or filter keys before they reach your loop body, implement early-exit conditions, or walk the table in a non-standard way. For example, skipping a specific key while iterating everything else:

local k = next(t, nil)
while k ~= nil do
    if k ~= "skip_me" then
        print(k, t[k])
    end
    k = next(t, k)
end

Summary

AspectDetail
Starting callnext(t, nil) to get the first entry
Advancingnext(t, previous_key)
End of tableReturns nil
Empty tablenext(t) returns nil
OrderNot guaranteed — not insertion order, not sorted
Safe to modifyExisting keys only; never add new keys during iteration
Internal usepairs() calls next internally

next is rarely called directly in everyday code — pairs() handles most use cases. But when you need manual iteration control, need to detect empty tables efficiently, or want to understand how Lua’s iteration machinery works, next is the function to reach for.

See Also