Cheatsheet: Lua

Last updated 2026-10-06

Variables and Types

Declare local variables (always prefer local over implicit global)

local name = "Alice"
local age = 30
local height = 1.72
local active = true
local nothing = nil

Check a value's type

print(type("text"))   --> string
print(type(42))        --> number
print(type(true))      --> boolean
print(type(nil))       --> nil
print(type({}))        --> table
print(type(print))     --> function

Numbers: integers and floats are both "number"

local count = 10        -- integer subtype
local price = 19.99     -- float subtype
print(10 / 3)            --> 3.3333333333333 (always float division)
print(10 // 3)           --> 3 (floor division, Lua 5.3+)
print(10 % 3)            --> 1 (modulo)

Falsy values: only nil and false are falsy

if 0 then print("0 is truthy") end        -- prints! unlike JS/Python
if "" then print("empty string is truthy") end -- prints too

Control Flow

if / elseif / else

local score = 88
if score >= 90 then
    print("A")
elseif score >= 80 then
    print("B")
else
    print("C")
end

while and repeat-until (repeat always runs at least once)

local n = 0
while n < 3 do
    n = n + 1
end

repeat
    n = n - 1
until n == 0

Numeric for loop: for i = start, stop, step do

for i = 1, 5 do
    print(i)
end

for i = 10, 1, -2 do -- counts down by 2
    print(i)
end

Generic for loop with ipairs/pairs

local fruits = { "apple", "banana", "cherry" }
for index, value in ipairs(fruits) do
    print(index, value)
end

Tables

Array-style table (1-indexed)

local colors = { "red", "green", "blue" }
print(colors[1])       --> red
print(#colors)          --> 3 (length operator)

Map/dictionary-style table

local person = { name = "Ada", age = 36 }
print(person.name)        --> Ada
print(person["age"])      --> 36 (bracket access works too)
person.language = "Lua"   -- add a new key

Insert, remove, and nested tables

local list = { 1, 2, 3 }
table.insert(list, 4)        -- append: {1,2,3,4}
table.insert(list, 1, 0)     -- insert at index: {0,1,2,3,4}
table.remove(list, 1)        -- removes index 1

local nested = { user = { name = "Ada", tags = { "admin", "dev" } } }
print(nested.user.tags[1])   --> admin

Strings

Concatenation and length

local greeting = "Hello, " .. "world" .. "!"
print(#greeting)   --> 13 (string length)

Common string library functions

local s = "Hello World"
print(string.upper(s))          --> HELLO WORLD
print(string.sub(s, 1, 5))      --> Hello
print(string.find(s, "World"))  --> 7  11
print(string.gsub(s, "World", "Lua")) --> Hello Lua  1

string.format works like printf/sprintf

local name, score = "Ada", 97.5
print(string.format("%s scored %.1f%%", name, score))
--> Ada scored 97.5%

Splitting a string with gmatch (no built-in split())

for word in string.gmatch("one two three", "%a+") do
    print(word)
end

Functions

Basic function and multiple return values

local function divide(a, b)
    return a // b, a % b -- quotient, remainder
end

local q, r = divide(17, 5)
print(q, r) --> 3  2

Variadic arguments with ...

local function sum(...)
    local total = 0
    for _, v in ipairs({...}) do
        total = total + v
    end
    return total
end

print(sum(1, 2, 3, 4)) --> 10

Closures capture upvalues

local function makeCounter()
    local count = 0
    return function()
        count = count + 1
        return count
    end
end

local counter = makeCounter()
print(counter()) --> 1
print(counter()) --> 2

Metatables (OOP)

A minimal class pattern with colon-method syntax

local Animal = {}
Animal.__index = Animal

function Animal.new(name)
    local self = setmetatable({}, Animal)
    self.name = name
    return self
end

function Animal:speak()
    return self.name .. " makes a sound"
end

local dog = Animal.new("Rex")
print(dog:speak()) --> Rex makes a sound

Operator overloading with metamethods

local Vector = {}
Vector.__index = Vector
Vector.__add = function(a, b)
    return setmetatable({ x = a.x + b.x, y = a.y + b.y }, Vector)
end

local v1 = setmetatable({ x = 1, y = 2 }, Vector)
local v2 = setmetatable({ x = 3, y = 4 }, Vector)
local v3 = v1 + v2 -- invokes __add
print(v3.x, v3.y)  --> 4  6

Coroutines

Create, resume, and yield

local function counter(limit)
    for i = 1, limit do
        coroutine.yield(i)
    end
end

local co = coroutine.create(counter)
print(coroutine.resume(co, 3)) --> true  1
print(coroutine.resume(co))    --> true  2
print(coroutine.resume(co))    --> true  3
print(coroutine.status(co))    --> suspended (one more resume -> dead)

Error Handling

pcall: call a function and catch errors

local ok, result = pcall(function()
    error("something went wrong")
end)

if not ok then
    print("caught:", result) --> caught: ...: something went wrong
end

assert: raise an error if a condition is false

local function divide(a, b)
    assert(b ~= 0, "division by zero")
    return a / b
end

Quick Recipes

Ternary-style expression (Lua has no ?: operator)

local age = 20
local label = (age >= 18) and "adult" or "minor"
print(label) --> adult

Default value for a possibly-nil argument

local function greet(name)
    name = name or "world"
    print("Hello, " .. name)
end

greet()        --> Hello, world
greet("Ada")   --> Hello, Ada

Simple module pattern (require)

-- mathutils.lua
local M = {}

function M.square(x)
    return x * x
end

return M

-- main.lua
-- local mathutils = require("mathutils")
-- print(mathutils.square(5)) --> 25

FAQ