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

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 version

2️⃣ 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.summary

3️⃣ 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 too

Visibility 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)
#    true

Mini-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 here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Inheritance

ConceptRuby Syntax
Subclassclass Dog < Animal
Call parent (same args)super
Call parent (chosen args)super(a, b)
Type checkobj.is_a?(Animal)
Exact classobj.instance_of?(Dog)
Mix in a moduleinclude Swimmer

🎉 Lesson Complete!

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

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.