Skip to content

Lua OOP Guide — Object-Oriented Programming with Prototypes

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Lua OOP Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

Lua achieves object-oriented programming through Prototype-based inheritance -- using tables as objects, metatables for the prototype chain, and the colon operator for the implicit self parameter.

What You'll Learn

  • Creating classes and instances with metatables
  • Using colon syntax for methods
  • Implementing inheritance through prototype chains
  • Adding mixins and multiple inheritance

Why It Matters

Lua doesn't have built-in classes. Instead, it uses a flexible prototype system that lets you build OOP patterns naturally. This approach is simpler than class-based OOP but requires understanding the mechanics. Durga Antivirus Pro uses Lua's prototype OOP for its plugin architecture where each scanner type inherits from a base Scanner prototype.

Real-World Use

A game engine defines Entity and Player prototypes where Player inherits from Entity. A UI library defines Button, TextBox, and Window prototypes that inherit from Widget. Each prototype adds or overrides methods while sharing common behavior.

flowchart LR
    A["OOP Prototypes"] --> B["Classes"]
    A --> C["Inheritance"]
    A --> D["Encapsulation"]
    A --> E["Polymorphism"]
    B --> F["Instances"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

The Class Pattern

A Lua "class" is a table that acts as a prototype. Instances have their metatable's __index pointing to the class.

-- Define the class (prototype)
local Animal = {}
Animal.__index = Animal

-- Constructor
function Animal:new(name, species)
    local instance = {
        name = name or "Unknown",
        species = species or "Unknown"
    }
    setmetatable(instance, self)
    return instance
end

-- Method
function Animal:speak()
    print(self.name .. " makes a sound")
end

-- Method
function Animal:describe()
    print(self.name .. " is a " .. self.species)
end

-- Create instances
local rex = Animal:new("Rex", "Dog")
local luna = Animal:new("Luna", "Cat")

rex:speak()     --> Rex makes a sound
luna:describe() --> Luna is a Cat

Colon Syntax

The : operator is syntactic sugar that passes the table on the left as the first argument self.

-- These are equivalent:
rex:speak()              -- implicit self
rex.speak(rex)           -- explicit self

-- So are these:
function Animal:speak()  -- implicit self parameter
    -- ...
end
function Animal.speak(self)  -- explicit self parameter
    -- ...
end

Inheritance

Create a subclass by setting its metatable to point to the parent class.

-- Subclass: Dog inherits from Animal
local Dog = {}
setmetatable(Dog, {__index = Animal})
Dog.__index = Dog

-- Override constructor
function Dog:new(name, breed)
    local instance = Animal:new(name, "Dog")
    instance.breed = breed or "Mixed"
    setmetatable(instance, self)
    return instance
end

-- Override method
function Dog:speak()
    print(self.name .. " says Woof!")
end

-- New method
function Dog:fetch()
    print(self.name .. " fetches the ball")
end

-- Usage
local buddy = Dog:new("Buddy", "Golden Retriever")
buddy:speak()      --> Buddy says Woof!
buddy:describe()   --> Buddy is a Dog (inherited from Animal)
buddy:fetch()      --> Buddy fetches the ball

Multiple Inheritance

Lua doesn't have built-in multiple inheritance, but you can implement it with metatables.

-- Mixin tables
local Flyable = {
    fly = function(self)
        print(self.name .. " is flying")
    end
}

local Swimmable = {
    swim = function(self)
        print(self.name .. " is swimming")
    end
}

-- Function to mix in methods from multiple tables
local function mixin(target, ...)
    local sources = {...}
    for _, source in ipairs(sources) do
        for key, value in pairs(source) do
            target[key] = value
        end
    end
    return target
end

-- Duck inherits Animal + Flyable + Swimmable
local Duck = {}
setmetatable(Duck, {__index = Animal})
Duck.__index = Duck
mixin(Duck, Flyable, Swimmable)

function Duck:new(name)
    local instance = Animal:new(name, "Duck")
    setmetatable(instance, self)
    return instance
end

function Duck:speak()
    print(self.name .. " says Quack!")
end

local donald = Duck:new("Donald")
donald:speak()   --> Donald says Quack!
donald:fly()     --> Donald is flying
donald:swim()    --> Donald is swimming
donald:describe() --> Donald is a Duck

Encapsulation (Private Data)

Lua doesn't have private fields, but you can achieve encapsulation using closures or a naming convention.

-- Using closures for truly private data
local function create_bank_account(initial_balance)
    local balance = initial_balance  -- private

    local account = {}

    function account:deposit(amount)
        if amount > 0 then
            balance = balance + amount
        end
        return balance
    end

    function account:withdraw(amount)
        if amount > 0 and amount <= balance then
            balance = balance - amount
            return true
        end
        return false
    end

    function account:get_balance()
        return balance
    end

    return account
end

local acc = create_bank_account(1000)
print(acc:get_balance())  --> 1000
acc:deposit(500)
print(acc:get_balance())  --> 1500
print(acc.balance)        --> nil (private!)

Polymorphism

Lua's dynamic typing makes polymorphism natural -- any object with the right method can be used interchangeably.

local function animal_sound(animal)
    -- Works with any object that has a speak method
    animal:speak()
end

local rex = Dog:new("Rex", "Lab")
local donald = Duck:new("Donald")
local luna = Animal:new("Luna", "Cat")

animal_sound(rex)     --> Rex says Woof!
animal_sound(donald)  --> Donald says Quack!
animal_sound(luna)    --> Luna makes a sound

Common Mistakes

1. Forgetting self.__index = self

Without setting __index to itself, the class doesn't inherit its own methods. Instances won't find methods defined on the class.

2. Not calling superclass constructor

When overriding new, always call the parent constructor first to initialize inherited fields.

3. Confusing : with .

Using . instead of : requires explicit self parameter. Mixing them causes "attempt to index a nil value" errors.

4. Modifying class after creating instances

If you change a method on the prototype, existing instances see the change (since __index points to the prototype). This can be intentional or surprising.

5. Using global variable names for class tables

Name your class table with local scope to avoid polluting the global namespace.

Practice Questions

1. How does Lua implement inheritance without classes? Through metatable __index. When a method isn't found on an instance, Lua looks up the prototype chain via __index.

2. What does the colon operator do? It passes the table on the left as the first argument (self) to the method. obj:method(args) is equivalent to obj.method(obj, args).

3. How do you create truly private data in Lua? Using closures. Variables local to the constructor function are captured by the returned methods but inaccessible from outside.

Challenge: Create a Shape hierarchy with Circle, Rectangle, and Triangle prototypes. Each should have area() and perimeter() methods. Demonstrate polymorphism by calling these methods on a list of shapes.

FAQ

{{< faq question="Can I use traditional class-based OOP in Lua?" >}} Lua doesn't have a class keyword, but you can simulate it with metatables. Libraries like middleclass and 30log provide class syntax if you prefer it. {{< /faq >}}

{{< faq question="What is the difference between prototype-based and class-based OOP?" >}} In class-based OOP (Java, C++), classes are blueprints and instances are created from them. In prototype-based OOP (Lua, JavaScript), objects are created from existing objects (prototypes) and can be extended individually. {{< /faq >}}

{{< faq question="Can I call the parent class method from an override?" >}} Yes. Store the parent method and call it: Dog.speak = function(self) Animal.speak(self); print(self.name .. " also barks") end. {{< /faq >}}

{{< faq question="Is there a standard way to define classes in Lua?" >}} No single standard, but the pattern shown here (class table with __index = self, :new() constructor) is the most widely used. Libraries offer syntactic sugar but the underlying mechanism is always metatables. {{< /faq >}}

{{< faq question="Can I add methods to a single instance?" >}} Yes. Since each instance is its own table, you can add methods directly: rex.jump = function(self) print(self.name .. " jumps") end. This won't affect other instances. {{< /faq >}}

Try It Yourself

local Vehicle = {}
Vehicle.__index = Vehicle

function Vehicle:new(make, model, year)
    local inst = {make = make, model = model, year = year, speed = 0}
    setmetatable(inst, self)
    return inst
end

function Vehicle:accelerate(amount)
    self.speed = self.speed + (amount or 10)
    print(self.model .. " accelerated to " .. self.speed .. " mph")
end

function Vehicle:describe()
    print(self.year .. " " .. self.make .. " " .. self.model)
end

local Car = {}
setmetatable(Car, {__index = Vehicle})
Car.__index = Car

function Car:new(make, model, year, doors)
    local inst = Vehicle:new(make, model, year)
    inst.doors = doors or 4
    setmetatable(inst, self)
    return inst
end

function Car:honk()
    print(self.model .. " honks: Beep beep!")
end

local civic = Car:new("Honda", "Civic", 2024, 4)
civic:describe()
civic:honk()
civic:accelerate(20)

Expected output:

2024 Honda Civic
Civic honks: Beep beep!
Civic accelerated to 20 mph

What's Next

Now that you understand OOP with prototypes, learn about coroutines -- Lua's cooperative multitasking mechanism.

Topic Description Link
Coroutines Cooperative multitasking and yield {{< ref "08-coroutines" >}}
File I/O Reading and writing files {{< ref "09-file-io" >}}
Python OOP Compare Python and Lua OOP approaches Python

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro