Nested Tables
In Lua, nesting tables lets you express complex data structures such as multi-dimensional arrays, object-like records, and lists of records. This page covers how to write and use nested tables, including multi-dimensional arrays, object-style tables, and record lists.
Syntax
-- -----------------------------------------------
-- Multi-dimensional array (table of tables)
-- -----------------------------------------------
local matrix = {
{ val1, val2, val3 }, -- row 1: indices [1][1] [1][2] [1][3]
{ val4, val5, val6 }, -- row 2: indices [2][1] [2][2] [2][3]
}
print(matrix[row][col]) -- access an element with [row][col]
-- -----------------------------------------------
-- Object-style table (fields + methods)
-- -----------------------------------------------
local obj = {
field = value, -- define a data field
method = function(self) -- self gives access to its own fields
return self.field
end,
}
obj:method() -- colon syntax: self is passed automatically as the first argument
obj.method(obj) -- dot syntax equivalent (self must be passed explicitly)
-- -----------------------------------------------
-- Array of tables (record list)
-- -----------------------------------------------
local records = {
{ fieldA = value, fieldB = value }, -- record 1
{ fieldA = value, fieldB = value }, -- record 2
}
for i, record in ipairs(records) do
print(record.fieldA) -- traverse in order with ipairs
end
Syntax Summary
| Pattern | Description |
|---|---|
t[i][j] | Access an element of a multi-dimensional array. t[i] returns the inner table; [j] retrieves the element from it. |
t.key.subkey | Access a field of a nested associative table using dot notation. |
t["key"]["subkey"] | Access a nested field using bracket notation. Use this when the key name contains special characters. |
obj:method() | Call a method with colon syntax. self is passed automatically as the first argument. |
obj.method(obj) | Call a method with dot syntax. self must be passed explicitly. |
ipairs(records) | Iterator that traverses an array of tables (record list) in index order. |
pairs(obj) | Iterator that traverses all keys and values of an associative table (object-style table). |
table.insert(t, value) | Appends an element (including a nested table) to the end of a table. |
Sample Code
dragonball_nested.lua
-- dragonball_nested.lua — sample for nested tables
-- Uses Dragon Ball characters to demonstrate
-- multi-dimensional arrays, object-style tables, and record lists
-- -----------------------------------------------
-- Multi-dimensional array: power-level ranking (2D table)
-- -----------------------------------------------
-- Rows = rank, columns = [name, power, race] — a 2D table
local power_table = {
{ "Son Goku", 150000000, "Saiyan" }, -- [1][1] [1][2] [1][3]
{ "Vegeta", 120000000, "Saiyan" }, -- [2][1] [2][2] [2][3]
{ "Son Gohan", 80000000, "Saiyan" }, -- [3][1] [3][2] [3][3]
{ "Piccolo", 20000000, "Namekian" }, -- [4][1] [4][2] [4][3]
{ "Frieza", 100000000, "Frieza Race" }, -- [5][1] [5][2] [5][3]
}
print("=== Power Level Ranking ===")
for i = 1, #power_table do
-- [i][1]: name, [i][2]: power, [i][3]: race
print(string.format(" #%d. %s (%s) Power: %d",
i, power_table[i][1], power_table[i][3], power_table[i][2]))
end
print("")
-- -----------------------------------------------
-- Object-style table: define a character as an object
-- -----------------------------------------------
-- Define Son Goku as an object-style table
local goku = {
name = "Son Goku",
race = "Saiyan",
base_power = 9000,
-- A method defined with colon syntax receives self as its first argument
greet = function(self)
return self.name .. ": I'm always up for a good fight!"
end,
-- A method that calculates the post-transformation power
transform = function(self, multiplier)
return self.name .. " transformed power: " .. (self.base_power * multiplier)
end,
}
print("=== Character Info (Object-style) ===")
print(" Name: " .. goku.name) -- access a field with dot notation
print(" Race: " .. goku.race)
print(" Base power: " .. goku.base_power)
print(" " .. goku:greet()) -- call a method with colon syntax
print(" " .. goku:transform(50)) -- Super Saiyan: 9000 × 50
print("")
-- -----------------------------------------------
-- Nested associative table: detailed character profile
-- -----------------------------------------------
-- Express Frieza's profile as a nested associative table
local frieza = {
name = "Frieza",
origin = {
planet = "Planet Frieza", -- access with origin.planet
race = "Frieza Race",
},
forms = {
first = { name = "First Form", power = 530000 },
second = { name = "Second Form", power = 1000000 },
third = { name = "Third Form", power = 1500000 },
final = { name = "Final Form", power = 12000000 },
},
}
print("=== " .. frieza.name .. "'s Forms ===")
-- Access nested fields with dot notation
print(" Origin: " .. frieza.origin.planet .. " (" .. frieza.origin.race .. ")")
-- Traverse nested table with pairs (order is unspecified)
for key, form in pairs(frieza.forms) do
print(string.format(" [%s] %s — Power: %d",
key, form.name, form.power))
end
print("")
-- -----------------------------------------------
-- Array of tables (record list): technique list
-- -----------------------------------------------
-- Manage characters and their techniques as a record list
local techniques = {} -- start with an empty table and add records dynamically
-- Append records (tables) to the end with table.insert
table.insert(techniques, { user = "Son Goku", name = "Kamehameha", type = "Ki blast", power = 5000 })
table.insert(techniques, { user = "Vegeta", name = "Galick Gun", type = "Ki blast", power = 4800 })
table.insert(techniques, { user = "Son Goku", name = "Spirit Bomb", type = "Charge move",power = 9999 })
table.insert(techniques, { user = "Piccolo", name = "Special Beam Cannon", type = "Piercing", power = 4200 })
table.insert(techniques, { user = "Son Gohan", name = "Kamehameha", type = "Ki blast", power = 3500 })
print("=== Technique List ===")
-- Traverse records in index order with ipairs
for i, tech in ipairs(techniques) do
print(string.format(" %d. %s's %s (%s) Power: %d",
i, tech.user, tech.name, tech.type, tech.power))
end
print("")
-- Filter example: extract only Ki blast techniques
print("=== Ki Blast Techniques Only ===")
for _, tech in ipairs(techniques) do
if tech.type == "Ki blast" then
print(" " .. tech.user .. ": " .. tech.name)
end
end
lua dragonball_nested.lua === Power Level Ranking === #1. Son Goku (Saiyan) Power: 150000000 #2. Vegeta (Saiyan) Power: 120000000 #3. Son Gohan (Saiyan) Power: 80000000 #4. Piccolo (Namekian) Power: 20000000 #5. Frieza (Frieza Race) Power: 100000000 === Character Info (Object-style) === Name: Son Goku Race: Saiyan Base power: 9000 Son Goku: I'm always up for a good fight! Son Goku transformed power: 450000 === Frieza's Forms === Origin: Planet Frieza (Frieza Race) [first] First Form — Power: 530000 [second] Second Form — Power: 1000000 [third] Third Form — Power: 1500000 [final] Final Form — Power: 12000000 === Technique List === 1. Son Goku's Kamehameha (Ki blast) Power: 5000 2. Vegeta's Galick Gun (Ki blast) Power: 4800 3. Son Goku's Spirit Bomb (Charge move) Power: 9999 4. Piccolo's Special Beam Cannon (Piercing) Power: 4200 5. Son Gohan's Kamehameha (Ki blast) Power: 3500 === Ki Blast Techniques Only === Son Goku: Kamehameha Vegeta: Galick Gun Son Gohan: Kamehameha
Common Mistakes
Common Mistake 1: Chaining access through a nil field in a nested table causes an error
When accessing a nested table, if an intermediate field is nil, accessing any further field on it triggers a runtime error. You must verify that each level is not nil before accessing the next.
ng_nil_chain.lua
local character = {
name = "Frieza",
-- the origin field does not exist
}
-- NG: character.origin is nil, so accessing .planet raises an error
print(character.origin.planet)
-- Runtime error -- attempt to index a nil value (field 'origin')
Use a chained and check to guard each level before accessing it, or ensure the field is always present.
ok_nil_chain.lua
local character = {
name = "Frieza",
-- the origin field does not exist
}
-- OK: chain nil checks with and
local planet = character.origin and character.origin.planet
print(planet)
local character2 = {
name = "Son Goku",
origin = { planet = "Planet Vegeta" },
}
local planet2 = character2.origin and character2.origin.planet
print(planet2)
nil Planet Vegeta
Common Mistake 2: Assigning a table copies the reference, not the value
Tables in Lua are reference types. Assigning a table to another variable does not create a new table — both variables point to the same table. Modifying one affects the other.
ng_ref_copy.lua
local goku = { name = "Son Goku", power = 9000 }
-- NG: assignment copies the reference, so both variables point to the same table
local vegeta = goku
vegeta.name = "Vegeta"
vegeta.power = 8000
-- goku is also changed
print(goku.name, goku.power)
Vegeta 8000
When you need an independent copy of a table, write a shallow-copy function that copies each field individually.
ok_ref_copy.lua
local function shallow_copy(t)
local copy = {}
for k, v in pairs(t) do
copy[k] = v
end
return copy
end
local goku = { name = "Son Goku", power = 9000 }
-- OK: shallow_copy creates an independent table
local vegeta = shallow_copy(goku)
vegeta.name = "Vegeta"
vegeta.power = 8000
print(goku.name, goku.power)
print(vegeta.name, vegeta.power)
Son Goku 9000 Vegeta 8000
Overview
In Lua, nesting tables allows you to represent any data structure — multi-dimensional arrays, object-style structures, record lists, and more. Multi-dimensional arrays are accessed with consecutive brackets such as t[i][j]. Object-style tables group fields and methods into a single table and are called with colon syntax (obj:method()), which automatically passes self (the table itself) as the first argument, keeping field access concise. Record lists are commonly built by appending records with table.insert and traversed in order with ipairs. There is no limit to nesting depth, but deeper nesting reduces readability, so splitting into well-named variables is worth considering. For the table basics, see also Table Basics.
If you find any errors or copyright issues, please contact us.