Appearance
Modes
Written: 2026-10-14
Learning objectives
- You place the line-1 directive correctly.
- You state what strict, nonstrict, and nocheck each prove.
- You explain default any in nonstrict.
- You set the project default without hiding errors.
- You keep game code strict and isolate unchecked data files.
Prerequisites
The directive sits on line one
One line tells the checker how hard to look. Everything below follows that choice.
Write --!strict, --!nonstrict, or --!nocheck as the first line of the file. The type checking guide lists exactly these three modes. The luau.org types intro lists the same three spellings.
A misspelled directive behaves like no directive. The checker does not warn you about the typo from inside the type system. Spell the line exactly.
Start every game file with the intended mode. Reviewers see intent before logic. Mode mistakes surface in review, not in playtests.
Put this Script in ServerScriptService.
luau
--!strict
local coins = 10
local doubled = coins * 2This block checks clean in strict. You infer coins as number and doubled as number. The header proves you asked for full checking.
Nonstrict fills gaps with any
When the checker cannot tell, nonstrict lets the code pass. You move fast and miss some bugs.
In nonstrict, an unannotated variable with no inferable type gets any. The type checking guide states this default directly. The luau.org types intro shows an uninitialized variable inferring any and letting a bad addition pass.
any disables checks on that value. Bad arithmetic, bad calls, and bad field reads pass silently. You find out at runtime.
Use nonstrict while sketching or while migrating old code. Move each gameplay file to strict as soon as you can annotate its edges.
Put this Script in ServerScriptService.
luau
--!nonstrict
local stash
stash = 1
local out = stash + "hello"This block checks clean in nonstrict. You infer stash as any at declaration, per the type checking guide and the luau.org types intro. The addition passes without a type error. At runtime the addition fails. That gap is why you do not keep gameplay in nonstrict.
Put this Script in ServerScriptService.
luau
--!strict
local stash: number = 1
local out = stash + "hello"This block errors in strict. The checker reports that a string cannot convert to number. You fix it by converting the string first or by not adding mismatched types. The same logic that passed in nonstrict now fails fast.
Strict tracks types across statements
Strict remembers what you assigned. Later lines must respect earlier facts.
The precise rule is flow plus inference. Strict infers locals from assignments and checks every use against that flow. The luau.org types intro shows strict catching a string-plus-number bug that nonstrict missed. The type checking guide describes strict as asserting all types from inference or annotations.
You must annotate boundaries and narrow uncertain values. Generated tables and loosely shaped data need extra work.
Autocomplete improves. Refactors break loudly in the editor. You ship gameplay with fewer runtime type crashes.
Put this Script in ServerScriptService.
luau
--!strict
local rate = 4
rate = "fast"This block errors in strict. You infer rate as number from the first assignment. The second assignment tries to store a string. The checker rejects the change. You fix it with a new variable or a correct number.
Nocheck skips the checker
Some files are data, not logic. Checking them adds noise without safety.
The precise rule is full opt-out. --!nocheck disables type inference feedback for the file. The type checking guide defines nocheck as no checking. The luau.org types intro shows a bad addition passing silently under nocheck.
No errors, no inference, no autocomplete help. A typo in a data file stays hidden until runtime reads it.
You reserve nocheck for generated files and raw data dumps. You keep every hand-written gameplay file in a checked mode. You never spread nocheck to hide a logic error.
Put this ModuleScript in ReplicatedStorage.
luau
--!nocheck
local raw = {}
raw.coins = 10
raw.coins = "many"This block passes with no diagnostics because nocheck is active. You infer no reliable types here. Runtime sees the last assignment win. You use this only for machine-written data you do not hand-edit.
You set the project default once
New scripts should start strict. Old scripts keep their headers until you migrate them.
The precise rule is a Workspace setting. You set the type-check default on the Workspace object so fresh scripts inherit your choice. The type checking guide points to the Workspace property for this default. The per-file header still wins when present.
The setting does not rewrite existing headers. A file with --!nonstrict stays nonstrict until you change that line. Keep every header exact on line one; an unrecognized header gets no documented fallback, so a header that does not take effect means a typo to fix.
You set the project default to strict for game code. You migrate legacy files one by one. You track remaining nonstrict files as tech debt.
Put this Script in ServerScriptService.
luau
--!strict
local Workspace = game:GetService("Workspace")
local mode = Workspace.LuauTypeCheckModeThis block checks clean. You infer mode as the enum value that stores the default. You read it to confirm your Studio default. You change the default in Studio settings, not by editing this read.
You pick strict and keep it
Strict fits game code. Nonstrict fits a sketch. Nocheck fits generated data.
Strict fits Services, Controllers, shared logic, and net boundaries. Nonstrict fits a quick sketch you plan to tighten. Nocheck fits generated or bulk data files you do not reason about.
Downgrading a file hides the error without fixing the bug. The bug still ships. The next reader inherits a weaker file.
You never downgrade to silence strict. You annotate, narrow, or remodel instead. Later chapters give you those tools.
Put this Script in ServerScriptService.
luau
--!strict
local function payout(coins: number): number
return coins * 2
end
local got = payout(6)This block checks clean. You infer got as number. This is the shape you want for gameplay. Params and returns are explicit. Calls are proven at edit time.
Common mistakes
Wrong: you place the directive below code.
Put this Script in ServerScriptService.
luau
--!nonstrict
local coins = 1This header is correct only because it sits on line one. Move it to line two and the checker ignores it. You fix placement by keeping the mode line first with no blank line above it. The fixed block checks clean in its stated mode. You infer coins as number.
luau
--!strict
local coins = 1This fix checks clean. You infer coins as number.
Wrong: you downgrade to hide a mismatch.
Put this Script in ServerScriptService.
luau
--!nonstrict
local total = 10 + "5"This block can pass in nonstrict while still failing at runtime, which is the documented nonstrict gap: untyped locals skip the check the strict solver would run. You hide the bug instead of fixing it. You fix it by staying strict and converting openly.
luau
--!strict
local total = 10 + tonumber("5")This fix is wrong as written because tonumber returns an optional. The honest strict fix needs a nil guard, which you learn in Narrowing and Guards. The lesson here is mode choice: you stay strict and fix the value.
Wrong: you put game logic in nocheck.
Put this Script in ServerScriptService.
luau
--!nocheck
local price = 20
local charge = price + "fee"This block reports nothing because checking is off. Runtime still fails on the bad addition. You fix it by moving logic back to strict and typing the values.
luau
--!strict
local price: number = 20
local fee: number = 5
local charge = price + feeThis fix checks clean. You infer charge as number.
Exercises
- Write the same bad addition in all three modes and note which modes flag it.
- Show nonstrict default any with an uninitialized local.
- Fix a strict reassignment error without changing the mode.
- Mark a generated data module nocheck and explain why logic must not live there.
- Confirm your project default and state which mode new gameplay files use.
Solution 1
Put this Script in ServerScriptService.
luau
--!strict
local bad = 1 + "x"This strict sample errors. The checker rejects string-to-number conversion. Change the header to --!nonstrict or --!nocheck and the diagnostic disappears while the runtime bug remains. That contrast proves why gameplay stays strict.
Solution 2
Put this Script in ServerScriptService.
luau
--!nonstrict
local pending
pending = 2This block checks clean in nonstrict. You infer pending as any at first. Add pending + "oops" and nonstrict still passes while strict on the same code would flag the mix. You annotate pending as number to close the hole.
Solution 3
Put this Script in ServerScriptService.
luau
--!strict
local speed: number = 6
local speedLabel = tostring(speed)This block checks clean. You infer speedLabel as string. You keep the number intact and derive a separate string. You do not reassign speed to a string.
Solution 4
Put this ModuleScript in ReplicatedStorage.
luau
--!nocheck
local dump = {}
dump.count = 3This block passes unchecked. You infer no reliable type. You keep this pattern only for machine output. You require hand-written logic to live in strict files that validate this data on load.
Solution 5
Put this Script in ServerScriptService.
luau
--!strict
local Workspace = game:GetService("Workspace")
local current = Workspace.LuauTypeCheckMode
local strictReady = trueThis block checks clean. You infer current as the Workspace default value and strictReady as boolean. You set the Studio default to strict for new gameplay. You migrate old files file by file.
Summary
- The mode line sits on line one and selects the proof level.
- Nonstrict defaults unknown locals to any and misses bugs.
- Strict tracks flow and rejects mismatches early.
- Nocheck fits generated data only.
- You set the Workspace default to strict and never downgrade to hide errors.
- Next: Narrowing and Guards.