Names, Values, and the Only Two Things That Are False
Luau agrees with most languages about what a variable is and disagrees sharply about what counts as false. Hand it a zero and an empty string, and find that it treats both as true.
Luau agrees with most languages about what a variable is and disagrees sharply about what counts as false. Hand it a zero and an empty string, and find that it treats both as true.
local is not optional decoration
Write health = 100 in Luau and it works. Write local health = 100 and it also works, and the second one is right. Without local, the name becomes global — visible to every other piece of code in that script, kept alive for as long as the script is, and slower to read every single time.
local laser = script.Parent -- lives in this script, and only herelocal damage = 100 count = 0 -- global: works, and you did not mean itThe practical damage is collisions. Two scripts that both forget local on a variable called count are now sharing one number, and each will see it change for reasons that are nowhere in its own code.
The types you will actually meet
Luau has a short list of types and you will use six of them constantly.
One numeric type. 3 and 3.5 are both numbers; there is no separate integer.
Text, in double or single quotes. Joined with two dots, not a plus.
true or false. The properties you toggle in Studio are these.
Nothing here. What you get from a lookup that found no object.
The only container. Acts as a list, a dictionary, or both at once.
An object in the tree — a Part, a Script, a Humanoid. Roblox's own addition.
Use typeof() rather than Lua's older type(). The old one reports every Roblox object as "userdata", which tells you nothing; typeof() reports "Instance", "Vector3", "CFrame" and the rest by name.
Luau also lets you annotate a type, and Studio will check it as you write rather than when you run.
local damage: number = 100local partName: string = "Laser"local target: BasePart? = nil -- the ? means "or nil", and is honestnil is a value, and it spreads
nilis not an error and not an absence of a variable. It is a value meaning "there is nothing here", and it is what you get from any lookup that failed.
The trouble is that nil travels. A part that was not found is nil; storing it in a variable stores nil; passing that variable to a function passes nil; and the crash finally happens wherever somebody tries to use it, which can be a long way from where the lookup went wrong.
local platform = workspace.Obby:FindFirstChild("Platfrom") -- typoprint(platform) --> nil, and no error yet platform.CanCollide = false --> attempt to index nil with 'CanCollide'Line one is the bug and line four is the error. This is the single most common shape of failure in Roblox scripting, and the chapter on debugging is largely about reading your way back from the second line to the first.
Only nil and false are false
Here is the one that will catch you. In Python, an empty list is false. In JavaScript, zero is false. In Luau, nil and false are false, and every other value in the language is true.
Zero is true. An empty string is true. An empty table is true. The string "false" is true.
local value = 0
print(typeof(value)) --> number
if value then ... end --> the block runs
typeof
number
an if sees it as
true
True. This is the one that catches people arriving from Python, JavaScript, or C: a health of 0 passes an if check in Luau.
The consequence in an obby is immediate: if humanoid.Health then is true for a player who is already dead, because their health is 0 and 0 is true. What you meant was if humanoid.Health > 0 then. Compare explicitly whenever the value is a number.
The upside is that if part thenis an exact and idiomatic test for "did that lookup find anything", because the only way it fails is nil. That guard appears in nearly every script in the rest of this track.
Joining strings with two dots
Luau joins strings with .., not +. A plus between two strings is an arithmetic error, and the message it gives you names the operation rather than the mistake.
local name = "Amara" print("Welcome, " .. name) --> Welcome, Amaraprint("Score: " .. 42) --> Score: 42 (numbers convert on their own) -- print takes several arguments and adds the spaces itself:print("Welcome,", name) --> Welcome, AmaraThat last form is the one to prefer while debugging. It needs no concatenation, so it cannot fail on a nil, and it prints something useful even when a value is not what you expected — print("part:", part) on a nil prints part: nil, where the .. version would throw before telling you anything.
Key takeaways
- Put local in front of every variable. Without it the name is global, shared with every other script, and collides silently.
- Use typeof() rather than type() — the old one reports every Roblox object as "userdata".
- Luau has one number type. 3 and 3.5 are both numbers.
- nil is a value meaning nothing is here, and it travels: the crash lands wherever it is finally used, not where the lookup failed.
- Only nil and false are false. Zero, the empty string, and the empty table are all true.
- if humanoid.Health then is true for a dead player. Compare numbers explicitly with > 0.
- if part then is an exact test for whether a lookup found anything, because nil is the only way it fails.
- Join strings with .. rather than +. While debugging, prefer print("label:", value) — it never fails on a nil.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.