Core Syntax at a Glance
Lua has eight basic types -- nil, boolean, number, string, function, table, userdata, and thread -- and only two falsy values in conditionals, nil and false. Blocks are delimited with keywords rather than braces or indentation: if condition then ... end, for i = 1, 10 do ... end (numeric for, inclusive on both ends, optional step as a third value), for k, v in pairs(t) do ... end (generic for), and while condition do ... end, with function name(args) ... end for defining functions, including local functions via local function name(args) ... end.
Cricket analogy: Lua's eight basic types are like the fixed set of dismissal categories in cricket's scorebook -- bowled, caught, lbw, run out, and so on -- a small, complete, well-understood vocabulary that covers every situation without needing to invent new categories mid-match.
-- variables
local x = 10
local name = "Ada"
local isReady = true
-- numeric for: start, stop, step(optional)
for i = 1, 10, 2 do print(i) end --> 1, 3, 5, 7, 9
-- generic for over a table
local t = {"a", "b", "c"}
for index, value in ipairs(t) do print(index, value) end
-- while / repeat-until
local n = 0
while n < 3 do n = n + 1 end
repeat n = n - 1 until n == 0
-- functions (local by convention in modules)
local function add(a, b)
return a + b
end
-- multiple return values
local function minMax(t)
local lo, hi = t[1], t[1]
for _, v in ipairs(t) do
if v < lo then lo = v end
if v > hi then hi = v end
end
return lo, hi
end
local low, high = minMax({4, 1, 9, 2})Tables and String Library Essentials
The table library covers the operations you need constantly: table.insert(t, value) appends (or table.insert(t, pos, value) inserts at a position), table.remove(t, pos) removes and returns the element at pos (defaulting to the last), table.concat(t, sep) joins array elements into one string, and table.sort(t, comparator) sorts in place with an optional custom comparator function. The string library is similarly essential: string.format builds formatted strings printf-style (%d, %s, %.2f), string.sub(s, i, j) extracts a substring (also 1-indexed, and negative indices count from the end), string.find/string.match/string.gmatch search using Lua patterns (a lighter-weight cousin of regex), and string.gsub performs pattern-based substitution.
Cricket analogy: table.insert and table.remove are like a captain adjusting the batting order mid-innings -- bringing a pinch-hitter in at a specific position or sending an underperforming batsman back to the pavilion -- precise, positional edits rather than rebuilding the whole lineup from scratch.
-- table library
local fruits = {"banana", "apple"}
table.insert(fruits, "cherry") --> {"banana", "apple", "cherry"}
table.insert(fruits, 1, "avocado") --> {"avocado", "banana", "apple", "cherry"}
table.remove(fruits, 2) --> removes "banana"
table.sort(fruits) --> alphabetical in place
print(table.concat(fruits, ", ")) --> "apple, avocado, cherry"
-- string library
local msg = string.format("%s scored %.1f%%", "Ada", 92.5)
print(msg) --> "Ada scored 92.5%"
print(("hello world"):sub(1, 5)) --> "hello" (method-call syntax)
print(string.gsub("2026-07-10", "-", "/")) --> "2026/07/10", 2Common Standard Library Functions
Beyond table and string, three other standard libraries come up constantly: math provides math.floor, math.ceil, math.max, math.min, math.random (seeded via math.randomseed), and math.huge for infinity; os provides os.time(), os.date(format) for formatted timestamps, and os.clock() for measuring CPU time elapsed; and io provides io.open(path, mode) for file access (returning nil, errorMessage on failure, per Lua's error convention) along with the convenience globals io.read and io.write for stdin/stdout.
Cricket analogy: The math, os, and io libraries each covering one concern is like a team's specialist support staff -- a fitness trainer, a video analyst, and a physio -- each an expert in one domain rather than one generalist trying to cover every need.
Every one of these libraries is itself just a global table (math, string, table, os, io), so math.floor is really 'the floor field of the math table' -- the exact same mechanism your own modules use when they return M from a require'd file.
Quick Gotchas Reference
Five things trip up newcomers repeatedly and are worth memorizing outright: arrays are 1-indexed (t[1] is the first element); only nil and false are falsy (0 and "" are truthy); the # operator is only reliable on sequences with no nil holes; a function can return multiple values, but only the last call in an expression list keeps all of them (earlier ones are truncated to their first return value); and ... (varargs) inside a function collects extra arguments, accessible via select('#', ...) for the count or {...} to pack them into a table.
Cricket analogy: The rule that only the last function call in an expression list expands fully is like a scorecard convention where only the final ball of an over gets its full replay and commentary logged, while earlier balls in that same over are noted with just the headline outcome.
Concatenating inside a table constructor truncates multi-return calls to a single value: {f(), g()} keeps only f()'s first return value plus all of g()'s return values, because only the last expression in a table constructor or argument list expands fully -- an extremely common source of 'why did I lose a value' bugs.
- Lua has eight basic types; only nil and false are falsy, everything else (including 0 and "") is truthy.
- Numeric for loops are inclusive on both ends and take an optional step: for i = 1, 10, 2 do ... end.
- table.insert/remove/concat/sort cover the array operations you need most; string.format/sub/find/gsub cover string handling.
- math, os, and io are just standard-library tables, the same mechanism your own require()d modules use.
- The # operator is only reliable on gap-free sequences; it's undefined on tables with nil holes.
- Only the last call in an expression/table-constructor list expands all its return values; earlier calls keep just the first.
- Varargs (...) collect extra arguments; use select('#', ...) for the count or {...} to pack them into a table.
Practice what you learned
1. What is the range behavior of Lua's numeric for loop, for i = 1, 10 do ... end?
2. Which table library function removes and returns an element, defaulting to the last one if no position is given?
3. What does io.open return when it fails to open a file?
4. In local a, b = f(), g(), why does 'a' only get f()'s first return value even if f() returns multiple values?
5. How do you access the count of extra arguments passed via varargs (...) inside a function?
Was this page helpful?
You May Also Like
Lua vs Python
A practical comparison of Lua and Python covering syntax, performance, embedding, typing, and when to choose each language.
Lua Best Practices
Practical conventions and patterns for writing clean, efficient, and maintainable Lua code.
Lua Interview Questions
Common Lua interview questions and answers covering tables, metatables, scoping, closures, and coroutines.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics