| name | Lua Tables Patterns |
| user-invocable | false |
| description | Use when lua tables as the universal data structure including arrays, dictionaries, objects, metatables, object-oriented patterns, data structures, and advanced table manipulation for building flexible, efficient Lua applications. |
| allowed-tools | [] |
Lua Tables Patterns
Introduction
Tables are Lua's sole compound data structure, serving as arrays, dictionaries,
objects, modules, and more. This versatility makes tables fundamental to Lua
programming, enabling implementations of virtually any data structure through
creative use of table features and metatables.
Tables use associative arrays that can be indexed with any Lua value except nil
and NaN. Array-like tables use consecutive integer indices starting from 1,
while dictionary-like tables use arbitrary keys. Metatables enable operator
overloading and advanced behaviors.
This skill covers table fundamentals, array and dictionary patterns, metatables,
object-oriented programming, common data structures, performance optimization,
and idiomatic table manipulation techniques.
Table Fundamentals
Tables combine arrays and hash maps into a single versatile data structure with
1-based indexing.
local t1 = {}
local t2 = table.create(100)
local array = {10, 20, 30, 40, 50}
print(array[1])
print(array[5])
print(#array)
local dict = {
name = "Alice",
age = 30,
email = "alice@example.com"
}
print(dict.name)
print(dict["age"])
local mixed = {
10, 20, 30,
name = "Bob",
age = 25
}
print(mixed[2])
print(mixed.name)
local point = {x = 10, y = 20}
local point2 = {["x"] = 10, ["y"] = 20}
local nested = {
user = {
name = "Charlie",
address = {
city = "Seattle",
zip = "98101"
}
}
}
print(nested.user.address.city)
local list = {}
table.insert(list, "first")
table.insert(list, "second")
table.insert(list, 2, "middle")
print(list[1])
print(list[2])
print(list[3])
local removed = table.remove(list, 2)
print(removed)
for key, value in pairs(dict) do
print(key, value)
end
for index, value in ipairs(array) do
print(index, value)
end
for i = 1, #array do
print(i, array[i])
end
local original = {1, 2, 3, x = 10}
local copy = {}
for k, v in pairs(original) do
copy[k] = v
end
local fruits = {"apple", "banana", "cherry"}
local str = table.concat(fruits, ", ")
print(str)
local numbers = {5, 2, 8, 1, 9}
table.sort(numbers)
local people = {
{name = "Alice", age = 30},
{name = "Bob", age = 25},
{name = "Charlie", age = 35}
}
table.sort(people, function(a, b)
return a.age < b.age
end)
Tables efficiently combine arrays and hash tables, with the implementation
automatically optimizing storage based on usage patterns.
Array Patterns
Lua arrays use 1-based indexing and provide efficient sequential access through
the array part of tables.
local empty = {}
local numbers = {10, 20, 30, 40, 50}
local strings = {"hello", "world", "lua"}
print(#numbers)
numbers[#numbers + 1] = 60
table.insert(numbers, 70)
table.insert(numbers, 1, 0)
local last = table.remove(numbers)
local first = table.remove(numbers, 1)
function map(array, fn)
local result = {}
for i = 1, #array do
result[i] = fn(array[i])
end
return result
end
local doubled = map(numbers, function(x) return x * 2 )
result = {}
i = , #array
predicate(array[i])
.(result, array[i])
result
evens = filter(numbers, x % == )
accumulator = initial
i = , #array
accumulator = fn(accumulator, array[i])
accumulator
sum = reduce(numbers, acc + x , )
result = {}
end_idx = end_idx #array
i = start_idx, end_idx
.(result, array[i])
result
subset = slice(numbers, , )
result = {}
i = #array, ,
.(result, array[i])
result
n = #array
i = , .(n / )
array[i], array[n - i + ] = array[n - i + ], array[i]
result = {}
i = , #array
(array[i]) ==
nested = flatten(array[i])
j = , #nested
.(result, nested[j])
.(result, array[i])
result
nested = {, {, }, {, {, }}}
flat = flatten(nested)
result = {}
i = , #array, size
chunk = {}
j = i, .(i + size - , #array)
.(chunk, array[j])
.(result, chunk)
result
chunked = chunk({, , , , , , }, )
seen = {}
result = {}
i = , #array
seen[array[i]]
seen[array[i]] =
.(result, array[i])
result
set = {}
i = , #a
set[a[i]] =
result = {}
i = , #b
set[b[i]]
.(result, b[i])
result
Use arrays for sequential data with numeric indices, leveraging Lua's
optimizations for consecutive integer keys.
Dictionary and Set Patterns
Dictionary tables use arbitrary keys for fast lookups, while sets use keys with
true values.
local user = {
id = 1,
name = "Alice",
email = "alice@example.com",
age = 30
}
local key = "name"
print(user[key])
user.city = "Seattle"
user.age = 31
user.age = nil
if user.name then
print("Name exists")
end
function table_length(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
print(table_length(user))
function merge(t1, t2)
local result = {}
for k, v in pairs(t1) do
result[k] = v
end
for k, v in pairs(t2) do
result[k] = v
result
(obj) ~= obj
copy = {}
k, v (obj)
copy[deep_copy(k)] = deep_copy(v)
(copy, (obj))
Set = {}
set = {}
i = , #list
set[list[i]] =
set
set[value] =
set[value] =
set[value] ==
result = {}
k (a) result[k] =
k (b) result[k] =
result
result = {}
k (a)
b[k] result[k] =
result
result = {}
k (a)
b[k] result[k] =
result
list = {}
k (set)
.(list, k)
list
set1 = Set.new({, , , , })
set2 = Set.new({, , , , })
Set.add(set1, )
(Set.contains(set1, ))
union = Set.union(set1, set2)
inter = Set.intersection(set1, set2)
OrderedDict = {}
{_keys = {}, _values = {}}
dict._values[key]
.(dict._keys, key)
dict._values[key] = value
dict._values[key]
i =
i = i +
key = dict._keys[i]
key
key, dict._values[key]
Dictionaries provide O(1) average-case lookups and enable flexible key-value
storage patterns.
Metatables and Metamethods
Metatables enable operator overloading, property access control, and custom
behaviors through metamethods.
local t = {}
local mt = {
__index = function(table, key)
return "default value"
end
}
setmetatable(t, mt)
print(t.anything)
local Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
return setmetatable({x = x, y = y}, Vector)
end
function Vector.__add(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end
function Vector.__sub(a, b)
return Vector.new(a.x - b.x, a.y - b.y)
end
function Vector.__mul(a, scalar)
if type(scalar) == "number" then
return Vector.new(a.x * scalar, a.y * scalar)
end
end
function Vector.__tostring(v)
return string.format("Vector(%d, %d)", v.x, v.y)
a.x == b.x a.y == b.y
v1 = Vector.new(, )
v2 = Vector.new(, )
v3 = v1 + v2
(v3)
proxy = {}
mt = {
= ,
=
()
}
(proxy, mt)
= read_only({
api_key = ,
timeout =
})
cache = {}
mt = {
=
value = constructor(key)
(t, key, value)
value
}
({}, mt)
fibonacci = lazy_table(
n <= n
fibonacci[n - ] + fibonacci[n - ]
)
(fibonacci[])
mt = {
= default
}
({}, mt)
counts = table_with_default()
counts.apple = counts.apple +
counts.banana = counts.banana +
Object = {}
Object. = Object
({}, Object)
(, )
obj = Object.new()
obj:method()
Factory = {}
instance = {count = }
mt = {
=
.count = .count +
...
}
(instance, mt)
f = Factory.new()
f()
(f.count)
data = {}
mt = {
= data,
=
(.(, k, v))
data[k] = v
}
({}, mt)
Metatables enable powerful metaprogramming patterns and domain-specific
languages in Lua.
Object-Oriented Patterns
Lua supports multiple OOP approaches through tables and metatables, from simple
prototypes to class-based systems.
local Animal = {species = "Unknown"}
function Animal:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Animal:speak()
print("Some sound")
end
local Dog = Animal:new{species = "Canine"}
function Dog:speak()
print("Woof!")
end
function Dog:fetch()
print("Fetching...")
end
local dog = Dog:new{name = "Buddy"}
dog:speak()
print(dog.species)
local Class = {}
function Class:new(...)
local instance = setmetatable({}, self)
self.__index = self
if instance.init
instance:init(...)
instance
Person = Class:new()
.name = name
.age = age
.(, .name)
.age = .age +
alice = Person:new(, )
(alice:greet())
alice:birthday()
(alice.age)
Employee = Class:new()
(Employee, { = Person})
Person.init(, name, age)
.company = company
.(, .name, .company)
bob = Employee:new(, , )
(bob:greet())
(bob:work())
balance = initial_balance
{
deposit =
amount >
balance = balance + amount
,
withdraw =
amount > amount <= balance
balance = balance - amount
,
get_balance =
balance
}
account = BankAccount()
account.deposit()
account.withdraw()
(account.get_balance())
Flyable = {}
(.name .. )
(.name .. )
k, v (source)
target[k] = v
Bird = Person:new()
mixin(Bird, Flyable)
bird = Bird:new(, )
bird:fly()
Shape = Class:new()
. =
Circle = Shape:new()
Shape.init(, )
.radius = radius
Rectangle = Shape:new()
Shape.init(, )
.width = width
.height = height
AreaCalculator = {}
. * circle.radius * circle.radius
rect.width * rect.height
method_name = .. shape.
AreaCalculator[method_name](shape)
Choose OOP patterns based on needs: prototypes for simple hierarchies, classes
for structured systems, closures for encapsulation.
Common Data Structures
Implement classic data structures using tables for specific algorithmic needs.
local Stack = {}
function Stack.new()
return {items = {}}
end
function Stack.push(stack, item)
table.insert(stack.items, item)
end
function Stack.pop(stack)
return table.remove(stack.items)
end
function Stack.peek(stack)
return stack.items[#stack.items]
end
function Stack.is_empty(stack)
return #stack.items == 0
end
local Queue = {}
function Queue.new()
return {items = {}, head = 1, tail = 0}
end
function Queue.enqueue(queue, item)
queue.tail = queue.tail + 1
queue.items[queue.tail] = item
end
function Queue.dequeue(queue)
queue.head > queue.tail
item = queue.items[queue.head]
queue.items[queue.head] =
queue.head = queue.head +
item
LinkedList = {}
{head = , tail = , size = }
node = {value = value, = }
list.tail
list.tail. = node
list.tail = node
list.head = node
list.tail = node
list.size = list.size +
node = {value = value, = list.head}
list.head = node
list.tail
list.tail = node
list.size = list.size +
array = {}
current = list.head
current
.(array, current.value)
current = current.
array
BST = {}
{root = }
node
{value = value, left = , right = }
value < node.value
node.left = insert_node(node.left, value)
value > node.value
node.right = insert_node(node.right, value)
node
tree.root = insert_node(tree.root, value)
node
value == node.value
value < node.value
search_node(node.left, value)
search_node(node.right, value)
search_node(tree.root, value)
result = {}
node
traverse(node.left)
.(result, node.value)
traverse(node.right)
traverse(tree.root)
result
PriorityQueue = {}
{
heap = {},
compare = comparator a < b
}
.(pq.heap, item)
i = #pq.heap
i >
parent = .(i / )
pq.compare(pq.heap[i], pq.heap[parent])
pq.heap[i], pq.heap[parent] = pq.heap[parent], pq.heap[i]
i = parent
#pq.heap ==
result = pq.heap[]
pq.heap[] = pq.heap[#pq.heap]
.(pq.heap)
i =
left = i *
right = i * +
smallest = i
left <= #pq.heap pq.compare(pq.heap[left], pq.heap[smallest])
smallest = left
right <= #pq.heap pq.compare(pq.heap[right], pq.heap[smallest])
smallest = right
smallest == i
pq.heap[i], pq.heap[smallest] = pq.heap[smallest], pq.heap[i]
i = smallest
result
Graph = {}
{vertices = {}}
graph.vertices[vertex]
graph.vertices[vertex] = {}
Graph.add_vertex(graph, from)
Graph.add_vertex(graph, to)
.(graph.vertices[from], {to = to, weight = weight })
graph.vertices[vertex] {}
Implement data structures as needed for specific algorithmic requirements
rather than using them universally.
Best Practices
-
Use local variables for tables to improve performance and avoid global
namespace pollution
-
Pre-allocate tables when size is known to reduce reallocation overhead
-
Prefer ipairs for arrays and pairs for dictionaries to match iteration
semantics
-
Use metatables sparingly as they add overhead; reserve for when needed
-
Avoid holes in arrays (nil values) as they break length operator and
iteration
-
Cache table.insert and table.remove in tight loops for better performance
-
Use rawget and rawset to bypass metamethods when building metatables
-
Prefer weak tables for caches to allow garbage collection of unused
entries
-
Document table structure in comments for complex nested tables
-
Use consistent key types to avoid confusion between string and numeric
keys
Common Pitfalls
-
Forgetting 1-based indexing causes off-by-one errors when translating
from other languages
-
Using # on dictionaries returns incorrect length as it only counts array
part
-
Creating holes in arrays with nil values breaks length operator and
ipairs
-
Not checking for nil keys in dictionaries leads to missed values
-
Mutating tables during iteration causes unpredictable behavior and
missing elements
-
Overusing metatables adds performance overhead without clear benefits
-
Confusing . and : syntax for method calls causes incorrect self parameter
-
Not handling empty tables in recursive functions causes infinite loops
-
Using tables as boolean since empty tables are truthy in Lua
-
Shallow copying nested tables leaves references to nested structures
When to Use This Skill
Apply table patterns throughout Lua development as tables are the primary data
structure.
Use arrays for ordered collections with numeric indices and sequential access
patterns.
Leverage dictionaries for key-value storage, lookups, and mapping between values.
Apply metatables when implementing operator overloading, property access control,
or DSLs.
Implement OOP patterns when building complex systems requiring encapsulation and
inheritance.
Use custom data structures when specific algorithmic properties are needed beyond
basic tables.
Resources