---
url: /programming-fundamentals/debugging-basics.md
description: >-
  Read diagnostics, trace with print, isolate bugs, and decode common error
  messages.
---

# Debugging Basics

*Written: {{ $frontmatter.date }}*

## Learning objectives

* You read a red error message word by word and find the line it blames.
* You trace code with `print` to 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](../getting-started/first-script.md). You can run a Script and open output.
* Read [Variables and Values](variables-and-values.md). You create variables with `local`.
* Read [Operators and Logic](operators-and-logic.md). You compare values and convert with `tonumber`.
* Read [Conditionals and Loops](conditionals-and-loops.md). You read `if` branches and loops.
* Read [Functions and Scope](functions-and-scope.md). You define and call functions.
* Read [Tables, Part 1](tables-part-1.md). You read keys and positions.
* Read [Strings, Numbers, and Nil](strings-numbers-nil.md). You know what [nil](https://create.roblox.com/docs/luau/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:

1. Line 1 turns on strict mode. Strict mode catches misspelled names for you.
2. Line 2 creates `nickname`.
3. Line 3 prints `nicknam`, which does not exist. The red message names line `3` and 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
Max
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 creates `nickname`.
3. Line 3 prints it correctly, so the output is `Max` with 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:
7
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 stores `10` in `coins`.
3. Lines 3 and 4 print the label `start:` and the starting value `10`.
4. Line 5 subtracts `3`.
5. Lines 6 and 7 print the label `after shop:` and the new value `7`.

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
8
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 stores the string `"7"`.
3. Line 3 converts it to the number `7`.
4. Line 4 prints `7`, proving the conversion worked.
5. 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
8
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 stores the string `"7"`.
3. Line 3 converts it.
4. Line 4 checks `asNumber ~= nil`. The conversion worked, so the assert passes silently.
5. 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
nil
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 builds a three-item array.
3. Line 3 prints position `3`, which is `12`.
4. Line 4 prints position `4`, which does not exist, so `nil`.

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
4
```

**Arithmetic on nil.** You did math with a variable holding [nil](https://create.roblox.com/docs/luau/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 nil
```

Line 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
15
```

**Arithmetic on string.** You used `+` on text. `+` needs [numbers](https://create.roblox.com/docs/luau/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 number
```

Fix 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
8
```

**Index 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
Maya
```

## Common 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
4
```

**Printing 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
7
```

The 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:
7
```

**Changing 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:
7
```

One 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 number
```

The 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 number
```

## Exercises

Run every solution below in a Script in ServerScriptService and press Play. Try each exercise yourself before you open its solution.

1. This script has a typo. Run it, read the line number in red, and fix only that line.

::: details Solution 1

```luau
--!strict
local playerName = "Maya"
print(playerName)
```

```txt
Maya
```

The broken line read `print(playerNmae)`. The error named that line and suggested `playerName`. One spelling fix solves it.

:::

2. The output should end at `7` but you are not sure where the math drifts. Add two labeled prints, one before and one after the subtraction, and run it.

::: details Solution 2

```luau
--!strict
local coins = 10
print("start:")
print(coins)
coins = coins - 3
print("after shop:")
print(coins)
```

```txt
start:
10
after shop:
7
```

The labels prove the subtraction ran once and produced `7`. If your output differs, the line between your labels is wrong.

:::

3. `tonumber("seven")` returns `nil`. Write a script that converts `"seven"`, asserts the result is not `nil` with the message `"must be a number"`, and prints the value. Run it and read your own message in red.

::: details 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 number
```

The assert stops the script and shows your message. That red line is clearer than the arithmetic error you would get one line later.

:::

4. This prints `nil` and 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.

::: details Solution 4

```luau
--!strict
local levels = { 4, 8, 12 }
print(#levels)
print(levels[2])
```

```txt
3
8
```

The 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](../luau-basics/syntax-and-control-flow.md).
