Lua cheat sheet
A scannable Lua reference: 16 short snippets across 10 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Lua syntax and types | Lua is dynamically typed and compiles to a small register-based bytecode. There is no declaration syntax beyond local | lesson |
| Tables, metatables and object orientation | Tables are compared and assigned by reference. Two names can point at one table, and passing a table to a function | lesson |
| Embedding Lua in a host program | Lua is designed to be embedded: the interpreter is a library, and your program owns the lua_State that holds the stack | lesson |
| Functions, closures and multiple returns | A trailing nil in a multiple return disappears when the values land in a table, because the table constructor stops at | lesson |
| Iterators: pairs, ipairs and generic for | The generic for takes three values: an iterator function, an invariant state, and an initial control value. Returning a | lesson |
| The standard library in daily use | table, math, string, os and io functions you actually call, plus the differences between Lua 5.1, 5.3, 5.4 and LuaJIT | lesson |
| Modules, require and package.path | A searcher returns either a loader function plus the path it used, or a string explaining why that searcher failed | lesson |
| Environments, sandboxing and bytecode | _ENV is an ordinary local variable. Declaring local _ENV = {...} inside a function affects only the code lexically | lesson |
| Performance, garbage collection and LuaJIT | A finaliser (__gc) is not a destructor. It runs at an unspecified time, possibly never if the program exits, and an | lesson |
| Lua idioms, style and common pitfalls | Write idiomatic Lua, avoid global leaks and silent coercions, and review someone else's Lua quickly and confidently | lesson |
Quick snippets
Lua syntax and types
Values, locals and globals
-- 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… 3 more lines in the full lesson.
Tables are the only structure
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… 4 more lines in the full lesson.
Control 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… 14 more lines in the full lesson.
Full lesson: Lua syntax and types →
Tables, metatables and object orientation
References and copying
local a = { 1, 2 }
local b = a -- the same table, not a copy
b[3] = 3
print(#a) -- 3
local shallow = {}
for k, v in pairs(a) do shallow[k] = v end -- copy the top level only
shallow[1] = 99
print(a[1]) -- 1 (numbers are copied by value)
-- table.clone is standard in 5.4; a hand-written deep copy needs recursion
local function deepcopy(t, seen)… 8 more lines in the full lesson.
Operator metamethods
local Vec = {}
Vec.__index = Vec
Vec.__add = function(a, b) return Vec.new(a.x + b.x, a.y + b.y) end
Vec.__eq = function(a, b) return a.x == b.x and a.y == b.y end
Vec.__tostring = function(v) return "(" .. v.x .. ", " .. v.y .. ")" end
Vec.__len = function(v) return math.sqrt(v.x ^ 2 + v.y ^ 2) end
function Vec.new(x, y) return setmetatable({ x = x, y = y }, Vec) end
local p = Vec.new(1, 2) + Vec.new(3, 4)
print(p, #p) -- (4, 6) 7.211102550928
print(Vec.new(1, 1) == Vec.new(1, 1)) -- true… 4 more lines in the full lesson.
Full lesson: Tables, metatables and object orientation →
Embedding Lua in a host program
The C API in one breath
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
int main(void) {
lua_State *L = luaL_newstate(); /* one state per thread, never shared */
luaL_openlibs(L); /* string, table, math, io, os, ... */
if (luaL_dofile(L, "config.lua") != LUA_OK) {
fprintf(stderr, "%s\n", lua_tostring(L, -1));
lua_pop(L, 1); /* remove the error message */… 12 more lines in the full lesson.
Sandboxing untrusted scripts
-- 5.2+ / 5.4: a chunk's globals live in its _ENV upvalue
local function sandbox(code, extra)
local env = {
math = math, string = string, table = table,
ipairs = ipairs, pairs = pairs, tostring = tostring, tonumber = tonumber,
print = print, error = error, type = type,
}
for k, v in pairs(extra or {}) do env[k] = v end
local chunk, err = load(code, "sandbox", "t", env)
if not chunk then return nil, err end
local ok, result = pcall(chunk)… 9 more lines in the full lesson.
Full lesson: Embedding Lua in a host program →
Functions, closures and multiple returns
Functions as values
-- three spellings of the same thing
local function add(a, b) return a + b end
local sub = function(a, b) return a - b end
local tbl = { mul = function(a, b) return a * b end }
-- functions are first-class: store, pass and return them
local ops = { add = add, sub = sub, mul = tbl.mul }
local function apply(op, a, b) return op(a, b) end
print(apply(ops.add, 2, 3)) --> 5
-- higher-order: return a new function… 15 more lines in the full lesson.
Full lesson: Functions, closures and multiple returns →
Iterators: pairs, ipairs and generic for
What generic for calls
local t = { 10, 20, 30, name = "ada" }
-- ipairs walks 1..n and stops at the first nil: array part only
for i, v in ipairs(t) do print(i, v) end --> 1 10, 2 20, 3 30
-- pairs walks every key, in an unspecified order: use it for maps
for k, v in pairs(t) do print(k, v) end
-- generic for is sugar for this loop, which explains the contract
local f, state, control = next, t, nil
while true do
local k, v = f(state, control)… 13 more lines in the full lesson.
Full lesson: Iterators: pairs, ipairs and generic for →
The standard library in daily use
io and os
-- version differences that break real code
-- 5.1 unpack(t) | 5.2+ table.unpack(t)
-- 5.1 math.mod, math.log10 | 5.3+ math.fmod, removed log10
-- 5.1 setfenv / getfenv | 5.2+ _ENV and load
-- 5.2 bit32 library | 5.3+ native operators & | ~ << >>
-- 5.3+ integer subtype | LuaJIT: all numbers are doubles unless it uses FFI
-- 5.4 <close> and <const> | LuaJIT: not supported
-- # on a table with holes | any version: the result is undefined
table and math
-- table: the workhorse
local t = { 3, 1, 2 }
table.insert(t, 4) -- append
table.insert(t, 1, 0) -- insert at position 1, shifting the rest
print(table.remove(t)) --> 4, removes the last
print(table.remove(t, 1)) --> 0
table.sort(t)
print(table.concat(t, ",")) --> 1,2,3
print(table.unpack({ 1, 2, 3 })) --> 1 2 3
-- Lua 5.1 called it unpack; 5.2+ moved it to table.unpack
local unpack = table.unpack or unpack… 12 more lines in the full lesson.
Full lesson: The standard library in daily use →
Modules, require and package.path
Circular dependencies and structure
-- circular requires: A needs B and B needs A
-- file: a.lua
local B = require("b") -- b.lua runs, requires a, and gets a PARTIAL table
local M = {}
function M.run() return B.helper() end
return M
-- file: b.lua
local A = require("a") -- receives the incomplete a table, whose run is not set yet
local M = {}
function M.helper() return A.run end -- capture lazily: do not call at load time
return M… 7 more lines in the full lesson.
Full lesson: Modules, require and package.path →
Environments, sandboxing and bytecode
Bytecode and what it is not
-- produce a binary chunk
local f = assert(load("return 1 + 1"))
local bytes = string.dump(f)
print(#bytes > 0) --> true
-- loading a binary chunk works the same way
local back = assert(load(bytes))
print(back()) --> 2
-- refuse binary chunks in a sandbox: the mode argument
print(load(bytes, "no-binary", "t")) --> nil, "attempt to load a binary chunk"
print(load("return 1", "no-binary", "t")) --> function… 8 more lines in the full lesson.
Full lesson: Environments, sandboxing and bytecode →
Performance, garbage collection and LuaJIT
The garbage collector
-- inspect and tune the collector
print(collectgarbage("count")) -- kilobytes in use
-- the incremental collector (5.1 through 5.4, default in 5.4 is generational)
collectgarbage("setpause", 200) -- start a new cycle at 200% of the live set
collectgarbage("setstepmul", 200) -- work per step
-- a full collection: useful at a loading screen or between levels
collectgarbage("collect")
-- stop the collector while building a large structure, then restart
collectgarbage("stop")… 16 more lines in the full lesson.
LuaJIT: traces and FFI
-- run under LuaJIT: check which build you are on
print(jit.version) --> LuaJIT 2.1.0-beta3
print(jit.arch, jit.os)
-- is this function being compiled?
jit.on() -- enable the JIT for the current function
jit.off() -- disable it, for debugging
local compiled = jit.status()
print(compiled)
-- warm up the call site before measuring: the first runs are interpreted
for i = 1, 100 do f(i) end -- let the trace form… 16 more lines in the full lesson.
Full lesson: Performance, garbage collection and LuaJIT →
Lua idioms, style and common pitfalls
Reviewing Lua quickly
-- the shape of a file that is easy to review
local M = {}
local DEFAULTS = { timeout = 5, retries = 3 }
local unpack = table.unpack or unpack
local function check(opts) -- private helper at the top
assert(type(opts) == "table", "opts must be a table")
return opts
end
function M.configure(opts)… 13 more lines in the full lesson.
Full lesson: Lua idioms, style and common pitfalls →
FAQ
Is this Lua cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
Last refreshed 2026-09-27.