Why Tables Matter
**Tables are the single, central data structure in Lua** — they implement associative arrays and can be indexed by numbers, strings, or almost any Lua value (except nil), and they grow dynamically. This makes them the foundation for arrays, dictionaries, objects, sets, and more in Lua programs.
Basics & Syntax
Create a table with curly braces. Tables are references (objects), not primitive values; assigning a table to another variable copies the reference, not the contents.
-- empty table
local t = {}
-- array-style
local fruits = {"apple", "banana", "cherry"}
-- dictionary-style
local person = { name = "Ada", age = 28 }
Access: numeric indices use square brackets or implicit numeric indexing; string keys can use dot notation when the key is a valid identifier.
Common Patterns
Array
Use sequential numeric keys starting at 1 (Lua convention). Use # for length in simple cases.
local a = {10, 20, 30}
for i = 1, #a do
print(i, a[i])
end
Dictionary / Map
Use arbitrary keys for lookups and sparse data.
local map = {}
map["x"] = 42
map["y"] = "hello"
Set
Represent sets by using table keys with boolean true values.
local set = { apple = true, banana = true }
if set["apple"] then print("has apple") end
Object-like tables
Tables can hold functions and be used as objects or modules; methods commonly use the colon syntax to pass self.
local Stack = {}
function Stack:new()
return setmetatable({items = {}}, {__index = self})
end
function Stack:push(v) table.insert(self.items, v) end
function Stack:pop() return table.remove(self.items) end
Traversal: pairs vs ipairs
Use pairs to iterate all key/value pairs (unordered), and ipairs for array-like sequences (stops at the first nil). Choose based on whether your table is sparse or dense.
for k, v in pairs(t) do print(k, v) end
for i, v in ipairs(a) do print(i, v) end
Metatables & Metamethods
Metatables let you customize table behavior: arithmetic, concatenation, indexing fallbacks, and more. They unlock powerful idioms like operator overloading and prototype-style inheritance. Use setmetatable and define metamethods such as __index, __newindex, __add, and __tostring.
local proto = {greet = function(self) print("hi") end}
local obj = setmetatable({}, {__index = proto})
obj:greet() -- falls back to proto.greet
Performance Tips
Tables are fast and flexible, but a few rules help performance:
- Preallocate array capacity by inserting known-size elements or using contiguous numeric keys to avoid frequent resizing.
- Avoid mixing dense array and sparse keys when you rely on
#oripairs. - Use local variables for frequently accessed tables or functions to reduce global lookups.
Pro tip: When implementing large sets or maps, prefer direct key lookups over linear scans; Lua table lookups are O(1) on average.
Common Pitfalls
- Nil holes: Inserting
nilinto an array breaksipairsand#semantics. - Reference semantics: Copying a table variable copies the reference; clone explicitly when you need a deep copy.
- Mutable shared state: Beware of using the same table as a default value across instances without cloning.
Practical Examples
1. Frequency counter
local function freq(list)
local counts = {}
for _, v in ipairs(list) do
counts[v] = (counts[v] or 0) + 1
end
return counts
end
2. Simple class-like pattern
local Person = {}
Person.__index = Person
function Person:new(name)
return setmetatable({name = name}, self)
end
function Person:greet() print("Hello, " .. self.name) end
local p = Person:new("Rin")
p:greet()