skip to content

In Ruby, what does a method return when it has no return keyword, and when is an explicit return still worth writing?

level: juniorimportance: must knowfreq 78%

answer

  1. whatever ran last
  2. if with no branch taken: nil
  3. puts gives nil, each gives self
  4. guard clause exits early
  5. return a, b builds an Array

basics

~20 s

A Ruby method returns the value of the last expression it actually evaluated, so no return keyword is needed. Explicit return earns its place for early exits such as guard clauses; return a, b returns one Array.

solid answer

~40 s

Every Ruby method returns the value of the **last expression evaluated** in its body, along whichever branch actually ran. That is why an `if` with no matching branch yields `nil`, a trailing `puts` yields `nil`, and a trailing `each` yields its receiver rather than anything computed in the block. `return` is still worth writing for an **early exit**: a guard clause such as `return "n/a" if @celsius.nil?` keeps the happy path unindented, and a bare `return` gives `nil`. `return a, b` does not return two objects; it packs them into one `Array` the caller can destructure. A `return` on the last line changes nothing, and RuboCop's `Style/RedundantReturn` flags it.

code

ruby · 18 lines
ruby
class Temperature
  def initialize(celsius)
    @celsius = celsius
  end

  def both_scales
    return @celsius, @celsius * 9 / 5.0 + 32
  end

  def print_readings(readings)
    readings.each { |r| puts r }
  end
end

temp = Temperature.new(20)
temp.both_scales                # => [20, 68.0]
c, f = temp.both_scales         # c = 20, f = 68.0
temp.print_readings([18, 21])   # prints 18 and 21, returns [18, 21]

go deeper

for a junior

Recall the rule in one line: the last evaluated expression is the return value. Then name two traps, an if with no branch taken giving nil and puts returning nil.

for a middle

Explain why each returns its receiver, how return a, b becomes one Array that callers destructure, and why guard clauses are the main legitimate use of return.

for a senior

Show how an escaping nil from an unguarded if surfaces far from its cause, and argue for explicit else branches or guard clauses on public methods whose callers rely on the value.

for a principal

Weigh positional multi-value returns against small value objects for public APIs, and decide which RuboCop return-style settings a team enforces so reviews stop debating them.

## The last evaluated expression is the return value In Ruby almost everything is an **expression** that produces a value: `if`, `case`, `begin`, assignments and method calls all evaluate to something. A method body is a sequence of expressions, and when the body finishes, the method hands back the value of the **last expression that was evaluated**. That rule has two parts, and both matter: - **Last** means the final expression on the path the code took, not the final line of source text. - **Evaluated** means a branch that did not run contributes nothing; the enclosing `if` or `case` evaluates to `nil` instead. So `def one_plus_one = 1 + 1` and a three-line `def ... end` whose final line is `1 + 1` both return `2`, and neither needs the `return` keyword. ## Last lines that surprise people Most "my method returned the wrong thing" bugs come from a last line whose value is not what the author pictured: | Last expression in the body | What the method returns | |---|---| | `if` / `elsif` with no branch taken and no `else` | `nil` | | `puts "done"` | `nil` (`puts` always returns `nil`) | | `@readings.each { ... }` | the receiver, `@readings` (`Array#each` returns `self`) | | `@total = @total + reading` | the assigned value | | `case` with no matching `when` and no `else` | `nil` | The `each` row is the classic interview trap: the block's values are thrown away, and the method returns the collection it iterated. When the transformed values are the point, the last expression should be `map` (or another method that returns them), not `each`. ## When an explicit return earns its place The `return` keyword ends the method immediately with the value given, or with `nil` when none is given. Idiomatic Ruby uses it for: 1. **Guard clauses** at the top of a method, such as `return "n/a" if @celsius.nil?`, so the main logic below is not wrapped in an `if`. 2. **Leaving a loop early** from inside the method once the answer is known. 3. **Returning several values** with `return a, b` (see below). On the final line, `return x` behaves exactly like `x`. RuboCop's `Style/RedundantReturn` cop, enabled by default, reports it and autocorrects it away. ```ruby class Temperature def initialize(celsius) @celsius = celsius end def label return "n/a" if @celsius.nil? # guard clause if @celsius >= 30 "hot" elsif @celsius <= 0 "freezing" end # 15 => nil: no branch ran end end Temperature.new(35).label # => "hot" Temperature.new(15).label # => nil Temperature.new(nil).label # => "n/a" ``` ## Why Ruby leans on implicit return Ruby treats a method body as an expression that produces a value, the same way `if` and `case` do. That has practical effects: - Short methods read as a description of their result: `def freezing? = @celsius <= 0` needs nothing else. - A conditional can be the last expression and supply the value directly, which avoids temporary variables such as `result = ...` followed by `return result`. - Blocks follow the same rule: the value a block hands back to `map`, `select` or `sort_by` is its last expression, which is why `temps.map { |t| t.celsius * 2 }` needs no keyword. The flip side is that every last line is a return value whether you meant it or not. A method written for its side effect, such as logging a reading, still returns something, and callers can start depending on it. Ending such methods with a deliberate `nil` or `self`, and documenting which, keeps an accidental contract from forming. ## Returning several values A Ruby method always returns exactly **one object**. Writing `return @celsius, fahrenheit` builds an `Array` of the listed values and returns that, so `Temperature.new(20).both_scales` can return `[20, 68.0]`. The caller then unpacks it with multiple assignment, `c, f = temp.both_scales`. A few details follow from the Array rule: - `return *list` splats a list into the returned Array, and an empty list returns `[]`. - `return [a, b]` and `return a, b` return equal Arrays; the second form is just shorter syntax. - `Style/RedundantReturn` has an `AllowMultipleReturnValues` option, `false` by default, for teams that prefer `return a, b` over a bare `[a, b]` on the last line. For more than two or three values, a named value object communicates better than a positional Array, because the caller no longer has to remember which slot holds which value. ## How to reason about a method's value - Trace the path the call actually takes and find the last expression evaluated on it. - Treat every `if` without an `else` as a place where `nil` can escape. - Remember which common methods return `nil` (`puts`, `print`, `p` with no arguments) or their receiver (`each`, `each_with_index`). - Keep `return` for early exits, where it makes intent clearer, rather than for decoration on the last line.

  • In Ruby, what does a method whose last line is `@readings.each { |r| puts r }` return?
    It returns `@readings` itself, because `Array#each` returns its receiver once it finishes. The `puts` calls inside the block return `nil`, but their values are discarded by `each`. If the caller needs transformed values, the last expression should be `map` or another method that returns them.
  • If a Ruby method can only ever return one object, how does `return 20, 68.0` hand back two values?
    It builds the Array `[20, 68.0]` and returns that single object. The caller can destructure it with multiple assignment, `c, f = temp.both_scales`, which makes it look like two return values. `return *list` works the same way and returns `[]` for an empty list.
  • What does a bare `return` with no value give back in a Ruby method, and when is it idiomatic?
    A bare `return` ends the method and returns `nil`. It is idiomatic in guard clauses of methods called for their side effects, where the caller ignores the value, such as `return if readings.empty?` at the top of a method that writes a report.

saying these in an interview costs you the question

  • A Ruby method without a return statement returns nil.
  • An if whose condition fails returns false as the method's value.
  • return a, b hands the caller two separate objects, like a tuple on the stack.
  • A method ending in each returns the values its block produced.
  • The last line needs return, or the value is lost.
  • puts at the end of a method returns the string it printed.