Inheritance & super
Reviewed & published by Brayan K
Inheritance is the object-oriented mechanism that lets one Ruby class reuse and extend another, written class Child < Parent, so shared behaviour lives in one place instead of being copied everywhere.
Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll override methods, reach back into a parent with super, check types with is_a?, and control access with protected and private.
What You'll Learn in This Lesson
- Defining a subclass with class Child < Parent
- Overriding inherited methods
- Calling super with and without arguments
- Checking types with is_a? and kind_of?
- Single inheritance plus module mixins
- protected vs private method visibility
1️⃣ Subclassing & Method Overriding
Write class Dog < Animal and Dog instantly gains every method Animal defines. When you define a method with the same name in the child, it overrides the parent's version for that class only — the parent and its other children are untouched.
# A subclass inherits everything from its parent with: class Child < Parent
class Animal
def initialize(name)
@name = name
end
def speak
"#{@name} makes a sound"
end
def describe
"I am #{@name}"
end
end
# Dog < Animal means Dog inherits initialize, speak, and describe.
class Dog < Animal
# Override speak to give dogs their own behaviour:
def speak
"#{@name} says Woof!"
end
end
rex = Dog.new("Rex")
puts rex.speak # overridden in Dog
puts rex.describe # inherited from Animal
generic = Animal.new("Creature")
puts generic.speak # the original Animal version2️⃣ Reusing the Parent with super
Overriding often means "do what the parent does, and then a bit more." That's what super is for: it calls the parent's version of the current method. Use super(a, b) to pass specific arguments, bare super to forward the same arguments you received, and super() to call the parent with none.
# super calls the SAME method in the parent class.
class Account
def initialize(owner, balance)
@owner = owner
@balance = balance
end
def summary
"#{@owner}: $#{@balance}"
end
end
class SavingsAccount < Account
def initialize(owner, balance, rate)
# super(args) passes specific arguments to Account#initialize:
super(owner, balance)
@rate = rate
end
def summary
# bare "super" (no parens) forwards the SAME args this method got...
# here summary takes no args, so super calls Account#summary with none.
super + " at #{@rate}% interest"
end
end
acct = SavingsAccount.new("Ada", 1000, 2.5)
puts acct.summary3️⃣ Types, Modules & Visibility
Use is_a? (alias kind_of?) to ask whether an object descends from a class or includes a module; use instance_of? for an exact-class check. Ruby allows only one parent class, but you can include any number of modules to share behaviour across unrelated classes.
# is_a? / kind_of? ask whether an object descends from a class or module.
class Animal; end
class Dog < Animal; end
rex = Dog.new
puts rex.is_a?(Dog) # true - exact class
puts rex.is_a?(Animal) # true - parent class
puts rex.is_a?(Object) # true - everything descends from Object
puts rex.kind_of?(Animal) # true - kind_of? is an alias of is_a?
puts rex.instance_of?(Dog) # true - instance_of? checks EXACT class only
puts rex.instance_of?(Animal) # false - not exactly an Animal
# Ruby has SINGLE inheritance (one parent) but modules add shared behaviour:
module Swimmer
def swim
"swimming"
end
end
class Fish < Animal
include Swimmer # mix in the module's methods
end
nemo = Fish.new
puts nemo.swim
puts nemo.is_a?(Swimmer) # true - is_a? sees included modules tooVisibility controls who may call a method. A private method can only be called without an explicit receiver; a protected method can be called with a receiver as long as the caller is the same class — perfect for comparing two objects' internals.
# protected lets objects of the SAME class compare each other privately.
class Wallet
def initialize(amount)
@amount = amount
end
def richer_than?(other)
# We can call other.amount because amount is PROTECTED, not private:
amount > other.amount
end
protected
# protected: callable by other Wallet instances, but not from outside.
def amount
@amount
end
end
a = Wallet.new(100)
b = Wallet.new(50)
puts a.richer_than?(b) # true
puts b.richer_than?(a) # false
# Trying to call a protected method from outside raises NoMethodError:
begin
puts a.amount
rescue NoMethodError => e
puts "Blocked: #{e.class} (protected method called from outside)"
end🎯 Your Turn
A Motorcycle is a Vehicle with two wheels. Replace each ___ with super so the subclass reuses its parent, then run it.
# 🎯 YOUR TURN — fill in the ___ blanks, then run with: ruby vehicle.rb
class Vehicle
def initialize(wheels)
@wheels = wheels
end
def description
"A vehicle with #{@wheels} wheels"
end
end
class Motorcycle < Vehicle
def initialize
___(2) # 👉 pass 2 wheels up to Vehicle (hint: super)
end
def description
___ + " (it's a motorcycle)" # 👉 reuse the parent's text (hint: super)
end
end
bike = Motorcycle.new
puts bike.description
puts bike.is_a?(Vehicle)
# ✅ Expected output:
# A vehicle with 2 wheels (it's a motorcycle)
# trueMini-Challenge: A Shape Hierarchy
Build a three-level hierarchy: Square < Rectangle < Shape. Each level uses super to reuse its parent's setup, and a Square should report as both a Rectangle and a Shape. Run with ruby shapes.rb.
# 🎯 MINI-CHALLENGE: a three-level shape hierarchy.
# Save as shapes.rb and run with: ruby shapes.rb
#
# 1. Shape stores a @name and has an area method (return 0) and a to_s.
# 2. Rectangle < Shape stores width and height; override area.
# Call super("Rectangle") in initialize to set the name.
# 3. Square < Rectangle: a square IS a rectangle with equal sides.
# super(side, side) reuses Rectangle's setup; then rename to "Square".
# 4. Prove Square is_a? Rectangle AND is_a? Shape.
#
# ✅ Example output:
# Rectangle with area 12
# Square with area 25
# true
# true
# your code hereCommon Errors (and the fix)
- ❌ "wrong number of arguments" from super — bare super forwards every current argument. ✅ Call super(only, what, parent, needs) explicitly.
- ❌ "superclass mismatch for class X" — you reopened a class with a different parent. ✅ Keep class X < Parent consistent everywhere.
- ❌ "private method called for ..." — you used an explicit receiver on a private method. ✅ Drop the receiver, or make the method protected.
- ❌ Forgetting super in initialize — the parent's instance variables never get set. ✅ Call super before using inherited state.
- ❌ Expecting multiple inheritance — class A < B, C is a syntax error. ✅ Inherit one class and include modules for the rest.
Pro Tips
- 💡 Favour composition: deep inheritance trees get brittle — modules and small collaborating objects often read better.
- 💡 ancestors lists the chain: call Dog.ancestors to see exactly where Ruby looks up a method.
- 💡 super is the open/closed friend: extend behaviour without copying the parent, keeping classes open to extension but closed to rewriting.
📋 Quick Reference — Inheritance
| Concept | Ruby Syntax |
|---|---|
| Subclass | class Dog < Animal |
| Call parent (same args) | super |
| Call parent (chosen args) | super(a, b) |
| Type check | obj.is_a?(Animal) |
| Exact class | obj.instance_of?(Dog) |
| Mix in a module | include Swimmer |
🎉 Lesson Complete!
- ✅ class Child < Parent inherits all the parent's methods
- ✅ Redefining a method in the child overrides the parent's version
- ✅ super reuses the parent method; mind the parentheses
- ✅ is_a? checks ancestry; instance_of? checks exact class
- ✅ One parent class, but many included modules
Practice quiz
How do you declare that Dog inherits from Animal?
- class Dog : Animal
- class Dog extends Animal
- class Dog < Animal
- class Dog from Animal
Answer: class Dog < Animal. Ruby uses the < operator for single inheritance.
When a subclass defines a method with the same name as its parent, the method is:
- overridden for that subclass
- deleted from the parent
- run twice
- a syntax error
Answer: overridden for that subclass. Overriding takes priority for the subclass; the parent's version is untouched.
What does bare super (no parentheses) pass to the parent method?
- no arguments
- only the first argument
- a copy of self
- the same arguments the current method received
Answer: the same arguments the current method received. Bare super forwards all of the current method's arguments.
What does super() with empty parentheses do?
- forwards all current arguments
- calls the parent method with no arguments
- is a syntax error
- calls the child method again
Answer: calls the parent method with no arguments. Empty parentheses call the parent explicitly with zero arguments.
Given class Dog < Animal, what does Dog.new.is_a?(Animal) return?
- true
- false
- nil
- an error
Answer: true. is_a? is true for any ancestor class or included module.
What does dog.instance_of?(Animal) return when dog is a Dog?
- true
- nil
- false
- raises NoMethodError
Answer: false. instance_of? checks the EXACT class only, so a Dog is not an Animal.
How many direct superclasses can a Ruby class have?
- zero
- exactly one
- exactly two
- unlimited
Answer: exactly one. Ruby has single inheritance; share extra behaviour with module mixins.
What is kind_of? in relation to is_a??
- its opposite
- an exact-class check
- deprecated
- an alias of is_a?
Answer: an alias of is_a?. kind_of? and is_a? are aliases that behave identically.
A protected method can be called with an explicit receiver when:
- never
- the caller is the same class or a subclass
- always
- only from outside the class
Answer: the caller is the same class or a subclass. protected allows same-class collaboration like a.richer_than?(b).
How do you add a module's shared behaviour to a class?
- import Swimmer
- inherit Swimmer
- include Swimmer
- use Swimmer
Answer: include Swimmer. include mixes a module's instance methods into the class.
Continue this course
- Previous: Checkpoint: Ruby Basics
- Next: self, Class Methods & Class Variables — What self means, def self.method, and @@ vs class instance variables
- Quick reference: Ruby cheat sheet › Classes & Modules
Frequently asked questions
What is the difference between super and super()?
They look almost identical but behave differently. Bare super (no parentheses) forwards the exact same arguments the current method received to the parent's method — so def initialize(a, b); super; end passes a and b up automatically. super() with empty parentheses calls the parent method with NO arguments. And super(x, y) passes specific arguments you choose. A classic bug is writing bare super in a subclass initialize that takes more arguments than the parent — Ruby forwards all of them and the parent complains about the wrong number.
Does Ruby support multiple inheritance?
No — Ruby has single inheritance, meaning a class can have exactly one direct parent (class Child < Parent). But Ruby gets the benefits of multiple inheritance through modules: you include a module to mix its methods into a class, and a class can include as many modules as it likes. This 'mixin' approach (used by Enumerable and Comparable) gives you shared behaviour across unrelated class trees without the ambiguity that true multiple inheritance causes.
When should I use is_a? versus instance_of?
Use is_a? (or its alias kind_of?) when you care whether an object descends from a class OR includes a module anywhere up its ancestry — dog.is_a?(Animal) is true for any subclass. Use instance_of? only when you need the EXACT class and want to reject subclasses — dog.instance_of?(Animal) is false because dog is a Dog, not literally an Animal. In day-to-day code is_a? is far more common; explicit type checks of either kind are best kept rare in favour of duck typing.
What is the difference between protected and private methods?
Both hide methods from outside callers, but protected is slightly more permissive. A private method can only be called without an explicit receiver (you cannot write other.method, only method). A protected method CAN be called with an explicit receiver as long as the caller is the same class or a subclass — which is exactly what you need to let two objects of the same class compare their internals, like wallet_a.richer_than?(wallet_b). Use private for pure internal helpers and protected for same-class collaboration.