// 1993 — 2026 · PUC-Rio · Roberto Ierusalimschy · "moon" in Portuguese

Lua:200KB

Born in 1993 at PUC-Rio in Brazil, the design goal was almost provocatively simple: fit the language in 200 KB, use only C89, zero external dependencies. 33 years on, Lua lives inside every game engine, every database, every editorit doesn't seek the spotlight, it lives inside everything.

1993PUC-Rio · Brazil
byproduct of an import ban
200KBcore interpreter size
~50× smaller than embeddable Python
8 typesall value types · just 8
nil / bool / num / str / fn / userdata / thread / table
70M DAURoblox Luau daily
Lua's largest active dialect
local t = {}metatablecoroutine.yieldrequire "foo"__indexlua_State *LLuaJIT 2.15.1 foreversetmetatablefunction() endredis.callnvim init.lua
scroll
01

What is Lua

Lua is the embedded scripting language built by three PUC-Rio researchers in 1993: Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes. The design fits in three words — "small, embeddable, fast." The language itself fits in a 250-page book; its power isn't in the language, it's in how many programs it lives inside.

Language as a library philosophy

Lua is a .a static library you link into your C program to get scripting. Not "Lua calls you"; you embed Lua. This 1993 design philosophy shaped 30 years of where it ended up.

Dynamic · 8 value types types

Dynamically typed, exactly 8 value types: nil / boolean / number / string / function / userdata / thread / table. table does the work of seven — arrays, dicts, objects, namespaces, modules all collapse into it.

GC + coroutines runtime

Incremental GC (generational since 5.4), coroutines since 1993 (yield / resume). Concurrency primitives without OS-thread cost — OpenResty's entire concurrency model sits on these.

Builds with ANSI C89 portable

The whole interpreter is ~30 .c files, strict ANSI C89, no POSIX dependency. Anywhere C runs, Lua runs — the precondition for it landing on PS3s, routers, ESP32s.

embed.pyCPython embed
// C side
// link libpython3 (~30 MB)
// + libffi · + libssl · + libz · ...

Py_Initialize();
PyRun_SimpleString("print('hi')");
Py_Finalize();

// runtime: ~10-30 MB resident
// startup: hundreds of ms
// 不适合塞进游戏 frame · 路由器 · MCU
embed.cLua embed
// C side · 5 行就完事
// link liblua (~200 KB)
// 0 外部依赖

lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dostring(L, "print('hi')");
lua_close(L);

// runtime: ~200 KB resident
// startup: microseconds
// WoW client · Redis · Nginx · ESP32 都靠这套
02

History : Timeline

33 years, from a small Brazilian university lab to 70 million Roblox dailies — Lua has no "star moment"; it's a textbook case of quiet, steady progress. One step a year, then one day you notice it's already inside everything you use daily.

  1. 1993·07

    Born at PUC-Rio

    At the Pontifical Catholic University of Rio de Janeiro's Tecgraf lab, Roberto Ierusalimschy, Luiz Henrique de Figueiredo and Waldemar Celes ship Lua 1.0. Context: Brazil's 80s ban on imported commercial software forced universities to write their own tooling — Lua is a byproduct of that restriction. "Lua" is Portuguese for moon.

  2. 1995

    Lua 2.0 — design philosophy locks in

    The "language as a library" principle crystallises: Lua doesn't try to own your program — it's a .a file inside your C application. Config files, scripted hooks, embedded evaluators: these use cases are baked into Lua's DNA from here onward.

  3. 1996·12

    Dr. Dobb's article — Lua leaves Brazil

    The team publishes Lua in Dr. Dobb's Journal, the English-speaking world's first proper look at it. Game developers read it almost immediately — the seed moment for LucasArts, Bioware and others embedding Lua into their engines.

  4. 2003

    Lua 5.0 — the start of modern Lua

    This release brings lexically-scoped closures, coroutines, and a truly functional metatable model. After 5.0 the language is "shaped" — the next 20 years touch the corners, not the core. Many older Lua books are still describing 5.0.

  5. 2004·11

    World of Warcraft ships — Lua reaches tens of millions

    Blizzard embeds Lua inside the WoW client; the entire UI + addon system runs on it. WoW addons turn Lua overnight into one of the world's most-indirectly-used scripting languages: tens of millions of players, tens of thousands of addon authors.

  6. 2005·05

    LuaJIT lands — a one-man project

    Austrian programmer Mike Pall releases LuaJIT 1.0: a trace-based JIT for Lua, jumping performance by orders of magnitude. LuaJIT 2 later matches or beats V8 / JVM on numeric loopsextraordinarily rare for a scripting language. From here on, the "Lua 5.1 compat" line is locked.

  7. 2006

    Lua 5.1 — the long-term anchor

    5.1 looks like a +0.1 release; it became the most influential version in Lua's history. LuaJIT is permanently 5.1-compatible; OpenResty, Redis, game engines, and Roblox's Luau all pin themselves to the 5.1 ABI. 5.1 isn't "the old version" — it's the de-facto standard.

  8. 2008

    Adobe Lightroom — desktop apps in Lua

    Adobe reveals that Lightroom's UI layer is largely written in Lua (the Lightroom SDK is Lua too). This is the strongest early evidence that "Lua isn't just for game scripts" — full professional desktop software ships on it.

  9. 2011

    Lua 5.2 — the fork begins

    5.2 reworks setfenv, adds goto, changes __pairs. The community splits: LuaJIT stays on 5.1, mainline moves to 5.2. From this point on, "Lua 5.x" is no longer a single label — which line you pick determines your library ecosystem.

  10. 2011·11

    OpenResty 1.0 — agentzh turns Nginx into a Lua platform

    Yichun Zhang (agentzh) releases OpenResty: Nginx plus LuaJIT, running Lua coroutines inside worker processes. Cloudflare ran this stack at the edge for years; many large Chinese sites' early gateways did too. Lua's first move into "internet infrastructure".

  11. 2012·06

    Redis 2.6 — EVAL puts Lua inside the world's most-used cache

    antirez (Salvatore Sanfilippo) adds EVAL in Redis 2.6, making Lua the embedded scripting language of Redis. This is the open door for atomic compound operations — hundreds of millions of web back ends quietly call redis.call. Why Lua? Tiny to embed, zero deps, deterministic.

  12. 2015

    Lua 5.3 — integers return

    5.3 brings native integers back (previously Lua had only doubles). Floor-division //, bitwise & | ~ <<, string packing. Important — but LuaJIT doesn't follow, deepening the 5.1-vs-5.3 split.

  13. 2015

    Mike Pall steps back

    LuaJIT's author Mike Pall publicly announces stepping away from leading development, looking for a successor. The community is shaken: a project competitive with V8 in performance was written, essentially end-to-end, by one person. Forks pick up the slack (OpenResty's luajit2, moonjit, etc.); mainline LuaJIT is still maintained by the community today.

  14. 2017

    Defold / Love2D consolidate the indie-game position

    King open-sources its internal engine Defold (Lua-scripted); Love2D stays a fixture of indie game dev. Together with Garry's Mod and Factorio mods, "Lua = de-facto game-scripting language" is nailed down.

  15. 2019·02

    Roblox open-sources Luau — typed Lua fork

    Roblox renames its internal Lua fork to Luau and open-sources it: gradual typing, sandboxing, removal of dangerous features like loadstring. Roblox has ~70M daily players and millions of developers — Luau is the most-active Lua dialect on Earth by user count.

  16. 2019·11

    Neovim 0.4 — Lua becomes first-class

    Neovim (the 2014 Vim fork) ships embedded Lua 5.1 in 0.4. Even Bram Moolenaar admitted "VimL was a bad design"; the Neovim team uses LuaJIT to turn the page. From here on nvim configs and plugins prefer Lua, and the VimL → Lua migration takes off.

  17. 2020·06

    Lua 5.4 — to-be-closed variables

    5.4 adds <close> attributes (RAII-style auto-close at scope end) and switches to a generational GC. The first time Lua has language-level resource management semantics. But LuaJIT still stays on 5.1 — the split continues.

  18. 2020·07

    Neovim 0.5 + the lua-first plugin wave

    0.5 ships the full vim.api Lua surface. telescope.nvim, nvim-treesitter, lazy.nvim — a wave of Lua-only plugins emerges. Within two years, mainstream Neovim plugins are pure Lua.

  19. 2022·04

    Redis 7.0 — Functions land, EVAL deprecation chatter

    Redis adds Functions (persistent Lua functions); there's brief discussion of "when does EVAL get deprecated." Community pushback is strong — Lua is already embedded in countless production codebases. Redis 7.4 ends up restoring Lua to first-class status, side by side with Functions.

  20. 2023·05

    Lua 5.4.6 + LuaJIT 2.1 final RCs

    Mainline Lua enters its "small polishing" steady state; LuaJIT 2.1 finally cuts an official RC with performance fine-tuning. In 2023, Lua is in a "so mature even the corners are tidied" shape — the happiest possible state for an embedded language.

  21. 2026

    33 years in — running in tens of millions of embedded slots

    Lua in 2026: mainline 5.4 stable; LuaJIT 2.1 maintained by community + OpenResty's fork; Luau is the most active dialect (Roblox's tens of millions of dailies); Neovim is Lua's new public face; Redis / OpenResty / WoW / Factorio / Lightroom keep humming. Lua doesn't seek the spotlight — it lives inside everything else.

