Skip to content
Lua Programming Language Guide — Scripting and Embedded Development

Lua Programming Language Guide — Scripting and Embedded Development

DodaTech Updated Jun 7, 2026 7 min read

Lua is a lightweight, embeddable scripting language designed for extensibility — used everywhere from video games to Redis scripts to configuration files, with a simple syntax that fits in a small C library.

What You’ll Learn

  • Lua syntax fundamentals (no semicolons, 1-indexed arrays)
  • Tables as the universal data structure
  • Metatables and metamethods for operator overloading
  • Coroutines for cooperative multitasking
  • LuaJIT and performance optimization

Why It Matters

Lua is the most embedded language in the world — it powers World of Warcraft addons, Roblox game scripting, Redis custom commands, Nginx configuration (OpenResty), and Adobe Lightroom plugins. Durga Antivirus Pro uses Lua for its rule-based threat detection engine because Lua’s sandboxing makes it safe to run untrusted scripts. Understanding Lua gives you a scripting superpower that fits in 200KB and runs anywhere — from game consoles to IoT devices.

Learning Path

    flowchart LR
  A[Lua Basics<br/>You are here] --> B[Tables & Functions]
  B --> C[Metatables & OOP]
  C --> D[Coroutines & I/O]
  D --> E[LuaJIT & Real-World Apps]
  

Your First Lua Script

Lua doesn’t require semicolons. Comments use -- for single-line and --[[ ]] for multi-line.

-- hello.lua
print("Hello, Lua!")

-- Variables are global by default
local name = "World"   -- local keyword scopes the variable
print("Hello, " .. name)
lua hello.lua
# Hello, Lua!
# Hello, World!

Tables — Lua’s Only Data Structure

Everything in Lua is a table — arrays, dictionaries, objects, modules, and sets.

