cv.cm

Coding · Programming languages

Lua tutorial for beginners

7 min read

Short answer

Lua is a lightweight, embeddable scripting language used in games, Roblox (through its Luau dialect), Neovim, Redis and embedded devices. It has a small syntax and a single data structure, the table, which serves as array, dictionary and object. This page covers installing Lua, variables and types, control flow, functions, tables, metatables, error handling with pcall and a complete example.

What Lua is and what it is used for

Lua is a small, fast, dynamically typed scripting language created in 1993 at PUC-Rio, a university in Brazil. Its whole interpreter is a compact library written in C, which makes it easy to embed inside larger programs. It has one data structure, the table, and very few concepts, so it is quick to learn.

Lua is mostly used as an embedded scripting language: game engines and games (Roblox uses a Lua dialect called Luau; LÖVE and Defold use Lua), the Neovim editor, the Redis database, network tools such as Nginx with OpenResty, and many embedded devices. LuaJIT is a popular just-in-time compiler for Lua 5.1 code.

Install Lua and run Hello, world

Install Lua with your package manager (for example brew install lua or apt install lua5.4) or build it from the source at lua.org. Run lua alone for an interactive prompt.

-- hello.lua
print("Hello, world!")
lua hello.lua
# Hello, world!

Comments start with --. A block comment is written --[[ ... ]]. Statements do not need semicolons.

Variables and basic types

Variables are global by default. Always declare them with local unless you really mean a global. Lua has eight basic types; the ones you use every day are below.

local name = "Lua"
local year = 1993              -- integer
local pi = 3.14159             -- float
local ok = true
local nothing = nil

