Tables: Lua's Universal Data Structure
Tables are the only data structure in Lua. But don’t let that simplicity fool you—tables are incredibly versatile. They can behave like arrays, dictionaries (hash maps), objects, modules, and more. Understanding tables thoroughly is essential to writing effective Lua code.
What Is a Table?
A table is a collection of key-value pairs. Keys can be any value except nil. Values can be anything, including other tables. This flexibility is what makes tables so powerful.
Creating a Table
The simplest way to create a table is using curly braces:
-- An empty table
local empty = {}
-- A table with some values
local person = {
name = "Alice",
age = 30,
city = "London"
}
When you don’t specify keys, Lua automatically assigns numeric indices starting from 1:
-- This creates an array-like table
local fruits = {"apple", "banana", "cherry"}
print(fruits[1]) -- apple
print(fruits[2]) -- banana
print(fruits[3]) -- cherry
Note: Lua arrays are 1-indexed, not 0-indexed like in many other languages. This is a deliberate design choice that makes Lua more readable for non-programmers.
Tables as Dictionaries
The name-value syntax creates what other languages call a dictionary, hash map, or associative array:
local config = {
host = "localhost",
port = 8080,
debug = true
}
-- Access values using dot notation or bracket notation
print(config.host) -- localhost
print(config["port"]) -- 8080
-- Add new key-value pairs
config.timeout = 30
config["max_connections"] = 100
Both config.host and config["host"] are equivalent. The dot notation is cleaner for static keys, while bracket notation is useful when keys are dynamic:
local key = "username"
local user = {}
user[key] = "john_doe"
print(user.username) -- john_doe
Tables as Arrays
When you need ordered collections, tables act as arrays. Lua doesn’t have a separate array type—arrays are just tables with sequential numeric keys:
local numbers = {10, 20, 30, 40, 50}
-- Get the length using #
print(#numbers) -- 5
-- Loop through all elements
for i = 1, #numbers do
print(numbers[i])
end
-- Add elements to the end
table.insert(numbers, 60)
print(#numbers) -- 6
The # operator returns the length of an array, but it only works reliably with contiguous numeric keys starting from 1.
Iterating Over Tables
Lua provides three ways to iterate over tables:
Using pairs() for Key-Value Iteration
local data = {a = 1, b = 2, c = 3}
for key, value in pairs(data) do
print(key, value)
end
pairs() iterates over all key-value pairs in no particular order.
Using ipairs() for Array-Style Iteration
local items = {"first", "second", "third"}
for index, value in ipairs(items) do
print(index, value)
end
ipairs() only iterates over values with sequential numeric keys starting from 1. It stops as soon as it encounters a nil value.
Using next() for Manual Iteration
local t = {x = 10, y = 20}
local key, value = next(t)
while key do
print(key, value)
key, value = next(t, key)
end
next(t, key) returns the next key-value pair after the given key. This is the foundation that pairs() is built on.
Common Table Operations
Lua’s table library provides helpful functions for working with tables:
local list = {3, 1, 4, 1, 5, 9, 2, 6}
-- Sort the table
table.sort(list)
-- Find an element (linear search)
-- Note: table.find is available in Lua 5.4+
local found = false
for i, v in ipairs(list) do
if v == 4 then
found = true
break
end
end
-- Remove the last element
local last = table.remove(list)
-- Concatenate into a string
local str = table.concat(list, ", ")
Practical Example: A Simple Contact List
Let’s build something practical—a contact list that demonstrates multiple table patterns:
-- Define a contact
local function create_contact(name, email, phone)
return {
name = name,
email = email,
phone = phone,
tags = {}
}
end
-- Our contact list (array of contacts)
local contacts = {}
-- Add a contact
table.insert(contacts, create_contact("Alice", "alice@example.com", "123-456"))
table.insert(contacts, create_contact("Bob", "bob@example.com", "789-012"))
-- Find contacts by name
local function find_by_name(name)
for _, contact in ipairs(contacts) do
if contact.name == name then
return contact
end
end
return nil
end
local found = find_by_name("Alice")
if found then
print(found.email) -- alice@example.com
end
Table Gotchas to Remember
- Tables are references: When you assign a table to a new variable, you’re copying the reference, not the table itself.
local a = {x = 1}
local b = a
b.x = 2
print(a.x) -- 2 (both point to the same table)
- nil values create gaps: Setting a table element to
nilremoves it, which can affect#length:
local t = {1, 2, 3, 4, 5}
t[3] = nil
print(#t) -- May be 2 or 5 (undefined behavior)
- Tables are dynamic: You don’t need to declare table size upfront. Just add keys as needed.
Summary
Tables in Lua are deceptively simple but incredibly powerful:
- Arrays are tables with sequential numeric keys (starting at 1)
- Dictionaries use string or other keys for named access
- Objects combine data with functions in a single structure
- Modules use tables to organize related functions and values
Once you master tables, you can express almost any data structure in Lua. The next articles in this series will explore specific patterns like using tables for object-oriented programming and metatables for custom behavior.
Next in series: Strings and Pattern Matching Basics