Duck Typing
Part of the Object Oriented Programming section of Coddy's Lua journey — lesson 39 of 70.
Lua doesn't care about an object's class—it only cares about what the object can do. This concept is called duck typing: "If it walks like a duck and quacks like a duck, it's a duck." In programming terms, if an object has the method you're calling, it works.
This means completely unrelated classes can be used together, as long as they share the same method name:
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endThese classes have no inheritance relationship—they're completely independent. Yet we can treat them identically:
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- Output:
-- Drawing a circle
-- Drawing a square
-- Drawing a circleThe loop doesn't check what type each object is. It simply calls :draw() and trusts that the method exists. This flexibility is powerful—you can add new shape types without modifying the loop code at all. As long as the new class implements :draw(), it just works.
Challenge
EasyLet's demonstrate the power of duck typing by building a notification system with completely unrelated classes that can all be processed the same way!
You'll create three independent notification classes—none of them inherit from each other—but they all implement a :send() method. This means you can loop through a mixed collection and call :send() on each one without caring about its actual type.
You'll organize your code across four files:
Email.lua: A class with a:new(recipient)constructor that stores who the email goes to. Its:send()method should printEmailing {recipient}.SMS.lua: A completely separate class with a:new(phoneNumber)constructor. Its:send()method should printTexting {phoneNumber}.PushNotification.lua: Another independent class with a:new(deviceId)constructor. Its:send()method should printPushing to {deviceId}.main.lua: Bring everything together! Read three inputs, create one instance of each notification type, put them all in a single table, then loop through and call:send()on each one.
You will receive three inputs:
- An email recipient (e.g.,
alice@example.com) - A phone number (e.g.,
555-1234) - A device ID (e.g.,
device_99)
In your main file, create all three notification objects, store them in a table, and iterate through the table calling :send() on each. The order should be: Email first, then SMS, then PushNotification.
For example, if the inputs are bob@mail.com, 555-9876, and phone_42, the output should be:
Emailing bob@mail.com
Texting 555-9876
Pushing to phone_42Notice how your loop doesn't need to check what type each object is—it just trusts that every object in the collection has a :send() method. That's duck typing in action! If it can :send(), it's a notification.
Cheat sheet
Lua uses duck typing: if an object has the method you're calling, it works, regardless of its class. The principle is "if it walks like a duck and quacks like a duck, it's a duck."
Completely unrelated classes can be used interchangeably as long as they share the same method name:
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endThese classes have no inheritance relationship, yet can be treated identically:
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- Output:
-- Drawing a circle
-- Drawing a square
-- Drawing a circleThe loop doesn't check object types—it simply calls the method and trusts it exists. You can add new types without modifying existing code, as long as they implement the required method.
Try it yourself
-- main.lua
-- Bring all notification types together and demonstrate duck typing
-- Import the notification classes
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')
-- Read inputs
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()
-- TODO: Create one instance of each notification type
-- TODO: Put all notifications in a single table (Email first, then SMS, then PushNotification)
-- TODO: Loop through the table and call :send() on each notification
-- This demonstrates duck typing - we don't check the type, we just call :send()
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1The 'Self' Concept
Tables with FunctionsExplicit 'self'The Colon SyntaxDot vs ColonRecap - Moving Point4Project: Digital Bank
Project SetupDeposit Method7Polymorphism & Overriding
Overriding MethodsCalling Parent MethodsDuck TypingCommon InterfaceChecking TypeRecap - Employee Roles10Project: Shape Manager
Project SetupRectangle Class2Class Prototype Pattern
The Prototype ConceptLinking with __indexThe :new() ConstructorInitializing AttributesIndependent InstancesRecap - Car Factory5Operator Overloading in OOP
Adding ObjectsSubtracting ObjectsConcatenating ObjectsComparing Objects (<, >)Recap - Wallet Math8Encapsulation
Naming ConventionsClosures for PrivacyAccess via ClosuresRead-Only TablesValidation LogicRecap - Secure Vault11Design Patterns (Lite)
Factory FunctionsSingleton TableIterator PatternObserver (Listener)Recap - Logger Factory3Object State and Behavior
Instance VariablesGetter MethodsSetter MethodsCalculated PropertiesFormatting StringsEquality ChecksRecap - Student Grade6Inheritance Basics
The Inheritance SetupInheriting MethodsExtending the ConstructorAdding Child MethodsShared vs UniqueRecap - Shape Hierarchy