luaguides

Single and Multiple Inheritance in Lua

How do you share behavior between objects when your language has no classes? Lua answers this with inheritance in Lua, a metatable-driven approach that delegates lookups rather than copying fields. It takes some getting used to, but it’s surprisingly flexible once you understand the mechanism.

Lua doesn’t have built-in class-based inheritance. Instead, it uses metatables: a powerful mechanism that lets objects delegate property lookups to other tables. This approach is flexible enough to support both single inheritance and the more complex multiple inheritance pattern.

What makes Lua’s inheritance unique is that there’s no compiler-enforced hierarchy; you’re working directly with tables and metatables, which means you can change inheritance relationships at runtime or even mix patterns that would be impossible in a rigid class system.

By the end, you’ll know how to implement both inheritance styles, trace method lookup through metatable chains, and sidestep the pitfalls that trip up newcomers.

Prerequisites

Before you tackle inheritance in Lua, you’ll need a solid grasp of three concepts. First, basic Lua table operations: creating tables, accessing keys, and understanding that tables are reference types. Second, metatables and the __index metamethod: you should know how setmetatable works and what happens when Lua can’t find a key in a table. Third, colon syntax for methods: the difference between table.method(self, ...) and table:method(...) matters throughout this tutorial. If any of these feel unfamiliar, start with the metatables-intro tutorial first.

Understanding metatables and __index

Before diving into inheritance, you need to understand how metatables work in Lua. A metatable is a table that defines custom behavior for another table. The __index metamethod is the key to inheritance; it’s called when you access a key that doesn’t exist in a table.

local parent = {
    name = "Parent",
    greet = function(self)
        return "Hello, I'm " .. self.name
    end
}

local child = {}
setmetatable(child, { __index = parent })

print(child.name)      -- Output: Parent
print(child:greet())   -- Output: Hello, I'm Parent

Notice that child has no name property of its own, yet accessing child.name returns "Parent". The __index metamethod intercepts the lookup and finds the value in parent.

Single inheritance with metatables

Single inheritance is straightforward: each subclass has exactly one parent. The pattern involves creating a constructor that sets up the metatable chain.

-- Base Animal class
local Animal = {}
Animal.__index = Animal

function Animal:new(name, sound)
    local instance = setmetatable({}, self)
    instance.name = name
    instance.sound = sound
    return instance
end

function Animal:speak()
    return self.name .. " says " .. self.sound
end

-- Dog subclass
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog

function Dog:new(name)
    local instance = setmetatable({}, self)
    instance.name = name
    instance.sound = "woof"
    return instance
end

-- Create instances
local generic = Animal:new("Creature", "grrr")
local buddy = Dog:new("Buddy")

print(generic:speak())  -- Output: Creature says grrr
print(buddy:speak())    -- Output: Buddy says woof

The key is setmetatable(Dog, { __index = Animal }). When buddy:speak() is called, Lua first looks in the Dog table, doesn’t find speak, then checks the metatable’s __index and finds it in Animal.

The Dog class inherits from Animal through that single setmetatable call; Dog gets a metatable whose __index points to Animal. This means any method or field not found directly on Dog falls through to Animal for lookup. Each instance created via Dog:new() gets its own metatable pointing back to the Dog class, forming a three-level lookup chain: instance → Dog → Animal. Lua walks this chain automatically whenever it can’t resolve a key, which is what makes inherited method calls feel natural even though there’s no compiler support for classes.

Adding methods to subclasses

You can override methods in the subclass:

function Dog:speak()
    return self.name .. " barks: " .. self.sound
end

print(buddy:speak())  -- Output: Buddy barks: woof

The method lookup finds speak in Dog first, so the subclass version is used. This is the foundation of polymorphism in Lua.

The Cat class below demonstrates a different scenario: calling a parent’s implementation explicitly. When Cat:speak() invokes Animal.speak(self), it bypasses the normal lookup chain and goes straight to the Animal table. The call passes self manually because Lua has no built-in super keyword to reference the parent class. Using self:speak() instead would look up speak starting from Cat, find it immediately, and create infinite recursion — so the explicit parent reference is both necessary and intentional.

Calling super methods

When you override a method but need to call the parent’s version, you reference it directly:

local Cat = setmetatable({}, { __index = Animal })
Cat.__index = Cat

function Cat:new(name)
    local instance = setmetatable({}, self)
    instance.name = name
    instance.sound = "meow"
    return instance
end

function Cat:speak()
    -- Call the parent method first
    local parentSpeech = Animal.speak(self)
    return parentSpeech .. " and " .. self.sound .. "!"
end

local whiskers = Cat:new("Whiskers")
print(whiskers:speak())
-- Output: Whiskers says meow and meow!

Notice the syntax: Parent.method(self) passes the instance explicitly. This is different from self:method() which would look in the current class first.

Multiple Inheritance

Multiple inheritance is useful when a class naturally belongs to more than one category. A duck is both a flyer and a swimmer, and modeling that with single inheritance forces awkward choices. Lua handles this cleanly because you control the lookup logic yourself. The tradeoff is that you need to write more setup code and decide on a conflict resolution strategy upfront.

Multiple inheritance allows a class to inherit from more than one parent. This is more complex because the __index metamethod needs to search multiple tables.

-- Two parent classes
local Flyer = {
    fly = function(self)
        return self.name .. " is flying"
    end
}

local Swimmer = {
    swim = function(self)
        return self.name .. " is swimming"
    end
}

-- Duck inherits from both
local Duck = setmetatable({}, {
    __index = function(self, key)
        -- Search Flyer first
        if Flyer[key] then
            return Flyer[key]
        end
        -- Then search Swimmer
        if Swimmer[key] then
            return Swimmer[key]
        end
        return nil
    end
})

