The standard library in daily use

table, math, string, os and io functions you actually call, plus the differences between Lua 5.1, 5.3, 5.4 and LuaJIT.

table and math

-- table: the workhorse
local t = { 3, 1, 2 }
table.insert(t, 4)               -- append
table.insert(t, 1, 0)            -- insert at position 1, shifting the rest
print(table.remove(t))           --> 4, removes the last
print(table.remove(t, 1))        --> 0
table.sort(t)
print(table.concat(t, ","))      --> 1,2,3
print(table.unpack({ 1, 2, 3 })) --> 1   2   3

-- Lua 5.1 called it unpack; 5.2+ moved it to table.unpack
local unpack = table.unpack or unpack

-- math
print(math.floor(3.7), math.ceil(3.2), math.max(1, 9, 4))
print(math.type(1), math.type(1.0))      --> integer   float   (5.3+)
print(math.tointeger(3.0), math.tointeger(3.5))   --> 3   nil
print(math.huge, -math.huge, math.pi)
print(math.random(1, 6))                  -- seed it once: math.randomseed(os.time())
print(math.fmod(7, 3), 7 % 3)             --> 1   1   (both are 1 here)

-- integer division and the modulo sign
print(7 // 2, -7 // 2)                    --> 3   -4
print(7 % 3, -7 % 3)                      --> 1   2    (result takes the divisor's sign)
FunctionReturnsNote
table.concat(t, sep)A single stringMuch faster than .. in a loop
table.insert(t, [pos,] v)NothingShifts elements after pos
table.remove(t, [pos])The removed valueDefault is the last element
table.unpack(t, i, j)The elements as multiple valuesWatch the stack size for large tables
table.pack(...)A table with a n fieldKeeps trailing nils
math.tointeger(x)An integer or nilUse it to test for a whole number
math.type(x)"integer", "float" or nilDistinguishes 1 from 1.0

io and os

-- read a whole file
local function readAll(path)
  local f, err = io.open(path, "rb")
  if not f then return nil, err end
  local content = f:read("a")            -- 5.3+; in 5.1 use "*a"
  f:close()
  return content
end

-- read line by line for large files
for line in io.lines("data.csv") do
  local field = line:match("^([^,]+)")
end

-- write
local out, err = io.open("out.txt", "w")
if not out then error("cannot open: " .. tostring(err)) end
out:write("header\n")
out:write(("row %d\n"):format(1))
out:close()

-- os functions you will actually use
print(os.time())                       -- seconds since the epoch
print(os.date("%Y-%m-%d %H:%M:%S"))
print(os.date("!%Y-%m-%dT%H:%M:%SZ", 0))
print(os.clock())                      -- CPU time, for measuring
local ok, why, code = os.execute("ls -1 | wc -l")
print(os.getenv("HOME"))
print(os.tmpname())
  • Always check the second return value of io.open. A failed open returns nil plus a message, and calling a method on nil gives a confusing error.
  • io.lines closes the file automatically when the loop finishes, which is why it is the preferred form.
  • os.execute changed its return values between versions. In 5.1 it returns an exit status; in 5.2 and later it returns true or nil plus a reason and a code.
  • os.remove renames and removes files; it does not delete directories. Use a shell call or a library for that, and be careful with paths built from input.
-- version differences that break real code
-- 5.1  unpack(t)            | 5.2+  table.unpack(t)
-- 5.1  math.mod, math.log10 | 5.3+  math.fmod, removed log10
-- 5.1  setfenv / getfenv    | 5.2+  _ENV and load
-- 5.2  bit32 library        | 5.3+  native operators & | ~ << >>
-- 5.3+ integer subtype      | LuaJIT: all numbers are doubles unless it uses FFI
-- 5.4  <close> and <const>  | LuaJIT: not supported
-- # on a table with holes   | any version: the result is undefined
⚠️
LuaJIT is Lua 5.1 plus extensions. Code written for 5.4 will not run on it, and code that relies on LuaJIT's FFI or on its 64-bit integer behaviour will not run on the reference implementation. Decide which target you support before you use a version-specific feature.

FAQ

What does #t return for a table with holes?
Any boundary. The length operator is only defined for a sequence: a table where the keys are 1 through n with no nil in between. With holes the result is one of the possible boundaries, so track the length yourself.
How do I copy a table?
A shallow copy is for k, v in pairs(t) do copy[k] = v end. A deep copy needs a recursive function and a visited set to handle shared references and cycles.

Strings and Lua patterns Tables, metatables and object orientation

Last refreshed 2026-09-18.