rubyguides

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

ParameterTypeDefaultDescription
klassClass or ModuleThe 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