Tables: Lua's Universal Data Structure

· 5 min read · Updated March 18, 2026 · beginner
tables data-structures fundamentals beginner

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

  1. 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)
  1. nil values create gaps: Setting a table element to nil removes it, which can affect # length:
local t = {1, 2, 3, 4, 5}
t[3] = nil
print(#t)  -- May be 2 or 5 (undefined behavior)
  1. 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

See Also

  • — Insert elements into arrays