string.reverse
string.reverse(s) string.reverse returns a new string with the bytes of its argument in reverse order. It lives in the string library and has shipped in every Lua release since 5.0, so it behaves the same on 5.1, 5.3, 5.4, and LuaJIT. The original string is never modified because Lua strings are immutable.
Signature and return value
string.reverse(s)
s:reverse()
Parameter s (string, required): the input string. string.reverse does not coerce other types for you. Pass a number through tostring first if you need to reverse digits.
Returns (string): a new string of the same length as s, with its bytes written back to front. The source string is untouched. The cost is O(n) time and a fresh O(n) allocation.
The colon form s:reverse() is syntactic sugar for string.reverse(s) and requires s to be a real string value. Literal strings always qualify because the string library attaches a metatable to them at load time.
Basic usage
print(string.reverse("Hello, World!")) --> !dlroW ,olleH
print(("Lua"):reverse()) --> auL
print(string.reverse("")) --> (empty string)
print(string.reverse("ab")) --> ba
A string with an embedded NUL byte reverses the NUL along with everything else; it is just another byte to string.reverse, and the runtime never treats the NUL as a terminator. The next snippet drops a zero in the middle of a longer string to confirm that position is preserved, so the byte at index 5 in the input lands back at index 5 in the output.
print(string.reverse("hello\0world")) --> dlrow\0olleh
Palindrome checks
Comparing a string against its own reversal is the idiomatic palindrome test. You can drop that comparison into a small helper that first guards against non-string inputs, since calling :reverse() on a number or nil would error out before the equality check ever runs. The function below also uses the colon method form to keep the call site compact, and the type guard keeps the helper safe to call with values that came in from untrusted sources.
local function is_palindrome(s)
return type(s) == "string" and s == s:reverse()
end
print(is_palindrome("level")) --> true
print(is_palindrome("Level")) --> false
print(is_palindrome(123)) --> false
For a case-insensitive check, normalize first with string.lower. You can also strip whitespace if the input is a phrase. Skipping string.lower makes the function case-sensitive, so Level fails the test against level even though they differ only in the first letter. The next snippet chains gsub to remove runs of whitespace before comparing, which is what you want for phrase-style inputs like the classic canal-panama example.
local function is_palindrome_ci(s)
s = string.lower(s):gsub("%s+", "")
return s == s:reverse()
end
print(is_palindrome_ci("A man a plan a canal Panama")) --> true
UTF-8 strings get corrupted
Lua strings are sequences of bytes, not characters, and string.reverse operates on that byte buffer. Multi-byte UTF-8 sequences get split and the individual bytes get reordered, which usually produces invalid UTF-8:
print(string.reverse("café")) --> garbled bytes (é reversed to invalid UTF-8)
The standard library has no utf8.reverse. Lua 5.3 and later ship utf8.char, utf8.codepoint, utf8.codes, utf8.len, and utf8.offset, but reversing a UTF-8 string requires walking code points yourself or pulling in a library such as lua-utf8 from LuaRocks.
A quick custom reversal that respects UTF-8 walks code points with a pattern that matches a leading byte plus its continuation bytes:
local function utf8_reverse(s)
local chars = {}
for c in s:gmatch(".[\128-\191]*") do
chars[#chars + 1] = c
end
local out = {}
for i = #chars, 1, -1 do
out[#out + 1] = chars[i]
end
return table.concat(out)
end
print(utf8_reverse("café")) --> éfac
Type errors and number conversion
string.reverse does not coerce non-strings, unlike tostring. Pass a number and you get an error: the function raises bad argument #1 to 'reverse' (string expected, got number) and execution stops. Booleans and tables fail in exactly the same way, since nothing in that argument list is special-cased. That strictness is useful in practice: it surfaces the bug at the call site instead of silently coercing the value into something you didn’t intend.
print(string.reverse(123)) -- error: bad argument #1 to 'reverse' (string expected, got number)
Wrap the value in tostring if you want digit reversal: doing so converts the number to its decimal string first, then walks the digits back to front. A couple of edge cases are worth knowing about up front. The sign character on negative numbers stays in front, and any trailing zeros vanish if you round-trip the reversed string back through tonumber, because leading zeros are not significant in numeric form.
print(string.reverse(tostring(12345))) --> 54321
nil errors too. The colon form on a non-string has the same problem from a different angle: (123):reverse() tries to index a number and raises a separate error. There is no overload for integers, floats, or booleans, and unlike string.lower and string.upper in some implementations, string.reverse is a pure byte operation that does not depend on the C locale.
See also
string.bytefor inspecting raw bytes; useful for diagnosing whatstring.reversedid to a UTF-8 string.string.lenfor the byte-length companion; both run in O(n).string.lowerpairs withreversefor case-insensitive palindrome tests.string.repfor repetition; combine withreverseto mirror a pattern.- Strings and patterns is a beginner-friendly primer on the string library.
- Lua string patterns covers
gmatchand pattern matching in depth.