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

TopicWhat it covers
Lua syntax and typesLua is dynamically typed and compiles to a small register-based bytecode. There is no declaration syntax beyond locallesson
Tables, metatables and object orientationTables are compared and assigned by reference. Two names can point at one table, and passing a table to a functionlesson
Embedding Lua in a host programLua is designed to be embedded: the interpreter is a library, and your program owns the lua_State that holds the stacklesson
Functions, closures and multiple returnsA trailing nil in a multiple return disappears when the values land in a table, because the table constructor stops atlesson
Iterators: pairs, ipairs and generic forThe generic for takes three values: an iterator function, an invariant state, and an initial control value. Returning alesson
The standard library in daily usetable, math, string, os and io functions you actually call, plus the differences between Lua 5.1, 5.3, 5.4 and LuaJITlesson
Modules, require and package.pathA searcher returns either a loader function plus the path it used, or a string explaining why that searcher failedlesson
Environments, sandboxing and bytecode_ENV is an ordinary local variable. Declaring local _ENV = {...} inside a function affects only the code lexicallylesson
Performance, garbage collection and LuaJITA finaliser (__gc) is not a destructor. It runs at an unspecified time, possibly never if the program exits, and anlesson
Lua idioms, style and common pitfallsWrite idiomatic Lua, avoid global leaks and silent coercions, and review someone else's Lua quickly and confidentlylesson

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?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 10 lessons of the Lua course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full Lua course — it carries the worked explanations, the edge cases and the exercises behind every line here.

C C++ C# Scala Dart

Last refreshed 2026-09-27.