5 Luau Mistakes Every New Roblox Developer Makes
March 12, 2026
Learning Luau is the first step to building awesome Roblox games. But every new developer runs into the same set of traps. This guide covers the five most common Luau scripting mistakes and shows you exactly how to avoid them. If you are just starting out, bookmark this one.
1. Not Using --!strict Mode
Luau has a built-in type checker that catches bugs before your game even runs. The problem? It is turned off by default. Most new developers never turn it on, so they spend hours debugging errors that the type checker would have caught instantly.
Add this line at the very top of every script you write:
--!strict
local Players = game:GetService("Players")
local function getPlayerCoins(player: Player): number
local leaderstats = player:FindFirstChild("leaderstats")
if not leaderstats then
return 0
end
local coins = leaderstats:FindFirstChild("Coins")
if not coins then
return 0
end
return coins.Value
endWith --!strict enabled, Luau will underline type mismatches, missing arguments, and nil access right in the Script Editor. It takes two seconds to add and saves you hours of debugging.
2. Putting Server Code in LocalScripts
This is the number one security mistake beginners make. If you put game logic (like giving coins, saving data, or spawning items) inside a LocalScript, exploiters can manipulate it. LocalScripts run on the player's computer. Anything running there can be modified by cheaters.
The rule is simple: if the code changes game state (data, currency, inventory), it belongs in a ServerScript inside ServerScriptService. LocalScripts handle UI and input only.
-- DO NOT DO THIS in a LocalScript!
local player = game.Players.LocalPlayer
player.leaderstats.Coins.Value = player.leaderstats.Coins.Value + 100--!strict
-- ServerScript in ServerScriptService
local Players = game:GetService("Players")
local function awardCoins(player: Player, amount: number)
local leaderstats = player:FindFirstChild("leaderstats")
if leaderstats then
local coins = leaderstats:FindFirstChild("Coins")
if coins then
coins.Value += amount
end
end
end3. Not Using pcall for DataStore Operations
DataStore calls can fail at any time. Roblox's DataStore service has rate limits and the servers occasionally hiccup. If you call GetAsync or SetAsync without wrapping them in pcall, your script will error and break.
local DataStoreService = game:GetService("DataStoreService")
local store = DataStoreService:GetDataStore("PlayerData")
-- This will CRASH your script if the call fails
local data = store:GetAsync("Player_123")--!strict
local DataStoreService = game:GetService("DataStoreService")
local store = DataStoreService:GetDataStore("PlayerData")
local MAX_RETRIES = 3
local function safeGet(key: string): any?
for attempt = 1, MAX_RETRIES do
local success, result = pcall(function()
return store:GetAsync(key)
end)
if success then
return result
end
warn("DataStore get failed (attempt " .. attempt .. "): " .. tostring(result))
task.wait(1)
end
warn("DataStore get failed after " .. MAX_RETRIES .. " attempts")
return nil
endAlways wrap DataStore calls in pcall. Add a retry loop for extra reliability. Your players will thank you when their save data does not randomly vanish.
4. Ignoring RemoteEvent Validation
RemoteEvents let the client talk to the server. But here is the catch: exploiters can fire RemoteEvents with any data they want. If your server just trusts whatever the client sends, you are wide open to cheating.
-- ServerScript
local remote = game.ReplicatedStorage.BuyItem
remote.OnServerEvent:Connect(function(player, itemName, price)
-- DANGER: An exploiter can send price = 0
player.leaderstats.Coins.Value -= price
giveItem(player, itemName)
end)--!strict
local remote = game.ReplicatedStorage.BuyItem
local ITEM_PRICES: {[string]: number} = {
Sword = 100,
Shield = 75,
Potion = 25,
}
remote.OnServerEvent:Connect(function(player: Player, itemName: unknown)
-- Validate the type
if typeof(itemName) ~= "string" then
return
end
-- Look up the REAL price on the server
local price = ITEM_PRICES[itemName]
if not price then
return
end
local coins = player.leaderstats.Coins
if coins.Value >= price then
coins.Value -= price
giveItem(player, itemName)
end
end)Never trust data from the client. Always validate types, check against server-side values, and treat every RemoteEvent fire as potentially malicious.
5. Hardcoding Values Instead of Using ModuleScripts
New devs love to sprinkle magic numbers all over their code. The sword does 25 damage in one script, but then you have to find and change that number in five different places. ModuleScripts solve this.
-- Script 1: SwordHandler
humanoid:TakeDamage(25)
-- Script 2: ShopUI
priceLabel.Text = "Sword: 100 coins"
-- Script 3: CombatLog
print(player.Name .. " dealt 25 damage")--!strict
-- ModuleScript in ReplicatedStorage/Config/WeaponStats
local WeaponStats = {
Sword = {
damage = 25,
price = 100,
cooldown = 0.8,
},
Bow = {
damage = 15,
price = 150,
cooldown = 1.2,
},
}
return WeaponStats--!strict
local WeaponStats = require(game.ReplicatedStorage.Config.WeaponStats)
-- Now changing damage is a one-line edit in WeaponStats
humanoid:TakeDamage(WeaponStats.Sword.damage)Put your game constants in a ModuleScript inside ReplicatedStorage. Every script that needs those values just require() the module. One place to edit, zero chance of mismatched values.
Wrapping Up
These five mistakes trip up almost every new Roblox developer. The good news is they are all easy to fix. Turn on --!strict, keep game logic on the server, wrap DataStore calls in pcall, validate RemoteEvents, and use ModuleScripts for shared config. Do that and you are already ahead of most beginners.
Ready to try BloxCode?
Get AI-powered Roblox development help for free. No credit card required.
Start Building for Free