What is Luau, and does Roblox use Lua or Luau?#
Luau is the scripting language used in Roblox Studio, derived from Lua 5.1. Roblox’s Luau overview distinguishes the language from the editor support around it, including completion, linting, and type analysis. For a new Roblox project, learn Luau directly; Lua fundamentals remain useful background.
A Lua lesson about local variables, functions, or table construction can teach concepts that carry into Luau. A lesson that opens a local file, imports a native library, or relies on a later Lua feature needs a compatibility review. Shared ancestry does not establish that a whole program can move between environments unchanged.
The comparison has three layers: language grammar, libraries included with the language implementation, and APIs exposed by the host application. A backtick string belongs to grammar; a table helper belongs to a library; a call that creates a Roblox Part belongs to the engine. Diagnose the layer before changing code.
For third-party listings elsewhere on redzhub.dev, we fetch loaders as text and never run them, so a status is not proof a script works. The examples here are learning exercises for Roblox Studio in an experience you own.
Learning goals and prerequisites#
The lesson assumes familiarity with a local variable, a function call, and a basic table. Readers needing those foundations can start with getting started with Luau before comparing dialects. The examples below are original teaching examples, reviewed against documentation; no execution results are claimed.
By the end, a reader should be able to:
- Identify Luau additions in a short function.
- Annotate a function boundary and recognize an optional value.
- Separate a type-analysis warning from a runtime failure.
- Identify a Roblox-dependent call before transferring code.
- Explain why type checking does not validate client requests.
Follow the sections in order: establish compatibility, read the added syntax, introduce types, then attach a small calculation to a Roblox object. The comparison table provides the reference point for that sequence.
Luau vs Lua at a glance#
Luau shares a Lua 5.1 baseline but follows its own development path. The Luau compatibility reference documents selective adoption of later Lua features, omissions, and behavior differences. The Lua column below refers specifically to Lua 5.1 syntax and its standard libraries, rather than treating every Lua release as interchangeable.
| Topic | Lua 5.1 baseline | Luau in Roblox development |
|---|---|---|
| Local variables and functions | Core language constructs | Familiar baseline remains useful |
| Optional type annotations | No Luau annotation syntax | Supports annotations and inference |
| Strict analysis directive | No Luau analysis mode | --!strict enables strict checking |
| Embedded expressions in strings | Concatenation is available | Backtick interpolation is available |
| Updating an existing value | score = score + 1 | Also supports score += 1 |
| Skipping a loop iteration | Usually restructure control flow | continue is available |
| Iterating a table | Iterator functions such as pairs | Also supports direct table iteration |
| Conditional value selection | Statement or logical expression | Also supports if expressions |
| Direct file access | Standard libraries include file access | Removed from Luau’s sandboxed libraries |
| Roblox object model | Not included in Lua itself | Provided by Roblox’s host environment |
Before moving code, name its destination. If you are building only for Studio, you can use Luau grammar and Roblox APIs deliberately. A reusable algorithm destined for a Lua 5.1 host needs a separate review of grammar, libraries, and behavior; removing type annotations addresses only part of that task.
Mark each dependency before translating anything so you know which kind of change it needs. Highlight an annotation as a grammar dependency, an unavailable library call as a library dependency, and workspace as a host dependency. Rewriting a loop will not provide a missing object model, and replacing an API call will not teach a Lua parser new syntax. Start with grammar changes, then check the values and engine dependencies separately.
Which syntax additions change everyday Roblox code?#
Luau adds several ways to express calculations, branching, and iteration beyond the Lua 5.1 baseline. Start with the examples below to see how those additions affect a counter, a setting, and a filtered list. Luau’s syntax reference documents the wider grammar and restrictions.
Compound assignment and backtick strings#
Compound assignment updates an existing variable or indexed value. Backtick interpolation places an expression inside braces within a string. For a small progress counter, the two features keep the updated value and its display close together:
local completed = 2
local total = 5
completed += 1
local message = `Completed {completed} of {total}`
print(message)
For these values, the expression constructs the text Completed 3 of 5. The corresponding baseline style is completed = completed + 1 followed by concatenation. Neither style changes what a progress counter means; Luau provides additional notation for describing the same calculation.
A compound assignment is a statement. Keep completed += 1 on its own line rather than attempting to pass it as an argument to print. Backticks also differ from ordinary quotes: braces in a quoted string are plain characters, while braces inside the backtick form introduce an expression.
Can an if expression preserve a false result?#
An if expression can select false as its actual result. A logical chain such as condition and value or fallback can instead select the fallback when the intended value is false. Roblox’s control-structure documentation explains that false and nil are falsey, while zero and empty strings are truthy.
local useOverride = true
local overrideValue = false
local enabled = if useOverride then overrideValue else true
print(enabled)
The selected value is false because useOverride selects overrideValue. The expression has an else branch and supplies one value to the assignment. Use an ordinary if statement when the branches need several actions; use the expression when the purpose is choosing a value.
Avoid importing assumptions from languages where zero means false in a condition. A quantity of zero needs an explicit comparison such as quantity > 0 when positive quantity is the intended rule. Truthiness describes branching behavior, not whether a numeric value is acceptable for the application.
Generalized iteration and continue#
Generalized iteration permits a table directly after in. The continue statement skips the rest of the current iteration, while break exits the loop. The distinction matters when filtering a list instead of abandoning the whole task.
local quantities = {3, 0, 2}
local total = 0
for _, quantity in quantities do
if quantity <= 0 then
continue
end
total += quantity
end
print(total)
The calculation adds the positive entries, giving a total of five. Replacing continue with break would stop at the zero and omit the final entry. A baseline-style version can put the addition inside if quantity > 0 then and iterate with ipairs(quantities).
Choose iteration to match the data model. The example uses a dense list and does not depend on dictionary traversal order. A dictionary of item names should not become a display-order contract accidentally; store a separate ordered list when presentation order is part of the requirement.
The snippets isolate syntax so that a reader can reason about one change at a time. Type annotations add a second dimension: expectations about the values moving through those statements.
How does Luau type checking differ from runtime behavior?#
Luau type checking analyzes expected value shapes before execution; runtime behavior is what happens when statements execute with actual values. Gradual typing allows typed and less constrained code to coexist. Luau’s type introduction describes inference and annotations as complementary tools rather than requiring an annotation on every local variable.
Start adding annotations where a function accepts input and returns a result. For example, this score adjustment accepts two numbers and returns one number:
--!strict
local function addPoints(current: number, bonus: number): number
return current + bonus
end
local score = addPoints(12, 4)
print(score)
The parameter annotations describe the expected arguments, and the return annotation describes the result. Luau can infer the local score type from the function result, so you do not need to repeat the annotation. Changing the call to supply a table would violate the declared parameter contract; keep intentionally invalid variations outside the runnable example.
Choosing strict, nonstrict, or nocheck#
Luau provides --!strict, --!nonstrict, and --!nocheck directives for per-file analysis behavior. Put the selected directive at the top of the script. Roblox’s type-checking guide describes how mismatches appear in the editor and Script Analysis.
For a small function you are learning to write, strict analysis helps you check its declared inputs. When moving an existing project to stricter checking, review one file at a time and investigate its warnings. A warning can reveal a missing assumption, such as a function that sometimes returns no value.
Disabling checking does not repair an invalid operation. A runtime error can remain after the analyzer stops reporting it. Conversely, a static warning calls for investigation of the stated types and control flow; changing gameplay behavior blindly just to remove a warning can introduce a different defect.
What does an optional type mean?#
An optional type includes nil as a possible value. A value annotated string? therefore needs a branch that handles absence before code assumes a string. The following helper chooses a display label without asserting that a missing name exists:
--!strict
local function displayName(name: string?): string
if name == nil then
return "Unnamed"
end
return name
end
print(displayName(nil))
print(displayName("Builder"))
The nil branch returns Unnamed immediately. When execution reaches the remaining return, the name is a string. You can change the fallback text without changing that policy: missing input is allowed, and the function still promises its caller a string in either case.
A cast such as value :: SomeType changes the type assertion presented to analysis; it does not manufacture an object or validate a remote payload. Prefer a branch that checks an uncertain value when the uncertainty exists at runtime. A declared table type becomes more useful once those boundary assumptions are explicit.