-- Array-style table (1-indexed!)
local fruits = {"apple", "banana", "cherry"}
print(fruits[1])   -- apple (not fruits[0]!)
print(#fruits)     -- 3 (length operator)

-- Dictionary-style table
local person = {
  name = "Alice",
  age = 30,
  job = "Engineer"
}
print(person.name)   -- Alice
print(person["age"]) -- 30

-- Mixed table
local mixed = {
  "first",
  key = "value",
  [10] = "ten"
}
print(mixed[1])    -- first
print(mixed.key)   -- value
apple
3
Alice
30
first
value

Arrays are 1-indexed. The # operator returns the length of the array portion (contiguous integer keys starting from 1).

Functions as First-Class Values

Functions can be stored in variables, passed as arguments, and returned from other functions.

-- Store function in a variable
local greet = function(name)
  return "Hello, " .. name
end

print(greet("Lua"))  -- Hello, Lua

-- Higher-order function
local function apply(func, value)
  return func(value)
end

local result = apply(function(x) return x * x end, 5)
print(result)  -- 25

-- Lua's sugary syntax for OOP
local obj = {}
function obj:say(text)
  print(self.name .. " says: " .. text)
end

obj.name = "Bot"
obj:say("Hello")  -- Bot says: Hello
Hello, Lua
25
Bot says: Hello

Metatables

Metatables let you change the behavior of a table — similar to operator overloading in C++ or Python’s magic methods.

-- Vector addition using metatables
local Vector = {}
Vector.__index = Vector

function Vector:new(x, y)
  return setmetatable({x = x, y = y}, self)
end

function Vector.__add(a, b)
  return Vector:new(a.x + b.x, a.y + b.y)
end

function Vector.__tostring(v)
  return "(" .. v.x .. ", " .. v.y .. ")"
end

local v1 = Vector:new(3, 4)
local v2 = Vector:new(1, 2)
local v3 = v1 + v2

print(v3)  -- (4, 6)
(4, 6)

Common metamethods: __add, __sub, __mul, __index (for inheritance), __newindex, __tostring, __gc (garbage collection), __call.

Coroutines

Coroutines allow cooperative multitasking — functions that can pause and resume.

local function counter()
  local i = 0
  while true do
    i = i + 1
    coroutine.yield(i)  -- pause and return i
  end
end

local co = coroutine.create(counter)

print(coroutine.resume(co))  -- true    1
print(coroutine.resume(co))  -- true    2
print(coroutine.resume(co))  -- true    3
print(coroutine.status(co))  -- suspended

-- Closing the coroutine
coroutine.close(co)
true    1
true    2
true    3
suspended

Coroutines are not preemptive — only one coroutine runs at a time. They yield control explicitly.

File I/O

-- Write to a file
local file = io.open("data.txt", "w")
file:write("Line 1\n")
file:write("Line 2\n")
file:close()

-- Read from a file
file = io.open("data.txt", "r")
for line in file:lines() do
  print(line)
end
file:close()
Line 1
Line 2

LuaJIT

LuaJIT is a Just-In-Time compiler for Lua that achieves C-like performance for numeric code.

-- LuaJIT automatically JIT-compiles hot paths
local function sum(n)
  local s = 0
  for i = 1, n do
    s = s + i
  end
  return s
end

local start = os.clock()
local result = sum(10000000)
local elapsed = os.clock() - start

print("Sum: " .. result)
print("Time: " .. elapsed .. " seconds")
Sum: 50000005000000
Time: 0.035 seconds

LuaJIT can match C speed for tight loops. Use jit.dump to see which traces the JIT compiler produces.

Common Mistakes

1. Using index 0 instead of 1

Lua arrays start at 1. fruits[0] returns nil, not an error. Always start loops at 1.

2. Forgetting local for variables

Without local, variables are global. Large scripts leak globals and cause subtle bugs. Declare local inside functions.

3. Confusing = with == in conditions

if x = 5 then  -- ERROR: assignment in condition
if x == 5 then -- correct

4. Modifying a table while iterating

for k, v in pairs(t) do
  t[k] = nil  -- dangerous during iteration
end

Collect keys in a separate list first, then remove.

5. Assuming tables copy by value

Tables are passed by reference. local t2 = t1 creates an alias, not a copy. Use a loop or table.clone in Lua 5.4+.

6. Forgetting return in module files

Lua modules must return the module table. Without it, require returns nil.

Practice Questions

  1. Why are Lua arrays 1-indexed instead of 0-indexed? Lua was designed for non-programmers (domain experts, artists) who naturally count from 1. The language designers prioritized approachability.

  2. What is a metatable and when would you use one? A metatable controls table behavior through metamethods. Use it for operator overloading (vectors, matrices), inheritance, and custom index fallback.

  3. How do coroutines differ from threads? Coroutines are cooperative — they yield explicitly. Threads are preemptive — the OS scheduler can interrupt them at any time. Coroutines share the same thread with no race conditions.

  4. What is the difference between pairs and ipairs? ipairs iterates over integer keys from 1 to the first nil. pairs iterates over all keys (including non-integer). Use ipairs for arrays, pairs for dictionaries.

  5. What makes LuaJIT faster than standard Lua? LuaJIT uses a tracing JIT compiler that identifies hot loops and compiles them to native machine code, achieving near-C performance for numeric workloads.

Challenge: Write a Lua script that reads a CSV file, parses it into a table of records, and prints a summary (total rows, column names, and the average of the first numeric column).

Mini Project — Config File Parser

Build a Lua script that parses INI-style configuration files:

-- config_parser.lua
local function parse_ini(filename)
  local config = {}
  local current_section = nil

  for line in io.lines(filename) do
    -- Remove whitespace
    line = line:match("^%s*(.-)%s*$")

    -- Skip empty lines and comments
    if line ~= "" and not line:match("^[;#]") then
      -- Section header
      local section = line:match("^%[(.-)%]$")
      if section then
        current_section = section
        config[current_section] = {}
      else
        -- Key=value pair
        local key, value = line:match("^(.-)%s*=%s*(.-)$")
        if key and current_section then
          -- Try to convert numeric values
          local num = tonumber(value)
          config[current_section][key] = num or value
        end
      end
    end
  end

  return config
end

-- Example INI file
-- [[
-- [database]
-- host = localhost
-- port = 5432
-- name = myapp
--
-- [logging]
-- level = debug
-- file = /var/log/app.log
-- ]]

local config = parse_ini("config.ini")
print(config.database.host)  -- localhost
print(config.database.port)  -- 5432 (number)
print(config.logging.level)  -- debug
localhost
5432
debug

FAQ

Is Lua the same as Luau?
Luau is a derivative of Lua 5.1 developed by Roblox with added type syntax, performance improvements, and sandboxing. Lua is the original language maintained by PUC-Rio.
What games use Lua?
World of Warcraft, Roblox, Garry’s Mod, Civilization V, Balatro, Factorio, and many more use Lua for modding and scripting. It’s the most common game scripting language.
Can Lua replace Python?
Lua is faster to embed and lighter (200KB vs 50MB+), but Python has a vastly larger ecosystem. Lua excels as an embedded language; Python excels as a general-purpose language.
Is Lua used in Redis?
Yes. Redis supports Lua scripting for atomic operations. You send a Lua script with EVAL, and Redis executes it atomically on the server — used for complex transactions.
What is the difference between Lua and LuaJIT?
LuaJIT is a JIT-compiled implementation of Lua 5.1 (with some 5.2 features). It’s much faster for numeric code and has its own FFI library for direct C calls. Standard Lua 5.4 is the reference implementation.
How do I call C from Lua?
Use the Lua C API (lua_State*, lua_pushnumber, lua_pcall) to expose C functions to Lua. LuaJIT has an FFI library that makes this much easier without writing C wrapper code.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro