skip to content

Syntax & Naming

Variables and their sigils, the naming conventions Ruby code relies on, how values print, and operators that are really method calls. Interviewers use these to spot real Ruby experience.

on this pageshow

explore

questions

22

In Ruby, what do the trailing ? in overdue? and the trailing ! in renew! promise, and what do they not promise?

level: juniorimportance: must knowfreq 74%

answer

  1. characters in the name, nothing enforced
  2. ? returns truthy or falsy
  3. nonzero? returns self or nil
  4. ! is dangerous relative to its twin
  5. push mutates without a bang

basics

~20 s

Both are conventions Ruby does not enforce. A ? method answers a yes/no question with a truthy or falsy value, not always true or false. A ! method is the more dangerous twin of a same-named method, often mutating or raising.

solid answer

~40 s

`?` and `!` are simply characters allowed at the end of a method name; Ruby enforces nothing about them. By convention a **predicate** such as `overdue?` answers a question with a truthy or falsy value, not always `true` or `false`: `5.nonzero?` returns `5`, `0.nonzero?` returns `nil`, and `File.size?` returns an Integer or `nil`. A **bang** method such as `renew!` marks the *more dangerous* member of a pair. In the core library that usually means it modifies the receiver where the plain method returns a new object (`sort` vs `sort!`), but it can also mean skipping safety steps: `exit!` ends the process without running `at_exit` handlers. `!` does **not** mean "mutates": `Array#push`, `String#concat` and `Hash#delete` all mutate with no bang. The convention is to add `!` only when a non-bang twin exists.

code

ruby · 22 lines
ruby
class LibraryLoan
  MAX_RENEWALS = 2
  RenewalLimitReached = Class.new(StandardError)

  def initialize(due_day)
    @due_day = due_day
    @renewals = 0
  end

  def overdue?(today) = today > @due_day

  def renew
    return false if @renewals >= MAX_RENEWALS
    @renewals += 1
    @due_day += 14
    true
  end

  def renew!
    renew or raise RenewalLimitReached, "limit #{MAX_RENEWALS}"
  end
end

go deeper

for a junior

Know that ? names a question returning truthy or falsy and ! names the dangerous twin, and that neither is enforced by Ruby.

for a middle

Give counterexamples both ways: push and delete mutate without a bang, exit! is dangerous without mutating, and nonzero? returns a number or nil.

for a senior

Design paired methods deliberately, such as renew returning false and renew! raising, and never rely on a missing bang as proof of immutability.

for a principal

Set API naming guidance for the team so that bang and predicate methods mean the same thing across libraries that others will consume.

## Characters in a name, not operators A Ruby method name may end in `?`, `!` or `=`. The first two carry **no special behaviour**: the interpreter calls `overdue?` and `renew!` exactly like any other method. Their meaning is a **convention** that readers rely on, and Ruby's own documentation notes that it holds for the core library but may or may not hold for other libraries. ## The ? predicate convention A method ending in `?` answers a yes/no question. - It returns a **truthy or falsy** value. Many predicates return exactly `true` or `false`, such as `Array#empty?`, but that is not guaranteed. - `Integer#nonzero?` returns the number itself when it is not zero and `nil` when it is, which lets it be chained in sort comparisons. - `File.size?` returns the file size as an Integer, or `nil` when the file is missing or empty. - Ruby style names predicates `overdue?`, not `is_overdue` or `is_overdue?`. So code should test a predicate's result for truthiness (`if loan.overdue?`), never compare it with `== true`. ## What ! means: dangerous relative to its twin A bang method is the **more dangerous version of a method with the same name without the `!`**. "Dangerous" is relative to that twin: | Pair | Plain version | Bang version | |---|---|---| | `sort` / `sort!` | returns a new sorted array | sorts the receiver in place | | `upcase` / `upcase!` | returns a new string | changes the receiver | | `exit` / `exit!` | raises `SystemExit`, so `ensure` blocks and `at_exit` handlers run | ends the process immediately, no exit handlers | | `renew` / `renew!` (our loan class) | returns `false` when renewal is not allowed | raises when renewal is not allowed | In the core library the danger is almost always that the bang version **modifies its receiver**, and nearly every core bang method has a non-bang counterpart. In application code the same suffix commonly marks the version that **raises** instead of returning a failure value. ## What ! does not promise 1. **Not every mutating method has a bang.** `Array#push`, `Array#<<`, `Array#delete`, `Array#clear`, `String#concat`, `String#replace` and `Hash#delete` all change their receiver, with no `!`, because there is no safe twin to contrast them with. 2. **A bang does not always mean mutation.** `exit!` changes nothing about an object; it skips cleanup. 3. **A bang on its own is a smell.** If there is no `renew`, naming a method `renew!` gives readers nothing to compare against; use a descriptive verb instead. 4. **Return values differ by method.** Read each bang method's documentation rather than assuming it returns the receiver. ## Reading an unfamiliar API 1. **Look for the twin.** If `x!` exists, compare it with `x` to learn what the danger is: mutation, raising, or skipped cleanup. 2. **Do not treat a missing bang as a promise.** Check whether a method without `!` mutates; `Hash#update` changes the receiver and is simply another name for `Hash#merge!`. 3. **Read the return value in the documentation.** Bang and predicate methods each document what they return, and the conventions do not fix it. 4. **Test predicates for truthiness.** `if loan.overdue?` works whatever truthy value comes back. ## Designing a LibraryLoan - `overdue?` returns whether the due date has passed: a predicate with no side effects. - `renew` returns `false` when the loan has hit `MAX_RENEWALS`, and otherwise extends the due date and returns `true`. - `renew!` extends the due date too, but raises when the limit is reached, so callers that cannot handle failure use it. - `due_date=` is the setter for the date itself, a third suffix with a different meaning. In an interview, the strong answer states the two conventions, gives a counterexample to "`!` means mutation" in each direction, and explains that the language enforces none of it.

  • Why does Array#push mutate the array without ending in a bang?
    Because the bang convention marks the dangerous member of a pair, and `push` has no safe twin: appending to an array only makes sense in place. The same holds for `<<`, `delete`, `clear` and `concat`. Readers therefore cannot assume that a method without `!` leaves its receiver alone; they have to know the method.
  • Should a predicate method return exactly true or false?
    It is good practice for your own predicates, because callers may print or serialise the result. Ruby does not require it, and core methods break it on purpose: `nonzero?` returns the number or `nil`, and `File.size?` returns the size or `nil`. Callers should rely on truthiness, never on `== true`.

