luaguides

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.byte for inspecting raw bytes; useful for diagnosing what string.reverse did to a UTF-8 string.
  • string.len for the byte-length companion; both run in O(n).
  • string.lower pairs with reverse for case-insensitive palindrome tests.
  • string.rep for repetition; combine with reverse to mirror a pattern.
  • Strings and patterns is a beginner-friendly primer on the string library.
  • Lua string patterns covers gmatch and pattern matching in depth.