Beginner

Operators in Lua

Learn arithmetic, relational, logical, string, and bitwise operators in Lua with runnable Docker examples

Operators are the verbs of a programming language—they perform the work of combining, comparing, and transforming values. Lua, true to its minimalist philosophy, keeps its operator set small but expressive. Most operators look familiar to programmers coming from C or Python, but Lua has a few distinctive choices that catch newcomers off guard.

As a dynamically typed scripting language, Lua resolves operator behavior at runtime based on the operand types. This means the same + can work on integers, floats, or even tables (via metamethods). Where Lua diverges sharply from the C family is in its choice of .. for string concatenation, ~= for inequality, and the use of English keywords and, or, not for logical operations.

This tutorial covers Lua’s full operator set: arithmetic, relational, logical, string, length, and the bitwise operators introduced in Lua 5.3. You’ll also see how Lua 5.3+ distinguishes integer and float arithmetic—an important detail when porting code from older Lua versions.

Arithmetic Operators

Lua provides the standard arithmetic operators plus floor division and exponentiation. Since Lua 5.3, integers and floats are distinct subtypes of the number type, and operator behavior subtly depends on which you use.

Create a file named arithmetic.lua:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
-- Basic arithmetic
local a = 17
local b = 5

print("a + b  =", a + b)    -- Addition
print("a - b  =", a - b)    -- Subtraction
print("a * b  =", a * b)    -- Multiplication
print("a / b  =", a / b)    -- Float division (always returns float)
print("a // b =", a // b)   -- Floor division (since Lua 5.3)
print("a % b  =", a % b)    -- Modulo
print("a ^ b  =", a ^ b)    -- Exponentiation (always returns float)
print("-a     =", -a)       -- Unary minus

-- Integer vs float distinction in Lua 5.3+
print("Type of 10 / 2  :", math.type(10 / 2))    -- float (/ is float division)
print("Type of 10 // 2 :", math.type(10 // 2))   -- integer
print("Type of 2 ^ 3   :", math.type(2 ^ 3))     -- float

Relational and Logical Operators

Lua uses ~= for “not equal” instead of !=. The logical operators are spelled out as words. A critical detail: in Lua, only nil and false are falsy—everything else (including 0 and the empty string "") is truthy.

Create a file named logical.lua:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
local x = 10
local y = 20

-- Relational operators
print("x == y :", x == y)   -- equal
print("x ~= y :", x ~= y)   -- not equal (note: ~= not !=)
print("x <  y :", x < y)
print("x >  y :", x > y)
print("x <= y :", x <= y)
print("x >= y :", x >= y)

-- Logical operators return one of their operands, not just true/false
print("true and 'hello' :", true and "hello")  -- "hello"
print("nil  and 'hello' :", nil and "hello")   -- nil
print("false or  'default' :", false or "default")  -- "default"
print("'first' or 'second' :", "first" or "second") -- "first"

-- Common idiom: default values via 'or'
local name = nil
local display = name or "Anonymous"
print("display =", display)

-- Truthiness: only nil and false are falsy
print("0 is truthy  :", 0 and "yes" or "no")
print("'' is truthy :", "" and "yes" or "no")

String and Length Operators

The .. operator concatenates strings. The # unary operator returns the length of a string or the number of elements in a sequence (array-like table). Numbers passed to .. are automatically converted to strings.

Create a file named strings.lua:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
local greeting = "Hello"
local target = "Lua"

-- Concatenation with ..
local message = greeting .. ", " .. target .. "!"
print(message)

-- Numbers auto-convert during concatenation
local version = 5.4
print("Lua version: " .. version)

-- Length operator # on strings
print("length of 'Hello' =", #"Hello")
print("length of message =", #message)

-- Length operator # on a sequence table
local fruits = {"apple", "banana", "cherry", "date"}
print("number of fruits =", #fruits)

-- Chaining concatenations
local parts = "a" .. "b" .. "c" .. "d"
print("parts =", parts)

Bitwise Operators and Precedence

Lua 5.3 introduced bitwise operators that work on integers. They use symbols (&, |, ~, <<, >>) that may clash visually with logical operators in other languages—remember that Lua’s logical operators are words, so symbols are always bitwise.

Operator precedence in Lua follows familiar mathematical conventions: ^ binds tightest, then unary operators, then * / // %, then + -, then .., then comparisons, then and, then or. Note that both ^ and .. are right-associative.

Create a file named bitwise_precedence.lua:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
-- Bitwise operators (Lua 5.3+, integers only)
local a = 0xF0   -- 11110000
local b = 0x0F   -- 00001111

print(string.format("a & b  = 0x%02X", a & b))   -- AND
print(string.format("a | b  = 0x%02X", a | b))   -- OR
print(string.format("a ~ b  = 0x%02X", a ~ b))   -- XOR (binary ~)
print(string.format("~a     = 0x%X",  ~a & 0xFF)) -- NOT (unary ~), masked
print(string.format("1 << 4 = 0x%02X", 1 << 4))  -- left shift
print(string.format("a >> 4 = 0x%02X", a >> 4))  -- right shift

-- Precedence demonstrations
print("2 + 3 * 4    =", 2 + 3 * 4)     -- 14, not 20
print("2 ^ 3 ^ 2    =", 2 ^ 3 ^ 2)     -- 512 (right-assoc: 2^(3^2) = 2^9)
print("-2 ^ 2       =", -2 ^ 2)        -- -4 (^ binds tighter than unary -)
print("'x' .. 1 + 2 =", "x" .. 1 + 2)  -- "x3" (+ binds tighter than ..)

-- Lua has no compound assignment: no +=, -=, *= etc.
local n = 10
n = n + 1            -- write it out long-form
print("n after n = n + 1 :", n)

Running with Docker

Pull the image once, then run each example:

1
2
3
4
5
6
7
8
# Pull the official image
docker pull nickblah/lua:5.4-alpine

# Run each example
docker run --rm -v $(pwd):/app -w /app nickblah/lua:5.4-alpine lua arithmetic.lua
docker run --rm -v $(pwd):/app -w /app nickblah/lua:5.4-alpine lua logical.lua
docker run --rm -v $(pwd):/app -w /app nickblah/lua:5.4-alpine lua strings.lua
docker run --rm -v $(pwd):/app -w /app nickblah/lua:5.4-alpine lua bitwise_precedence.lua

Expected Output

Output from arithmetic.lua:

a + b  =	22
a - b  =	12
a * b  =	85
a / b  =	3.4
a // b =	3
a % b  =	2
a ^ b  =	1419857.0
-a     =	-17
Type of 10 / 2  :	float
Type of 10 // 2 :	integer
Type of 2 ^ 3   :	float

Output from logical.lua:

x == y :	false
x ~= y :	true
x <  y :	true
x >  y :	false
x <= y :	true
x >= y :	false
true and 'hello' :	hello
nil  and 'hello' :	nil
false or  'default' :	default
'first' or 'second' :	first
display =	Anonymous
0 is truthy  :	yes
'' is truthy :	yes

Output from strings.lua:

Hello, Lua!
Lua version: 5.4
length of 'Hello' =	5
length of message =	11
number of fruits =	4
parts =	abcd

Output from bitwise_precedence.lua:

a & b  = 0x00
a | b  = 0xFF
a ~ b  = 0xFF
~a     = 0xF
1 << 4 = 0x10
a >> 4 = 0x0F
2 + 3 * 4    =	14
2 ^ 3 ^ 2    =	512.0
-2 ^ 2       =	-4.0
'x' .. 1 + 2 =	x3
n after n = n + 1 :	11

Key Concepts

  • .. is concatenation, not + — Lua reserves + for numeric addition; using it on strings is an error unless they convert to numbers.
  • ~= means “not equal”!= is not valid Lua syntax. The ~ symbol pulls double duty as bitwise XOR (binary) and bitwise NOT (unary).
  • Logical operators short-circuit and return valuesa and b returns a if a is falsy, otherwise b. This enables the idiomatic value or default pattern.
  • Only nil and false are falsy — Unlike Python or JavaScript, 0 and "" are both truthy in Lua.
  • / always returns a float, // is floor division — Since Lua 5.3 the two division operators produce different result types; choose deliberately.
  • ^ is exponentiation, right-associative, and float-valued2^3^2 evaluates as 2^(3^2) = 512, not (2^3)^2 = 64. Lua has no +=, -=, ++, or --; write x = x + 1 explicitly.
  • # returns length — Works on strings (byte length) and on sequence tables (array-like portion). Behavior on tables with holes is implementation-defined.
  • Bitwise operators require integers — Available since Lua 5.3; floats with integer values are accepted, but non-integer floats raise an error.

Running Today

All examples can be run using Docker:

docker pull nickblah/lua:5.4-alpine
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining