Lua syntax and types
Locals versus globals, the eight value types, 1-based tables, and the operators that surprise newcomers.
Values, locals and globals
Lua is dynamically typed and compiles to a small register-based bytecode. There is no declaration syntax beyond local: assigning to a name that has no local binding writes into the global table _G.
-- local is the default choice: it is faster and confined to the block
local name = "Lua"
local version = 5.4
-- no "local" means a real global, shared by every module in the state
counter = 0
print(type(name)) -- string
print(10 / 4) -- 2.5 division always produces a float
print(10 // 4) -- 2 floor division, Lua 5.3+
print(2 ^ 10) -- 1024.0
print(#"hello") -- 5 length operator
print("a" .. "b") -- ab .. concatenates, .. is not "or"
print(1 == 1.0) -- true integer and float compare numerically
print(2 ~= 3) -- true "not equal" is ~=, never !=- Numbers have two subtypes since 5.3: 64-bit integers and 64-bit floats.
math.type(3)is"integer". - Strings are immutable and 8-bit clean, so they hold arbitrary bytes as well as UTF-8 text.
- Everything is a first-class value: functions can be stored in tables, passed and returned.
- Names are case sensitive, cannot start with a digit, and cannot be a reserved word such as
endorlocal.
Tables are the only structure
There are eight types in total — nil, boolean, number, string, function, table, thread and userdata — and table covers arrays, records, objects, modules and namespaces. Array-like parts are indexed from 1.
| Type | Example | What it is |
|---|---|---|
nil | nil | Absence of a value; the only value that is falsy besides false |
boolean | true, false | No implicit conversion from numbers or strings |
number | 42, 3.14 | Integer or float subtype, both 64-bit |
string | "hi", 'hi' | Immutable byte sequence, concatenated with .. |
table | { 1, 2, k = 3 } | The one composite type; keys may be any value except nil |
function | function() end | Closures with lexical scoping |
thread | coroutine.create(f) | A coroutine; not an OS thread |
userdata | io.stdout | Opaque host (C) data exposed through methods |
local t = { "a", "b", "c" } -- array part
print(t[1], #t) -- a 3 index 1 is the first element
local record = { id = 1, tags = { "x" } }
record.name = "Ada" -- sugar for record["name"]
record[1] = "first" -- positional key, unrelated to id
-- ipairs walks the array part until the first nil; pairs walks every key
for i, v in ipairs(t) do print(i, v) end
for k, v in pairs(record) do print(k, v) end
table.insert(t, "d") -- append
table.insert(t, 1, "z") -- shift everything right
table.remove(t, 1) -- and back
print(table.concat(t, ",")) -- a,b,c,d
print(#({ 1, 2, nil, 4 })) -- undefined: a table with holes has no reliable lengthControl flow and functions
local function classify(n)
if n == nil then return "missing" end
if n % 2 == 0 then return "even" elseif n > 100 then return "big odd" end
return "odd"
end
-- repeat runs the body before testing, so it always executes once
local tries = 0
repeat tries = tries + 1 until tries >= 3
-- numeric for: start, limit, optional step
for i = 10, 1, -3 do io.write(i, " ") end -- 10 7 4 1
print()
-- varargs and multiple returns
local function stats(...)
local count, sum = select("#", ...), 0
for _, v in ipairs({ ... }) do sum = sum + v end
return count, sum -- returning a list, not a table
end
local n, total = stats(2, 4, 6)
print(n, total) -- 3 12
-- a table constructor keeps all return values in the last position
print({ stats(1, 2) }) -- a two-element table
print({ stats(1, 2), 9 }) -- a single-element tableFunctions are values, so callbacks and modules are just tables of functions. local function binds the name before the body, which is what lets the function call itself recursively.
false and nil are falsy. if 0 then and if "" then both take the true branch, so test lengths and counts explicitly rather than relying on truthiness the way you would in Python or JavaScript.FAQ
Should I always declare locals?
local everywhere and expose a single return table.Why does my array print garbage keys?
pairs on an array, which has no defined order. Use ipairs for sequences and pairs for records, and never mix the two meanings on one table.Related
Tables, metatables and object orientation Python: getting started
Last refreshed 2026-09-18.