Single and Multiple Inheritance in Lua

· 5 min read · Updated March 18, 2026 · intermediate
lua oop inheritance metatables programming

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.

In this tutorial, you’ll learn how to implement both inheritance styles, understand how method lookup works, and avoid common pitfalls.

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.

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.

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 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.

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

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.

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

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.

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

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.

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.