What Metatables Are
Every table in Lua can have an associated metatable -- itself just another ordinary table -- that defines how the original table behaves for operations Lua doesn't natively know how to handle, such as adding two tables together, comparing them, or looking up a key that isn't present. The metatable's entries are special keys called metamethods, always named with a double underscore prefix like __index or __add, and Lua's core interpreter checks for these keys automatically whenever the relevant operation occurs. Metatables are the mechanism underlying nearly every advanced Lua feature: object-oriented inheritance, operator overloading, read-only tables, and proxy objects are all built from a handful of metamethods.
Cricket analogy: A ground's local playing conditions document (like DRS rules) is a metatable of sorts: base cricket laws govern play, but this extra document tells umpires how to handle edge cases like a ball lodging in the sightscreen, just as a metatable tells Lua how to handle undefined operations.
Setting and Getting a Metatable
You attach a metatable to a table using setmetatable(t, mt), which returns t after linking mt as its metatable, and you can inspect the current metatable with getmetatable(t). If the metatable itself defines a __metatable field, getmetatable returns that value instead of the real metatable and setmetatable raises an error, which is the standard trick for protecting a table from having its metatable swapped out by user code -- commonly used to make library objects tamper-resistant.
Cricket analogy: Appointing a match referee via the ICC's official process (setmetatable) attaches decision-making authority to a match, and checking who the appointed referee is (getmetatable) is straightforward, unless the board declares the appointment confidential ('__metatable'), in which case the public gets a placeholder answer instead of the real name.
The __index Metamethod
The most commonly used metamethod is __index, which is consulted only when you look up a key that is nil in the original table. If __index is set to another table, Lua looks up the missing key in that table instead, and this chaining is exactly how Lua implements 'class' inheritance: an object's metatable's __index points to a class table holding shared methods, so every instance can call those methods without duplicating them. If __index is instead set to a function, Lua calls it with the table and the missing key as arguments and uses whatever the function returns, which enables computed or lazily-generated fields.
Cricket analogy: When a young player doesn't know a shot the ball demands, he defers to his franchise coach's playbook (the __index table) -- exactly how a missing table key falls through to __index for a shared answer, letting every player share one manual instead of memorizing it.
The __newindex Metamethod
While __index intercepts reads of missing keys, __newindex intercepts writes to keys that don't already exist in the table -- but crucially, it only fires for new keys, not for overwriting existing ones. Setting __newindex to a function lets you validate, log, or redirect assignments (for example, throwing an error to make a table effectively read-only), while setting it to another table redirects the new value's storage there instead of the original table. This asymmetry with __index -- one hooks missing reads, the other hooks missing-key writes -- is what makes patterns like read-only proxies and change-tracking tables possible.
Cricket analogy: A stadium's ground staff only get involved (like __newindex firing) when a brand-new advertising board is installed in a spot that's never had one, not when an existing board is repainted -- existing slots update directly, but a genuinely new slot triggers approval.
-- Base "class" table holding shared methods
local Animal = {}
Animal.__index = Animal
function Animal.new(name, sound)
local self = setmetatable({}, Animal)
self.name = name
self.sound = sound
return self
end
function Animal:speak()
print(self.name .. " says " .. self.sound)
end
local dog = Animal.new("Rex", "Woof")
dog:speak() --> Rex says Woof
-- dog itself has no 'speak' field; the lookup falls through
-- dog's metatable's __index (Animal) to find it.
-- Read-only table via __newindex
local function readOnly(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function(_, k, v)
error("attempt to modify read-only table, key = " .. tostring(k), 2)
end,
__metatable = "protected"
}
return setmetatable(proxy, mt)
end
local constants = readOnly({ PI = 3.14159, E = 2.71828 })
print(constants.PI) --> 3.14159
-- constants.PI = 4 -- would raise an error
The colon syntax function Animal:speak(...) is sugar for function Animal.speak(self, ...), and calling dog:speak() is sugar for dog.speak(dog, ...). This implicit self parameter combined with __index chaining to a shared table is the entire mechanism behind Lua's 'class' style object orientation -- there is no built-in class keyword.
- A metatable is an ordinary table attached to another table via setmetatable to define behavior for otherwise undefined operations.
- getmetatable(t) returns the current metatable, or the __metatable field's value if the library author wants to hide/protect it.
- __index is consulted only when a key lookup finds nil in the original table; it can be a fallback table (inheritance) or a function (computed values).
- __newindex is consulted only when assigning to a key that doesn't already exist; existing keys update directly without triggering it.
- Chaining __index between a table and a shared 'class' table is the standard way Lua implements object-oriented inheritance.
- __newindex set to a function is the standard technique for building read-only or validated tables.
Practice what you learned
1. When is the __index metamethod consulted?
2. What does __newindex do when set to a function?
3. How do you make getmetatable(t) return something other than the real metatable?
4. In the common Lua OOP idiom Animal.__index = Animal, what is Animal.__index used for?
5. What is the relationship between dog:speak() and dog.speak(dog)?
Was this page helpful?
You May Also Like
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.
Tables in Lua
Learn how Lua's single, versatile data structure -- the table -- powers arrays, dictionaries, objects, and modules across every Lua program.
Arrays vs Dictionaries in Lua
See how Lua uses one table type to model both array-like sequences and dictionary-like maps, and learn the rules that keep each style predictable.
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