type()
type(value) type() is a built-in function that returns the type name of any Lua value as a string. It works on any value — numbers, strings, tables, functions, booleans, nil, and userdata. It is the standard way to do runtime type checking in Lua, replacing the need for manual string comparison or complex conditional logic.
Return Values
type() always returns one of eight strings:
| Return value | Meaning |
|---|---|
"nil" | The value is nil |
"number" | The value is a number (integer or float) |
"string" | The value is a string |
"boolean" | The value is true or false |
"table" | The value is a table |
"function" | The value is a function |
"thread" | The value is a coroutine |
"userdata" | The value is userdata |
type(nil) -- "nil"
type(42) -- "number"
type("hello") -- "string"
type(true) -- "boolean"
type({}) -- "table"
type(type) -- "function"
type(coroutine.create(print)) -- "thread"
type(io.stdout) -- "userdata" (standard library)
Basic Type Checking
Use type() in conditional statements to branch based on the type of a value:
function describe(value)
local t = type(value)
if t == "nil" then
return "no value"
elseif t == "number" then
return string.format("number: %.2f", value)
elseif t == "string" then
return string.format("string of length %d", #value)
elseif t == "table" then
return string.format("table with %d keys", #value)
else
return t
end
end
describe(nil) -- "no value"
describe(3.14) -- "number: 3.14"
describe("hello") -- "string of length 5"
describe({a = 1}) -- "table with 0 keys"
Guard Clauses
type() is commonly used as a guard to validate function arguments:
function add(a, b)
if type(a) ~= "number" then
error(string.format("bad argument #1 to 'add' (number expected, got %s)", type(a)))
end
if type(b) ~= "number" then
error(string.format("bad argument #2 to 'add' (number expected, got %s)", type(b)))
end
return a + b
end
add("hello", 2) -- error: bad argument #1 to 'add' (number expected, got string)
add(1, 2) -- 3
This pattern — checking the type and raising an error with the actual type — is the conventional Lua approach for argument validation, matching how built-in functions like io.write or table.insert report type errors.
Table Type vs Empty Table
Note that an empty table still has type "table":
type({}) -- "table"
type({1, 2, 3}) -- "table"
type({a = 1}) -- "table"
There is no “empty table” as a separate type. Use #table == 0 or next(table) == nil to check if a table is empty.
Comparing Types
When comparing types, always use "string" comparison rather than checking against the type function directly:
-- Correct
if type(x) == "number" then ...
-- Incorrect (compares to the type function itself, not its result)
if type(x) == number then ...
number without quotes is a variable name, not a string, and will cause a attempt to compare string with boolean error.
nil vs Nonexistent Keys
Accessing a nonexistent table key returns nil, and type() on nil returns "nil":
t = {}
type(t.missing) -- "nil"
type(undefined_var) -- "nil" (even for global variables that don't exist)
This means you can’t use type() alone to distinguish between a missing key and a key explicitly set to nil. Use rawget() or check for key presence:
t = {k = nil}
type(t.k) -- "nil"
rawget(t, "k") -- nil (but key exists)
next(t) -- "k", nil (key exists)
type() with Metatables
type() returns the type of the object itself, not the value of the metatable’s __type field. Metatables do not change what type() returns:
mt = {}
mt.__index = mt
t = setmetatable({}, mt)
type(t) -- "table" (not "custom")
getmetatable(t) -- the metatable
To implement custom types, store the type name in the table itself or in the metatable and check it separately:
Point = {}
Point.__index = Point
function Point.new(x, y)
return setmetatable({x = x, y = y}, Point)
end
p = Point.new(2, 5)
type(p) -- "table"
getmetatable(p) == Point -- true (check via metatable)
type() vs inspect
For debugging where type() gives you the name and you want more detail:
-- type() gives you the category
type(42) -- "number"
-- For tables, you often want more detail
function describe_table(t)
local mt = getmetatable(t)
if mt and mt.__type then
return mt.__type
end
return "table"
end
Common Patterns
Validate configuration tables:
function init(config)
if type(config) ~= "table" then
error("config must be a table")
end
if type(config.host) ~= "string" then
error("config.host must be a string")
end
if type(config.port) ~= "number" then
error("config.port must be a number")
end
-- proceed with config.host, config.port
end
Dispatch on type:
function tostring_safe(v)
if type(v) == "string" then return v
elseif type(v) == "number" then return tostring(v)
elseif type(v) == "boolean" then return tostring(v)
elseif type(v) == "table" then return tostring(v)
else return tostring(v) end
end
See Also
- /reference/core-functions/ref-print/ — output values to the console
- /guides/lua-metatables/ — custom types with metatables and __index