function Duck:new(name)
    local instance = setmetatable({}, { __index = getmetatable(self).__index })
    instance.name = name
    return instance
end

local donald = Duck:new("Donald")

print(donald:fly())   -- Output: Donald is flying
print(donald:swim())  -- Output: Donald is swimming

The custom __index function searches each parent table in order. You can extend this pattern to handle any number of parents.

The Duck implementation searches Flyer first, then Swimmer, returning whichever parent has the requested key. The custom __index function iterates through the parents list and returns the first match it finds. This approach works reliably, but it does require careful instance setup: each Duck instance needs its own metatable with the same __index function, which is why Duck:new() calls getmetatable(self).__index to copy the search function from the class’s metatable to the instance’s metatable. Forgetting this step means instances won’t inherit from the parent tables at all.

A reusable multiple inheritance helper

For production code, create a helper function:

function createClass(...)
    local parents = {...}
    local class = {}
    
    setmetatable(class, {
        __index = function(self, key)
            for _, parent in ipairs(parents) do
                if parent[key] then
                    return parent[key]
                end
            end
            return nil
        end
    })
    
    return class
end

-- Now easy multiple inheritance
local Amphibian = createClass(Flyer, Swimmer)

function Amphibian:new(name)
    return setmetatable({ name = name }, { __index = self })
end

The createClass helper generalizes the multiple-inheritance pattern into a reusable function. It accepts any number of parent tables via ..., builds a new class table, and sets up __index to search the parents in the order they were passed. The Amphibian example shows how clean this becomes: a single call to createClass(Flyer, Swimmer) replaces all the manual metatable wiring from the Duck example. You can pass two parents, three, or more, and the lookup order always follows the argument order, so the first parent listed gets priority when multiple parents define the same method.

Common Gotchas

Method lookup order

The lookup order matters more than you might expect. Lua searches in this sequence:

  1. The object itself
  2. The object’s metatable’s __index (which may be the class table)
  3. If __index is a function, it runs and may delegate to other tables

This means if two parents define the same method, the first one in your search order wins.

When __index is a plain table, Lua checks it directly for the missing key; this is the common single-inheritance case. When __index is a function, Lua calls it with the object and the missing key as arguments, and the function’s return value becomes the lookup result. The function can delegate further by checking additional tables or computing values on the fly. This distinction is what makes multiple inheritance possible: the __index function is a dispatcher that consults multiple parent tables in whatever order you choose.

Private fields don’t exist

Lua has no true private fields. By convention, prefix with underscore to indicate privacy, but it’s purely social:

function Account:new(balance)
    local instance = setmetatable({}, self)
    instance._balance = balance  -- "private" by convention
    return instance
end

local acc = Account:new(100)
print(acc._balance)  -- Still accessible! No true privacy

This naming convention is widely used in the Lua community, but it’s important to understand that it provides no runtime protection whatsoever. Any code with a reference to the instance can read or modify _balance directly. If you need actual encapsulation, you’ll need to use closures to capture private state inside a constructor function, though that approach has its own tradeoffs around memory usage and metatable compatibility.

Super calls are manual

Unlike languages with explicit super keywords, Lua requires you to call parent methods directly:

function SubClass:method()
    ParentClass.method(self)  -- Must explicitly name the parent
end

If you rename a parent class, you’ll need to update all super calls manually.

The lack of a super keyword means refactoring class names is more expensive than in languages with built-in OOP. If you rename ParentClass to BaseClass, every explicit ParentClass.method(self) call must be found and updated. This is one reason larger Lua codebases sometimes build thin wrapper functions that abstract away the parent class reference, keeping the hard-coded name in a single place rather than scattered across every subclass method.

Self reference gotcha

When calling parent methods, always pass self:

-- Correct
function Dog:new(name)
    local instance = Animal.new(self, name)  -- Pass self as first arg
    instance.sound = "woof"
    return instance
end

Remember that self inside a colon-defined method refers to the table the method was called on, not the table where the method is defined. When you call Animal.new(self, name), the self passed in is the Dog class table, so Animal.new creates an instance whose metatable points to Dog. This pattern chains constructors together correctly, with each subclass constructor calling its parent’s constructor before adding its own fields.

Metatable chain gotcha

Be careful when mixing setmetatable calls. Each class needs its own metatable setup:

-- Wrong: all classes share the same metatable
local Class2 = {}
Class2.__index = Class2
setmetatable(Class2, { __index = Class1 })  -- Modifies Class2's metatable

-- Correct: instance gets the class, class gets its parent
local instance = setmetatable({}, Class2)

Summary

Lua’s inheritance system is built on metatables and the __index metamethod. Single inheritance is simple: set your subclass’s metatable to point to the parent with __index = parent. Multiple inheritance requires a custom __index function that searches multiple parent tables.

What makes this work is Lua’s delegation model — rather than copying fields from parent to child, lookup failures are redirected through the metatable chain at access time. This means changes to a parent table are immediately visible to all subclasses, and you can even swap out a parent table at runtime without touching instance code.

Key takeaways:

  • Use setmetatable(subclass, { __index = parent }) for single inheritance
  • Implement a search function in __index for multiple inheritance
  • Call parent methods explicitly: Parent.method(self)
  • Remember there’s no true privacy — use conventions, not language features
  • Understand method lookup order to predict which method gets called

With these patterns, you can build class hierarchies in Lua that rival those of traditional OOP languages.

Next steps

Now that you understand how inheritance works with metatables, explore alternative approaches to code reuse. Mixins and composition offer a different way to share behavior without rigid parent-child relationships; they’re often a better fit for Lua’s flexible table model. For building complete class systems on top of these primitives, the classes-with-metatables tutorial walks through creating constructors, static methods, and type-checking utilities.

See also