Difference between in {}, in pairs and in ipairs

I’m not actually having any issues, I’m just looking to learn and understand code on a deeper level.
If this is the wrong category to post this in, I apologise. I wasn’t sure where else to post this.

So lua allows you to write for-loops in many different ways. Some of the most common forms are:

for i = first, last, interval do
end

for key, value in pairs ({}) do
end

for key, value in ipairs({}) do
end

for key, value in {} do
end

The last form in {} was most recently added to luau. But how is it different from in pairs() and how do you determine which form of for-loop you should use? So far, I’ve always used in pairs() and never needed to use in ipairs(). Am I doing something wrong here? This topic explains how ipairs differs from in pairs, though I’ve never had a use case where the functionality of ipairs outweighs that of in pairs.

I’m eager to learn and find out more about this.

The first loop is used to iterate through numbers first to last (e.g. 1, 2, 3, 4, 5, in default 1 intervals). Adding an interval allows you to iterate backward (-1), or skip numbers (e.g. 2, in which case you will have 2, 4, 6, 8 if you start with 0)


The second is used to separate key-value tables, or dictionaries.

local T = {A = true, B = 4, C = 10, [2] = 4, [Part] = "WOW"}

for Key, Index in pairs(T) do
	print(Key, Index)
end

-- Output:
--[[
A true
B 4
C 10
2 4
Part WOW
]]

The third loop is the same, but only usable for tables with numbers only for index, an array.

local T = {true, 4, 10, 4, "WOW"}

for Key, Index in pairs(T) do
	print(Key, Index)
end

-- Output:
--[[
true
4
10
4
WOW
]]

Using ipairs() is better as it runs faster than pairs(). Downside is, you can’t use dictionaries.

Other cons: This type of loop will not work with dictionaries, arrays with mixed keys (some keys are strings, some are any object class, some are numbers, etc.), or arrays that skip indices (e.g. {[1] = 2, [2] = 4, [10] = 3}).


The last loop is the same as the second loop.

1 Like

image

It seems to work the same as pairs, but my speed test shows that for key,value in {} do is faster on average. This test was done by simulating a thousand times each and finding the average results. I did the same test a second time and still had it come on top (over pairs):

image

You can test it yourself here:

Code
local number = 1000000
local array = {124,'hi',workspace}
for i=1,100 do
	table.insert(array,math.random(0.01,10000))
end

local results = {}

function avg(list)
	local total=0
	for _,v in pairs(list) do
		total+=v
	end
	return total/#list
end

local average = {}
for loop=1,number do
	local start = tick()
	local array2 = {}
	for i=1,#array do
		table.insert(array2,array[i])
	end
	table.insert(average,tick()-start)
	if loop%10000==0 then
		task.wait()
	end
end
results['iteration loop']=avg(average)

local average = {}
for loop=1,number do
	local start = tick()
	local array2 = {}
	for _,v in pairs(array) do
		table.insert(array2,v)
	end
	table.insert(average,tick()-start)
	if loop%10000==0 then
		task.wait()
	end
end
results['pairs']=avg(average)

local average = {}
for loop=1,number do
	local start = tick()
	local array2 = {}
	for _,v in ipairs(array) do
		table.insert(array2,v)
	end
	table.insert(average,tick()-start)
	if loop%10000==0 then
		task.wait()
	end
end
results['iPairs']=avg(average)

local average = {}
for loop=1,number do
	local start = tick()
	local array2 = {}
	for _,v in array do
		table.insert(array2,v)
	end
	table.insert(average,tick()-start)
	if loop%10000==0 then
		task.wait()
	end
end
results['forTable']=avg(average)

table.sort(results,function(a,b) return a < b end)
local text = '\n'
for i,v in pairs(results) do
	text=text..i..': '..v..'\n'
end
warn(text)
3 Likes

With a performance benefit so small that it’s negligible you don’t need to worry about speed when deciding if pairs, ipairs or just a table iteration is the best option for your code.

1 Like

Generalized iteration

Luau uses the standard Lua syntax for iterating through containers, for vars in values , but extends the semantics with support for generalized iteration. In Lua, to iterate over a table you need to use an iterator like next or a function that returns one like pairs or ipairs . In Luau, you can simply iterate over a table:

for k, v in {1, 4, 9} do
    assert(k * k == v)
end

This works for tables but can also be extended for tables or userdata by implementing __iter metamethod that is called before the iteration begins, and should return an iterator function like next (or a custom one):

local obj = { items = {1, 4, 9} }
setmetatable(obj, { __iter = function(o) return next, o.items end })

for k, v in obj do
    assert(k * k == v)
end

The default iteration order for tables is specified to be consecutive for elements 1..#t and unordered after that, visiting every element; similarly to iteration using pairs , modifying the table entries for keys other than the current one results in unspecified behavior.

tl;dr
use for k,v in container unless u need some of the very specific functionality of ipairs or backwards compatibility with vanilla lua

Only os.clock() should be used for benchmarking since that is its designed purpose. Here is the same checks done 1000 times:

local testTable = {}
for i = 1, 1000, 1 do
	testTable[i] = i
end


local counter = 0
local timer = os.clock()
for i = 1, 1000, 1 do
	counter += 1
end
print("Non-table iteration: ", os.clock() - timer, "    Counter: "..counter)

counter = 0
timer = os.clock()
for key, value in pairs(testTable) do
	counter+= 1
end
print("Pairs: ", os.clock() - timer, "    Counter: "..counter)

counter = 0
timer = os.clock()
for key, value in ipairs(testTable) do
	counter += 1
end
print("iPairs: ", os.clock() - timer, "    Counter: "..counter)

counter = 0
timer = os.clock()
for key, value in testTable do
	counter += 1
end
print("No pairs table iteration: ", os.clock() - timer, "    Counter: "..counter)

Output:

--[[
Non-table iteration:  0.0000039000005926936865     Counter: 1000
Pairs:  0.000006100002792663872     Counter: 1000
iPairs:  0.000006099988240748644     Counter: 1000
No pairs table iteration:  0.000005699999746866524     Counter: 1000
--]]

This is more expected since I would’ve thought it was more costly to iterate through a table with associated memory locations rather than just doing something 1000 times. But as @BullfrogBait said, the differences are completely negligible; choose the easiest way to get the job done.

1 Like
local t = {}

for i = 1, #t do
	local v = t[i]
end

for i, v in pairs(t) do
	
end

for i, v in ipairs(t) do
	
end

for i, v in next, t do
	
end

for i, v in t do
	
end

The problem with ‘Generic Iteration’ is that it can’t be used to indicate if an iterable is an array or a dictionary whereas pairs and ipairs can.

2 Likes