<!-- fivemad.com, Markdown source of https://fivemad.com/docs/code-optimisation/functions -->
> Source: https://fivemad.com/docs/code-optimisation/functions
> Section: Code optimisation
> Tags: functions, naming, parameters, closures, annotations, lua language server, clean code
> Last updated: 2026-09-25
> Site map for assistants: https://fivemad.com/llms.txt

# Functions that stay small

One job per function, names that say what happens, parameters that do not grow, the cost of a function literal in a hot path, and annotating exports.

A function that does one thing is easy to name, easy to test, and easy to delete. A function that does four things has a name like `handlePlayer` and you have to read all of it before you can change any of it.

## One job, one level

Two rules cover most of it. A function should do one thing, and it should stay at one level of detail. Mixing "decide what happens" with "poke the game" is what turns a 20 line function into a 200 line one:

```lua title="client.lua (two jobs, two levels)"
local function useItem(item)
    if item.type == 'food' then
        local ped = PlayerPedId()
        RequestAnimDict('mp_player_inteat@burger')
        while not HasAnimDictLoaded('mp_player_inteat@burger') do Wait(0) end
        TaskPlayAnim(ped, 'mp_player_inteat@burger', 'mp_player_int_eat_burger', 8.0, -8.0, 3000, 49, 0, false, false, false)
        Wait(3000)
        ClearPedTasks(ped)
        TriggerServerEvent('food:server:ateItem', item.name)
    elseif item.type == 'drink' then
        -- another 10 lines of the same
    end
end
```

```lua title="client.lua (split)"
local function playAnim(dict, clip, duration)
    lib.requestAnimDict(dict)
    TaskPlayAnim(PlayerPedId(), dict, clip, 8.0, -8.0, duration, 49, 0, false, false, false)
    Wait(duration)
    ClearPedTasks(PlayerPedId())
end

local function eat(item)
    playAnim('mp_player_inteat@burger', 'mp_player_int_eat_burger', 3000)
    TriggerServerEvent('food:server:ateItem', item.name)
end

local function useItem(item)
    if item.type == 'food' then return eat(item) end
    if item.type == 'drink' then return drink(item) end
end
```

`useItem` now reads as a routing table, `eat` reads as one action, and `playAnim` is the only place that knows the native signature. When `TaskPlayAnim` gains another argument, you change one line.

## Names

| Kind | Style | Example |
| --- | --- | --- |
| Local function | camelCase | `getShopStock`, `playAnim` |
| Resource global | PascalCase | `GetItemPrice` |
| Export | PascalCase | `exports('GetItemPrice', ...)` |

Start with a verb. `player()` tells you nothing, `getPlayerData()` tells you what comes back and that nothing changes. `dropPlayer()` tells you something happens. A function whose name needs "and" in it is two functions.

## Parameters

Once you are past about three parameters, nobody remembers the order:

```lua
-- What is the fourth argument again?
createCharacter('Ali', 'Demir', 34, true, false, 'tr')

-- Self describing, and adding a field breaks nothing
createCharacter({
    first = 'Ali',
    last = 'Demir',
    age = 34,
    male = true,
    nationality = 'tr',
})
```

The table form also lets the caller leave things out without counting `nil`s. The cost is one table allocation per call, so in a function you call thousands of times per frame keep the positional form. Everywhere else the readability is worth more.

**Required parameters come first, optional ones last.** Lua has no way to skip an argument, so `doThing(nil, nil, value)` is what happens when you get this wrong.

**A boolean parameter usually means two functions.** `notify(text, isError)` has two bodies inside one name, and at the call site `notify('Saved', false)` tells the reader nothing. `notifySuccess(text)` and `notifyError(text)` are clearer and each one is shorter. The exception is a flag that really is an option on one behaviour, like `alive` on a native.

## Function literals in hot paths

In Lua 5.4 a function expression creates a new closure value every time that line runs. There is no cache reusing an identical one, so a literal inside a loop or a per frame callback allocates every pass and gives the garbage collector something to clean up.

```lua
-- A new closure every frame
CreateThread(function()
    while true do
        Wait(0)
        drawSomething(function(x) return x * 2 end)
    end
end)

-- One closure, created once
local function double(x) return x * 2 end

CreateThread(function()
    while true do
        Wait(0)
        drawSomething(double)
    end
end)
```

This matters in loops and callbacks that fire constantly. Passing a literal to something that runs once, like a command handler or a one off timer, is fine and clearer than naming it.

The same applies to closures that capture upvalues you do not need. If the inner function only uses its arguments, lift it out of the enclosing function so it is created once when the file loads.

## Return values

Returning several values is normal in Lua and cheaper than building a table for them:

```lua
local function getSpawn()
    return vector3(-269.4, -955.3, 31.2), 205.0
end

local coords, heading = getSpawn()
```

Inside one resource, returning a table does **not** copy it, it hands back a reference, so returning a big table to your own code is not expensive. That changes the moment the value crosses into another resource: an export call packs and unpacks its arguments and returns. See [Exports and resource boundaries](/docs/code-optimisation/exports).

## Annotate what other people call

Any function other resources call should carry [Lua Language Server](https://luals.github.io/) annotations. With the CfxLua IntelliSense setup from [Start developing for FiveM](/docs/development#2-visual-studio-code), these turn into autocomplete and warnings for whoever uses your resource:

```lua title="server/api.lua"
---Price of one item for one player, including job discount and zone tax.
---@param item string Item name, e.g. 'water'
---@param job string Job name, or 'unemployed'
---@param zone string Zone id from the config
---@return number price Final price, never below zero
function GetItemPrice(item, job, zone)
    ...
end

exports('GetItemPrice', GetItemPrice)
```

It costs four lines and removes an entire category of support questions.

## Guard clauses

The best tool for keeping a function small is refusing to continue. That has its own page: [Conditionals and early returns](/docs/code-optimisation/conditionals).

Next: [Variables, constants and enums](/docs/code-optimisation/variables).
