Polyglot
Languages Lua conditionals
Lua § conditionals

Conditionals

Lua’s principal conditional construct is if ... then ... elseif ... then ... else ... end. The condition admits any expression — only nil and false are falsy; all other values are truthy (including 0, "", {}). Lua does not admit if as expression — the conventional substitute is the and-or trick (cond and a or b). Lua admits no ternary operator. The combination — strict nil/false truthiness, the keyword-delimited blocks, the and-or substitute for ternary, the absence of unless/switch — is the substance of Lua’s selection surface.

if / elseif / else

The principal form:

if condition then
    -- body
elseif other then
    -- body
else
    -- body
end

Examples:

if x > 0 then
    print("positive")
elseif x < 0 then
    print("negative")
else
    print("zero")
end

The elseif is one keyword (not else if) — distinct from C-family languages. The then is required after each condition; end terminates the entire chain.

For single-line admit:

if x > 0 then print("positive") end
if x > 0 then print("positive") else print("non-positive") end

Truthiness

Only nil and false are falsy:

if 0 then print("yes") end                         -- prints (0 is truthy)
if "" then print("yes") end                        -- prints
if {} then print("yes") end                        -- prints
if nil then print("yes") end                       -- nothing
if false then print("yes") end                     -- nothing

The strictness eliminates the C-family truthiness pitfalls. The conventional defences:

  • Use explicit comparisonsif n ~= 0, if s ~= "".
  • Use next(t) for empty table check — if next(t) ~= nil.

And-or ternary

Lua does not admit a ternary operator. The conventional substitute uses and/or:

local max = (a > b) and a or b
local sign = (n > 0) and "positive" or (n < 0) and "negative" or "zero"

The mechanism:

  • cond and a returns a if cond is truthy, else cond.
  • (cond and a) or b returns a if cond is truthy, else b.

The principal pitfall: if a is false or nil, the pattern returns b (the “else” value):

local x = true and false or "default"              -- "default" (false treated as falsy)

The defence is explicit if/else for cases involving false/nil:

local x
if condition then x = false else x = "default" end

Default value with or

The or admits substantial default-value patterns:

local options = options or {}
local timeout = options.timeout or 30
local name = arg or "world"

The form returns the first truthy operand. The same pitfall applies — if arg is false, the default is used:

local enabled = false
local effective = enabled or true                  -- true (false is falsy!)

-- Defence: explicit nil check:
local effective
if enabled == nil then
    effective = true
else
    effective = enabled
end

Compound conditions

if a > 0 and b > 0 then
    -- both positive
end

if a > 0 or b > 0 then
    -- at least one positive
end

if not is_ready then
    -- not ready
end

-- With multiple checks:
if user and user.active and user:has_permission("delete") then
    -- ...
end

Ranges

For range checks, no chained comparison — Lua does not admit 0 < n < 100:

-- Wrong (works but produces unexpected behaviour):
-- if 0 < n < 100 then ... end

-- Right:
if n > 0 and n < 100 then
    -- ...
end

-- Or:
if 0 < n and n < 100 then
    -- ...
end

if as statement only

if does not return a value:

-- Cannot:
-- local max = if a > b then a else b end           -- ERROR

-- Conventional substitutes:
local max = a > b and a or b                       -- and-or trick

-- Or explicit:
local max
if a > b then max = a else max = b end

nil and false distinction

The two falsy values are distinct:

print(nil == false)                                -- false (different values)

local x                                            -- nil (uninitialised)
local y = false                                    -- false (explicit)

if x == nil then ... end                           -- explicit nil check
if y == false then ... end

For checking “neither nil nor false”:

if value then ... end                              -- truthy check

For checking “is nil specifically”:

if value == nil then ... end                       -- nil only

Common patterns

Early return

local function process(input)
    if input == nil then return "null input" end
    if input == "" then return "empty input" end
    if #input > 100 then return "too long" end

    return input:upper()
end

The pattern admits substantial linear code paths.

Validate-and-bail

local function divide(a, b)
    if b == 0 then
        error("division by zero")
    end
    return a / b
end

local function process(input)
    if type(input) ~= "string" then
        error("expected string, got " .. type(input))
    end
    -- ...
end

Default with or

local function greet(name, greeting)
    name = name or "world"
    greeting = greeting or "Hello"
    return greeting .. ", " .. name
end

print(greet())                                     -- "Hello, world"
print(greet("Alice"))                              -- "Hello, Alice"
print(greet("Alice", "Hi"))                        -- "Hi, Alice"

Conditional value with and-or

local sign = (n > 0) and 1 or (n < 0) and -1 or 0
local label = active and "running" or "stopped"
local size = (#items > 10) and "large" or "small"

Validation chain

local function validate(form)
    local errors = {}
    if form.name == "" or form.name == nil then
        table.insert(errors, "name required")
    end
    if form.age == nil or form.age < 0 then
        table.insert(errors, "invalid age")
    end
    if form.email and not form.email:match("@") then
        table.insert(errors, "invalid email")
    end
    return errors
end

Empty checks

-- Empty string:
if not s or s == "" then
    print("no string")
end

-- Empty table:
if not t or next(t) == nil then
    print("no table or empty")
end

-- Empty array:
if not arr or #arr == 0 then
    print("no array or empty")
end

Nested conditions

local function categorize(score)
    if score < 0 then return "invalid" end
    if score >= 90 then return "A" end
    if score >= 80 then return "B" end
    if score >= 70 then return "C" end
    if score >= 60 then return "D" end
    return "F"
end

The early-return pattern is conventionally clearer than nested if/elseif.

Conditional method call

if callback then
    callback(result)
end

-- With and-or for "call if non-nil and use result":
local result = handler and handler(data) or default

Optional chain

local name = user and user.profile and user.profile.name or "anonymous"

-- Or with an explicit helper:
local function dig(t, ...)
    for _, key in ipairs({...}) do
        if t == nil then return nil end
        t = t[key]
    end
    return t
end

local name = dig(user, "profile", "name") or "anonymous"

The chaining via and admits substantial nil-safe access; the dig helper admits substantial readability.

Boolean from condition

local is_admin = user.role == "admin"
local is_valid = email:match("@") ~= nil and #password >= 8

Dispatch via if-elseif

local function handle_command(cmd, args)
    if cmd == "help" then
        show_help()
    elseif cmd == "version" then
        show_version()
    elseif cmd == "run" then
        run_with_args(args)
    elseif cmd == "quit" then
        return false                               -- signal exit
    else
        print("unknown command: " .. cmd)
    end
    return true
end

For substantial dispatch, a table-based approach is conventionally clearer:

local commands = {
    help = function() show_help() end,
    version = function() show_version() end,
    run = function(args) run_with_args(args) end,
    quit = function() return false end
}

local function handle(cmd, args)
    local handler = commands[cmd]
    if handler then
        return handler(args)
    else
        print("unknown command: " .. cmd)
        return true
    end
end

Explicit nil check vs truthy check

-- Truthy check (any falsy value triggers):
if value then ... end

-- Explicit nil check (only nil triggers):
if value == nil then ... end

-- Explicit false check:
if value == false then ... end

The conventional discipline:

  • Use truthy check (if value then) for substantial general use.
  • Use explicit nil check (if value == nil then) when false is a valid value.

Conditional table field

local options = {host = host, port = port}
if use_ssl then
    options.ssl = true
    options.cert = cert_path
end

-- Or with table constructor and conditional:
local options = {
    host = host,
    port = port,
    ssl = use_ssl and true or nil
}

Trailing condition

The repeat ... until admits a trailing condition:

repeat
    local input = io.read()
    process(input)
until input == "quit"

Treated in Loops.

Switch-like via table

local actions = {
    [1] = function() print("one") end,
    [2] = function() print("two") end,
    default = function() print("other") end
}

local action = actions[n] or actions.default
action()

The pattern admits substantial dispatch via table lookup.

A note on the absence of unless / switch

Lua does not admit:

  • unless — use if not cond then.
  • switch — use if/elseif/else chain or table dispatch.
  • when (Kotlin-style) — use if/elseif/else.

The mechanism admits fewer constructs; conventional table-dispatch and if-chains cover the substantial use cases.

A note on the conventional discipline

The contemporary Lua conditional advice:

  • Use if/elseif/else/end for the standard branching.
  • Use the and-or trick for short value-returning ternaries.
  • Use or for default values — beware false/nil valid values.
  • Use and for chained nil-safe access.
  • Use early returns for precondition validation.
  • Use explicit nil checks (x == nil) when false is admitted.
  • Use table dispatch for substantial multi-way branching.
  • Trust strict truthiness — only nil/false are falsy.
  • Use explicit comparisons — clearer than truthy checks.

The combination — if/elseif/else/end blocks, the strict-nil/false truthiness, the and-or ternary substitute, the absence of switch, the keyword-delimited construction — is the substance of Lua’s selection surface. The discipline produces straightforward, explicit conditional code with substantial brevity through the and-or patterns.