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 editor — it doesn't seek the spotlight, it lives inside everything.
byproduct of an import ban
~50× smaller than embeddable Python
nil / bool / num / str / fn / userdata / thread / table
Lua's largest active dialect
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.
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.
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.
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.
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.
// 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// 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 都靠这套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.
- 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.
- 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
.afile inside your C application. Config files, scripted hooks, embedded evaluators: these use cases are baked into Lua's DNA from here onward. - 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.
- 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.
- 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.
- 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 loops — extraordinarily rare for a scripting language. From here on, the "Lua 5.1 compat" line is locked.
- 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.
- 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.
- 2011
Lua 5.2 — the fork begins
5.2 reworks
setfenv, addsgoto, 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. - 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".
- 2012·06
Redis 2.6 —
EVALputs Lua inside the world's most-used cacheantirez (Salvatore Sanfilippo) adds
EVALin 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 callredis.call. Why Lua? Tiny to embed, zero deps, deterministic. - 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. - 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. - 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.
- 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. - 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.
- 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. - 2020·07
Neovim 0.5 + the lua-first plugin wave
0.5 ships the full
vim.apiLua surface. telescope.nvim, nvim-treesitter, lazy.nvim — a wave of Lua-only plugins emerges. Within two years, mainstream Neovim plugins are pure Lua. - 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.
- 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.
- 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.
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.
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)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)
endcoroutine — 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 1closure — 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
endOOP — 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) endrequire + 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"Types: just 8
nil / boolean / number / string / function / userdata / thread / table — that'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)) -- functionC 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
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 compilerZero 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 luaEmbedding 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 maintainedWho'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.
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.
| Python | Lua | JavaScript | |
|---|---|---|---|
| Origin | Guido · 1991 | PUC-Rio · 1993 | Brendan Eich · 1995 |
| Designer | Guido van Rossum | R. Ierusalimschy + 2 | Brendan Eich |
| Design goal | Readability · general scripting | Embed in C · small · portable | Browser form glue |
| Core size | Embeddable ~10-30 MB | ~200 KB | V8 ~50+ MB · QuickJS ~1 MB |
| Keyword count | ~35 | 22 | ~60 |
| Data structures | list / dict / tuple / set / ... | table does it all | Array / Object / Map / Set / ... |
| OOP | class keyword | built from metatables · no keyword | class (ES6) + prototype |
| Coroutines | asyncio (retrofitted) · generators | first-class since 1993 | async/await (ES2017) |
| Performance (typed loop) | CPython slow · PyPy mid | LuaJIT on par with V8 | V8 · one of the best JITs |
| Embedding ease | Hard · libpython is huge | 5 lines of C · 200 KB | QuickJS yes · V8 no way |
| Ecosystem size | PyPI ~500k packages | LuaRocks ~5k · vendoring is common | npm ~2.5M packages |
| Typical use | Data · ML · web · scripts | Game scripts · config · embedded | Browsers · Node · full-stack |
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.
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.
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'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.
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).