A bang is like a caution sign on one of two doors: it tells you that door is riskier than its neighbour, not what the risk is, and some risky doors have no sign at all.

saying these in an interview costs you the question

  • Ruby checks that a ? method returns true or false
  • The ! suffix means the method modifies its receiver, always
  • A method without ! never changes the object it is called on
  • Any method that mutates state should be given a bang
  • exit! is simply a louder alias of exit
open as a page

In Ruby, which naming rules for a LibraryLoan class are enforced by the parser, and which are only snake_case and CamelCase conventions?

level: juniorimportance: must knowfreq 58%

basics

~20 s

The parser enforces only the first character: an uppercase initial makes a constant, so class and module names must be capitalised, while a lowercase or underscore initial makes a local or method. snake_case, CamelCase and SCREAMING_SNAKE_CASE are conventions.

open as a page

In Ruby, what do 7 / 2, -7 / 2 and 7 / 2.0 return, and how do you split 100 cents three ways?

level: juniorimportance: must knowfreq 70%

basics

~20 s

7 / 2 is 3 and -7 / 2 is -4, because Integer division floors; 7 / 2.0 is 3.5, because a Float operand gives Float division. Split with 100.divmod(3), which returns [33, 1], and hand out the leftover cent.

open as a page

In Ruby, how do puts, print, p and pp differ in what they write to standard output and what they return?

level: juniorimportance: must knowfreq 80%

basics

~10 s

puts and print write each argument's to_s (puts adds newlines, print does not) and return nil; p writes inspect plus a newline and returns its argument; pp is p with line-wrapped pretty-printing.

open as a page

In Ruby, what do the sigils in total, @total, @@total and $total tell you about where each variable is visible?

level: juniorimportance: must knowfreq 72%

basics

~20 s

The prefix is the scope: a bare name is a local limited to its current scope, @name belongs to the current object, @@name is shared by a class, its subclasses and instances, and $name is visible to the whole process.

open as a page

In Ruby, what does `@rate ||= fetch_rate` actually do, and why does it call fetch_rate again when the result was nil or false?

level: middleimportance: must knowfreq 66%

basics

~20 s

a ||= b behaves like a || a = b: it assigns only when a is nil or false. So memoizing a method that returns nil or false never caches, and the work reruns on every call; use a presence check instead.

open as a page

In Ruby, what is the difference between to_s and inspect, and which of them do puts, p, interpolation and Array#to_s use?

level: middleimportance: must knowfreq 64%

basics

~10 s

to_s is an object's plain, human-facing text; inspect is the developer-facing form showing type and contents. puts and string interpolation call to_s, p calls inspect, and Array#to_s and Hash#to_s are aliases of inspect.

open as a page

In a Ruby temperature-conversion script, why can't def to_fahrenheit(celsius) read a factor variable assigned at the top level?

level: middleimportance: must knowfreq 62%

basics

~20 s

def, class and module are scope gates: each starts a new, empty set of local variables, so a top-level local is invisible inside the method and the bare name raises NameError. Pass the value as an argument or make it a constant.

open as a page

In Ruby, what does the safe navigation operator &. return when the receiver is nil, and what does it not protect against?

level: juniorimportance: should knowfreq 50%

basics

~10 s

obj&.method(args) returns nil without calling the method or evaluating its arguments when obj is nil. It skips only that one call, still calls methods on false, and does not rescue NoMethodError on other objects.

open as a page

In Ruby, what do `puts nil`, `puts []`, `puts [1, [2, nil]]`, `print nil` and `p nil` each write to standard output?

level: juniorimportance: should knowfreq 42%

basics

~20 s

puts nil writes an empty line, puts [] writes nothing, puts [1, [2, nil]] writes 1, 2 and an empty line, print nil writes nothing, and p nil writes the text nil plus a newline.

open as a page

In Ruby, how does parallel assignment such as a, b = b, a swap two values without a temporary variable?

level: juniorimportance: should knowfreq 52%

basics

~10 s

Ruby evaluates every value on the right-hand side first, then assigns them to the left-hand targets in order, so a, b = b, a reads both old values before writing either one.

open as a page

In Ruby, why must a LibraryLoan method write self.due_date = day instead of due_date = day to call its due_date= setter?

level: middleimportance: should knowfreq 48%

basics

~20 s

Inside a method, name = value without a receiver always assigns a local variable, so the due_date= setter never runs. An explicit receiver, self.due_date = day, makes Ruby call the method, and self works even when the setter is private.

open as a page

In Ruby, why does `ready = true and false` leave ready set to true, and when are and, or and not appropriate?

level: middleimportance: should knowfreq 55%

basics

~20 s

and, or and not bind more loosely than =, so ready = true and false parses as (ready = true) and false. Use && || ! for boolean expressions and reserve and/or for control flow such as find_bill(id) or raise.

open as a page

In Ruby, why does -7 % 3 return 2 while -7.remainder(3) returns -1, and when does the difference matter?

level: middleimportance: should knowfreq 36%

basics

~10 s

Integer#% is a modulo whose result takes the divisor's sign and pairs with floored division, so -7 % 3 is 2; Integer#remainder takes the receiver's sign and pairs with truncation, so -7.remainder(3) is -1.

open as a page

In Ruby, what does the defined? keyword return for a local, an instance variable, a constant, a method and an undefined name?

level: middleimportance: should knowfreq 40%

basics

~20 s

defined? is a keyword that returns a string naming what its argument is, such as "local-variable", "instance-variable", "constant" or "method", or nil when the name is not defined, without running the method or assignment it inspects.

open as a page

In Ruby, why does x = 10 if false followed by p x print nil instead of raising NameError?

level: middleimportance: should knowfreq 34%

basics

~20 s

Ruby creates a local variable when the parser sees an assignment to it, not when the assignment runs. After x = 10 if false, x exists as a local for the rest of the scope and holds nil.

open as a page

In Ruby 4.0, why might a file's # frozen_string_literal: true magic comment be ignored, and what syntax rules must magic comments follow?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Magic comments are read only from the comments at the top of a file, before any code. A frozen_string_literal line after a require, with trailing text, without its colon or with a non-boolean value is ignored.

open as a page

In Ruby, if an Amount class for restaurant bills defines + and ==, what happens with += and !=, and which operators can never be overloaded?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Most Ruby operators are methods on the left operand: defining + makes += work, since it expands to a = a + b, and BasicObject#!= negates ==. Assignment, &&, ||, and, or, ?:, &. and :: are syntax, not overloadable.

open as a page

In Ruby 4.0, how do you keep a secret instance variable such as @password out of an object's inspect output, and where can it still leak?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Ruby 4.0's Kernel#inspect shows only the instance variables returned by an instance_variables_to_inspect method; before 4.0 you override inspect. Secrets still leak through a hand-written to_s, direct ivar access and pp when the hook is private.

open as a page

In Ruby, what happens when code reassigns a constant such as MAX_RETRIES, and why is assigning one inside a method a SyntaxError?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Reassigning a constant at top level or in a class body succeeds but prints 'already initialized constant' plus the previous location. Inside a method it is rejected at parse time as 'dynamic constant assignment', because a method runs many times.

open as a page

In Ruby, how do =begin and =end create a multi-line comment, and why do most Rubyists use consecutive # lines instead?

level: juniorimportance: nice to knowfreq 24%

basics

~20 s

Everything between =begin and =end is ignored, but both markers must start at the very beginning of their own lines, so they cannot be indented inside a class or method. Most Rubyists put # on each line instead, which works at any indentation.

open as a page

In Ruby, when does pp print differently from p, and how can a class customise its pp output with pretty_print?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

pp prints the same inspect-style text as p but breaks values wider than the output width over indented lines; a class customises it by defining pretty_print(q) with the PP builder's group, text, breakable and pp calls.

open as a page