# Luau Cheat Sheet: Type Annotations, Syntax and Roblox Basics

Source: https://srvscripts.com/guides/luau-cheat-sheet/
Updated: 2026-10-09
Publisher: srvScripts (https://srvscripts.com/)

**Short answer:** Luau is the gradually typed language derived from Lua 5.1 that Roblox uses. It adds optional type annotations (`local port: number = 8080`), `--!strict` checking, generics, string interpolation with backticks, `continue`, compound assignment (`+=`), `if` expressions, iteration without `pairs`, and library extras such as `table.find` and `string.split`. It drops `goto`, the `io` library and 64-bit integers. The tables below cover the syntax and the first Roblox APIs you will meet.

The language examples in this Luau cheat sheet were run on the Luau 0.740 VM on 9 October 2026. That run checks syntax and runtime behaviour, not type errors; type-checker rules and the Roblox section were checked against the official documentation.

## Luau vs Lua 5.1 at a glance

In short: A compact Luau reference covering type annotations, the syntax Luau adds to Lua 5.1, its standard library extras and the Roblox basics you meet first.

Luau Cheat Sheet: what this guide covers.

| Feature | Lua 5.1 | Luau |
| --- | --- | --- |
| Types | Dynamic only | Optional annotations, inference, --!strict |
| String interpolation | string.format | `Host {name}:{port}` |
| Skip an iteration | No continue (5.2+ uses goto) | continue; goto is not supported |
| Compound assignment | n = n + 1 | n += 1, ..=, //= and others |
| Conditional expression | a and b or c | if a then b else c |
| Iterate a table | pairs(t) / ipairs(t) | for k, v in t do (pairs and ipairs still work) |
| Floor division | math.floor(a / b) | a // b |
| Numbers | Double | Double only (exact integers up to 2^53); no 5.3 integer subtype |
| Bitwise | None | bit32 library; no & or | operators |
| Files and processes | io, os.execute | Removed by sandboxing; os keeps time, clock, date, difftime |
| Finalisers | __gc | Not supported |
| Constants | None | const LIMIT = 10 in recent Luau releases |

Luau keeps `getfenv` and `setfenv` for compatibility, but using them disables some optimisations. Roblox also disables `loadstring` unless `ServerScriptService.LoadStringEnabled` is turned on, and then only on the server.

## Variables, operators and string interpolation

| Syntax | What it does | Example |
| --- | --- | --- |
| local x: number = 1 | Local with an optional type | local name: string = "web01" |
| const X = 10 | Binding that cannot be reassigned (the value can still change if it is a table) | const MAX_PLAYERS = 12 |
| `… {expr} …` | Interpolated string; \{ for a literal brace | print(`sum {1 + 2}`) |
| +=, -=, *=, /=, //=, %=, ^=, ..= | Compound assignment (statements, not expressions) | score += 10 |
| if c then a elseif d then b else e | If expression; else is required | local label = if port == 443 then "https" else "http" |
| 1_000_000, 0b1010, 0xFF | Digit separators, binary and hex literals | local mask = 0b_0101_0101 |
| typeof(x) | Like type, but names engine types ("Instance", "Vector3" on Roblox) | typeof(part) == "Instance" |
| .., #, ~=, and/or/not | As in Lua 5.1 | local v = x or "default" |

Backtick strings must be wrapped in parentheses when passed to a function: `print(`hi`)` works, `print`hi`` does not. A double brace `{{` inside a backtick string is a parse error.

## Type annotations

| Type syntax | Meaning | Example |
| --- | --- | --- |
| number, string, boolean, nil, any, unknown, never | Built-in types | local ok: boolean = true |
| T? | Optional: same as T | nil | local nick: string? = nil |
| A | B, A & B | Union and intersection | local id: string | number = 5 |
| {number} | Array of numbers | local scores: {number} = {1, 2, 3} |
| {[string]: number} | Dictionary | local counts: {[string]: number} = {} |
| {x: number, y: number} | Table with named fields | type Point = {x: number, y: number} |
| {read x: number} | Read-only property | Writes are type errors |
| "up" | "down" | String literal types | type Dir = "up" | "down" |
| (number, string) -> boolean | Function type | type Callback = (number) -> () |
| (): (number, string) | Multiple return values | local function pair(): (number, string) |
| ...: number | Typed varargs | local function sum(...: number): number |
| expr :: T | Type cast | local s = value :: string |
| typeof(expr) in a type | Type of an existing value (not evaluated at runtime) | type Config = typeof(DEFAULTS) |
| export type | Make a type visible to modules that require this one | export type Server = {host: string} |

```
--!strict
type Pair = {first: T, second: T}

local function add(a: number, b: number): number
  return a + b
end

local function first(list: {T}): T?
  return list[1]
end

local p: Pair = {first = 1, second = 2}
print(add(1, 2), first({"a", "b"}), p.first)   --> 3  a  1
```

| Mode (first line) | Behaviour |
| --- | --- |
| --!nocheck | No type checking for the file |
| --!nonstrict | Default. Unknown types become any, so fewer errors are reported |
| --!strict | Full inference; catches mismatches such as adding a string to a number |

Narrow a union with a check before use: inside `if typeof(v) == "string" then … end` the checker treats `v` as a string, and after `assert(x)` an optional `x` is no longer `nil`.

## Tables and the standard library extras

| Function | What it does | Example |
| --- | --- | --- |
| table.find(t, v) | Index of a value in an array, or nil | table.find({"a", "b"}, "b") gives 2 |
| table.create(n, v) | Pre-sized array filled with v | table.create(3, 0) |
| table.clear(t) | Empty a table but keep its capacity | table.clear(cache) |
| table.clone(t) | Shallow copy | local copy = table.clone(defaults) |
| table.freeze(t) / table.isfrozen(t) | Make read-only (shallow); writes raise an error | local CONFIG = table.freeze({…}) |
| string.split(s, sep) | Split into an array | string.split("a,b,c", ",") |
| math.clamp(x, min, max) | Limit to a range | math.clamp(15, 0, 10) gives 10 |
| math.round(x), math.sign(x) | Round half away from zero; sign | math.round(2.5) gives 3 |
| bit32.band, bxor, lshift | Bitwise operations on 32-bit values | bit32.band(12, 10) gives 8 |
| buffer.create(n) | Fixed-size byte buffer with typed reads and writes | buffer.writeu32(b, 0, 42) |
| utf8.len(s) | Character count | utf8.len("héllo") gives 5 |

## Control flow and generalised iteration

```
local cfg = {host = "db01", port = 3306}
for key, value in cfg do            -- no pairs() needed
  print(key, value)
end

for i = 1, 5 do
  if i % 2 == 0 then continue end   -- continue must be the last statement in its block
  print(i)                          --> 1, 3, 5
end
```

Plain tables iterate `1..#t` in order first, then the remaining keys in no set order. Give a table or class an `__iter` metamethod to control what `for … in obj do` returns. `while`, `repeat … until`, numeric `for` and `break` work as in Lua 5.1.

## Functions, errors and coroutines

| Syntax | What it does | Example |
| --- | --- | --- |
| local function f(a: number): string | Typed local function | local function greet(n: string): string |
| function(x: T): T | Generic function | local function first(list: {T}): T? |
| error(msg, 0) | Raise without the location prefix | error("bad port", 0) |
| pcall(f, ...) | Returns true, results or false, err | local ok, err = pcall(fetch) |
| xpcall(f, handler, ...) | With a message handler; extra arguments go to f | xpcall(f, debug.traceback) |
| coroutine.wrap(fn) | Generator function | coroutine.yield(i) inside |
| coroutine.close(co) | Close a suspended coroutine | From Lua 5.4, supported in Luau |
| @native, @deprecated | Function attributes | @native local function hot() … end |

Luau does not perform tail calls, so deep recursion that relies on them in standard Lua will hit the stack limit (about 20,000 Lua calls per thread).

## Modules and typed classes

```
local Account = {}
Account.__index = Account

type AccountData = {owner: string, balance: number}
export type Account = typeof(setmetatable({} :: AccountData, Account))

function Account.new(owner: string): Account
  return setmetatable({owner = owner, balance = 0}, Account)
end

function Account.deposit(self: Account, amount: number)
  self.balance += amount
end

function Account:describe(): string
  return `{self.owner}: {self.balance}`
end

return Account   -- in a ModuleScript; callers use require(path.to.Account)
```

The `typeof(setmetatable({} :: Data, Class))` pattern gives the checker both the fields and the methods. Annotating `self` explicitly, as in `deposit`, helps in `--!strict` files. Another module refers to the exported type as `Account.Account` after `local Account = require(…)`.

## Roblox basics

| API | What it does | Example |
| --- | --- | --- |
| Script / LocalScript / ModuleScript | Runs on the server / runs on the client / returns a value to require | Server logic in ServerScriptService |
| game:GetService(name) | Get a service | local Players = game:GetService("Players") |
| Instance.new(class) | Create an object | local part = Instance.new("Part") |
| obj.Parent = … | Put it in the data model | part.Parent = workspace |
| :WaitForChild(name) / :FindFirstChild(name) | Wait for / look up a child (nil if missing) | script.Parent:WaitForChild("Handle") |
| event:Connect(fn) | Listen to an event; returns a connection :Disconnect() | part.Touched:Connect(onTouch) |
| task.wait(s) | Yield; returns the time actually waited | Use instead of the deprecated wait() |
| task.spawn(f), task.delay(s, f), task.defer(f), task.cancel(t) | Schedule threads | task.delay(5, cleanup) |
| RemoteEvent:FireServer(…) / .OnServerEvent | Client-to-server messages; the server receives the player first | remote.OnServerEvent:Connect(function(player, data) … end) |

```
local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player: Player)
  print(`{player.Name} joined`)
end)
```

Treat everything a client sends through a RemoteEvent as untrusted input: check types and ranges on the server before acting on it. Wrap DataStore calls such as `GetAsync` in `pcall`, because they can fail on network or quota errors.

**Official documentation (checked 9 October 2026):** [Luau syntax by example](https://luau.org/syntax) · [An introduction to Luau types](https://luau.org/types) · [Luau compatibility with Lua](https://luau.org/compatibility) · [Luau standard library](https://luau.org/library) · [Roblox Creator Hub: Luau](https://create.roblox.com/docs/luau) · [Roblox Creator Hub: task library](https://create.roblox.com/docs/reference/engine/libraries/task)

**Related guides:** [Lua Cheat Sheet: Lua 5.4 Syntax, Tables and Patterns](https://srvscripts.com/guides/lua-cheat-sheet/) · [TypeScript Cheat Sheet: Types, Generics and tsconfig](https://srvscripts.com/guides/typescript-cheat-sheet/).
