<!-- fivemad.com, Markdown source of https://fivemad.com/docs/code-optimisation/conditionals -->
> Source: https://fivemad.com/docs/code-optimisation/conditionals
> Section: Code optimisation
> Tags: conditionals, guard clause, early return, or, nil, readability, lua
> Last updated: 2026-09-25
> Site map for assistants: https://fivemad.com/llms.txt

# Conditionals and early returns

Guard clauses that keep functions flat, returning the expression instead of true or false, the or default trap, and ordering checks so the cheap one runs first.

Most unreadable Lua is not complicated logic. It is simple logic wrapped in four levels of `if`. These are the four changes that fix nearly all of it.

## Guard clauses

Handle the reasons to stop first, one per line, then write the real work at the left margin:

```lua title="server.lua (nested)"
RegisterNetEvent('shop:server:buy', function(item, amount)
    local src = source
    local player = GetPlayer(src)
    if player then
        if Items[item] then
            if type(amount) == 'number' and amount > 0 then
                if player.money >= Items[item].price * amount then
                    giveItem(src, item, amount)
                    takeMoney(src, Items[item].price * amount)
                else
                    notify(src, 'Not enough money')
                end
            end
        end
    end
end)
```

```lua title="server.lua (guarded)"
RegisterNetEvent('shop:server:buy', function(item, amount)
    local src = source
    local player = GetPlayer(src)
    if not player then return end

    local def = Items[item]
    if not def then return end
    if type(amount) ~= 'number' or amount < 1 or amount > 100 then return end

    local cost = def.price * amount
    if player.money < cost then
        return notify(src, 'Not enough money')
    end

    takeMoney(src, cost)
    giveItem(src, item, amount)
end)
```

Same logic, no nesting, and every precondition is visible in the first few lines. When you add a fifth check you add a line instead of another level.

Two practical notes. Do the **cheap checks first** so the expensive ones never run on bad input: a `type()` check before a database call, not after. And do the checks **in dependency order**, so nothing reads a value that an earlier check has not confirmed yet.

## Return the expression

If both branches return a boolean, the condition already is the answer:

```lua
-- Noise
if role == 'police' or role == 'ems' then
    return true
else
    return false
end

-- The same thing
return role == 'police' or role == 'ems'
```

The same goes for `if x then y = true else y = false end`. Assign the comparison.

## Default values with `or`

Lua's `or` returns the first value that is not `nil` or `false`, which makes defaults short:

```lua
local function greet(name)
    return 'Hello, ' .. (name or 'stranger')
end
```

> [!WARNING]
> This trap catches everyone once. `or` treats `false` exactly like `nil`, so a default for a **boolean** option silently ignores the caller.

```lua
-- Broken: passing false gives you true
local function setup(opts)
    local enabled = opts.enabled or true    -- always true
end

-- Correct
local function setup(opts)
    local enabled = opts.enabled
    if enabled == nil then enabled = true end
end
```

The same applies to numbers when `0` is meaningful in your code. `0` is truthy in Lua, so `count or 10` is safe, but `value ~= nil` is what you want whenever `false` is a legitimate value.

The related idiom `cond and a or b` is not a ternary operator. It breaks when `a` is `false` or `nil`, because the `and` fails and you get `b`:

```lua
local label = isDead and false or 'alive'   -- always 'alive'
```

Use it for values you know are never `false` or `nil`, and write a real `if` otherwise.

## Prefer the positive form

`not` costs the reader a mental flip, and two of them cost a re-read:

```lua
-- Backwards
if not isOnDuty then
    hideBlip()
else
    showBlip()
end

-- Forwards
if isOnDuty then
    showBlip()
else
    hideBlip()
end
```

Name variables positively for the same reason: `isReady` reads better than `isNotReady`, and `if not isNotReady` is how you get bugs.

The exception is a guard clause, which is nearly always negative on purpose (`if not player then return end`). That is fine: there is no `else` to flip, and the negative form is the whole point.

## A lookup instead of a chain

Once an `if / elseif` chain is comparing the same variable against five constants, a table says it better and does not get longer with each new case:

```lua
-- Grows a branch per item
if item == 'water' then
    restoreThirst(20)
elseif item == 'cola' then
    restoreThirst(35)
elseif item == 'coffee' then
    restoreThirst(15)
end

-- Grows a line per item, and the data can move to a config file
local DRINKS <const> = { water = 20, cola = 35, coffee = 15 }

local thirst = DRINKS[item]
if thirst then restoreThirst(thirst) end
```

The table version is also a single hash lookup instead of up to five comparisons, which matters if this runs per frame or per item in a loop.

Next: [Tables and loops](/docs/code-optimisation/tables-and-loops).
