Object#is_a?
obj.is_a?(klass) -> true or false is_a? is a Kernel method that checks whether an object belongs to a specific class or any of its ancestor classes (superclasses). It’s essential for type checking and implementing polymorphic behavior in Ruby.
Syntax
obj.is_a?(klass)
The method accepts a single argument: a class or module to test against. It returns true if the receiver is an instance of that class, a subclass descendant, or includes the given module anywhere in its ancestry chain. The check walks the entire inheritance tree, not just the immediate class.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
klass | Class or Module | — | The class or module to check against |
Examples
Basic type checking
"hello".is_a?(String)
# => true
"hello".is_a?(Integer)
# => false
123.is_a?(Numeric)
# => true
These basic checks show how the method reads in everyday code. The value is also about the broader family of types beyond the immediate match. The method tells you whether the object fits into the hierarchy you are asking about, which is more useful than a strict class comparison. That broader view is what makes the method practical for general-purpose validation.
Checking against superclasses
is_a? returns true if the object is an instance of the class or any of its superclasses:
[1, 2, 3].is_a?(Array)
# => true
[1, 2, 3].is_a?(Object)
# => true
[1, 2, 3].is_a?(Hash)
# => false
Superclass checks are useful because they let Ruby answer questions about ancestry for you. That keeps the code short when a method can accept more than one related type and the class hierarchy matters more than the exact leaf class.
Checking included modules
is_a? also returns true for modules included in the object’s class:
[1, 2, 3].is_a?(Enumerable)
# => true
"hello".is_a?(Comparable)
# => true
Module checks are a reminder that Ruby types are not just a single inheritance chain. Including a module can add behavior that matters just as much as the class itself, so is_a? remains a good fit when your code depends on shared interfaces.
Using with conditional logic
def process(item)
if item.is_a?(String)
puts "Processing string: #{item.upcase}"
elsif item.is_a?(Array)
puts "Processing array with #{item.length} elements"
elsif item.is_a?(Hash)
puts "Processing hash with #{item.keys.length} keys"
else
puts "Unknown type: #{item.class}"
end
end
This kind of branching stays readable because each type test lives right next to the behavior it enables. When the method body gets longer, the type check at the top helps future readers understand why each branch exists and what kind of input it is designed to handle.
Checking for multiple types
def numeric?(value)
value.is_a?(Integer) || value.is_a?(Float)
end
numeric?(42) # => true
numeric?(3.14) # => true
numeric?("hi") # => false
Checking several numeric types together is common in input validation and data conversion. The method makes it easy to say “either of these types is fine” without forcing the rest of the code into more complicated matching logic or nested conditional checks.
Common Patterns
Guard clauses
def greet(user)
return unless user.is_a?(User)
puts "Hello, #{user.name}!"
end
A guard clause with is_a? keeps the type requirement visible at the very top of the method. The caller can see immediately which class is expected, and the early return prevents the method body from executing with the wrong kind of object. This pattern is common in libraries and public APIs.
Type-based dispatch
def serialize(value)
case value
when String then value
when Array then value.map { |v| serialize(v) }
when Hash then value.transform_values { |v| serialize(v) }
else value.to_s
end
end
This is a good pattern when you want the data shape to steer the output shape. The type checks keep the serializer honest about what it is handling, and the code stays short enough to scan quickly and easy to extend later.
Testing and validation
def create_user(attrs)
raise ArgumentError, "expected Hash" unless attrs.is_a?(Hash)
# ...
end
Raising an ArgumentError when the input type is wrong gives the caller a clear, immediate signal about the contract. This is preferable to letting the code fail later with a confusing NoMethodError on an unexpected object type. The error message makes the requirement explicit.
Errors
Passing a non-Class argument raises a TypeError:
"hello".is_a?("string")
# => TypeError: class or module required
The error case is useful because it shows the contract clearly. The method wants a real Ruby class or module, not a string name, which means the caller has to be explicit about the type it is checking.
To check if something matches a module directly, use Module#=== directly:
String === "hello" # => true
Practical Notes
is_a? is most useful when the code needs to branch based on a broad family of types rather than a single exact class. That makes it a natural fit for validation, serialization, and general-purpose helpers that accept more than one input shape. It also reads clearly in guard clauses because the allowed type is spelled out right at the top of the method.
In some codebases, is_a? ends up being a small but important part of public API design. When a method accepts a class, module, or shared base type, the check helps communicate that the method is meant for a wider range of callers than a single strict class match would allow.
See Also
- Object#kind_of? — Alias for is_a?
- Object#instance_of? — Checks exact class only (not ancestors)
- Object#respond_to? — Checks if an object responds to a method