Lua Internals, Version History and Best Practices
52.1 How Lua Works Internally
Lua 5.0 introduced the register-based VM (earlier versions were stack-based), which led to faster execution.
Inspect bytecode with luac -l -l file.lua. The source code (lvm.c, lparser.c, ltable.c, lgc.c) is famously readable.
Source → Lexer/Parser (single pass, no AST) → Bytecode (register-based VM, 32-bit instructions) → VM interpreter loop (lvm.c)
Values: tagged unions (TValue); strings interned (short strings); tables = array part + hash part
Closures: prototypes + upvalues (open upvalues point to the stack; closed when the scope ends)
GC: tri-color incremental mark & sweep; generational mode (5.4); finalizers and weak tables52.2 Version History
Created at Tecgraf, PUC-Rio Fallbacks (precursor of metatables), closures Multiple states, new C API
Metatables, coroutines, lexical scoping (full closures), register VM
Module system (require/package), incremental GC, varargs ..., long strings [==[; the basis of LuaJIT, Luau, Redis, WoW, Neovim
_ENV, goto, bit32, ephemeron tables, yield across pcall/metamethods, table.pack/unpack
Integer subtype (64-bit), bitwise operators, //, utf8 library, string.pack
<const> and <close>, generational GC, warn, new math.random (xoshiro), integer for loop semantics, coroutine.close; 5.4.x releases continue (5.4.7 in 2024)
Expected: global declarations, more compact arrays, and other changes (betas/release candidates appeared in 2025; check lua.org)
52.3 Best Practices
✅ Use local everywhere. Make modules return tables. Never create accidental globals (run luacheck).
✅ Follow conventions: snake_case or camelCase consistently, UPPER_CASE for constants (with <const> in 5.4), and _ for unused variables.
✅ Return nil, err for expected failures, and use error() for programming errors. Wrap boundaries with pcall/xpcall.
✅ Use table.concat for building strings, and #t + 1 for appends.
✅ Document with LuaLS annotations. Test with busted. Format with StyLua.
✅ Know your target version (5.1/LuaJIT vs 5.4) and write compatible code when needed (use compat53 for 5.3 features on 5.1).
✅ Keep metatable-based classes simple. Prefer composition.
❌ Relying on # for tables with holes, or on pairs ordering
❌ Modifying a table (adding keys) while iterating it with pairs
❌ Implicit string/number coercion in logic ❌ a and b or c when b can be false/nil
❌ Deep __index chains in hot code, and excessive debug library use
❌ Loading untrusted code without a sandbox ❌ Forgetting that indices start at 1 (and LuaJIT FFI arrays start at 0)