Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. Lua Dictionary
  3. Nested Tables

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

PatternDescription
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.subkeyAccess 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 .