Luau Cheat Sheet
Type-checking modes
A comment on the first line of the file decides how strictly Luau checks it.
| Mode | Syntax |
|---|---|
| No type checking | --!nocheck |
| Default: check what's annotated | --!nonstrict |
| Strict: infer and check everything | --!strict |
| Run a file | luau main.luau |
| Type-check without running | luau-analyze main.luau |
| Turn off one lint warning | --!nolint UnknownGlobal |
Basic types
Primitive types and how to annotate a variable.
| Operation | Syntax |
|---|---|
| Number | local age: number = 25 |
| String | local name: string = "Ada" |
| Boolean | local ok: boolean = true |
| Optional (may be nil) | local nick: string? = nil |
| Any (opt out of checking) | local x: any = f() |
| Unknown (safer any) | local x: unknown = f() |
| Never (always throws) | function fail(): never error("boom") end |
| Nil | local n: nil = nil |
| Coroutine / buffer | local co: thread, local b: buffer |
| Inferred - no annotation needed | local hp = 100 -- number |
Typed tables: arrays & maps
Tables are still Lua tables - the type says what's inside.
| Operation | Syntax |
|---|---|
| Array of numbers | local xs: {number} = {1, 2, 3} |
| Array of strings | local names: {string} = {} |
| Map (string keys) | local ages: {[string]: number} = {} |
| Map (any key type) | local seen: {[Player]: boolean} = {} |
| Nested array | local grid: {{number}} = {} |
| Array of shapes | local items: {{id: number}} = {} |
| Read-only array | local t = table.freeze({1, 2, 3}) |
| Iterate (generalized, Luau only) | for i, v in xs do print(i, v) end |
| Iterate keys and values | for k, v in pairs(ages) do end |
Functions
Annotate parameters and the return type; -> () means returns nothing.
| Operation | Syntax |
|---|---|
| Typed parameters + return | function add(a: number, b: number): number |
| Returns nothing | function log(msg: string): () end |
| Multiple returns | function split(s: string): (string, string) |
| Optional parameter | function greet(name: string?) end |
| Default value | function greet(name: string?) name = name or "friend" end |
| Variadic | function sum(...: number): number end |
| Anonymous function | local f = function(x: number): number return x * 2 end |
| Function type | type Adder = (number, number) -> number |
| Callback parameter | function each(f: (number) -> ()) end |
Type aliases, unions & literals
Name a type once, reuse it everywhere.
| Operation | Syntax |
|---|---|
| Alias a primitive | type Health = number |
| Union | type Id = number | string |
| Optional is a union with nil | type Maybe = string | nil -- same as string? |
| Literal (singleton) type | type Dir = "up" | "down" |
| Intersection | type Both = Named & Aged |
| Generic alias | type List<T> = {T} |
| Export from a module | export type Point = { x: number, y: number } |
| Type of an existing value | type Config = typeof(config) |
| Keys of a table type | type K = keyof<Point> |
Table shapes & methods
Describing objects, and the self that comes with colon calls.
| Operation | Syntax |
|---|---|
| Inline shape | local p: { x: number, y: number } = { x = 0, y = 0 } |
| Named shape | type Point = { x: number, y: number } |
| Optional property | type User = { name: string, age: number? } |
| Shape + indexer | type Bag = { count: number, [string]: any } |
| Extend a shape | type Admin = User & { level: number } |
| Method (colon shorthand) | function Point.move(self: Point, dx: number) end |
| Method in a type | type Point = { move: (self: Point, dx: number) -> () } |
| Constructor pattern | function Point.new(x: number): Point end |
| Frozen constants (enum-ish) | local Color = table.freeze({ Red = 1, Blue = 2 }) |
Generics
One function or type that works for many value types.
| Operation | Syntax |
|---|---|
| Generic function | function id<T>(x: T): T return x end |
| Two type parameters | function pair<A, B>(a: A, b: B): (A, B) end |
| Generic over an array | function first<T>(xs: {T}): T? return xs[1] end |
| Generic type alias | type Stack<T> = { items: {T} } |
| Instantiate an alias | local s: Stack<string> = { items = {} } |
| Generic map function | function map<T, U>(xs: {T}, f: (T) -> U): {U} end |
| Generic pack (variadic) | function call<T...>(f: () -> T...): T... end |
Narrowing, assertions & guards
Convincing the type checker that a value is what you know it is.
| Operation | Syntax |
|---|---|
| Type assertion (cast) | local n = value :: number |
| Assert through any | local n = (value :: any) :: number |
| Narrow with typeof | if typeof(x) == "string" then -- x is string end |
| Narrow an optional | if name then print(#name) end |
| Early return on nil | if not name then return end |
| Narrow a literal union | if dir == "up" then end |
| Runtime check + type error | assert(typeof(id) == "number", "id must be a number") |
| Assert not nil, then use | local item = assert(find(id), "missing item") |
Operators & syntax Lua doesn't have
The everyday quality-of-life additions - this is where Luau code stops looking like Lua.
| Operation | Syntax |
|---|---|
| Add and assign | hp += 10 |
| Subtract / multiply / divide | hp -= 5, dmg *= 2, dmg /= 2 |
| Floor-divide and assign | n //= 2 |
| Modulo / power and assign | n %= 3, n ^= 2 |
| Concatenate and assign | msg ..= "!" |
| Floor division | local half = 7 // 2 -- 3 |
| String interpolation | Hello, {name}! You have {n} items. (wrap in backticks) |
| Interpolate an expression | Total: {price * qty} (wrap in backticks) |
| Continue a loop | for _, v in xs do if v < 0 then continue end end |
| if-then-else expression | local label = if hp > 0 then "alive" else "dead" |
| Chained else-if expression | local t = if n > 0 then "pos" elseif n < 0 then "neg" else "zero" |
| Binary / separated number literals | 0b1010, 1_000_000 |
Library extras & Lua differences
Standard-library functions Luau adds, and the Lua features it removes.
| Item | Detail |
|---|---|
| Find a value in an array | table.find(xs, 42) -- index or nil |
| Shallow copy | local copy = table.clone(t) |
| Make immutable / check | table.freeze(t), table.isfrozen(t) |
| Preallocate an array | table.create(10, 0) |
| Clamp / round / sign | math.clamp(x, 0, 1), math.round(x), math.sign(x) |
| Bitwise ops | bit32.band(a, b), bit32.lshift(a, 1) |
| Removed for sandboxing | io, package, loadstring, loadfile, dofile |
| Deprecated but still present | getfenv, setfenv (they disable optimizations) |
| Not in Luau | goto / labels (Luau is based on Lua 5.1) |
| Reading stdin on Coddy | io.read() works here - Coddy restores it on top of Luau |
| File extension | main.luau (.lua also works) |
| Roblox-only, not core Luau | game, workspace, Instance.new, task.wait |
Luau is Lua 5.1 plus a gradual type system - the language Roblox created and open-sourced. This Luau cheat sheet covers the parts that are *not* plain Lua: type annotations, typed tables, unions and literal types, generics, type assertions, and the syntax sugar Luau adds (+=, string interpolation, continue, if-then-else expressions). For everything the two languages share - tables, metatables, coroutines, the string library - keep the Lua cheat sheet open next to this one.
Every snippet here is standard Luau, runnable with the luau CLI and type-checkable with luau-analyze. Copy what you need, or try any of it live in the Luau playground - nothing to install. Learning the type system from scratch? Coddy's free interactive Luau course builds it up lesson by lesson.
Luau cheat sheet FAQ
Is this Luau cheat sheet free?
Does Luau have a += operator?
+=, -=, *=, /=, //=, %=, ^=, and ..= for string concatenation. So hp += 10 is valid Luau (and valid Roblox script code), while in Lua 5.1 you have to write hp = hp + 10. Note that there is no ++ or -- in Luau.What is the difference between Lua and Luau?
continue, if-then-else expressions, floor division //, generalized iteration (for i, v in t do), and library functions like table.find, table.clone, and table.freeze. For sandboxing it removes loadstring, loadfile, dofile, and the io and package libraries, and it has no goto because it's based on 5.1. Most ordinary Lua code runs unchanged as Luau.How do I turn on type checking in Luau?
--!strict checks everything and infers aggressively, --!nonstrict (the default) only checks what you annotated, and --!nocheck turns checking off. Outside an editor, run luau-analyze main.luau to type-check without executing, and luau main.luau to run.Do I write interface in Luau like in TypeScript?
interface keyword. Object shapes are written as table types and given a name with type: type User = { name: string, age: number? }. Combine shapes with & instead of extends, and use export type to make one available to other modules.Is this cheat sheet the same as Roblox scripting?
game, workspace, Instance.new, services, events), which is a library on top of the language and is documented by Roblox, not by Luau.Where can I run these Luau snippets?
luau interpreter in your browser, with stdin support, so you can paste any row from this page and press Run. For structured practice, the interactive Luau course covers the same material with exercises, projects, and a certificate.