
As web development has evolved, JavaScript and TypeScript have become the dominant languages of the internet. But what happens when web developers want to branch into game engineering? Roblox, with its 70+ million daily active users and creator economy, is one of the most accessible game platforms in the world.
Does Roblox use JavaScript? No; Roblox executes Luau, a high-performance, strictly-typed derivative of Lua 5.1 created specifically by Roblox engineers for low-latency game engine execution.
Below is your fast Rosetta Stone mapping common JavaScript and TypeScript idioms directly to idiomatic Luau.
roblox-ts, you can write modern TypeScript that compiles directly into production-ready Luau.table[1], not table[0].0 and "" are truthy! Only nil and false evaluate to false.null / undefined: Luau provides a single empty primitive: nil.No - Roblox does not use JavaScript. Roblox uses Luau, a fast, statically-typed scripting language derived from Lua 5.1. While JavaScript and Luau share some surface-level similarities (functions, loops, conditional logic), they are different languages with different syntax, type systems, and runtime environments.
The good news: if you already know JavaScript, the mental model transfer is fast. Most control flow patterns (loops, conditionals, functions) work the same way. The main differences are Lua-style indexing (1-based, not 0-based), the table data structure (which replaces arrays and objects), and Roblox's own APIs for game services.
| JavaScript / TypeScript | Luau Equivalent | Key Difference / Gotcha |
|---|---|---|
let x = 10; const y = 20; | local x = 10; local y = 20 | No native const; all variables scoped with local |
null or undefined | nil | Single falsy empty primitive |
true / false | true / false | In Luau, 0 and "" are truthy! Only nil and false are falsy |
array[0] (0-indexed) | table[1] (1-indexed) | Luau arrays start at index 1 |
array.length | #table | # operator counts contiguous array elements |
array.push(val) | table.insert(tbl, val) | Appends to end of array |
str1 + str2 | str1 .. str2 | .. is the concatenation operator (+ forces math) |
"Hello " + name / template string | string.format("Hello %s!", name) | Or "Hello " .. name .. "!" |
a === b and a !== b | a == b and a ~= b | ~= is the not-equal operator |
&&, ||, ! | and, or, not | Keyword-based boolean logic |
// Comment / /* Block */ | -- Comment / --[[ Block ]] | Double hyphen for comments |
setTimeout(fn, 1000) | task.delay(1, fn) | Uses the modern Roblox task scheduler |
setInterval(fn, 1000) | while true do task.wait(1) fn() end | Or RunService Heartbeat binding |
target.addEventListener('click', fn) | instance.Activated:Connect(fn) | RBXScriptSignal connection contract |
In JavaScript, you have distinct data structures: Arrays for ordered lists and Objects for key-value pairs.
// JavaScript Array
const players = ["Alice", "Bob", "Charlie"];
console.log(players[0]); // "Alice" - 0-indexed!
// JavaScript Object
const playerStats = {
health: 100,
mana: 50,
isAlive: true
};
console.log(playerStats.health); // 100
In Luau, there is only one core composite data structure: the Table. Tables serve as both arrays and hash dictionaries.
-- Luau Array-like Table (1-indexed!)
local players = {"Alice", "Bob", "Charlie"}
print(players[1]) -- "Alice" (Important: Luau is 1-indexed!)
-- Luau Dictionary-like Table
local playerStats = {
health = 100,
mana = 50,
isAlive = true
}
print(playerStats.health) -- 100
| Concept | JavaScript | Luau |
|---|---|---|
| Variable Scope | let / const | local |
| Array Indexing | 0-based (arr[0]) | 1-based (tbl[1]) |
| Nil / Null | null / undefined | nil |
| Async Delays | setTimeout / Promise | task.wait(n) |
In modern JavaScript, classes are syntactic sugar over prototypes:
class Player {
constructor(name) {
this.name = name;
this.health = 100;
}
takeDamage(amount) {
this.health -= amount;
}
}
const p1 = new Player("Hero");
p1.takeDamage(10);
Luau achieves OOP using Metatables and the __index metamethod:
local Player = {}
Player.__index = Player -- When a method is missing, lookup in Player table
function Player.new(name)
local self = setmetatable({}, Player)
self.name = name
self.health = 100
return self
end
function Player:TakeDamage(amount)
self.health = self.health - amount
end
local p1 = Player.new("Hero")
p1:TakeDamage(10) -- Automatically passes self as first argument
Roblox runs on a frame-based engine (60–120 FPS). To prevent blocking the main render thread, Luau uses Coroutines and the built-in Task Library (task.wait, task.spawn, task.defer).
-- Luau Task Library (Yielding)
local function loadGameData()
print("Loading inventory...")
task.wait(2) -- Yields this thread for 2 seconds without freezing the game
print("Inventory loaded")
end
task.spawn(loadGameData)
print("This prints immediately without waiting!")
--!strictIf you love TypeScript, Luau has first-class static typing. Simply add --!strict at the very top of your file:
--!strict
type PlayerProfile = {
name: string,
level: number,
inventory: {string}
}
local function levelUp(player: PlayerProfile): number
player.level += 1
return player.level
end
In web development, frontend client components communicate with backend servers using HTTP fetch(), REST APIs, or WebSocket JSON messages. In Roblox game engines, the client (player's device) and the dedicated game server synchronize state using RemoteEvent and RemoteFunction instances.
-- Server Script (ServerScriptService/RewardSystem.server.lua)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local GiveRewardEvent = Instance.new("RemoteEvent")
GiveRewardEvent.Name = "GiveReward"
GiveRewardEvent.Parent = ReplicatedStorage
GiveRewardEvent.OnServerEvent:Connect(function(player: Player, rewardAmount: number)
-- Crucial: Always validate client payload on the server (never trust the client)
if typeof(rewardAmount) == "number" and rewardAmount > 0 and rewardAmount <= 500 then
print(string.format("Awarded %d coins to %s", rewardAmount, player.Name))
end
end)
-- Client Script (StarterPlayerScripts/RewardButton.client.lua)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local GiveRewardEvent = ReplicatedStorage:WaitForChild("GiveReward") :: RemoteEvent
-- Trigger server action when client UI activates
local function onRequestReward()
GiveRewardEvent:FireServer(100)
end
If you prefer staying in standard TypeScript syntax with npm, Git, and VS Code, you don't even have to write raw Luau. The open-source roblox-ts compiler transpiles standard TypeScript into fully optimized Luau code.
# Initialize a modern roblox-ts project
npm create roblox-ts game-project
cd game-project
npm install
# Start the continuous compiler watcher
npx rbxtsc -w
With roblox-ts, you write standard TypeScript classes, async/await promises, and npm packages (like @rbxts/services), which compile automatically into idiomatic Luau files synced directly into Roblox Studio via Rojo.
In Luau, what will evaluate as falsy in an if-condition?
Roblox runs Luau natively on the client and server engines. However, web developers can write TypeScript and compile it into idiomatic Luau using the popular open-source compiler roblox-ts.
Luau inherits 1-based indexing from Lua 5.1, which was designed for non-programmers and mathematical notation where the first item is element 1.
Legacy wait() throttles down to 30Hz and can suffer from scheduler pipeline lag. Modern task.wait() is synchronized with Roblox's Heartbeat event (running at 60Hz+), making it faster and more deterministic.
Luau does not have a const keyword. All variables are declared with local. For tables, you can enforce immutability at runtime by wrapping the table in table.freeze(myTable).
Understand reference bindings vs value mutability and how table.freeze() parallels Object.freeze().
Bridge game netcode mental models with modern web CRDTs, Yjs, and real-time state synchronization.
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

TypeScript 5.8+ Stage 3 decorators in practice: building lightweight dependency injection, method tracing, and runtime validation without reflect-metadata.
Read more
Lessons from migrating a high-throughput Node.js microservice to Bun 1.2: native SQLite benchmarks, memory footprint drops, and CommonJS/ESM interop edge cases.
Read more
Master advanced enterprise TypeScript patterns: branded types, conditional response types, template literal routing, and the satisfies operator.
Read more