The best examples of type errors in Ruby: real-world examples and fixes
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.