LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 923 min read

Metatables & Object-Oriented Lua

Learn setmetatable and getmetatable, the __index metamethod for inheritance, operator overloading metamethods, and how to build a simple class system.

Introduction

Lua has no class keyword, no built-in inheritance syntax, and no operator overloading syntax — and yet real Lua codebases use all three constantly. The mechanism behind every one of them is the metatable: a special table you can attach to another table to change how it behaves.

What You Will Learn
  • How to attach and inspect a metatable with setmetatable and getmetatable.
  • How the __index metamethod enables inheritance-like behavior.
  • How to build a simple, working class system out of tables and metatables.
  • How metamethods like __add and __tostring let a table respond to operators and printing.

setmetatable and getmetatable

Use case: setmetatable(table, metatable) attaches a metatable to a table, and getmetatable(table) retrieves it. The metatable itself is just an ordinary table whose specially-named fields (like __index, described next) Lua checks automatically during certain operations.

local t = {}
local mt = {}
setmetatable(t, mt)
print(getmetatable(t) == mt) -- true
Output

Click Run to see what this code prints.

The __index Metamethod

Use case: when you look up a key that doesn't exist directly on a table, Lua checks that table's metatable for an __index field. If __index is itself a table, Lua looks the key up there instead — the exact mechanism that implements inheritance-like "fallback" lookup.

local defaults = {species = "Unknown", legs = 4}
local dog = {species = "Dog"}
setmetatable(dog, {__index = defaults})
print(dog.species) -- "Dog" -- found directly on dog
print(dog.legs) -- 4 -- not on dog, falls back to defaults via __index
Output

Click Run to see what this code prints.

Building a Simple Class System

Combining __index (falling back to a shared table of methods), the colon syntax from lesson 7, and a constructor function gives you a complete, working class system — no special syntax required.

local Animal = {}
Animal.__index = Animal -- Animal itself acts as the "class" of methods
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")
local cat = Animal.new("Whiskers", "Meow")
dog:speak()
cat:speak()
Output

Click Run to see what this code prints.

How This Actually Works

Animal.new creates an empty table and sets its metatable to Animal itself, with __index = Animal. When you call dog:speak(), Lua looks for speak on dog, doesn't find it, checks dog's metatable's __index (which is Animal), finds speak there, and calls it with dog as self — the exact same fallback mechanism as the earlier defaults example, just used to simulate a class.

Operator Overloading Metamethods

Metatables can also define how a table responds to operators like +, -, ==, and <, by setting specially-named metamethod fields.

local Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
return setmetatable({x = x, y = y}, Vector)
end
Vector.__add = function(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end
local v1 = Vector.new(1, 2)
local v2 = Vector.new(3, 4)
local v3 = v1 + v2 -- calls Vector.__add automatically
print(v3.x, v3.y)
Output

Click Run to see what this code prints.

MetamethodTriggered By
__adda + b
__suba - b
__eqa == b
__lta < b
__len#a
__calla(...) — calling a table like a function

__tostring for Readable Output

Without __tostring, printing a table shows an unhelpful memory address like table: 0x55b1a2c3d4e0. Defining __tostring gives you full control over how a value looks when passed to print() or tostring().

Vector.__tostring = function(v)
return "(" .. v.x .. ", " .. v.y .. ")"
end
print(v3) -- now calls __tostring automatically
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Forgetting Class.__index = Class in a constructor pattern — without it, __index has no value and method lookup silently fails.
  • Assuming metatables are copied when a table is copied — assigning a table to a new variable does not clone the table or its metatable, both still point to the same underlying table.
  • Overusing operator overloading for types where +/- don't have an obvious, intuitive meaning — it can make code harder to read, not easier.

Best Practices

  • Follow the Class.new() + Class.__index = Class pattern consistently — it's the de facto standard across the Lua ecosystem.
  • Define __tostring on any type you'll frequently print or log, for far easier debugging.
  • Reserve operator overloading (__add, __eq, and similar) for types where the operator's meaning is genuinely obvious, like vectors or money.

Frequently Asked Questions

Yes — by chaining __index metatables (a Dog's metatable's __index points to Animal, whose own __index could point to a further base class), you can build multi-level inheritance hierarchies the same way.

It's exactly what real-world Lua object-oriented code looks like — several small libraries exist purely to reduce the boilerplate of this exact pattern, but the underlying mechanism is always __index and setmetatable.

The counterpart to __index for writes — it lets you intercept assignment to a missing key on a table, commonly used to make read-only tables or to validate values before they're stored.

Key Takeaways

  • A metatable is an ordinary table attached to another table with setmetatable, whose special fields Lua checks automatically.
  • __index enables fallback lookup, which is the mechanism behind both defaults and class-style inheritance.
  • The Class.new() / Class.__index = Class pattern is Lua's de facto standard for object-oriented code.
  • Metamethods like __add, __eq, and __tostring let a table respond to operators and printing.

Summary

Metatables are how Lua stays small while still supporting real object-oriented patterns and operator overloading — everything is built from the same table and function mechanisms you already know. Next, you'll cover string manipulation and Lua's own lightweight pattern-matching syntax.

Next Lesson →

String Manipulation & Pattern Matching