Skip to content
AgileLogics
5 min read

Closures

A closure is a function that remembers the local variables around it — even after the code that created them has finished.

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

local c1 = makeCounter()
local c2 = makeCounter()
print(c1(), c1(), c1())
print(c2())
1	2	3
1
  • makeCounter returns an inner function that uses count.
  • Each call to makeCounter creates a new count, so c1 and c2 have independent state.
  • count is private: nothing outside can read or change it except through the returned function. (The captured variables are called upvalues in Lua.)

Configuring behavior

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

local double, triple = multiplier(2), multiplier(3)
print(double(21), triple(7))

local function makeGreeter(greeting)
  return function(name)
    return greeting .. ", " .. name .. "!"
  end
end
local hello = makeGreeter("Hello")
print(hello("Ada"))
42	21
Hello, Ada!

Closures in loops

Each loop iteration gets a fresh loop variable, so closures created in a loop capture different values:

local actions = {}
for i = 1, 3 do
  actions[i] = function() return "action " .. i end
end
for _, action in ipairs(actions) do
  print(action())
end
action 1
action 2
action 3

Callbacks

Closures are everywhere in Lua code: event handlers in games, key mappings in Neovim, and callbacks in servers all capture the state they need:

local handlers = {}
local function on(event, fn) handlers[event] = fn end
local function emit(event, ...) if handlers[event] then handlers[event](...) end end

local score = 0
on("coin", function(value) score = score + value end)
emit("coin", 10)
emit("coin", 5)
print("score: " .. score)
score: 15