Appearance
Debugging Basics
Written: 2026-10-14
Learning objectives
- You read a red error message word by word and find the line it blames.
- You trace code with
printto see what actually runs. - You shrink a bug into a small test that shows it clearly.
- You guard assumptions with
assert. - You isolate bugs by changing one thing at a time.
- You decode the most common error messages.
Prerequisites
- Read First Script. You can run a Script and open output.
- Read Variables and Values. You create variables with
local. - Read Operators and Logic. You compare values and convert with
tonumber. - Read Conditionals and Loops. You read
ifbranches and loops. - Read Functions and Scope. You define and call functions.
- Read Tables, Part 1. You read keys and positions.
- Read Strings, Numbers, and Nil. You know what nil means.
Read red messages word by word
A red message names three things: what went wrong, where it went wrong, and sometimes which value caused it. The where looks like ServerScriptService.Script:4. That means line 4 of your Script. Start there.
Slow down and read every word. Read the exact line number, open that line, and trust it. This habit fixes many bugs.
Two honesty notes before you run anything. First, exact red-text wording drifts between Studio versions, so every quoted message in this course shows the shape (what plus where), not a contract. Match the shape against your Output, not each character. Second, not all red stops your script: Script Analysis type warnings flag a line but the script still runs, while parse errors refuse to run at all. You learn the difference by watching which messages come with output and which come alone.
Run this: put it in a Script in ServerScriptService and press Play. It errors on purpose. Read the red text.
luau
--!strict
local nickname = "Max"
print(nicknam)txt
ServerScriptService.Script:3: Unknown global 'nicknam'; did you mean 'nickname'?Walkthrough:
- Line 1 turns on strict mode. Strict mode catches misspelled names for you.
- Line 2 creates
nickname. - Line 3 prints
nicknam, which does not exist. The red message names line3and guesses the name you meant.
Your fix loop is always the same. Read the line number. Open that line. Compare each name on it against where you defined it. Fix the spelling.
Run this fixed version: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local nickname = "Max"
print(nickname)txt
MaxWalkthrough:
- Line 1 turns on strict mode.
- Line 2 creates
nickname. - Line 3 prints it correctly, so the output is
Maxwith no red text.
Trace with print
When code runs but the result looks wrong, add print calls that show the value of each suspect variable at each step. This is called print-tracing. Print values, run again, and read the output to see what ran.
Place prints before and after the suspicious line. Label each one so you know which print is which in the output.
Run this: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local coins = 10
print("start:")
print(coins)
coins = coins - 3
print("after shop:")
print(coins)txt
start:
10
after shop:
7Walkthrough:
- Line 1 turns on strict mode.
- Line 2 stores
10incoins. - Lines 3 and 4 print the label
start:and the starting value10. - Line 5 subtracts
3. - Lines 6 and 7 print the label
after shop:and the new value7.
The labels matter. Five bare numbers in a row tell you nothing. "after shop: 7" tells you exactly where 7 came from. When the output surprises you, the label points at the line that produced it.
Remove or quiet your trace prints once the bug is fixed. A script full of leftover traces makes the next debugging session harder.
Shrink the bug with smaller tests
Big scripts hide bugs behind unrelated code. Copy the suspicious lines into a tiny new Script and run only those. If the small test still fails, the bug lives inside those lines. If it passes, the bug lives in the part you left out. Either way you ruled out many suspects.
Run this small test: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local typed = "7"
local asNumber = tonumber(typed)
print(asNumber)
print(asNumber + 1)txt
7
8Walkthrough:
- Line 1 turns on strict mode.
- Line 2 stores the string
"7". - Line 3 converts it to the number
7. - Line 4 prints
7, proving the conversion worked. - Line 5 prints
8, proving math works on the converted value.
Imagine your big script failed at asNumber + 1. This small test proves conversion and math are innocent here, so the real bug must be elsewhere, perhaps a different variable or a missing key. Shrink until the failing lines fit on a sticky note.
Guard assumptions with assert
An assert states what must be true at that point. You give it a condition and a message. When the condition holds, nothing happens and code continues. When it fails, Luau stops and shows your message in red. Place asserts where wrong values would cause confusing errors three lines later.
Run this: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local typed = "7"
local asNumber = tonumber(typed)
assert(asNumber ~= nil, "typed should convert to a number")
print(asNumber + 1)txt
8Walkthrough:
- Line 1 turns on strict mode.
- Line 2 stores the string
"7". - Line 3 converts it.
- Line 4 checks
asNumber ~= nil. The conversion worked, so the assert passes silently. - Line 5 prints
8.
Change "7" to "seven" and run again. The assert fires before the math line and tells you the real problem: the text never converted. Without the assert you would get a confusing arithmetic error instead. Assert the input, not just the output.
Isolate by changing one thing
When several things could be wrong, test them one at a time. Change one line, run, read. If the error moves, your change touched the bug. If nothing changes, undo and try the next suspect. Changing five lines at once teaches you nothing because you cannot tell which change mattered.
A clean isolation order is: spellings first, then values with prints, then types with type(), then shape with small tests. Most beginner bugs die at step one or two.
Run this: put it in a Script in ServerScriptService and press Play. It fails. Then apply the one fix below it.
luau
--!strict
local levels = { 4, 8, 12 }
print(levels[3])
print(levels[4])txt
12
nilWalkthrough:
- Line 1 turns on strict mode.
- Line 2 builds a three-item array.
- Line 3 prints position
3, which is12. - Line 4 prints position
4, which does not exist, sonil.
A beginner might "fix" this by rewriting the whole table. Resist that. The isolation reads: spellings fine, values print fine, but position 4 was never created. The array has length 3. One change fixes it: read position 3, or add a fourth item. Change one thing, rerun, and confirm.
Decode common messages
These four messages cover most beginner pain. Learn their shapes and you will fix them in seconds.
Unknown global. You misspelled a name, or you forgot local when creating it. The message suggests the closest match. Compare the blamed line against your definitions letter by letter. Run this to see it fail: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local teamScore = 4
print(teamscore)txt
ServerScriptService.Script:3: Unknown global 'teamscore'; did you mean 'teamScore'?Names are case-sensitive. teamscore and teamScore differ at one letter. Fix the spelling. Run the fix the same way.
luau
--!strict
local teamScore = 4
print(teamScore)txt
4Arithmetic on nil. You did math with a variable holding nil. Often a function missed an argument, since missing arguments arrive as nil, or a table key was missing. Run this to see it fail: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local function addBonus(score, bonus)
return score + bonus
end
print(addBonus(10))txt
ServerScriptService.Script:2: attempt to perform arithmetic (add) on number and nilLine 2 blames the +. bonus never arrived, so it is nil. Fix the call by passing both arguments. Run the fix the same way.
luau
--!strict
local function addBonus(score, bonus)
return score + bonus
end
print(addBonus(10, 5))txt
15Arithmetic on string. You used + on text. + needs numbers. Join text with .., or convert with tonumber first. Run this to see it fail: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local typed = "7"
print(typed + 1)txt
ServerScriptService.Script:2: attempt to perform arithmetic (add) on string and numberFix it by converting before the math. Run the fix the same way.
luau
--!strict
local typed = "7"
local asNumber = tonumber(typed)
print(asNumber + 1)txt
8Index nil. You used a dot on something that is nil. Usually a table key is misspelled or a function returned nothing. Print the thing before the dot to see which level is nil. Run this to see it fail: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local player = { name = "Maya" }
print(player.title.length)txt
ServerScriptService.Script:2: attempt to index nil with 'length'player.title is nil because the key does not exist, and nil has no length. Fix it by reading a key that exists, or by guarding with if player.title ~= nil then. Run the fix the same way.
luau
--!strict
local player = { name = "Maya" }
print(player.name)txt
MayaCommon mistakes
Guessing instead of reading the line number.
Run this to see it fail: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local nickname = "Max"
local teamScore = 4
print(nicknam)
print(teamScore)txt
ServerScriptService.Script:4: Unknown global 'nicknam'; did you mean 'nickname'?Beginners rewrite the whole script. Fix it by reading the blamed line first. The message says line 4, and line 4 holds the typo. Change one name and rerun. Run the fix the same way.
luau
--!strict
local nickname = "Max"
local teamScore = 4
print(nickname)
print(teamScore)txt
Max
4Printing values with no labels.
Run this: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local coins = 10
print(coins)
coins = coins - 3
print(coins)txt
10
7The output 10 then 7 is readable today and mysterious next week. Fix it by labeling every trace. Run the fix the same way.
luau
--!strict
local coins = 10
print("start:")
print(coins)
coins = coins - 3
print("after shop:")
print(coins)txt
start:
10
after shop:
7Changing five things between runs.
You rename two variables, reorder three lines, and rerun. The error vanishes, or a new one appears, and you learned nothing. Shrink the suspect code into a small test instead, then change one line at a time. Run this small test: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local typed = "7"
local asNumber = tonumber(typed)
print("converted:")
print(asNumber)txt
converted:
7One change, one run, and one reading of the output. Undo changes that did nothing. The labeled print proves conversion works, so the bug must live in the lines you left out.
Leaving assert out and blaming the wrong line.
Run this to see it fail: put it in a Script in ServerScriptService and press Play.
luau
--!strict
local typed = "seven"
local asNumber = tonumber(typed)
print(asNumber + 1)txt
ServerScriptService.Script:4: attempt to perform arithmetic (add) on nil and numberThe red text blames the math line, but the real failure is the conversion above it returning nil. Fix it by asserting right after the conversion so the message names the true cause. Run the fix the same way.
luau
--!strict
local typed = "seven"
local asNumber = tonumber(typed)
assert(asNumber ~= nil, "typed should convert to a number")
print(asNumber + 1)txt
ServerScriptService.Script:4: typed should convert to a numberExercises
Run every solution below in a Script in ServerScriptService and press Play. Try each exercise yourself before you open its solution.
- This script has a typo. Run it, read the line number in red, and fix only that line.
Solution 1
luau
--!strict
local playerName = "Maya"
print(playerName)txt
MayaThe broken line read print(playerNmae). The error named that line and suggested playerName. One spelling fix solves it.
- The output should end at
7but you are not sure where the math drifts. Add two labeled prints, one before and one after the subtraction, and run it.
Solution 2
luau
--!strict
local coins = 10
print("start:")
print(coins)
coins = coins - 3
print("after shop:")
print(coins)txt
start:
10
after shop:
7The labels prove the subtraction ran once and produced 7. If your output differs, the line between your labels is wrong.
tonumber("seven")returnsnil. Write a script that converts"seven", asserts the result is notnilwith the message"must be a number", and prints the value. Run it and read your own message in red.
Solution 3
luau
--!strict
local typed = "seven"
local asNumber = tonumber(typed)
assert(asNumber ~= nil, "must be a number")
print(asNumber)txt
ServerScriptService.Script:3: must be a numberThe assert stops the script and shows your message. That red line is clearer than the arithmetic error you would get one line later.
- This prints
niland you do not know why. Shrink it: print the table length and each position, find the gap, and fix the read with a single-line change.
Solution 4
luau
--!strict
local levels = { 4, 8, 12 }
print(#levels)
print(levels[2])txt
3
8The length is 3, so position 4 never existed. The original script read levels[4]. Changing that one read to levels[2] prints 8. One change and one rerun fix it.
Summary
You read red messages word by word and started from the blamed line. You traced values with labeled prints, shrank bugs into small tests, guarded inputs with assert, isolated causes one change at a time, and decoded unknown globals, arithmetic on nil and strings, and indexing nil.
Next: Syntax and Control Flow.