03

Language Essentials : LuaAlphabet

Eight cards covering where Lua differs hardest: table as everything, metatables as the root, coroutines, closures, plain-text OOP, require, 8 value types, the C ABI. The ninth covers what makes Lua the anti-Python: it doesn't try to replace Python, it picked the opposite track.

A

table — the only data structure

Lua has exactly one composite type: table. It is simultaneously array, dict, object, namespace, and module. Pouring all design effort into one data structure pays off — it's polished hard.

local t = {
    -- array part
    "hello", "world",
    -- hash part
    name = "lua",
    year = 1993,
}

print(t[1], t.name)
B

metatable — the root of every abstraction

OOP, operator overloading, read/write proxies, inheritance — all done by metatables. __index, __newindex, __add and friends are Lua's underlying magic.

local Vec = {}
Vec.__index = Vec
Vec.__add = function(a,b)
    return setmetatable({a.x+b.x}, Vec)
end
C

coroutine — first-class since day one

A scripting language with coroutines since 1993. coroutine.create / yield / resume — three primitives, no OS-thread overhead. OpenResty's concurrency model is built on these.

local co = coroutine.create(function()
    for i=1,3 do
        coroutine.yield(i)
    end
end)

print(coroutine.resume(co))  -- true 1
D

closure — lexical capture via upvalues

Since 5.0 Lua has been truly lexically scoped; closures capture upvalues. Functions are first-class values — store in tables, return, pass around. Most functional-style tooling is here.

local function counter()
    local n = 0
    return function()
        n = n + 1
        return n
    end
end
E

OOP — trivially elegant

Lua has no class keyword. OOP = table + metatable + the : sugar (implicit self). 20 lines is enough to show a beginner how OOP "grows out" of the primitives. Every engine picks its own idiom; no standard, none needed.

local Dog = {}
Dog.__index = Dog

function Dog.new(name)
    return setmetatable({name=name}, Dog)
end

function Dog:bark() print(self.name) end
F

require + package.path

The module system is minimal: require "foo" searches package.path for ?.lua, loads, caches, returns. None of npm's complexity. LuaRocks is the community's de-facto package manager but never mandatory — many projects still vendor entire files.

-- foo.lua
local M = {}
function M.hello() return "hi" end
return M

-- main.lua
local foo = require "foo"
G

Types: just 8

nil / boolean / number / string / function / userdata / thread / tablethat's all. The entire language fits on two pages. Luau layers gradual types on top, but core Lua stays pure.

print(type(nil))       -- nil
print(type(true))     -- boolean
print(type(1))         -- number
print(type({}))        -- table
print(type(print))     -- function
H

C ABI — embedding is trivial

Lua is designed to be embedded inside C programs. lua_State*, lua_push* / lua_to* stack ops — the API is tiny and stable. This is the core reason WoW / Redis / Nginx / Lightroom all chose Lua.

// C side
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dostring(L, "print('hi')");
lua_close(L);

The anti-Python philosophy — refusing to grow

