Ruby Duck Typing — respond_to? method_missing and Type Flexibility Explained
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