Appearance
Strings and Libraries
Written: 2026-10-14
Learning objectives
- You interpolate with backticks and escape safely.
- You format with string.format verbs.
- You search and replace with string patterns.
- You split and slice with sub and split.
- You apply math essentials with confidence.
- You tour the table library for string-adjacent work.
Prerequisites
Backtick interpolation builds readable text
Backtick strings let you drop expressions into text. Wrap the literal in backticks. Put each expression in single braces. Luau evaluates the braces and inserts the results. The string guide in strings shows the core shapes.
Run this pure snippet in the Command Bar.
luau
--!strict
local name = "Mara"
local wins = 3
local line = `{name} has {wins} wins`
print(line)txt
Mara has 3 winsTwo locals go into one literal. Braces accept any expression, including math and calls. Prefer this over long .. chains for messages, labels, and logs.
Run this pure snippet in the Command Bar.
luau
--!strict
local price = 25
print(`Total: {price * 2} coins`)txt
Total: 50 coinsThe brace holds price * 2. Luau computes 50 and inserts it. The sentence stays intact.
Backticks do not accept {{ as an escaped brace. Two opening braces in a row are a parse error. This rule stops confusion with languages that double braces to escape them. The restriction is documented in the syntax tour. You insert a literal brace with an escape instead.
Escapes keep special characters literal
Inside backticks, a backslash escapes the next character: backslash backtick for a literal backtick, backslash brace for a literal brace, and double backslash for a literal backslash. You can also escape a newline to continue a long literal.
Run this pure snippet in the Command Bar.
luau
--!strict
local shown = `Use \`backticks\` around code`
print(shown)txt
Use `backticks` around codeEach backslash backtick pair produces one visible backtick. The string stays one literal. You use the same habit for braces when you show template syntax to players.
Quoted strings use their own escapes. Double-quoted strings escape inner doubles with backslash. Single-quoted strings escape inner singles the same way. Newlines use \n and tabs use \t. These rules also live in strings.
String indices are 1-based everywhere. The first character is position 1. Negative positions count back from the end, so negative 1 is the last character. You carry this habit from tables into every slice and search.
string.format handles verbs and layout
Use string.format when you need verbs, padding, or precision. The first argument is a template with verbs. Later arguments fill the verbs in order. Common verbs include %s for strings, %d and %i for integers, %f for floats, %q for quoted re-readable strings, %x for hex, and %% for a literal percent sign.
Run this pure snippet in the Command Bar.
luau
--!strict
print(string.format("%s has %d coins", "Mara", 42))txt
Mara has 42 coins%s takes the name. %d takes the count. You keep the sentence shape while Luau converts each value.
Run this pure snippet in the Command Bar.
luau
--!strict
print(string.format("Price: %.2f", 3.5))
print(string.format("Hex: %x", 255))
print(string.format("Safe: %q", "say hi"))
print(string.format("%d%% sure", 100))txt
Price: 3.50
Hex: ff
Safe: "say hi"
100% sure%.2f rounds to two decimals for shop prices. %x prints lowercase hex for colors and ids. %q adds quotes and escapes so you can paste the value back into code. %% emits one percent sign. You pick format for layout work and backticks for plain insertion.
Patterns find shapes, not just text
A pattern describes a shape in text. %d matches any digit. %a matches any letter. %s matches whitespace including spaces and newlines. %w matches letters and digits. %l matches lowercase, %u matches uppercase, %p matches punctuation, %x matches hex digits. Uppercase versions invert the class, so %D means non-digit. The full class table sits in strings.
Modifiers widen a class. + means one or more. * means zero or more. - means as few as possible. ? means zero or one. Anchors pin the match: ^ pins to the start, $ pins to the end. Sets group choices in brackets. Captures in parentheses save pieces for later.
Run this pure snippet in the Command Bar.
luau
--!strict
print(string.match("Room 42B", "%d+"))
print(string.match("hello world", "%a+"))txt
42
hello%d+ grabs the digit run 42. %a+ grabs the letter run hello. Each call returns the first match. You add captures when you need pieces.
string.find returns start and end positions instead of text. It returns nil when nothing matches. Pass an init position to start later in the string. Use true as the fourth argument for a plain search with no pattern magic.
Run this pure snippet in the Command Bar.
luau
--!strict
local s = "hi there, hi again"
local a, b = string.find(s, "hi", 4)
print(a)
print(b)
print(string.find(s, "bye"))txt
10
11
nilThe search starts at 4 and finds the second hi at 10 through 11. The missing word returns nil. You guard nil before you slice with the positions.
string.gsub replaces every match and returns the new string plus a count. It accepts a string, a table, or a function as the replacement. Captures %1 through %9 reuse saved pieces.
Run this pure snippet in the Command Bar.
luau
--!strict
local out, n = string.gsub("I have 2 cats", "%d+", "3")
print(out)
print(n)txt
I have 3 cats
1The digit run becomes 3. The count reports one substitution. You cap replacements with a fourth numeric argument when only the first hits should change.
string.sub slices by byte positions. Both ends have defaults: start defaults to 1, finish defaults to negative 1. Out-of-range ends clamp. A start past the finish returns an empty string.
Run this pure snippet in the Command Bar.
luau
--!strict
local file = "script.lua"
local dot = string.find(file, "%.")
print(dot)
print(string.sub(file, dot + 1))
print(string.sub(file, 1, 6))txt
7
lua
scriptEscape the dot as %. because a bare dot means any character. The dot sits at 7. Slicing after it gives lua, and slicing 1 through 6 gives script. Combine find plus sub for extensions, tags, and prefixes.
string.split chops on a separator and returns an array. The default separator is a comma. Empty slices survive as empty strings. Surrounding whitespace stays put.
Run this pure snippet in the Command Bar.
luau
--!strict
local parts = string.split("a,b,c", ",")
print(#parts)
print(table.concat(parts, "|"))
local kept = string.split("x,,y", ",")
print(#kept)
print(kept[2] == "")txt
3
a|b|c
3
trueThree names split into three entries. The double comma keeps an empty middle entry, so the second table also has length 3 with an empty string at 2. Split suits CSV settings, chat commands, and simple configs. Use patterns when the separator varies.
Math essentials cover games
The math library holds rounding, clamping, roots, powers, randomness, and constants. Call it through the math table. Gameplay code rarely needs more.
Run this pure snippet in the Command Bar.
luau
--!strict
print(math.floor(4.9))
print(math.ceil(4.1))
print(math.round(4.5))
print(math.abs(-7))
print(math.min(3, 9, 1))
print(math.max(3, 9, 1))txt
4
5
5
7
1
9Floor drops down, ceil lifts up, round goes to the nearest integer away from zero at halves. Abs strips sign. Min and max take any number of arguments. You use these for grid snaps, damage bounds, and leaderboard extremes.
Run this pure snippet in the Command Bar.
luau
--!strict
print(math.clamp(15, 1, 10))
print(math.sqrt(16))
print(math.pow(2, 3))
print(math.pi)txt
10
4
8
3.1415926535898Clamp pins 15 into 1 through 10. Sqrt inverts squares. Pow raises 2 to 3. Pi ships as a constant for circles and waves. You pair clamp with health, speed, and UI ranges.
Run this pure snippet in the Command Bar.
luau
--!strict
print(math.random(1, 6))txt
4The output shows one possible die roll. Your run prints a value from 1 through 6. Call math.randomseed only when you need repeatable sequences for tests. Most games accept the default seed and call math.random directly.
tonumber("42") returns 42 and tonumber("hi") returns nil. Math on a non-numeric string errors, so validate with tonumber before you compute on player input.
Table helpers support string work
String work often ends in tables. Split returns arrays. Captures fill arrays. You join results with table.concat and search them with table.find. The deep tour lives in Tables in Depth and the reference in tables.
Run this pure snippet in the Command Bar.
luau
--!strict
local line = "tank,healer,dps"
local team = string.split(line, ",")
print(table.find(team, "healer"))
print(table.concat(team, " + "))txt
2
tank + healer + dpsOne line splits into three roles. Find locates "healer" at 2. Concat joins with a readable separator. This split, find, join loop covers most lobby and chat parsing.
Common mistakes
Wrong: you double braces to escape in backticks.
luau
--!strict
local x = 1
print(`{{x}}`)Studio reports a parse error on {{. Fix it with a backslash escape for a literal brace, or drop the extra brace.
luau
--!strict
local x = 1
print(`\{x} is {x}`)txt
{x} is 1Wrong: you forget that a dot is magic in patterns.
luau
--!strict
print(string.match("go to roblox#com", "roblox.com"))This matches roblox#com because . means any character. Fix it by escaping the dot.
luau
--!strict
print(string.match("go to roblox.com", "roblox%.com"))txt
roblox.comWrong: you index strings from 0.
luau
--!strict
print(string.sub("hello", 0, 1))Luau clamps 0 to 1 and returns h, which confuses zero-based readers. Fix it by starting at 1.
luau
--!strict
print(string.sub("hello", 1, 1))txt
hWrong: you do math on raw player text.
luau
--!strict
local raw = "ten"
print(raw + 5)Studio raises attempt to perform arithmetic (add) on string and number. Fix it with tonumber and a nil guard.
luau
--!strict
local raw = "10"
local n = tonumber(raw)
print(if n ~= nil then n + 5 else 0)txt
15Exercises
- Interpolate a welcome line with a name and a level. Include a literal brace via escape.
- Format a shop price with two decimals, a hex color id, and a quoted item name.
- Extract all numbers from a sentence with
%d+and join them with-. - Split a comma list, find one entry, replace a word with gsub, and clamp a math result.
Solution 1
Run this pure snippet in the Command Bar.
luau
--!strict
local who = "Mara"
local lvl = 6
print(`Welcome \{hero\} {who}, level {lvl}`)txt
Welcome {hero} Mara, level 6You escape the first brace pair so it prints literally. The real values interpolate normally.
Solution 2
Run this pure snippet in the Command Bar.
luau
--!strict
print(string.format("Sword: %.2f coins", 12.5))
print(string.format("Tint: %x", 255))
print(string.format("Name: %q", "Oak Blade"))txt
Sword: 12.50 coins
Tint: ff
Name: "Oak Blade"Two decimals suit money and hex suits tints, while the quoted form suits names you may paste back into code.
Solution 3
Run this pure snippet in the Command Bar.
luau
--!strict
local text = "Wave 3 spawns 12 bots in 4 s"
local hits = {}
for token in string.gmatch(text, "%d+") do
table.insert(hits, token)
end
print(table.concat(hits, "-"))txt
3-12-4You loop matches with gmatch, collect each digit run, and join with dashes.
Solution 4
Run this pure snippet in the Command Bar.
luau
--!strict
local roles = string.split("tank,healer,dps", ",")
print(table.find(roles, "dps"))
local msg, n = string.gsub("hp low, hp low", "low", "ok")
print(msg)
print(n)
print(math.clamp(99, 0, 50))txt
3
hp ok, hp ok
2
50Find locates "dps" at 3. Gsub replaces both hits. Clamp pins 99 down to 50.
Beyond string and math: utf8, vector, buffer, os
Four libraries from the standard library cover the gaps this chapter leaves open.
utf8 handles international text by codepoint, not by byte. string.len counts bytes and breaks on multibyte names; utf8.len counts codepoints and returns nil plus the first bad byte on malformed input. Use utf8.codes to iterate, utf8.offset to find byte positions, and utf8.char/utf8.codepoint to convert.
luau
--!strict
local name = "Mara✓"
print(string.len(name))
print(utf8.len(name))
for bytePos, code in utf8.codes(name) do
print(bytePos, code)
endvector is the immutable Luau vector type with .x/.y/.z components. The library offers create, magnitude, normalize, dot, cross, angle, floor, ceil, abs, sign, clamp, min, max, plus zero and one. There is no vector.lerp in the reference, blend manually or use Vector3:Lerp() at the Engine boundary.
luau
--!strict
local a = vector.create(0, 0, 0)
local b = vector.create(10, 0, 0)
local mid = a + (b - a) * 0.5
print(mid.X)
print(vector.magnitude(b - a))
print(vector.dot(vector.one, vector.one))buffer is a fixed-size mutable binary block for compact protocols and MemoryStore payloads. Create with buffer.create or buffer.fromstring, then use typed read*/write* calls with byte offsets in little-endian order, plus readstring/writestring, copy, and fill. Reads or writes outside the block throw.
luau
--!strict
local buf = buffer.create(8)
buffer.writeu32(buf, 0, 12345)
buffer.writestring(buf, 4, "hi")
print(buffer.readu32(buf, 0))
print(buffer.readstring(buf, 4, 2))
print(buffer.len(buf))os covers time and profiling. os.clock returns a high-precision timestamp for measuring duration (use it with the MicroProfiler chapter). os.time/os.date/os.difftime handle wall-clock dates; prefer Roblox DateTime for timezone-safe persistence of player data.
luau
--!strict
local start = os.clock()
local sum = 0
for i = 1, 1000 do
sum += i
end
print(os.clock() - start)
print(os.date("%Y-%m-%d"))Summary
- Backticks interpolate expressions. Escape backticks and braces.
{{is a parse error. - string.format verbs handle layout. Patterns with
%d,%a,%s, and friends find shapes. - find, gsub, sub, and split cover search, replace, slice, and chop. String positions start at 1.
- math covers rounding, bounds, roots, powers, and randomness. Tables hold string results.
- Next: Advanced Functions.