Python has bloated over 30 years to millions of lines of stdlib, dozens of keywords, hundreds of PEPs. Lua over 30 years? Still 22 keywords, still ~200 KB of core stdlib. Roberto has repeatedly refused to "modernise" the language — no classes, no types, no async/await. A BDFL who deliberately chose not to grow, because guarding the embedded niche is the identity.

"I'd rather not add it than make Lua look like not-Lua." — Roberto Ierusalimschy, paraphrased from talks

04

Why Lua : WhyLua

The decision axes for picking Lua are different from those for Python or Node. Python is picked for ecosystem; Lua is picked for "can I fit it inside my C program?" There's no second option — Lua has been the de-facto monopolist on the embedded slot for 30 years.

200 KB — the entire interpreter fits in a small package

Compiled Lua 5.4 is roughly 200 KB of core (embeddable Python 3 builds run ~10-30 MB, Node ~50+ MB). Not a "lightweight" rhetorical flourish — small enough to literally fit inside hardware config panels. This is the precondition for it to sit inside the WoW client, embedded devices, Redis workers.

-- lua-5.4.6 source
-- ~30 .c files
-- 0 external deps
-- builds on ANY C89 compiler

Zero deps — builds on any C compiler

Lua uses strict ANSI C89, with no POSIX requirement and no fancy libc dependencies. From mainframe to embedded RTOS, anywhere C runs, Lua runs. This is the hard precondition behind "language as a library".

// embed in 3 lines
// no autoconf · no cmake
// gcc *.c -o lua

Embedding philosophy — you embed Lua, not the other way around

Python and Node's design is "we own the runtime, you write scripts." Lua flips that — your C program is the host, Lua plugs in for extension power. This philosophy is why, for 30 years, it has lived inside every game engine, database, and editor.

// 你的 C app
//   ├── 业务逻辑 (C)
//   └── lua_State (脚本钩子)

LuaJIT — one person vs V8

Mike Pall's LuaJIT beats V8 / JVM on numeric loops — vanishingly rare for a scripting language. Trace-based JIT, minimal IR, obsessive low-level work. One Austrian developer, a few years, outdoing the JS industrial complex.

-- LuaJIT 2.1 (5.1 compat)
-- trace JIT · loop hot-path
-- C FFI for zero-cost interop

"5.1 lives forever" — compatibility's blessing and curse

LuaJIT freezes 5.1, so the embedded / gateway / game world freezes 5.1 too. Ten-year-old scripts still run — the blessing. But 5.3 integers and 5.4 RAII never reach this line — the curse. The Lua community accepts the two-track reality; nobody expects reunification anymore.

-- 5.1 line · OpenResty · WoW · Redis
-- 5.4 line · neovim host · mainline embed
-- both alive · both maintained
05

Who's Using : EmbeddedHallOfFame

Lua's roster is genuinely impressive: it really does live inside everything you use daily. From Roblox to WoW, Redis to Nginx, Neovim to Lightroom — the embedded-language hall of fame. Every entry is a real user, multi-year, no marketing fluff.

06

The Embedded Philosophy : 200KB FitsAWholeLang

This section is Lua's soul. Thirty years ago, three PUC-Rio researchers made one austere decision — don't try to replace any language; just be "the scripting layer inside someone's host program". That decision let Lua own an entire niche: the embedded slot. 30 years, no one's dislodged it.

"

From the very start, we didn't want to build a "standalone" language. Lua's home is somebody else's program — a game engine, a database, a config panel. The language has to stay small, fast, and embeddable with one line of C. We've been doing this for 30 years, and we haven't reversed that decision.

— Roberto IerusalimschyLua's lead designer · PUC-Rio · paraphrased from many interviews

Interpreter size: 200 KB vs the rest

// embedded build · core interpreter + minimal stdlib

Lua 5.4
~200 KB
LuaJIT 2.1
~500 KB
MicroPython
~600 KB
Duktape (JS)
~700 KB
QuickJS
~1.2 MB
Wren
~350 KB
CPython (embed)
~12-30 MB
Node.js (V8)
~50+ MB
200 KB
Core interpreter size