print(type(name), type(year), type(nothing))  -- string  number  nil
print(math.type(year), math.type(pi))         -- integer  float
print(name .. " " .. year)                    -- Lua 1993 (.. joins strings)
print(#name)                                  -- 3 (length)
print(7 / 2, 7 // 2, 7 % 2, 2 ^ 10)           -- 3.5  3  1  1024.0
print(string.format("%.2f", pi))              -- 3.14
print(tonumber("42") + 1, tostring(10))       -- 43  10
TypeExampleNotes
nilnilabsence of a value; unset variables are nil
booleantrue, falseonly false and nil are falsy; 0 and "" are truthy
number42, 3.14integer and float subtypes since Lua 5.3
string"hi", 'hi', [[long]]immutable byte strings
table{1, 2, x = 3}the only data structure
functionfunction() endfirst-class values

The not-equal operator is ~=, not !=. Logical operators are the words and, or and not.

Control flow

Blocks end with the keyword end. Lua has if/elseif/else, while, repeat ... until, a numeric for and a generic for that works with iterators.

local score = 72
if score >= 90 then
  print("A")
elseif score >= 70 then
  print("B")                   -- B
else
  print("C")
end

for i = 1, 3 do                -- 1 to 3, inclusive
  io.write(i, " ")             -- 1 2 3
end
print()

for i = 10, 1, -4 do io.write(i, " ") end   -- 10 6 2
print()

local n = 3
while n > 0 do n = n - 1 end

repeat
  n = n + 1
until n >= 2
print(n)                       -- 2

There is no continue statement; use goto continue with a ::continue:: label, or restructure the loop with an if.

Functions

Functions are values. They can take a variable number of arguments with ... and return multiple values. Closures capture local variables from the enclosing scope.

local function add(a, b)
  return a + b
end

local function minmax(t)
  local lo, hi = math.huge, -math.huge
  for _, v in ipairs(t) do
    lo, hi = math.min(lo, v), math.max(hi, v)
  end
  return lo, hi                -- two return values
end

local function counter()
  local count = 0
  return function()            -- a closure
    count = count + 1
    return count
  end
end

print(add(2, 3))               -- 5
local lo, hi = minmax({4, 9, 1})
print(lo, hi)                  -- 1  9
local next_id = counter()
print(next_id(), next_id())    -- 1  2
local function sum(...)
  local total = 0
  for _, v in ipairs({...}) do total = total + v end
  return total
end
print(sum(1, 2, 3))            -- 6

Tables: arrays, dictionaries and objects

A table is an associative array that can be indexed by any value except nil. The same structure serves as an array, a dictionary, a set, a module or an object. By convention arrays start at index 1.

local fruits = {"apple", "banana"}
table.insert(fruits, "cherry")
print(#fruits, fruits[1])            -- 3  apple

for i, f in ipairs(fruits) do        -- array part, in order
  print(i, f)
end

local ages = {ann = 31, bo = 25}
ages["cy"] = 40
ages.bo = nil                        -- remove a key
for k, v in pairs(ages) do           -- all keys, order not defined
  print(k, v)
end

table.sort(fruits, function(a, b) return a > b end)
print(table.concat(fruits, ", "))    -- cherry, banana, apple

Metatables and object-oriented Lua

Metatables are Lua's signature feature. A metatable attached to a table changes how it behaves: __index supplies missing keys (used for inheritance), and fields such as __add, __eq and __tostring overload operators. The colon syntax obj:method() passes the object as the hidden first parameter self.

local Vector = {}
Vector.__index = Vector

function Vector.new(x, y)
  return setmetatable({x = x, y = y}, Vector)
end

function Vector:length()
  return math.sqrt(self.x ^ 2 + self.y ^ 2)
end

Vector.__add = function(a, b) return Vector.new(a.x + b.x, a.y + b.y) end
Vector.__tostring = function(v) return "(" .. v.x .. ", " .. v.y .. ")" end

local v = Vector.new(3, 4) + Vector.new(0, 0)
print(tostring(v))       -- (3, 4)
print(v:length())        -- 5.0

Lua also has coroutines (coroutine.create, coroutine.resume, coroutine.yield), which let a function pause and resume later. Game scripts often use them for step-by-step behavior that spans several frames.

Error handling

Raise an error with error(message). Catch it with pcall (protected call), which returns false plus the error instead of stopping the program. Many library functions return nil and an error message instead of raising an error, as io.open does.

local function divide(a, b)
  if b == 0 then
    error("division by zero")
  end
  return a / b
end

local ok, result = pcall(divide, 10, 2)
print(ok, result)                  -- true  5.0

ok, result = pcall(divide, 1, 0)
print(ok, result)                  -- false  script.lua:3: division by zero

local f, err = io.open("missing.txt", "r")
if not f then print("open failed: " .. err) end

The file name and line number in the error message depend on where the code runs. assert(value, message) raises an error when value is false or nil and is a short way to check results.

A small complete program: word count

This program counts words in a text with a pattern match, sorts them by frequency and prints the top three. It uses string patterns, tables, sorting with a custom comparator and string formatting.

local text = [[
The cat and the dog. The dog sat,
and the cat ran.
]]

local counts = {}
for word in text:lower():gmatch("%a+") do
  counts[word] = (counts[word] or 0) + 1
end

local list = {}
for word, n in pairs(counts) do
  list[#list + 1] = {word = word, n = n}
end
table.sort(list, function(a, b)
  if a.n ~= b.n then return a.n > b.n end
  return a.word < b.word
end)

for i = 1, 3 do
  print(string.format("%-4s %d", list[i].word, list[i].n))
end
-- the  4
-- and  2
-- cat  2

Where to go next

  • The Lua Reference Manual at lua.org, short and complete.
  • Programming in Lua by Roberto Ierusalimschy, one of Lua's creators; the first edition is free online.
  • The documentation of your host environment, such as Neovim's Lua guide, LÖVE or Roblox's Luau docs.
  • LuaRocks, the package manager for Lua modules.

FAQ

Why do Lua arrays start at 1?

Lua's designers chose 1-based indexing to match common mathematical and non-programmer conventions. The standard library functions such as ipairs, # and table.insert all assume sequences start at 1.

Is Lua faster than Python?

The standard Lua interpreter is small and generally fast for a dynamic language, and LuaJIT is much faster for many workloads. Actual speed depends on the task, so measure your own case.

What is the difference between pairs and ipairs?

ipairs walks the array part in order from index 1 until the first nil. pairs visits every key in the table in an unspecified order.

What is Luau?

Luau is a language derived from Lua 5.1 and developed by Roblox. It adds gradual typing and performance improvements and is used for Roblox games.

Is Lua good for beginners?

Yes. It has a small, consistent syntax and few concepts, which is why it is popular for game modding and teaching. Its minimal standard library means you rely on the host program or modules for more features.

All coding tutorials