Method Naming Conventions (?, !)

Published by Programming Line in Ruby. Last updated .

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:

  1. Ruby evaluates the receiver, original, which currently points to the Array object [3, 1, 2] in memory.
  2. Ruby looks up the method named sort! starting at original‘s singleton class, then its class (Array), where it finds Array#sort! defined in Ruby’s core.
  3. sort! rearranges the elements of that same Array object in place — no new object is allocated for the result — and returns self, i.e. the very same object original already refers to.
  4. Because original and 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 to original.

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 return true, false, or nil — 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 returns true if a string reads the same forwards and backwards (ignoring case), and false otherwise. Test it with "Level" and "Ruby".
  • Write a class BankAccount with a balance attribute, a non-mutating method deposit(amount) that returns a new BankAccount with the increased balance, and a mutating method deposit!(amount) that changes the existing account’s balance in place. Verify that calling deposit leaves the original account unchanged while deposit! does not.
  • Given an array containing some nil values mixed with numbers, call compact! 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 return true, false, or nil, 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!) return nil when nothing changed — never chain directly off their result.
  • Ruby’s truthiness rules (only nil and false are 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.