Codectionary / Developer documentation / Luau

Tables, arrays and dictionaries

A Luau table can hold a sequence, a dictionary or a record. A sequence uses consecutive integer keys starting at one; named fields describe a record. Type annotations describe the intended shape to the checker, but do not copy tables or validate incoming data at runtime.

Syntax

local names: {string} = {"Ada", "Zain"}
local scores: {[string]: number} = {Ada = 4}

Examples

Keep an array dense

Standalone Luau or a Script in ServerScriptService. Remove the first name, append a replacement, then follow the indexes to see how the sequence changes.

local names: {string} = {"Ada", "Zain", "Jamal"}
table.remove(names, 1)
table.insert(names, "Mina")
for index, name in ipairs(names) do
    print(`index {index}: {name}`)
end
  1. Output is index 1: Zain, index 2: Jamal, then index 3: Mina. table.remove shifts the later names down; table.insert appends after the current sequence.
  2. {string} tells the checker that this is an array of strings. It does not change indexing: the first item is still names[1], not names[0].
  3. Assigning nil at index 1 would leave a hole. ipairs stops at the first absent index, and # is not a reliable item count for a table with holes.
  4. Change the removal index to 2. Predict the three names before running: Ada should remain first and Jamal should move into the second position.

Count dictionary entries explicitly

Count present keys independently of their order, then observe that two variables can refer to the same table.

local scores: {[string]: number} = {Ada = 4, Zain = 6}
local count = 0
for _ in pairs(scores) do
    count += 1
end
local alias = scores
alias.Ada = 9
print(`Players: {count}`)
print(`Ada: {scores.Ada}`)
  1. Output is Players: 2, then Ada: 9. The loop adds one for each present key; it does not depend on which key pairs visits first.
  2. The {[string]: number} annotation describes string keys and number values. #scores is not a dictionary entry count.
  3. alias = scores shares the original table. Updating alias.Ada therefore changes scores.Ada too; an annotation does not make assignment copy a table.

Know what table.clone leaves shared

A Luau shallow clone separates top-level fields. A nested table still belongs to both records until you copy it as well.

local original = {name = "Ada", progress = {lessons = 2}}
local copy = table.clone(original)
copy.name = "Mina"
copy.progress.lessons = 3
print(original.name)
print(original.progress.lessons)
print(original == copy)
print(original.progress == copy.progress)
  1. Output is Ada, 3, false, then true. Changing copy.name affects only the new outer table, but both records still refer to the same progress table.
  2. table.clone copies the outer keys and values. When a value is itself a table, the copied value refers to that same nested table.
  3. Insert copy.progress = table.clone(original.progress) before updating lessons. Now the nested records are independent, and original.progress.lessons stays 2. This extra copy handles this known shape; it is not a general deep-copy algorithm.
  4. table.clone is a Luau library function. Do not assume a standard Lua interpreter provides it.

Best practices

  • Choose sequence or dictionary operations to match the keys you actually store.
  • Use table.clone only when sharing nested tables is acceptable, or copy the specific nested state that must be independent.
  • A table type does not validate untrusted input. Check incoming values before using them.

At a glance

Purpose
Typed scripting and Roblox development
File extension
.luau
Runs in
Luau host; Roblox engine examples require Roblox Studio
Usually used with
Roblox APIs and Studio

Specifications & further reading

Related Luau documentation