Functions
By the end of this lesson you'll define and call functions, return several values at once , accept any number of arguments with ... , give arguments sensible defaults, pass functions around as values, capture state in closures , and solve nested problems with recursion — the toolkit behind every real Lua script.
Part of the free Lua course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
💡 Real-World Analogy
A function is a recipe card . The ingredients you hand it are the arguments ; the finished dish it gives back is the return value . Write the recipe once and you can cook it any night without re-deriving it. Lua's recipes have two superpowers most kitchens lack: one card can plate up several dishes at once (multiple returns), and a recipe can keep a private pantry that remembers what you used last time (a closure ). Master the recipe card and you stop repeating yourself.
1. Defining and Calling Functions
A function bundles a piece of work under a name so you can run it again whenever you like. The shape is function name(args) ... end , and you put local in front to keep it scoped — the same safe default you learned for variables. Inside, return hands a value back to whoever called it; a function with no return still runs (often for a side effect like printing) and quietly gives back nil . Read this worked example and run it.
2. Multiple Return Values
Here's something most languages can't do: a Lua function can return several values at once — just separate them with commas after return . You catch them by listing the same number of variables on the left: local q, r = divide(17, 5) . There's one rule that trips everyone up: a function call is cut down to a single value unless it's the last thing in a list . Put the multi-return call last and all its values survive; put it earlier and only the first is kept.
Your turn. Finish the minMax function so it returns the smaller value first and the larger second, then catch both returned values. Fill in the blanks marked ___ and compare with the expected output.
3. Varargs: Any Number of Arguments
Sometimes you don't know in advance how many arguments a function will get — think print , which accepts as many as you throw at it. Lua's answer is varargs , written as ... (three dots) in the parameter list. Pack them into a table with {...} so you can loop over them, count them with select("#", ...) , or read from a position with select(i, ...) . When some arguments might be nil , use table.pack(...) instead — it records an accurate count in .n that {...} can't promise.
Now you try. Build an average function that accepts any number of scores using ... , sums them, and divides by how many there were. Fill in the three blanks:
4. Default Argument Values
Lua has no special syntax for default arguments, but there's a one-line idiom every Lua programmer uses: x = x or fallback . The or operator returns its first truthy value, so when an argument is missing (it arrives as nil ) the fallback kicks in. The single gotcha: or also treats false as "missing", so for boolean flags compare against nil directly ( if flag == nil then ... end ) — otherwise a deliberately passed false would be overwritten.
5. Functions as Values & Closures
In Lua, functions are first-class values : you can store one in a variable, pass it into another function, return it, or drop it in a table — exactly like a number or a string. This unlocks the closure : a function that remembers the local variables alive where it was created. Those captured variables stay private and persist between calls, which is how you build counters and accumulators without reaching for globals. Crucially, each closure gets its own copy of the captured state.
6. Recursion
Recursion is a function that calls itself, solving a big problem by reducing it to a smaller version of the same problem. Every recursive function needs two parts: a base case that stops the chain, and a recursive step that moves toward that base case by shrinking the input. Forget the base case and the calls never stop — you'll get a stack overflow . Recursion is the natural fit for anything nested: folder trees, JSON, or maths like factorial defined in terms of itself.
Pro Tips
- 💡 Default to local function . A global function is namespace pollution unless you truly need it everywhere — and the local form lets the function call itself for recursion.
- 💡 Put multi-return calls last. If you only got the first value back, your call wasn't the final item in the list. Wrap a call in (parentheses) to force exactly one value.
- 💡 x = x or default handles missing arguments in one line — but use == nil for boolean flags so a real false isn't clobbered.
- 💡 Closures replace globals for state. Need a counter or a running total? Capture it in a closure instead of leaking a global variable.
Common Errors (and the fix)
- Only the first return value shows up: a multi-return call gets truncated to one value unless it's last in the list — print(pair(), 99) drops pair()'s second value. Fix: put the call last, or assign all values with local a, b = pair() .
- "'end' expected (to close 'function' ...)": you forgot the end that closes the function (or an if / for inside it). Fix: every function , if , and for needs a matching end .
- A function "works" but pollutes globals: writing function helper() without local creates a global that any library can overwrite. Fix: write local function helper() .
- "stack overflow": a recursive function with no reachable base case calls itself forever. Fix: add a base case (e.g. if n <= 1 then return ... end ) and make sure each call shrinks the input.
📋 Quick Reference
Task
Code
Result / Note
Local function
local function f(x) end
scoped to block
Anonymous function
local f = function(x) end
stored in a variable
Multiple return
return a, b
local x, y = f()
Varargs
function f(...) end
{...} packs them
Count varargs
select("#", ...)
how many passed
Default argument
x = x or 0
fallback when nil
Force one value
(f())
truncates to 1
Frequently Asked Questions
Mini-Challenge: Bank Account Closure
No blanks this time — just a brief and an outline to keep you on track. You'll combine three of this lesson's ideas: the default-argument idiom, returning multiple functions, and closures that share captured state. Build it, run it, and check your output against the example in the comments.
🎉 Lesson Complete!
- ✅ Define functions with function name(args) ... end ; prefer local function
- ✅ Return several values with return a, b — and a call keeps them all only when it's last in a list
- ✅ Accept any number of arguments with varargs ... ; count with select("#", ...) , pack safely with table.pack
- ✅ Give defaults with x = x or fallback (use == nil for boolean flags)
- ✅ Functions are first-class values; closures capture and remember local state
- ✅ Recursion solves nested problems — always include a base case to avoid a stack overflow
- ✅ Next lesson: Control Flow and Loops — make decisions and repeat work
Practice quiz
Which keyword starts the return of a value from a Lua function?
- yield
- return
- give
- out
Answer: return. return hands a value back to the caller.
How do you make a function scoped to its block?
- scoped function
- private function
- local function
- static function
Answer: local function. local function keeps the function out of the global namespace.
How do you capture both values of return a, b ?
- local x = f()
List two variables on the left to receive both returns.
A multi-return call keeps all its values only when it is...
- The last item in a list
- The first item in a list
- Wrapped in parentheses
- Assigned to one variable
Answer: The last item in a list. Calls are truncated to one value unless they are last in a list.
What does ... mean in a parameter list?
- A range
- Varargs: any number of extra arguments
- A default value
- A comment
Answer: Varargs: any number of extra arguments. ... collects any number of extra arguments passed by the caller.
Which idiom gives a default for a missing argument?
- x = default(x)
- x = x and default
- x = x or default
- x ??= default
Answer: x = x or default. or returns its first truthy value, so a nil argument falls back to the default.
What is a closure?
- A loop that closes itself
- A type of table
- A way to end a function early
- A function that remembers variables from where it was created
Answer: A function that remembers variables from where it was created. A closure captures and keeps the local variables alive where it was made.
Every recursive function must have a...
- Base case that stops it
- Global counter
- Vararg parameter
- Return of nil
Answer: Base case that stops it. Without a base case the recursion never stops and overflows the stack.
What does select("#", ...) return?
- The first vararg
- The last vararg
- How many varargs were passed
- A packed table
Answer: How many varargs were passed. select("#", ...) counts the varargs received.
A function with no return statement gives back...
- 0
- An empty string
- An error
- nil
Answer: nil. With no return, a Lua function hands back nil.
Continue this course
- Previous: Variables and Data Types
- Next: Control Flow and Loops