Functions in Lua
Learn how to define and call functions in Lua, including multiple return values, varargs, closures, and recursion with Docker-ready examples
Functions are the primary unit of reuse in Lua, and they are also one of the language’s most powerful features. In Lua, functions are first-class values: you can store them in variables, pass them as arguments, return them from other functions, and keep them in tables. This is what allows Lua to support a functional style of programming on top of its procedural core.
Lua keeps the syntax minimal—there is one keyword, function, and one way to return values, return. But beneath that simplicity sit features that many older languages lack: a function can return multiple values at once, accept a variable number of arguments, and capture surrounding variables in a closure. Because Lua is dynamically typed, parameters have no declared types, and the language has no built-in default-parameter syntax—idioms fill that gap instead.
In this tutorial you’ll learn how to define and call functions, work with parameters and return values, control variable scope, write recursive functions, and use higher-order functions and closures. Each example is a self-contained file you can run with Docker.
Defining and Calling Functions
A function is defined with the function keyword. Prefixing the definition with local keeps it scoped to the current chunk—the recommended default. Lua functions can return more than one value, which is idiomatic rather than exotic.
Create a file named functions.lua:
| |
The minmax function returns two values, and the assignment local lo, hi = minmax(10, 3) unpacks them into two variables. No tuples or arrays are needed.
Default Arguments and Varargs
Lua has no dedicated syntax for default parameter values. The common idiom uses the or operator: if an argument is nil (omitted), or falls back to the default. For functions that accept any number of arguments, Lua provides the ... (vararg) expression.
Create a file named defaults.lua:
| |
Inside a variadic function, {...} packs the arguments into a table you can iterate, and select("#", ...) reports how many were supplied.
Variable Scope: Local vs Global
By default, an assignment that is not marked local creates a global variable. Globals are visible everywhere and are easy to create by accident, so idiomatic Lua uses local for almost everything. Variables declared local inside a function disappear once the function returns.
Create a file named scope.lua:
| |
Notice that x is visible inside show() because the function is defined in the same chunk, while y is invisible outside the function. The unmarked counter leaks into the global scope—a reminder to reach for local deliberately.
Recursion
A function can call itself. Because a local function name is in scope within its own body, recursion works naturally. Classic examples are factorial and the Fibonacci sequence.
Create a file named recursion.lua:
| |
The loop uses io.write, which prints without adding spaces or a newline, so the Fibonacci numbers appear on one line separated by a single space. The trailing print() adds the final newline.
Higher-Order Functions and Closures
Because functions are values, you can pass them to other functions and return them from functions. A function that takes or returns another function is a higher-order function. When a returned function captures a local variable from its defining scope, that captured state is a closure.
Create a file named higher_order.lua:
| |
Each call to make_counter creates a fresh count variable. The inner function captures that variable, so next_id and other count independently—the essence of a closure.
Running with Docker
| |
Expected Output
Running functions.lua:
Hello, Lua!
7
min = 3 max = 10
Running defaults.lua:
Hello, Ada!
Welcome, Ada!
15
3
Running scope.lua:
inside: x = 10 y = 20
outside: x = 10
global counter = 100
y outside = nil
Running recursion.lua:
5! = 120
10! = 3628800
0 1 1 2 3 5 8 13 21 34
Running higher_order.lua:
36
42
1
2
3
1
Key Concepts
- Functions are first-class values — they can be stored in variables, passed as arguments, returned from other functions, and held in tables.
- Multiple return values are built in —
return a, bandlocal x, y = f()unpack naturally without tuples or arrays. - No default-parameter syntax — use the
name = name or defaultidiom, since an omitted argument arrives asnil. - Varargs with
...— collect extra arguments with{...}and count them withselect("#", ...). localcontrols scope — unmarked assignments create global variables, so preferlocalfor functions and variables to avoid accidental globals.- Recursion is straightforward — a
local function’s name is in scope inside its own body, so it can call itself directly. - Closures capture local state — a function returned from another function remembers the locals it referenced, each instance keeping independent state.
Next Steps
Continue to I/O Operations to learn how Lua reads input, writes formatted output, and works with files.
Running Today
All examples can be run using Docker:
docker pull nickblah/lua:5.4-alpine
Comments
Loading comments...
Leave a Comment