100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Tables in Lua

Learn how Lua's single, versatile data structure -- the table -- powers arrays, dictionaries, objects, and modules across every Lua program.

Tables & DataBeginner8 min readJul 10, 2026
Analogies

Introduction to Tables

Lua provides exactly one built-in composite data structure: the table. Unlike languages that expose separate array, list, dictionary, and struct types, Lua tables are associative arrays that can use any non-nil, non-NaN value as a key -- integers, strings, booleans, functions, or even other tables -- and store any value as the corresponding entry. This single mechanism is powerful enough to implement arrays, records, sets, queues, and objects, and it forms the foundation for modules and namespaces in idiomatic Lua code.

🏏

Cricket analogy: A cricket scorebook tracks runs by over, wickets by bowler, and partnerships by pair all in one object, much like a single Lua table holding numeric and string keys together -- Sachin Tendulkar's career stats could live in one table indexed by both season and format.

Creating and Populating Tables

Table constructors use curly braces and can freely combine three forms: comma-or-semicolon separated expressions that get auto-indexed starting at 1 (array style), explicit key = value pairs, and computed keys written as [expression] = value. For example, {10, 20, name = 'queue', [100] = 'far'} creates a table with integer keys 1 and 2, a string key 'name', and an integer key 100, all in one constructor. Tables can also nest arbitrarily, so a table's value can itself be another table, which is how Lua represents multi-dimensional grids, trees, and JSON-like structures without any special syntax.

🏏

Cricket analogy: Setting up a T20 squad sheet in one go, you list batsmen 1 through 11 in order, then add named extras like captain = 'Rohit Sharma' and viceCaptain = 'Bumrah', exactly how a Lua constructor mixes positional and keyed entries in a single {} expression.

Accessing and Modifying Table Fields

Table fields are read and written with either dot notation, t.field, which is pure syntactic sugar for t['field'], or bracket notation, t[expr], which is required whenever the key is not a valid identifier, is computed at runtime, or is a non-string value like a number or another table. Assigning nil to a key effectively deletes that entry from the table -- t.field = nil removes 'field' entirely rather than storing a null placeholder, so subsequent iteration with pairs will skip it and #t may change if the removed key was part of the sequence.

🏏

Cricket analogy: Calling a player by nickname like 'Boom Boom' for Shahid Afridi is shorthand access, but looking him up by a computed jersey number needs the roster table -- just as t.name sugars t['name'] while t[jerseyNumber] needs brackets; dropping a player (nil) removes them from selection entirely.

Table Length and the # Operator

The length operator # returns a 'border' of a sequence -- an index n such that t[n] is non-nil and t[n+1] is nil -- but this is only well-defined when the table has no holes (gaps of nil values) in its positive integer keys. If you create holes by assigning nil to a middle element or by mixing sparse integer keys, #t can return any valid border, which is often surprising. For predictable behavior when adding or removing elements, use table.insert(t, value) and table.remove(t, index) rather than manual index arithmetic, since these functions correctly shift subsequent elements and keep the sequence dense.

🏏

Cricket analogy: If a batting order has a gap because player 6 didn't turn up, asking 'how many batsmen are there' becomes ambiguous -- just like #t is undefined with holes; table.insert/remove is like a manager reshuffling the order instead of leaving an empty slot.

lua
-- Table constructor mixing array and keyed fields
local queue = {10, 20, 30, name = "jobQueue", [100] = "far"}

print(queue[1], queue[2], queue[3])   --> 10  20  30
print(queue.name)                      --> jobQueue
print(queue[100])                      --> far

-- Iterate the array part
for i, v in ipairs(queue) do
  print(i, v)
end

-- Iterate all keys (order not guaranteed for non-array keys)
for k, v in pairs(queue) do
  print(k, v)
end

-- Deleting a key
queue.name = nil
print(queue.name)  --> nil

-- Length operator (only reliable without holes)
print(#queue)  --> 3

-- Safe insertion/removal
table.insert(queue, 40)      -- append to end
table.insert(queue, 1, 5)    -- insert 5 at front, shifts others
table.remove(queue, 2)       -- remove second element, shifts others

Lua tables are reference types: when you assign a table to a new variable or pass it to a function, you copy the reference, not the underlying data. Two variables can point to the same table, so mutating one is visible through the other -- use a manual loop to build an actual deep copy if you need independent tables.

  • A Lua table is the language's only built-in data structure and can represent arrays, dictionaries, records, sets, and objects.
  • Table constructors {} can mix auto-indexed array entries, key = value pairs, and computed [expr] = value keys in one expression.
  • t.field is syntactic sugar for t['field']; bracket notation is required for non-identifier or computed keys.
  • Assigning nil to a key removes it from the table entirely rather than storing a null placeholder.
  • The # operator only gives predictable results on sequences without holes; use table.insert and table.remove for safe mutation.
  • Tables are reference types, so copying a variable copies the reference, and mutations are visible through all references to the same table.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#LuaStudyNotes#TablesInLua#Tables#Lua#Creating#Populating#StudyNotes#SkillVeris#ExamPrep