Method Naming Conventions (?, !)
In Ruby, a method’s name can end with a question mark (?) or an exclamation mark (!), and these aren’t decorative — they’re a widely followed naming convention that tells other programmers (and future you) what a method does before you even read its body. A method ending in ? signals that it returns a boolean-ish value and answers a yes/no question. A method ending in ! signals that it’s the more dangerous or mutating sibling of a same-named method without the punctuation. Understanding this convention is essential to reading and writing idiomatic Ruby.
Overview: How Method Naming Conventions Work
Ruby allows method names to end in a few special characters: ?, !, and = (the last one is reserved for setter methods like attr_name=). This is a feature of Ruby’s parser — anywhere an identifier can appear as a method name, ? and ! may be the final character. Ruby does not allow these characters in variable names, so valid? and valid! can only ever refer to methods, never local variables, which removes a whole class of ambiguity when reading code.
It’s important to understand that these characters carry no special behavior enforced by the Ruby interpreter itself. Nothing stops you from writing a ? method that returns the string "yes" instead of true, or a ! method that doesn’t mutate anything at all. The convention is a promise made between a method’s author and its callers, upheld by the Ruby community and style guides — not by the language. That is exactly why understanding it matters: violating it produces code that still runs, but misleads everyone who reads it.
The ? convention (predicate methods): a method ending in ? is called a predicate method. By convention it returns true, false, or occasionally nil (which behaves like false in a conditional), and is used to ask a yes/no question about an object. Ruby’s core library is full of them: Array#empty?, Integer#even?, String#include?, Object#nil?, Object#is_a?. This ties directly into Ruby’s truthiness rules: only nil and false are falsy, and everything else — including 0, "", and [] — is truthy. A well-written predicate method must return an actual false or nil when the answer is “no,” not just some falsy-looking value, because in Ruby a falsy-looking value like 0 is not actually falsy.
The ! convention (bang methods): a method ending in ! is informally called a “bang method” or “dangerous method.” The community convention, echoed in style guides, is roughly this: if a method foo exists and a variant foo! also exists, then foo! should be the version that is more surprising or risky — most commonly because it mutates its receiver in place, but sometimes because it raises an exception where the non-bang version would return nil or a default value instead. Array#sort returns a new, sorted array and leaves the original untouched; Array#sort! rearranges the original array’s contents in place and returns that same, now-mutated array. There isn’t always a non-bang counterpart — Kernel#exit! has no plain exit that behaves identically, it just means “exit more abruptly, skipping at-exit hooks and ensure blocks.”
Because ? and ! methods are just methods like any other, they participate fully in Ruby’s method lookup: when you call obj.valid?, Ruby searches obj‘s singleton class, then its class, then any included modules, then up the superclass chain, exactly as it would for a method named without any punctuation at all. The punctuation is purely cosmetic to the lookup algorithm — it exists for the humans reading the code.
Syntax
Defining a predicate or bang method looks exactly like defining any other method — the only difference is the trailing character in the name:
def method_name?
# should return true, false, or nil
end
def method_name!
# performs a more dangerous or mutating version of method_name
end
| Suffix | Meaning by convention | Examples from Ruby’s core library |
|---|---|---|
? |
Returns true/false/nil; answers a yes/no question | empty?, nil?, even?, include?, respond_to? |
! |
Mutates the receiver in place, and/or is more dangerous than a same-named method without the ! |
sort!, gsub!, upcase!, exit! |
| (none) | The safe, non-mutating default — usually returns a new object and leaves the receiver untouched | sort, gsub, upcase |
Call sites look the same as any other method call — the ? or ! is simply part of the method’s name:
def valid?
true
end
def save!
puts "saved!"
end
puts valid?
save!
Output:
true
saved!
Examples
Example 1: Writing and using a predicate method
def even_number?(n)
n.even?
end
numbers = [1, 2, 3, 4, 5, 6]
numbers.each do |n|
puts "#{n} is even? #{even_number?(n)}"
end
puts [].empty?
puts "hello".empty?
puts nil.nil?
Output:
1 is even? false
2 is even? true
3 is even? false
4 is even? true
5 is even? false
6 is even? true
true
false
true
The custom method even_number? simply delegates to Ruby’s own Integer#even?, but wrapping it shows the pattern: the method name asks a question, and the body answers it with a boolean. Notice how empty?, nil?, and the custom even_number? all read naturally inside string interpolation or a conditional — that readability is the entire point of the convention. None of these methods mutate anything; they only inspect and answer.
Example 2: The difference between a method and its bang counterpart
original = [3, 1, 2]
sorted = original.sort
puts "original: #{original}"
puts "sorted: #{sorted}"
original.sort!
puts "original after sort!: #{original}"
class Temperature
attr_reader :degrees
def initialize(degrees)
@degrees = degrees
end
def warm_up(amount)
Temperature.new(@degrees + amount)
end
def warm_up!(amount)
@degrees += amount
self
end
end
room = Temperature.new(20)
warmer_room = room.warm_up(5)
puts "room: #{room.degrees}, warmer_room: #{warmer_room.degrees}"
room.warm_up!(5)
puts "room after warm_up!: #{room.degrees}"
Output:
original: [3, 1, 2]
sorted: [1, 2, 3]
original after sort!: [1, 2, 3]
room: 20, warmer_room: 25
room after warm_up!: 25
original.sort leaves the array referenced by original completely alone and hands back a new, sorted array; original.sort! instead rearranges the elements of the very array original already points to and returns that same object. The Temperature class mirrors the same pattern by hand: warm_up returns a brand-new Temperature instance built from a slightly higher value, while warm_up! reaches into the existing object’s @degrees instance variable and changes it directly, then returns self so the mutating call can still be chained if needed.
Example 3: Combining ? and ! in a small class
class Task
attr_reader :title
def initialize(title)
@title = title
@done = false
end
def done?
@done
end
def complete!
@done = true
self
end
end
tasks = [Task.new("Write lesson"), Task.new("Review pull request")]
tasks.each do |task|
puts "#{task.title} done? #{task.done?}"
end
tasks.first.complete!
tasks.each do |task|
puts "#{task.title} done? #{task.done?}"
end
incomplete = tasks.reject(&:done?)
puts "Remaining: #{incomplete.map(&:title)}"
Output:
Write lesson done? false
Review pull request done? false
Write lesson done? true
Review pull request done? false
Remaining: ["Review pull request"]
done? is a predicate that simply exposes the @done instance variable — a common, minimal implementation for a boolean reader. complete! mutates that variable and returns self, following the bang convention even though there’s no separate complete (non-bang) method defined at all here; the bang still correctly signals “this changes the object’s state.” The reject(&:done?) call shows predicate methods composing naturally with Enumerable via the Symbol#to_proc shorthand — reject keeps every task for which done? answers false.
How It Works Step by Step
Walk through what happens when Ruby evaluates original.sort! from Example 2:
- Ruby evaluates the receiver,
original, which currently points to the Array object[3, 1, 2]in memory. - Ruby looks up the method named
sort!starting atoriginal‘s singleton class, then its class (Array), where it findsArray#sort!defined in Ruby’s core. sort!rearranges the elements of that same Array object in place — no new object is allocated for the result — and returnsself, i.e. the very same objectoriginalalready refers to.- Because
originaland the sorted array are the same object, every other variable or instance variable that also refers to that array sees the change immediately, even though nothing was ever reassigned tooriginal.
Contrast that with original.sort (no bang): step 3 changes — instead of mutating in place, sort builds and returns a brand-new Array object containing the sorted elements, and the object original points to is left completely untouched. This is the mechanical difference the naming convention is communicating: same question, two different strategies for answering it.
Common Mistakes
Mistake 1: Chaining off a bang method that can return nil
Several of Ruby’s built-in bang methods — including uniq!, compact!, reject!, and select! — return nil when they don’t actually change anything, instead of returning self. This is intentional (it lets you write array.uniq! || array to detect whether deduplication happened), but it silently breaks any code that chains another method call directly off the result.
def unique_upcased(words)
words.uniq!.map(&:upcase)
end
puts unique_upcased(["ruby", "gem", "ruby"]).inspect
puts unique_upcased(["ruby", "gem"]).inspect
Output:
["RUBY", "GEM"]
The first call works because uniq! found and removed a duplicate, so it returned the deduplicated array and .map ran fine. The second call passes an array with no duplicates, so uniq! returns nil — and calling .map on nil raises NoMethodError: undefined method 'map' for nil, crashing the program before a second line is ever printed. The fix is to never chain directly off a bang method that might return nil; use the plain, non-bang version when you just want the transformed value:
def unique_upcased(words)
deduped = words.uniq
deduped.map(&:upcase)
end
puts unique_upcased(["ruby", "gem", "ruby"]).inspect
puts unique_upcased(["ruby", "gem"]).inspect
Output:
["RUBY", "GEM"]
["RUBY", "GEM"]
Mistake 2: A predicate method that doesn’t actually return a boolean
Ruby doesn’t stop you from naming a method with a trailing ? and then returning something other than true, false, or nil. Combined with Ruby’s truthiness rules — where only nil and false are falsy, and everything else, including 0, is truthy — this is an easy way to introduce a bug that looks correct at a glance.
def positive_balance?(balance)
balance
end
balance = 0
if positive_balance?(balance)
puts "You can withdraw"
else
puts "Insufficient funds"
end
Output:
You can withdraw
This prints the wrong message: a balance of 0 should not allow a withdrawal, but positive_balance? just returns whatever balance is, and in Ruby 0 is truthy — it is not the same as false or nil. The method name promises a yes/no answer, but the implementation hands back a number instead. The fix is to make the predicate actually evaluate a condition and return the boolean result of that comparison:
def positive_balance?(balance)
balance > 0
end
balance = 0
if positive_balance?(balance)
puts "You can withdraw"
else
puts "Insufficient funds"
end
Output:
Insufficient funds
Best Practices
- Make every
?method returntrue,false, ornil— never leak a “truthy-looking” value like a number or string that could accidentally pass a conditional check. - Only define a
!method when there’s a genuine, more dangerous alternative to compare it against; a lone!method with no plain counterpart should still be more surprising or risky than its name without the bang would suggest. - Never chain a method call directly onto the result of a bang method that can return
nil(uniq!,compact!,reject!,select!, and similar) — capture the result in a variable and check it, or use the non-bang version. - Prefer the non-mutating version of a method by default; reach for the
!version deliberately, when you specifically want to avoid allocating a new object or you want the method to raise instead of silently doing nothing. - Document unusual bang methods that don’t mutate anything (like
exit!) — since most Rubyists assume!means “mutates,” a method that breaks that pattern needs a clear comment. - Remember that
?and!are purely conventional — the Ruby interpreter won’t stop you from breaking them, so treat the convention as a contract with your future readers, not a language rule.
Practice Exercises
- Write a predicate method
palindrome?(word)that returnstrueif a string reads the same forwards and backwards (ignoring case), andfalseotherwise. Test it with"Level"and"Ruby". - Write a class
BankAccountwith abalanceattribute, a non-mutating methoddeposit(amount)that returns a newBankAccountwith the increased balance, and a mutating methoddeposit!(amount)that changes the existing account’s balance in place. Verify that callingdepositleaves the original account unchanged whiledeposit!does not. - Given an array containing some
nilvalues mixed with numbers, callcompact!on it twice in a row and print the return value each time. Explain why the second call’s return value differs from the first.
Summary
- Method names in Ruby may end in
?or!— this is purely a naming convention, not something enforced by the interpreter. ?marks a predicate method that should returntrue,false, ornil, answering a yes/no question about the object.!marks a “bang” method that is more dangerous than its unbanged counterpart — most often because it mutates the receiver in place, sometimes because it raises where the plain version would not.- Many built-in bang methods (
uniq!,compact!,reject!,select!) returnnilwhen nothing changed — never chain directly off their result. - Ruby’s truthiness rules (only
nilandfalseare falsy) make it easy to write a broken predicate method that “returns something” instead of returning a real boolean — always compare or coerce to a genuine boolean. - Follow the convention faithfully in your own code: it’s how Rubyists communicate intent to each other without needing to read every method body.
