The best examples of type errors in Ruby: real-world examples and fixes

If you write Ruby for anything serious, you’ve hit type errors. They’re the bugs that make you mutter “but it worked in my head” while your app crashes in production. In this guide, we’ll walk through the best examples of type errors in Ruby: real-world examples pulled from everyday Rails apps, scripts, and APIs. These examples of type issues aren’t abstract puzzles — they’re the kind of bugs that break background jobs, corrupt JSON responses, or silently drop data. We’ll look at how Ruby’s dynamic typing lets you move fast, and how that same flexibility turns into confusing `NoMethodError`, `TypeError`, and `ArgumentError` failures when you mix the wrong objects together. Along the way, you’ll see examples of subtle type mismatches, how Ruby 3’s gradual typing (via RBS and tools like Steep) helps, and how to design code that fails loudly instead of mysteriously. If you want real examples and practical debugging patterns, you’re in the right place.
Written by
Jamie
Published
Updated

Let’s skip theory and start with real examples of type errors in Ruby: real-world examples that look innocent but blow up at runtime.

Ruby is dynamically typed, which means variables don’t have fixed types — objects do. That’s powerful, but it also means many type problems only show up when the code executes. The best examples of these failures usually come from production logs, not textbooks.


Example of calling string methods on nil

You think you have a String. Ruby thinks you have nil. Ruby wins.

user = User.find_by(email: params[:email])

## You expect `user` to always exist
if user.email.downcase.end_with?("@example.com")
#  # ...
end

In development with seed data, this passes. In production, someone signs in with an unknown email and you get:

NoMethodError: undefined method `email' for nil:NilClass

This is one of the best examples of type errors in Ruby: real-world examples where the type is wrong because of data, not because of a typo. user is nil, not a User. And then user.email tries to call email on nil.

How to fix it

Use safe navigation or explicit checks:

if user&.email&.downcase&.end_with?("@example.com")
#  # ...
end

Or fail fast:

user or raise ActiveRecord::RecordNotFound, "User not found for #{params[:email]}"

The pattern to remember from these examples of type errors: any find_by or external API call can return nil. Treat that as a different type, not just “no value.”


Mixing strings and numbers in arithmetic

Dynamic typing makes it easy to accidentally combine incompatible types.

price  = params[:price]      # "19.99" from a form
quantity = params[:quantity] # "3" from a form

total = price * quantity

You expect 59.97. Ruby gives you:

TypeError: String can't be coerced into Integer

In Ruby, "10" * 3 is valid (it repeats the string), but "10" * "3" is not. params are always strings, and these examples of type errors in Ruby: real-world examples show up constantly in form handling and JSON parsing.

Better approach

price    = BigDecimal(params[:price].to_s)
quantity = Integer(params[:quantity], exception: false)

raise ArgumentError, "Invalid quantity" unless quantity

total = price * quantity

You not only convert types explicitly, you also signal invalid input instead of letting a TypeError leak from deep inside the code.


JSON serialization: arrays vs hashes vs strings

APIs are a goldmine for examples of type errors in Ruby: real-world examples, because you’re constantly converting between Ruby objects and JSON.

payload = JSON.parse(request.body.read)

## You expect a hash like {"user" => {"name" => "..."}}
name = payload["user"]["name"].strip

If a client sends an array instead:

[{"user": {"name": "Alice"}}]

payload becomes an Array, not a Hash, and you get:

TypeError: no implicit conversion of String into Integer

Why? Because payload["user"] is trying to index an Array with a String key.

Safer pattern

payload = JSON.parse(request.body.read)

unless payload.is_a?(Hash) && payload["user"].is_a?(Hash)
  raise ArgumentError, "Invalid payload format"
end

name = payload["user"]["name"].to_s.strip

These examples include a key lesson: validate shapes, not just values. Use is_a? or pattern matching (Ruby 2.7+) to verify structure before you trust it.


to_i, to_s, and silent type corruption

Some of the most dangerous examples of type errors in Ruby: real-world examples don’t raise exceptions — they quietly return the wrong type or wrong value.

age = params[:age].to_i

If params[:age] is “abc”, age becomes 0. That’s not a type error in Ruby’s eyes, but it’s a logic bug.

Better validation

raw_age = params[:age]

age = Integer(raw_age)
## Raises ArgumentError if invalid

Now you get a loud failure instead of silently treating non-numeric input as 0. When you review examples of type errors in Ruby, real examples like this show why implicit conversions are risky.

You’ll see the same pattern with dates:

Date.parse(params[:birthday])  # ArgumentError if invalid

Using strict constructors instead of to_i-style coercions gives you clearer failure modes.


ActiveRecord and where with the wrong type

Type mismatches aren’t just about core classes. ORMs like ActiveRecord are another frequent source of type confusion.

## params[:user_ids] is expected to be an Array of IDs
users = User.where(id: params[:user_ids])

If params[:user_ids] is a comma-separated String like “1,2,3”, ActiveRecord will treat it as a single value. You don’t get an exception; you just get the wrong records.

A more explicit version:

user_ids = params[:user_ids]

user_ids = user_ids.split(",") if user_ids.is_a?(String)
user_ids = user_ids.map { |id| Integer(id) }

users = User.where(id: user_ids)

Examples of type errors in Ruby: real-world examples like this show a different failure mode: everything “works,” but the type is off enough to give you bad data. That’s often worse than an exception.


Keyword arguments vs hash: subtle Ruby 3 breakage

Ruby 3 tightened the rules around keyword arguments, and this created a wave of real examples of type errors in Ruby for older codebases.

def send_email(to:, subject:)
#  # ...
end

options = { to: "user@example.com", subject: "Hello" }

send_email(options)

In Ruby 2.6, this might have worked due to implicit hash-to-keyword conversion. In Ruby 3, you get:

ArgumentError: wrong number of arguments (given 1, expected 0; required keywords: to, subject)

The method expects keyword arguments, but you passed a single Hash positional argument — a type mismatch at the call site.

Fix: use the double-splat operator:

send_email(**options)

When you migrate to Ruby 3+, these examples of type errors in Ruby: real-world examples are common. Static analyzers and type checkers can help catch them before runtime.


Duck typing gone wrong: respond_to? isn’t enough

Ruby encourages duck typing: “if it quacks like a duck, treat it like a duck.” That’s powerful, but it can hide type assumptions.

def send_notification(channel)
  channel.deliver("Hello")
end

class EmailChannel
  def deliver(message)
#    # ...
  end
end

class SmsChannel
  def send(message)
#    # ...
  end
end

send_notification(SmsChannel.new)

This blows up with:

NoMethodError: undefined method `deliver' for #<SmsChannel:...>

