Skip to content

Ruby Duck Typing — respond_to? method_missing and Type Flexibility Explained

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Ruby Duck Typing. We cover key concepts, practical examples, and best practices to help you master this topic.

Ruby duck typing means objects are defined by what methods they respond to rather than their class hierarchy, enabling flexible polymorphic code that works across unrelated types.

What You'll Learn

  • The philosophy and practice of duck typing
  • Using respond_to? for runtime Type Checking
  • Leveraging method_missing for dynamic behavior
  • Building flexible interfaces without inheritance

Why It Matters

Duck typing is central to Ruby's flexibility. Durga Antivirus Pro uses duck typing for scanner plugins — any object that responds to scan works. Doda Browser uses duck typing for bookmark handlers, history backends, and cache stores. You write code against interfaces, not types.

Real-World Use

Rails routing, ActiveSupport extensions, and Rack middleware all rely on duck typing. A Rack app must respond to call(env) — any object that does works as middleware. Test doubles (stubs/mocks) use duck typing to replace real objects.

flowchart LR
    A["Duck Typing"] --> B["respond_to?"]
    B --> C["method_missing"]
    C --> D["Dynamic Dispatch"]
    D --> E["Polymorphism"]
    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 Duck Typing Philosophy

"If it walks like a duck and quacks like a duck, it's a duck." In Ruby terms: if an object responds to the method you're calling, it doesn't matter what class it belongs to.

def make_sound(animal)
  animal.sound
end

class Duck
  def sound
    "Quack!"
  end
end

class Cat
  def sound
    "Meow!"
  end
end

class AlarmClock
  def sound
    "Beep beep beep!"
  end
end

puts make_sound(Duck.new)       # Quack!
puts make_sound(Cat.new)        # Meow!
puts make_sound(AlarmClock.new) # Beep beep beep!

The make_sound method doesn't care about the class. It only cares that the argument responds to sound.

Why This Matters

In statically typed languages, you'd need all three classes to implement an interface or extend a base class. In Ruby, they can be completely unrelated. This makes it trivial to add new types that work with existing code.

Using respond_to?

When you need to check whether an object can do something, use respond_to?:

def process_item(item)
  if item.respond_to?(:each)
    item.each { |element| puts element }
  elsif item.respond_to?(:read)
    puts item.read
  else
    puts item.to_s
  end
end

process_item([1, 2, 3])
# 1
# 2
# 3

process_item("hello")
# hello

process_item(File.open("/dev/null", "r"))
# (empty — File.read returns "")

respond_to? with Private Methods

By default, respond_to? only checks public methods. Pass true as second argument to include private methods:

class Secretive
  private

  def hidden_method
    "shh"
  end
end

obj = Secretive.new
puts obj.respond_to?(:hidden_method)      # false
puts obj.respond_to?(:hidden_method, true) # true

Duck Typing with Collections

Ruby's Enumerable mixin is built on duck typing. Any object that defines each gets 50+ methods:

class Tree
  include Enumerable

  def initialize(value, children = [])
    @value = value
    @children = children
  end

  def each(&block)
    yield @value
    @children.each { |child| child.each(&block) }
  end
end

tree = Tree.new(1, [
  Tree.new(2, [Tree.new(4), Tree.new(5)]),
  Tree.new(3, [Tree.new(6)])
])

puts tree.map { |n| n * 2 }.inspect
# [2, 4, 8, 10, 6, 12]

The Enumerable contract is pure duck typing: "If it defines each, treat it as a collection."

method_missing for Dynamic Behavior

When Ruby can't find a method, it calls method_missing on the object. Override it for dynamic dispatch:

class DynamicConfig
  def initialize
    @config = {}
  end

  def method_missing(name, *args, &block)
    method_name = name.to_s

    if method_name.end_with?("=")
      key = method_name.chop
      @config[key] = args.first
    elsif @config.key?(method_name)
      @config[method_name]
    else
      super
    end
  end

  def respond_to_missing?(method_name, include_private = false)
    method_name.to_s.end_with?("=") || @config.key?(method_name.to_s) || super
  end
end

config = DynamicConfig.new
config.api_key = "secret-123"
config.timeout = 30

puts config.api_key  # secret-123
puts config.timeout  # 30
puts config.respond_to?(:api_key)  # true

Why respond_to_missing? Matters

Always override respond_to_missing? when you override method_missing. Otherwise, respond_to? returns wrong results, breaking Ruby's duck typing contract.

class BadDynamic
  def method_missing(name, *args)
    "handled #{name}"
  end
  # No respond_to_missing? defined!
end

b = BadDynamic.new
puts b.hello           # handled hello
puts b.respond_to?(:hello)  # false — WRONG!

Duck Typing with Structs and OpenStruct

require "ostruct"

# OpenStruct uses method_missing for dynamic attributes
person = OpenStruct.new(name: "Alice", age: 30)
puts person.name  # Alice
puts person.age   # 30
person.city = "New York"
puts person.city  # New York

The Delegation Pattern

Duck typing enables clean delegation:

class LoggerProxy
  def initialize(target)
    @target = target
  end

  def method_missing(name, *args, &block)
    if @target.respond_to?(name)
      puts "[LOG] Calling #{name} with #{args.inspect}"
      @target.send(name, *args, &block)
    else
      super
    end
  end

  def respond_to_missing?(name, include_private = false)
    @target.respond_to?(name) || super
  end
end

array = LoggerProxy.new([1, 2, 3])
puts array.length  # [LOG] Calling length with [] \n 3
puts array.first   # [LOG] Calling first with [] \n 1

respond_to? vs kind_of? vs is_a?

obj = "hello"

# Duck typing — what can it do?
puts obj.respond_to?(:upcase)  # true
puts obj.respond_to?(:fly)     # false

# Type checking — what is it?
puts obj.is_a?(String)   # true
puts obj.kind_of?(String) # true (same as is_a?)
puts obj.is_a?(Object)   # true (everything is an Object)

Prefer respond_to? over is_a? in Ruby. It makes your code more flexible and future-proof.

Common Mistakes

1. Not Overriding respond_to_missing?

class Proxy
  def method_missing(name, *args)
    "handled"
  end
end

p = Proxy.new
puts p.respond_to?(:anything)  # false — breaks duck typing

2. Using is_a? Instead of respond_to?

# Bad — breaks with new types
def process(items)
  if items.is_a?(Array)
    items.join(", ")
  end
end

# Good — works with any enumerable
def process(items)
  if items.respond_to?(:join)
    items.join(", ")
  end
end

3. Infinite Recursion in method_missing

class Infinite
  def method_missing(name, *args)
    self.send(name, *args)  # Calls method_missing again!
  end
end

4. Not Calling super for Unhandled Methods

class PartialProxy
  def method_missing(name, *args)
    if @target.respond_to?(name)
      @target.send(name, *args)
    end
    # Missing: else super
  end
end

5. Forgetting respond_to? for nil Check

# Risky — nil doesn't respond to most methods
def format_value(val)
  val.upcase if val
end

# Better
def format_value(val)
  val.upcase if val.respond_to?(:upcase)
end

Practice Questions

1. What is duck typing in Ruby?

The practice of determining an object's suitability by its methods rather than its class. If an object responds to the methods you need, it's accepted regardless of its type.

2. Why should you override respond_to_missing? with method_missing?

To maintain correct behavior of respond_to?. Without it, respond_to? returns false for methods handled by method_missing, breaking duck typing checks.

3. What's the difference between respond_to? and is_a??

respond_to? checks method availability (duck typing). is_a? checks class inheritance (type checking). Prefer respond_to? for flexible code.

4. When would you use method_missing?

For dynamic proxies, delegators, OpenStruct-like objects, and DSL builders where method names aren't known in advance.

Challenge: Create a SafeHash class that uses method_missing to access hash keys as methods, returning nil for missing keys without raising errors.

Solution
class SafeHash
  def initialize(hash = {})
    @hash = hash
  end

  def method_missing(name, *args)
    key = name.to_s
    if @hash.key?(key)
      @hash[key]
    elsif key.end_with?("=")
      @hash[key.chop] = args.first
    else
      nil
    end
  end

  def respond_to_missing?(name, include_private = false)
    key = name.to_s
    @hash.key?(key) || key.end_with?("=") || super
  end
end

h = SafeHash.new("name" => "Alice", "role" => "admin")
puts h.name    # Alice
puts h.role    # admin
puts h.missing # nil (no error!)
h.score = 100
puts h.score   # 100

FAQ

{{< faq question="Is duck typing the same as dynamic typing?" >}} No. Dynamic typing means variables don't have fixed types. Duck typing means objects are categorized by their methods, not their class. Ruby is both dynamically typed and duck typed. {{< /faq >}}

{{< faq question="Should I always use respond_to? instead of is_a?? >}} Generally yes. respond_to? makes your code more flexible and future-proof. Use is_a? only when you specifically need class-based behavior (e.g., Serialization, marshaling). {{< /faq >}}

{{< faq question="Does duck typing make code harder to debug?" >}} It can. When an object doesn't respond to a method, you get a NoMethodError with the object's class — not always obvious. Good test coverage and documentation mitigate this. {{< /faq >}}

{{< faq question="Can I use duck typing with inheritance?" >}} Yes. Duck typing complements inheritance. You can have a class hierarchy (Dog < Animal) and still use duck typing for unrelated objects that happen to share methods. {{< /faq >}}

{{< faq question="How does method_missing affect performance?" >}} method_missing is slower than regular method dispatch because it involves a failed lookup first. For frequently called methods, define them dynamically with define_method instead. {{< /faq >}}

Try It Yourself

# duck_typing_demo.rb

module Drivable
  def drive
    puts "#{self.class} is driving!"
  end
end

class Car
  include Drivable
end

class Truck
  include Drivable
end

class Bicycle
  def drive
    puts "Bicycle is pedaling!"
  end
end

class ToyCar
  # No drive method
end

def test_drive(vehicle)
  if vehicle.respond_to?(:drive)
    vehicle.drive
  else
    puts "This can't be driven!"
  end
end

test_drive(Car.new)      # Car is driving!
test_drive(Truck.new)    # Truck is driving!
test_drive(Bicycle.new)  # Bicycle is pedaling!
test_drive(ToyCar.new)   # This can't be driven!
puts Car.new.respond_to?(:drive)      # true
puts ToyCar.new.respond_to?(:drive)   # false

What's Next

Now that you understand duck typing, learn about open classes and how Ruby allows you to modify existing classes at runtime.

Topic Description Link
Ruby Open Classes Monkey patching, refinements {{< ref "16-open-classes" >}}
Ruby Mixins Comparable, Enumerable {{< ref "14-mixins" >}}
Python Duck Typing Compare Python's approach Python

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro