Skip to content
Lua logo

Lua

First appeared 1993 · Roberto Ierusalimschy

A few hundred kilobytes of scripting language, embedded in more software than you realise.

Overview

Lua is a small, fast scripting language designed from the beginning to be embedded inside other programs. The entire reference implementation is around thirty thousand lines of clean ANSI C and compiles to a few hundred kilobytes, which is why it appears anywhere a host application needs user-facing scripting without a heavy runtime: game engines, network appliances, editors, embedded devices and databases. The language itself is deliberately minimal, built on a single composite data structure — the table — that serves as array, dictionary, object and namespace. Metatables provide a small, powerful mechanism for operator overloading and inheritance, letting Lua support object-oriented and functional styles without building either into the core. LuaJIT, an independent just-in-time implementation, reaches performance competitive with C for many workloads and is widely used where speed matters.

Key facts

The reference details, without the paragraph.

First appeared
1993
Designed by
Roberto Ierusalimschy, Luiz Henrique de Figueiredo and Waldemar Celes, PUC-Rio
Typing
Dynamic and strong, with eight basic types and one composite type
Execution
Compiled to bytecode for a register-based virtual machine; LuaJIT adds a tracing JIT
Memory model
Automatic — incremental or generational garbage collection
Package manager
LuaRocks
File extensions
.lua
Implementation size
Roughly 30,000 lines of ANSI C; the binary is a few hundred kilobytes
Indexing
One-based, and `nil` marks the end of a sequence
Licence
MIT

History

How the language got here — the decisions that still shape how you write it.

Lua was created in 1993 at the Pontifical Catholic University of Rio de Janeiro by Roberto Ierusalimschy, Luiz Henrique de Figueiredo and Waldemar Celes. Its origin is unusually specific: Brazil had strict trade barriers on imported computer hardware and software through the 1980s, so the university's research group built its own tools rather than buying them. Lua — Portuguese for moon — grew out of two earlier in-house languages for configuring engineering applications at Petrobras, the Brazilian oil company. That heritage explains its defining characteristics: it had to be tiny, portable to whatever hardware was available, and easy to embed in existing C programs. The design proved unusually well suited to games, where a compiled engine needs designers and modders to script behaviour without recompiling. World of Warcraft, Roblox, Garry's Mod and countless others adopted it. Later, Neovim chose Lua as its configuration and plugin language, Redis embedded it for server-side scripting, and OpenResty built a high-performance web platform on LuaJIT inside nginx. Few languages are so widely deployed while being so rarely written as a primary language.

  1. 1993

    Born from a trade embargo

    Brazil's restrictions on imported software through the 1980s meant PUC-Rio's research group built its own tools. Lua grew from two in-house configuration languages written for Petrobras.

  2. 1996

    Lua 3.0 and international attention

    An article in Dr. Dobb's Journal introduces Lua to a wider audience, and game developers start noticing a scripting language small enough to embed anywhere.

  3. 2003

    Games adopt it broadly

    Lua becomes the default embedded scripting language in the games industry — a compiled engine with designer-editable behaviour is exactly the problem it solves.

  4. 2004

    World of Warcraft ships

    Its entire addon and interface system is Lua, exposing the language to an enormous number of people who were not otherwise programmers.

  5. 2005

    LuaJIT

    Mike Pall's tracing just-in-time compiler reaches performance competitive with C on many workloads, and becomes the implementation of choice where speed matters.

  6. 2015

    Lua 5.3 adds integers

    A distinct integer subtype arrives after two decades of all numbers being doubles — a small change with wide consequences for correctness in embedded and financial code.

  7. 2021–2025

    Neovim and Luau

    Neovim adopts Lua as its configuration and plugin language, and Roblox ships Luau, a gradually-typed dialect, to millions of young developers.

What it is good at

The reasons teams pick it, stated concretely.

  • Small enough to embed anywhere

    The whole interpreter compiles to a few hundred kilobytes of ANSI C with no dependencies. That is why it fits in game engines, routers, cameras and microcontrollers where a Python runtime would be unthinkable.

  • A genuinely clean C API

    The stack-based interface for exchanging values between C and Lua is small, well documented and stable across versions. Embedding it in an existing C program is a day's work, not a project.

  • One data structure that does everything

    Tables serve as arrays, dictionaries, objects, modules and namespaces. Learning one structure well covers most of the language, which is a large part of why Lua is quick to pick up.

  • LuaJIT is remarkably fast

    For numeric and loop-heavy code, LuaJIT frequently reaches within a small factor of C. Its FFI also calls C functions directly with no binding layer, which is why OpenResty and similar systems chose it.

  • Easy for non-programmers to reach

    The syntax is small and forgiving, which is why it succeeded as a modding and configuration language. Millions of people have written Lua without considering themselves developers.

Trade-offs

Every language costs you something. Knowing what, before you commit, is the whole point.

  • A very small standard library

    There is no built-in HTTP, JSON, filesystem traversal or date parsing beyond the basics. This is deliberate — the host application is expected to provide what it needs — but standalone Lua means assembling libraries yourself.

  • One-based indexing and nil-terminated sequences

    Arrays start at 1, and a nil in the middle of a table makes its length undefined. Both surprise people constantly and are a genuine source of bugs when moving between languages.

  • Versions are not compatible

    5.1, 5.2, 5.3 and 5.4 each broke something, and LuaJIT is pinned to 5.1 semantics. A library often targets one specific version, and the ecosystem is fragmented as a result.

  • Globals by default

    An undeclared variable is global, so a typo silently creates one rather than raising an error. `local` must be written deliberately and consistently.

  • No standard object system

    Object orientation is built by hand from tables and metatables, so every codebase and framework does it slightly differently. There is no single conventional class pattern to learn.

Code examples

Not syntax tours — the idioms that make code read like the language rather than a translation of another one.

Tables are everything
-- Array-like: indices start at 1.
local books = { "Dune", "Neuromancer", "Snow Crash" }
print(#books, books[1])          -- 3   Dune

-- Dictionary-like, in the same structure.
local book = {
  title = "Dune",
  year = 1965,
  tags = { "scifi", "classic" },
}

-- Both notations reach the same field.
print(book.title, book["title"])

-- ipairs stops at the first nil; pairs visits every key in no order.
for i, title in ipairs(books) do print(i, title) end
for key, value in pairs(book) do print(key, value) end

-- A nil in the middle makes # undefined — do not rely on it.
books[2] = nil
print(#books)   -- may be 1 or 3
The `#` operator on a table with holes is explicitly undefined behaviour in the specification, not a bug. If a sequence can contain gaps, track the count yourself.
Objects from metatables
local Account = {}
Account.__index = Account       -- lookups fall back to Account

function Account.new(owner, balance)
  return setmetatable({ owner = owner, balance = balance or 0 }, Account)
end

function Account:deposit(amount)   -- `:` passes self implicitly
  assert(amount > 0, "amount must be positive")
  self.balance = self.balance + amount
  return self
end

function Account:__tostring()
  return string.format("%s: %.2f", self.owner, self.balance)
end

local acct = Account.new("Ada", 100)
acct:deposit(50):deposit(25)
print(tostring(acct))   -- Ada: 175.00
`function Account:deposit(a)` is sugar for `function Account.deposit(self, a)`, and `acct:deposit(50)` for `Account.deposit(acct, 50)`. Mixing `.` and `:` is the most common Lua mistake — calling a method with `.` passes the first argument as self.
Closures, varargs and multiple returns
-- Functions return multiple values natively.
local function divide(a, b)
  if b == 0 then return nil, "division by zero" end
  return a / b, nil
end

local result, err = divide(10, 0)
if err then print("error: " .. err) end

-- Closures capture upvalues.
local function counter()
  local count = 0
  return function()
    count = count + 1
    return count
  end
end

local next_id = counter()
print(next_id(), next_id())   -- 1  2

-- Varargs
local function log(level, fmt, ...)
  print(string.format("[%s] " .. fmt, level, ...))
end
Returning `value, error` rather than throwing is the conventional Lua error style, and mirrors Go's approach. `pcall` exists for genuine exceptions, but expected failures are returned.
Embedding in C
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

static int l_add(lua_State *L) {
    double a = luaL_checknumber(L, 1);   /* validates and errors cleanly */
    double b = luaL_checknumber(L, 2);
    lua_pushnumber(L, a + b);
    return 1;                            /* number of return values */
}

int main(void) {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    lua_pushcfunction(L, l_add);
    lua_setglobal(L, "add");

    if (luaL_dostring(L, "print(add(2, 3))")) {
        fprintf(stderr, "%s\n", lua_tostring(L, -1));
    }

    lua_close(L);
    return 0;
}
Everything passes through a virtual stack, and the return value of a C function is how many results it left on it. This API is why Lua is embedded so widely — it is small enough to hold in your head.

Common pitfalls

The mistakes that cost everyone an afternoon at least once.

  • Forgetting `local`

    An undeclared variable is global. A typo creates a new global rather than erroring, and a loop variable without `local` leaks and can collide across modules. Run luacheck.

  • Mixing `.` and `:` on methods

    `obj.method(x)` passes x as self, while `obj:method(x)` passes obj as self and x as the first argument. The resulting errors are confusing because nothing looks wrong at the call site.

  • Relying on `#` with holes

    The length operator is only defined for sequences with no nil gaps. With holes it may return any boundary — track the count explicitly instead.

  • Assuming pairs iterates in order

    `pairs` visits keys in unspecified order that can change between runs. Use `ipairs` for sequences, or collect and sort the keys when order matters.

  • String concatenation in a loop

    Strings are immutable and interned, so `s = s .. x` in a loop is quadratic. Collect into a table and call `table.concat` once.

  • Version mismatches

    A rock built for 5.1 will not necessarily work on 5.4, and LuaJIT follows 5.1. Check what your host embeds before choosing libraries.

In production

Where it is running at scale, and what it is doing there.

  • Roblox

    Luau, a Lua dialect, is the scripting language for every experience on the platform.

  • Blizzard

    World of Warcraft's entire addon and user interface system is scripted in Lua.

  • Cloudflare

    OpenResty and LuaJIT power request processing at the edge.

  • Neovim

    Lua is the configuration and plugin language for the editor.

Learning path

A realistic order to learn things in, with something to build at each step.

  1. 1

    Days 1–3

    The whole language

    Lua is small enough to cover in a few days: values and types, tables, functions, closures, control flow, and `local` versus global. Read the reference manual — it is unusually short and readable.

    Build this: Write a script that reads a text file and reports word frequencies using a table.

  2. 2

    Week 1

    Metatables and idiom

    Metatables, `__index`, the colon syntax, multiple returns, varargs, and `pcall` for error handling. Metatables are the mechanism behind objects, operator overloading and most library magic.

    Build this: Implement a small class hierarchy with inheritance using metatables.

  3. 3

    Week 2

    The environment you will actually use

    Lua is nearly always embedded, so learn the host: Neovim's API, Roblox's Luau, Love2D, or the C API if you are embedding it yourself. The host's library is most of what you will call.

    Build this: Write a Neovim plugin or a small Love2D game, depending on where you are heading.

  4. 4

    Weeks 3–4

    Tooling and correctness

    LuaRocks, busted for tests, luacheck for static analysis — luacheck in particular catches accidental globals, which the language will not. Learn coroutines for cooperative concurrency.

    Build this: Add luacheck and a test suite to your project and fix everything it reports.

  5. 5

    Ongoing

    Performance and integration

    LuaJIT and its FFI, garbage collector tuning, profiling, and the details of the C API if you are embedding. Understand which version your host uses — it constrains everything.

    Build this: Port a hot path to LuaJIT's FFI and measure the difference.

Ecosystem and tooling

The tools you will end up installing whichever project you join.

ToolWhat it does
LuaJITTracing JIT compiler with a C FFI; near-C performance, pinned to Lua 5.1 semantics
LuaRocksThe package manager and module repository
luacheckStatic analysis — catches accidental globals, unused variables and shadowing
bustedThe standard BDD-style testing framework
NeovimUses Lua for configuration and plugins, with a large and active plugin ecosystem
Love2DA 2D game framework where Lua is the primary language rather than an embedded one
OpenRestynginx with LuaJIT embedded, for high-performance request processing at the edge
LuauRoblox's gradually-typed, sandboxed Lua dialect, now open source

Lua libraries

Library coverage for Lua is on the way.

The guide above is complete. In the meantime, the catalogues for Python, Java, JavaScript, C and C++ are fully written.

Browse all libraries

Frequently asked

Why does Lua index from 1?

It follows the mathematical and Fortran convention, and the authors considered it more natural for the engineers who were its original audience. It is a genuine source of off-by-one errors when moving between languages, and it will not change — too much code depends on it.

Should I use Lua or LuaJIT?

LuaJIT if you need speed or the C FFI, and your host allows it — for numeric and loop-heavy code the difference is large. Standard Lua if you need 5.2 or later features, since LuaJIT is frozen at 5.1 semantics with some backports. In practice the host application usually decides for you.

Is Lua worth learning if I am not doing games or Neovim?

As a general-purpose language, probably not — Python covers that ground far better. Learn it when your host uses it: a game engine, Redis scripting, nginx via OpenResty, a network appliance, or embedded firmware. Its value is being present where nothing else fits.

How does Lua handle errors?

Two ways. The convention for expected failures is returning `nil, message`, checked by the caller — as in Go. For genuine exceptions, `error()` raises and `pcall`/`xpcall` catch. Mixing the two arbitrarily makes code hard to follow, so pick a convention per module.

What are coroutines actually for?

Cooperative multitasking on a single thread — a coroutine yields control explicitly rather than being preempted. They are excellent for game state machines, generators and iterative algorithms that need to pause. They are not parallelism: only one runs at a time.

How do I do object-oriented programming in Lua?

By hand, with tables and metatables — there is no built-in class system. The common pattern is a table with `__index` pointing at itself, plus `setmetatable` in a constructor. Because every framework does this slightly differently, read the host's convention before inventing your own.