Compiled Lua 5.4's core is roughly 200 KB — small enough for 1980s-class hardware. Not a "lightweight" slogan, an architecture decision: interpreter + lexer + parser + GC + stdlib together are ~30 .c files. You can read the entire source in under a week.

22
Reserved keywords total

The whole of Lua fits in 22 keywords: and / break / do / else / elseif / end / false / for / function / goto / if / in / local / nil / not / or / repeat / return / then / true / until / while. Python has 30+, JS 60+, Rust 50+. Lua hasn't grown in 30 years.

0 deps
External dependencies

Building Lua requires no autoconf, no cmake, no libffi, nothing. gcc *.c -o lua. Anywhere C89 runs, Lua runs: mainframes, embedded RTOSes, PS3s, ESP32s, router firmware. The hard precondition for living inside everything.

LuaJIT — the scripting-language outlier// Mike Pall · solo project

In 2005, Austrian developer Mike Pall single-handedly built LuaJIT: a trace-based JIT, a minimal IR, hand-tuned to the metal. The most extraordinary part of the Lua story — an industrial-grade JIT, end-to-end written by one person, that beats Google's hundred-engineer V8 on numeric loops.

LuaJIT is pinned to Lua 5.1 compatibility — both its blessing and curse. Blessing: the entire embedded / gateway / game world is pinned with it, giving 20-year ABI stability. Curse: 5.3's integers and 5.4's <close> never reach this line. OpenResty's luajit2 fork patches on top with enterprise fixes — one of the de-facto active variants in 2026.

In 2015, Mike Pall publicly stepped away from leading development to search for a successor. Ten years on, no real "next Mike Pall" has appeared — LuaJIT's biggest long-term uncertainty. But the community maintains it; an official 2.1 RC only landed in 2023. The fate of a one-person project always hangs in the air.

SPOTLIGHT

Redis EVAL — Lua inside the world's largest cache — 2012

In 2012 antirez added EVAL to Redis — making Lua its embedded scripting language. That single move quietly carried Lua into hundreds of millions of web back ends through Redis. Every redis.call is a Lua VM running.

  • atomicityone EVAL is one atomic Redis op
  • 200 KB embedRedis binary barely grew
  • deterministicsame input → same output, replication-safe
  • Functions (7.0)persistent EVAL · still Lua underneath

Redis 7.0 in 2022 briefly floated "should EVAL be deprecated?"; community pushback was fierce. Redis 7.4 restored Lua to first-class status alongside Functions. Once an embedded language is entrenched, it's almost impossible to unseat.

-- Redis EVAL · atomic compare-and-set
local cur = redis.call("GET", KEYS[1])
if cur == ARGV[1] then
    redis.call("SET", KEYS[1], ARGV[2])
    return 1
else
    return 0
end

# client side
# EVAL "..." 1 mykey oldval newval
# → 1 (swapped) or 0 (skipped)

-- 一个 EVAL = 一次原子操作
-- 几亿 web 后端默默调过

2026 ecosystem / tools / dialects

LuaRocks
Community package manager
LuaJIT 2.1
5.1 compat · trace JIT
OpenResty
Nginx + Lua · gateway
Luau
Roblox · gradual typing
Fennel
Lisp-flavoured dialect → Lua
Teal
Statically-typed Lua
Moonscript
CoffeeScript-style → Lua
lua-language-server
LSP impl (sumneko)
Penlight
General utility library
busted
Testing framework
lapis
Web framework (OpenResty)
TIC-80 / PICO-8
Fantasy consoles · Lua
NEOVIM

VimL → Lua — Lua's editor moment

Neovim forked from Vim in 2014; 2019's release added embedded Lua 5.1. That single move reshaped Lua's public identity: for 30 years it was "game-engine scripting"; from 2020 it's "the init.lua every developer touches daily".

The 2020-2026 plugin wave: telescope.nvim (fuzzy finder), nvim-treesitter (syntax trees), lazy.nvim (plugin manager), nvim-lspconfig (LSP) — all pure Lua. VimScript is legacy; not even Vim 9.0's Vim9script can pull it back.

An interesting wrinkle: Neovim embeds LuaJIT (5.1-compat), not mainline 5.4. The "5.1 lives forever" pattern is reinforced again: mainline versions never make it into the LuaJIT ecosystem.