A worked example with a typed table#
A typed record makes the fields required by a calculation visible. The example below describes a small lesson score with a name and a list of numeric entries. Roblox’s type-checking reference covers table annotations; the sample values here are invented lesson inputs, not measurements from a game.
--!strict
type ScoreSheet = {
name: string,
entries: {number},
}
local function summarize(sheet: ScoreSheet): string
local total = 0
for _, points in sheet.entries do
if points <= 0 then
continue
end
total += points
end
local label = if total >= 10 then "Goal reached" else "Keep building"
return `{sheet.name}: {total} points - {label}`
end
local sheet: ScoreSheet = {
name = "Practice",
entries = {4, 0, 7},
}
print(summarize(sheet))
Read the sample by following the data from its declaration into the function. ScoreSheet describes the record; summarize accepts that shape and returns text; the loop chooses which entries contribute. The threshold is a rule chosen for this exercise. Changing it changes when the player reaches the goal; it does not change which language features the code needs.
The expected text by calculation is Practice: 11 points - Goal reached. The zero contributes nothing, while four and seven sum to eleven. The name appears through interpolation, and the if expression selects the label. No game service is required for the calculation.
To adapt the function to Lua 5.1, remove the type syntax, use an iterator function, restructure the skipped iteration, replace the conditional expression, and build the string differently. The result can preserve the same rule while changing notation. To move the function to another Luau host, check the available libraries and how that host displays output.
The example also exposes a design choice: nonpositive entries are ignored. A different scoring system might reject negative input or subtract penalties. Types cannot choose between those meanings because all three designs can accept numbers. Put such decisions in the function’s documented contract before connecting it to gameplay.
A calculation built from ordinary values is easier to inspect separately from an engine call. The next example adds the Roblox boundary without changing the arithmetic lesson.
Which features belong to Roblox rather than Luau?#
Roblox supplies services, Instances, properties, and execution contexts around Luau. A standalone Luau host does not automatically provide game, workspace, or the Roblox class hierarchy. The Instance reference documents object creation and methods separately from language grammar.
Place the following original example in a new Script under ServerScriptService in an experience you own. Keep its default Legacy run context for this placement, or deliberately use Server. Roblox’s script-location guide explains how container and run context determine execution.
--!strict
local marker = Instance.new("Part")
marker.Name = "LessonMarker"
marker.Anchored = true
marker.Size = Vector3.new(4, 1, 4)
marker.Position = Vector3.new(0, 5, 0)
marker.Parent = workspace
print(`Created {marker.Name}`)
The expected effect is an anchored Part sized 4 by 1 by 4 at position (0, 5, 0). local and assignment are language syntax; Instance.new, Vector3.new, and the Part properties come from Roblox. The printed message uses Luau interpolation around a Roblox property value.
A missing object property calls for checking the object’s class and API reference. A syntax error near a backtick in a Lua-only tool calls for checking the parser. Neither problem justifies treating every issue as a difference between Lua and Luau.
Execution context adds another boundary. A LocalScript runs on the client, while a ModuleScript supplies reusable code to callers rather than acting as an independent entry point. Learn the communication boundary next through RemoteEvents and RemoteFunctions, especially before letting a client request a change to shared game state.

Sandboxing, file access, and performance limits#
Luau’s library restrictions remove direct file access through io, loadfile, and dofile. The os library retains selected timing and date functions rather than unrestricted operating-system access. The sandbox documentation explains the restrictions and the responsibilities of a host that exposes additional APIs.
A desktop Lua example that reads a local configuration file therefore needs a different data boundary in Roblox. Choose the Roblox service appropriate to the actual task instead of assuming a replacement with the same function name exists. Language compatibility cannot grant a script permissions its host does not expose.
Sandbox restrictions and game authorization answer different questions. Restricting host access does not determine whether a player is entitled to a reward. Roblox requires server-side validation of client input, including context, structure, and value checks, in its client-server boundary guidance.
Third-party loader checks have a separate limit: we fetch loaders as text and never run them, so a status is not proof a script works. Keep that distinction in mind when comparing an index listing with a Studio exercise you can inspect yourself.
Luau optimizations do not tell you how fast your particular game will run. Luau’s performance documentation describes compiler and VM optimizations, but those descriptions are not a benchmark for your project. A syntax comparison cannot predict the cost of a particular game’s physics, rendering, networking, and script workload.
getfenv and setfenv deserve special attention when reading older code. Luau documents that environment access can disable optimizations associated with predictable globals. Prefer explicit parameters and module dependencies when those express the requirement; avoid introducing environment mutation as a shortcut for sharing state.
Strict annotations alone do not establish an end-to-end performance improvement. Profile the actual workload before selecting an optimization, and preserve behavior while changing implementation. For defensive application design beyond types, continue with Roblox game security basics. The practice tasks below focus on semantics a reader can reason through without a benchmark.
Practice, expected answers, and migration checks#
Practice becomes easier to follow when you change one assumption at a time and predict the result before running the example. Use a separate Script for each complete example rather than concatenating fragments with duplicate local names. Roblox’s Studio testing modes explain simulation controls and client/server views; consult those controls when checking your own experience.
- Change the score list to
{2, -1, 8}. The example still totals ten because its stated rule ignores nonpositive entries. The selected label remainsGoal reached. - Change the threshold to twelve. The original list totals eleven, so the selected label becomes
Keep building. The types remain valid while application behavior changes. - Pass nil to the display-name helper. The early return selects
Unnamed; removing that branch would remove the explicit handling of absence. - Replace continue with break. The original score list stops at zero, leaving four instead of eleven. The loop now represents a different algorithm.
- Move the Part example to a standalone Lua interpreter. Roblox globals and constructors are missing there, independently of any Luau grammar changes.
The answers above follow from reading the code; we have not run these examples in Studio. While reviewing a migration, first confirm the target parser, then inspect required libraries, then inspect host APIs and execution context. Finally, check boundary values and application rules, because successful parsing cannot establish intended behavior.
A portable arithmetic helper and an engine-dependent entry point need different review questions. Keep that distinction visible in a code review: identify the changed assumption, explain the expected result, and name the environment needed to check it. Use those same questions to choose what to learn next.
Choosing a learning path after Luau vs Lua#
Roblox development calls for Luau fundamentals plus Roblox API knowledge. Start with small typed functions when learning value flow; add Instances and services once the calculation is understandable. Developers targeting another Lua host should begin with that host’s documented language version and libraries before adopting Luau-specific notation.
The Luau learning index brings together the next lessons. Our index methodology explains how we review third-party listings and assign their status labels.
If you are coming from a Lua tutorial, keep a short translation record as you work. Record the original statement, the reason for changing it, and the intended observable result. For example, replacing concatenation with interpolation is a notation change; replacing a local file read with a Roblox data boundary is an architectural change. Treating both edits as syntax substitutions hides the second decision.
If you already know another typed language, start with function inputs and outputs before annotating every intermediate expression. Start with what a function promises its caller and what absence means in its result. The display-name example has a concrete absence policy, while the score-sheet example has a concrete field shape. An annotation is most useful when a reviewer can explain which misunderstanding it prevents.
When reviewing a growing Studio project, give the next developer three concrete things to check. First, explain the expected calculation using a tiny input. Second, state which objects and execution context the entry point requires. Third, identify which inputs come from outside the trusted calculation and need validation. A readable function is easier to reason about when those assumptions are recorded alongside it.
A migration exercise should also preserve a copy of the initial example. Make one change, predict its effect, and compare the revised statements with that prediction. Changing the score rule and the loop notation together makes a discrepancy harder to locate. Keeping the rule fixed while translating notation provides a clearer learning task; changing the rule afterward becomes a separate exercise in application design.
Persistence, network handlers, and interface construction each need further learning. Each adds decisions about data and behavior beyond the language comparison. The Part example supplies a visible engine boundary, while the scoring example supplies a calculation that can be reviewed independently. Combining those patterns into a larger experience requires additional API research and checks of the specific feature being built.
Use Luau vs Lua as a practical review sequence: identify the language, describe the values, name the host dependency, and check the intended rule. Keep those questions separate while learning, then combine them when building and reviewing a Roblox experience.