Here, your code assumes a shared interface that doesn’t actually exist.

Better pattern: define an explicit interface

module Notifiable
  def deliver(_message)
    raise NotImplementedError
  end
end

class EmailChannel
  include Notifiable
  def deliver(message)
#    # ...
  end
end

class SmsChannel
  include Notifiable
  def deliver(message)
#    # ...
  end
end

Now, if someone forgets to implement deliver, you get a clear NotImplementedError instead of a confusing NoMethodError. These examples include a broader lesson: define contracts, even in dynamic languages.


Ruby 3, RBS, and static type checking in 2024–2025

The Ruby ecosystem has moved steadily toward optional static typing. If you’re tired of debugging the same examples of type errors in Ruby: real-world examples over and over, this is where things get interesting.

Tools like:

  • RBS (Ruby’s type signature language)
  • Steep (a static type checker for Ruby)
  • Sorbet (a popular gradual type checker from Stripe)

let you annotate method signatures and catch type mismatches before runtime.

Example with RBS and Steep:

## user_service.rb
class UserService
  def send_welcome_email(user)
    Mailer.send_to(user.email)
  end
end
## user_service.rbs
class UserService
  def send_welcome_email: (User) -> void
end

class User
  attr_reader email: String
end

If you accidentally call:

UserService.new.send_welcome_email("not a user")

Steep flags it during static analysis. You’re catching the same kind of problems you see in all the earlier examples of type errors in Ruby, but before the code ever runs.

For teams maintaining large Rails apps in 2024–2025, adopting gradual typing is less about academic purity and more about not shipping the same category of bug twice.

Authoritative background reading on type systems and static analysis is widely available from university CS departments; for example, you can find foundational material on type checking and program analysis in course notes from institutions like MIT, Stanford, and Harvard.


Patterns for avoiding type errors in real Ruby code

Looking across all these examples of type errors in Ruby: real-world examples, a few patterns stand out.

Validate at the boundaries

Any time data crosses a boundary — HTTP request, message queue, file, environment variable — treat it as untyped. Parse and validate it immediately. Don’t let raw params or ENV values leak deep into your code.

Prefer explicit conversions

Use Integer, Float, BigDecimal, and Date.parse instead of to_i, to_f, and friends when correctness matters more than convenience. Silent coercion is how type errors turn into data bugs.

Use small, predictable interfaces

Rather than passing around generic Hashes and Arrays, define small value objects or structs with clear attributes. Fewer “mystery objects” means fewer surprises.

Consider gradual typing for core domains

You don’t need to type your whole app. Start with the modules where type errors have the highest cost: billing, security, data migrations. Let tools like Steep or Sorbet enforce the contracts you already assume in your head.

For deeper reading on software reliability and error prevention, you can explore engineering and programming language courses and resources from universities such as MIT OpenCourseWare, Harvard, and Carnegie Mellon, which often publish material on static analysis, program correctness, and defensive programming techniques.


FAQ: common questions about Ruby type errors

What are some common examples of type errors in Ruby?

Common examples of type errors in Ruby: real-world examples include calling methods on nil (like user.name.downcase when user is nil), mixing strings and numbers in arithmetic ("10" * "2"), passing the wrong structure to JSON or API code (array vs hash), and Ruby 3 keyword argument mismatches when a Hash is passed as a positional argument.

Can I get an example of a silent type bug that doesn’t raise an error?

Yes. A classic example of this kind of bug is age = params[:age].to_i. If params[:age] is “abc”, age becomes 0. No exception, but your logic is now wrong. These examples include to_f returning 0.0 for invalid input and Date.parse interpreted with the wrong format if you don’t specify one.

How do I debug recurring type errors in a large Ruby or Rails app?

Start by logging the types and shapes of data at boundaries: log payload.class and payload.keys for incoming JSON, or params.inspect for tricky controller actions. Add defensive checks (is_a?, respond_to?) where you see repeated failures. Then consider introducing gradual typing with RBS and a checker like Steep or Sorbet in the hotspots where examples of type errors in Ruby keep showing up.

Are type errors less of a problem in Ruby 3?

Ruby 3 itself doesn’t magically remove type errors, but the ecosystem around Ruby 3 — RBS, Steep, Sorbet, better linters — makes them easier to catch before deployment. The stricter keyword argument rules also surfaced many hidden type mismatches during upgrades, forcing code to be more explicit.

Do I need static typing everywhere to benefit from it?

No. Many teams get value by typing only the critical parts of their app: payment flows, background billing jobs, or complex data transformation pipelines. Even partial coverage can prevent the most expensive examples of type errors in Ruby: real-world examples from ever reaching production.

Explore More Type Errors

Discover more examples and insights in this category.

View All Type Errors