The String Library and Immutable Strings
Lua strings are immutable byte sequences -- once created, a string's contents can never be changed in place, so every string library function that appears to 'modify' a string actually returns a brand-new string, leaving the original untouched. The library is available both as functions called through the string table, like string.upper(s), and as methods called directly on any string value using colon syntax, like s:upper(), because Lua sets the metatable of the string type so that __index points to the string table itself. Both forms are equivalent and interchangeable; s:upper() is simply sugar for string.upper(s).
Cricket analogy: A printed scorecard from a match can never be altered after the game -- correcting an error means printing a fresh scorecard, just as Lua strings are immutable and every 'modification' produces a brand-new string rather than editing the original.
Common String Functions
The string library covers the essentials: #s or string.len(s) gives the byte length, string.sub(s, i, j) extracts a substring using 1-based indices where negative numbers count from the end (so s:sub(-3) grabs the last three characters), string.upper(s) and string.lower(s) change case, string.rep(s, n) repeats a string n times, and string.byte(s, i) / string.char(...) convert between characters and their numeric byte codes. Because indices are 1-based and inclusive on both ends, string.sub('hello', 2, 4) returns 'ell', which trips up programmers coming from 0-based-indexed languages.
Cricket analogy: Extracting overs 16 through 18 from a full 20-over innings scorecard for highlight analysis is like string.sub(s, 16, 18) -- both endpoints inclusive, 1-based numbering matching how commentators actually call out over numbers.
Pattern Matching Basics
Lua patterns are a lightweight alternative to full regular expressions, using character classes like %d (digit), %a (letter), %s (whitespace), and %w (alphanumeric), plus quantifiers *, +, -, and ? to search text. string.find(s, pattern) returns the start and end indices of the first match, string.match(s, pattern) returns the matched substring (or captured groups if the pattern has parentheses), and string.gmatch(s, pattern) returns an iterator that yields every match in turn, making it the tool of choice for looping over all occurrences of a pattern in a string, such as pulling every word out of a sentence.
Cricket analogy: Scanning a full commentary transcript for every instance of a boundary call is like string.gmatch(text, 'FOUR!?') iterating every match in turn, while string.find just locates the first six or four in the innings.
Formatting and Substitution with string.format and gsub
string.format(fmt, ...) builds strings using C-style format specifiers -- %d for integers, %s for strings, %f or %g for floating point, %x for hexadecimal -- so string.format('Level %d: %s (%.1f%% complete)', 5, 'Forest', 42.5) produces a fully assembled string in one call rather than many concatenations. string.gsub(s, pattern, replacement, n) is the substitution workhorse: it finds every occurrence of pattern (or up to n occurrences if given) and replaces it with replacement, which can be a literal string, a table (looked up by the match), or a function (called with the match, whose return value becomes the replacement), and it also returns the count of substitutions made as a second return value.
Cricket analogy: Generating a graphic like 'Kohli: 82 off 61 (SR: 134.4)' from raw stats in one templated call is like string.format assembling a string from specifiers in one shot; swapping every player's real name for a nickname across a fan article is like string.gsub with a lookup table.
local s = "Hello, Lua World!"
print(#s) --> 18
print(s:sub(1, 5)) --> Hello
print(s:sub(-6)) --> World! (from 6th-from-end to end)
print(s:upper()) --> HELLO, LUA WORLD!
print(string.rep("ab", 3)) --> ababab
-- Pattern matching
local text = "Order #1042 shipped on 2026-07-10, Order #1043 pending"
for id in text:gmatch("#(%d+)") do
print("Order ID:", id)
end
-- Order ID: 1042
-- Order ID: 1043
local year, month, day = text:match("(%d%d%d%d)%-(%d%d)%-(%d%d)")
print(year, month, day) --> 2026 07 10
-- Formatting
local msg = string.format("Level %d: %s (%.1f%% complete)", 5, "Forest", 42.5)
print(msg) --> Level 5: Forest (42.5% complete)
-- Substitution with a function replacement
local censored, count = ("badword badword ok"):gsub("badword", "****")
print(censored, count) --> **** **** ok 2
Lua patterns are not full regular expressions -- there is no alternation operator like | and no bounded repetition like {2,4}. They cover roughly 80% of everyday text-processing needs with a much simpler and faster implementation, but for genuinely complex matching (like validating a full email address against RFC rules), a dedicated regex library such as LPeg or lrexlib is a better fit.
- Lua strings are immutable; every string library function returns a new string rather than modifying the original.
- string.sub uses 1-based, inclusive indices, and negative indices count backward from the end of the string.
- string.find locates a match's position, string.match returns the matched text (or captures), and string.gmatch iterates all matches.
- Lua patterns use character classes like %d, %a, %s, %w and quantifiers *, +, -, ? and are simpler than full regular expressions.
- string.format builds strings with C-style specifiers like %d, %s, %f, and %x in a single readable call.
- string.gsub replaces matches with a literal string, a table lookup, or a function's return value, and returns the substitution count as a second value.
Practice what you learned
1. What does string.sub('hello', 2, 4) return?
2. Which function returns an iterator that yields every match of a pattern in a string?
3. Why does s:upper() work even though upper isn't literally a key stored in the string s?
4. What does string.gsub('aaa', 'a', 'b', 2) return?
5. In string.format, which specifier would you use to insert a floating-point number rounded to one decimal place?
Was this page helpful?
You May Also Like
Tables in Lua
Learn how Lua's single, versatile data structure -- the table -- powers arrays, dictionaries, objects, and modules across every Lua program.
Metatables Explained
Discover how Lua metatables let ordinary tables gain custom behavior for indexing, inheritance, and protection, powering nearly every advanced Lua idiom.
Metamethods and Operator Overloading
Learn how Lua's arithmetic, comparison, and conversion metamethods let custom tables respond naturally to +, ==, tostring(), and even direct function-call syntax.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics