Reaching Into the Tree From Code
script.Parent works beautifully until somebody renames a part, and then it fails with a message about indexing nil. Compare the four ways to find an object and see which survive a part that has not loaded yet.
Worth reading first: Everything Is an Instance in a Tree
script.Parent works beautifully until somebody renames a part, and then it fails with a message about indexing nil. Compare the four ways to find an object and see which of them survive a part that has not loaded yet.
script.Parent is the shortest path, and the most fragile
Inside any script, the global script refers to that script itself. So script.Parent is whatever it is sitting inside — for a killbrick, the laser part.
local laser = script.Parent -- the part this script lives in laser.Touched:Connect(function(otherPart) print("hit", otherPart.Name)end)This is the right tool for a script that belongs to one part, and it has a real advantage: duplicate the laser and the copy works immediately, with no names to update. Nothing about the path depends on what anything is called.
It becomes fragile the moment you chain it. script.Parent.Parent.Parent encodes the exact depth of your model into the script, and reorganising the Explorer — dropping the laser into a folder, say — silently changes what that expression means.
Dot notation is a lookup that can fail
workspace.Obby.Laserlooks like a path in a language that has paths. It is not. Each dot is a search of that object's children for one matching the name, done freshly, at the moment the line runs.
So the expression is only as reliable as the names in it. If Laser has been renamed, is inside a folder now, or has not loaded yet, that dot returns nil — and the next dot is the one that throws.
workspace.Obby.Laser.Transparency = 0.5 -- If Obby exists but Laser does not:--> attempt to index nil with 'Transparency'-- The error names Transparency. The missing thing is Laser.FindFirstChild asks without crashing
FindFirstChild does the same search and returns nil instead of throwing. That single difference is what lets you check before you act.
local laser = workspace.Obby:FindFirstChild("Laser") if laser then laser.Transparency = 0.5else warn("No Laser in Obby — was it renamed?")endThe warn in the else branch is doing real work. A script that silently does nothing when a part is missing is worse than one that crashes, because a crash at least tells you where to look.
There is a family of these, and two are worth knowing by name.
Searches direct children by name. Add true as a second argument to search all descendants — slower, and usually a sign the structure is wrong.
Searches by class rather than name. This is how you find a Humanoid without caring what the character is called.
Searches upward instead of down. The reliable way to get from a hat's handle back to the character wearing it.
WaitForChild is for things that arrive late
A Roblox place does not load all at once. On the client especially, a script can run before the object it needs has replicated in — so a lookup fails not because the object is missing but because it is late.
WaitForChild yields the script until the child appears, then returns it.
local obby = workspace:WaitForChild("Obby")local laser = obby:WaitForChild("Laser") -- Both exist by the time this line runs.laser.Transparency = 0.5It waits indefinitely by default, and after five seconds it prints an "Infinite yield possible" warning to the Output window. That warning is not an error and the script has not stopped — it is Roblox telling you the thing you are waiting for may never arrive, which usually means a typo.
Use WaitForChild when
You are on the client, or early in a server script, and the object is expected to exist. Waiting is correct — the object is coming.
Use FindFirstChild when
The object genuinely might not exist and your script has something sensible to do about that. Waiting forever for something optional is a hang, not a guard.
Setting a property is a line, not a method
Roblox has no getters and setters. A property is read and written with =, using exactly the name shown in the Properties panel — including its capitals.
local part = script.Parent part.Transparency = 0.5part.CanCollide = falsepart.BrickColor = BrickColor.new("Really red")part.Material = Enum.Material.NeonTwo things there are not plain values. BrickColor and Color3 are constructed types, and Material is an Enum — a fixed set of named options. Writing part.Material = "Neon" with a plain string fails, because the property wants the enum item and not text that resembles it.
Key takeaways
- script refers to the script itself, so script.Parent is the object it lives inside — and duplicating that object copies a working script with it.
- Chained script.Parent.Parent encodes your Explorer layout into the code. Reorganising the tree silently changes what it means.
- Dot notation is a fresh search by name every time the line runs, not a stored path.
- The nil error always names the step after the one that failed. Read it as 'the thing to the left was nil'.
- FindFirstChild returns nil instead of throwing, which is what lets you check before acting. Warn in the else branch.
- FindFirstChildWhichIsA searches by class; FindFirstAncestorOfClass searches upward. Both matter for the killbrick.
- WaitForChild yields until the object replicates in. 'Infinite yield possible' after five seconds is a warning, not an error, and usually means a typo.
- Wait for things that are coming; Find things that might not exist. Waiting forever for something optional is a hang.
- Properties are set with =, using the exact name and capitals from the Properties panel. Material wants an Enum, not a string.
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.