Functions, Closures, and Varargs

· 4 min read · Updated March 17, 2026 · beginner
functions closures varargs beginner

Functions are the building blocks of any Lua program. They let you encapsulate logic, avoid repetition, and structure your code into manageable pieces. In this tutorial, you’ll learn how to define functions, understand closures, work with variable arguments, and apply best practices.

Defining Functions

Lua provides several ways to define functions. The most common syntax uses the function keyword:

function greet(name)
    print("Hello, " .. name .. "!")
end

greet("World")  -- Output: Hello, World!

You can also assign functions to variables:

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

print(add(3, 5))  -- Output: 8

Both approaches create a function, but the first declares it globally while the second assigns it to a local variable. Prefer local functions—they’re cleaner and avoid polluting the global namespace.

Multiple Return Values

Lua functions can return multiple values, which is particularly useful:

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

local result, err = divide(10, 2)
print(result)  -- Output: 5
print(err)     -- Output: nil

This pattern lets you return both a value and an error indicator, similar to Go’s error handling style.

Closures

A closure is a function that captures variables from its surrounding scope. This powerful feature enables interesting patterns:

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

local c1 = counter()
local c2 = counter()

print(c1())  -- Output: 1
print(c1())  -- Output: 2
print(c2())  -- Output: 1

Each call to counter() creates a new closure with its own count variable. The returned function “remembers” the environment where it was created.

Closures are useful for:

  • Creating private variables
  • Building stateful functions
  • Implementing callbacks and event handlers

Variable Arguments (Varargs)

Lua supports variable arguments using the ... syntax:

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

print(sum(1, 2, 3))       -- Output: 6
print(sum(10, 20, 30, 40))  -- Output: 100

You can also use select to work with arguments without creating a table:

function average(...)
    local count = select("#", ...)  -- number of arguments
    local total = 0
    for i = 1, count do
        total = total + select(i, ...)
    end
    return total / count
end

print(average(10, 20, 30))  -- Output: 20

Multiple Assignments with Varargs

When a function returns multiple values, you can capture them directly:

function stats(numbers)
    local sum = 0
    for _, n in ipairs(numbers) do
        sum = sum + n
    end
    local mean = sum / #numbers
    return sum, mean, #numbers
end

local total, average, count = stats({10, 20, 30})
print(total, average, count)  -- Output: 60  20  3

Named Returns

Lua lets you return values by name using a table:

function rectangle(w, h)
    return {
        width = w,
        height = h,
        area = w * h,
        perimeter = 2 * (w + h)
    }
end

local r = rectangle(5, 3)
print(r.area)         -- Output: 15
print(r.perimeter)    -- Output: 16

This pattern improves readability when returning many values.

Best Practices

  1. Use local functions — Avoid polluting the global namespace.

  2. Keep functions focused — Each function should do one thing well.

  3. Validate inputs — Check for nil values and invalid arguments:

function safeDivide(a, b)
    assert(type(a) == "number", "first argument must be a number")
    assert(type(b) == "number", "second argument must be a number")
    assert(b ~= 0, "cannot divide by zero")
    return a / b
end
  1. Use multiple returns for errors — Return nil with an error message instead of throwing exceptions.

  2. Document with comments — Explain what the function does, its parameters, and return values.

Common Patterns

Callback Functions

function processItems(items, callback)
    local results = {}
    for i, item in ipairs(items) do
        results[i] = callback(item)
    end
    return results
end

local doubled = processItems({1, 2, 3}, function(x) return x * 2 end)
print(table.concat(doubled, ", "))  -- Output: 2, 4, 6

Function Factories

function multiplier(factor)
    return function(x)
        return x * factor
    end
end

local double = multiplier(2)
local triple = multiplier(3)

print(double(5))   -- Output: 10
print(triple(5))  -- Output: 15

Summary

Functions in Lua are versatile and powerful. Key takeaways:

  • Define functions with function name(args) ... end
  • Use closures to create stateful, reusable functions
  • Handle variable arguments with ... and select
  • Return multiple values for clean error handling
  • Prefer local functions and validate inputs

In the next tutorial, you’ll learn about Tables: Lua’s Universal Data Structure, which combine arrays, dictionaries, and objects into one flexible type.

See Also