-- ~/.config/nvim/init.lua
-- 一个文件取代了几百行 .vimrc

local vim = vim
vim.opt.number = true
vim.opt.tabstop = 4

-- LSP: 一行启动 rust-analyzer
require("lspconfig").rust_analyzer.setup{}

-- 快捷键: Lua 表 + 函数
vim.keymap.set("n", "<leader>ff",
    require("telescope.builtin").find_files)

-- 整个配置可以热重载
-- 这是 VimL 30 年做不到的

In one line: Lua isn't "the next Python" or "a faster JavaScript" — it's the de-facto monopolist of "scripting that lives inside your C program." Untouched for 30 years; still growing at 33.

07

vs Python / JS : Lua vs Python vs JS

Versus Python: Lua is Python's opposite — deliberately small, deliberately niche. Versus JS: both are 90s dynamic scripts, but JS bet on browsers while Lua bet on embedding. Versus C: Lua's home is C — a symbiosis, not a replacement.

PythonLuaJavaScript
OriginGuido · 1991PUC-Rio · 1993Brendan Eich · 1995
DesignerGuido van RossumR. Ierusalimschy + 2Brendan Eich
Design goalReadability · general scriptingEmbed in C · small · portableBrowser form glue
Core sizeEmbeddable ~10-30 MB~200 KBV8 ~50+ MB · QuickJS ~1 MB
Keyword count~3522~60
Data structureslist / dict / tuple / set / ...table does it allArray / Object / Map / Set / ...
OOPclass keywordbuilt from metatables · no keywordclass (ES6) + prototype
Coroutinesasyncio (retrofitted) · generatorsfirst-class since 1993async/await (ES2017)
Performance (typed loop)CPython slow · PyPy midLuaJIT on par with V8V8 · one of the best JITs
Embedding easeHard · libpython is huge5 lines of C · 200 KBQuickJS yes · V8 no way
Ecosystem sizePyPI ~500k packagesLuaRocks ~5k · vendoring is commonnpm ~2.5M packages
Typical useData · ML · web · scriptsGame scripts · config · embeddedBrowsers · Node · full-stack
08

Outlook : TheRoadAhead

Lua's 2026 question isn't "how to evolve"; it's "who could possibly unseat it from the embedded slot". No real threat in 30 years. Mainline 5.4 stable, LuaJIT's succession unresolved, Luau owning the typed-Lua niche, Neovim pushing it onto a new generation of developer desktops. This is a language that already won — now it's holding the ground it won.

HOT · 2026+

Luau's rise — is typed Lua the future?

Roblox's Luau adds gradual typing, sandboxing, and removes dangerous primitives (loadstring, setfenv). It doesn't fork mainline — it gives "Lua users who need types" a complete answer. ~70M daily players, millions of developers writing it.

The significance: mainline Lua has long refused to add typing (Roberto has said so explicitly). Luau lets the "I want types" crowd live in its own branch while mainline stays pure. This is the mature form of BDFL language design: don't be all things to all people; let dialects satisfy subsets.

Mainline Lua adopting types (refused)~0%
Luau owns the typed-Lua niche100%
NVIM

Neovim — Lua's "new identity"

For 30 years Lua's public face was "game-engine scripting." Post-2020, Neovim moves it onto the developer's desktop~/.config/nvim/init.lua is now a file millions of devs touch daily. This is an identity extension for Lua: from game world to editor world.

LUAJIT

LuaJIT's succession problem

Since Mike Pall stepped back in 2015, no "next Mike Pall" has emerged. Maintenance is stable but large-scale evolution has stalled. OpenResty's luajit2 fork patches some gaps, moonjit tried bolder changes; mainline still rules as the standard. Whether it keeps up with new targets (GPU, WASM) is an open question.

EMBED

Embedded niche keeps growing — IoT / edge

Lua's 200 KB + zero deps profile is more valuable in 2026 than it was in 1996: ESP32 / RP2040-class chips run Lua directly; routers and industrial controllers use it as their config layer. "Language as a library" is a market that never goes out of style — and Lua has no real rival there (MicroPython is several times bigger).