In Ruby, when is a default such as `def log(msg, at = Time.now, tags = [])` evaluated, and may it use earlier parameters?
answer
- evaluated on every call
- only when the argument is omitted
- earlier parameters are in scope
- explicit nil is a real value
- a new [] each call
basics
~20 sRuby evaluates a default expression on each call that omits that argument, left to right, so every call gets a fresh Time.now and a new []. A default may use earlier parameters, but passing nil explicitly does not trigger it.
solid answer
~40 sA Ruby default is an **expression evaluated at call time**, each time the caller omits that argument, and parameters are processed **left to right**. So `at = Time.now` stamps each call separately and `tags = []` builds a new Array per call, which means appending to it never leaks between calls. Unlike Python, whose defaults are evaluated once when the function is defined, Ruby has no shared-mutable-default trap. A default can use **earlier parameters**, instance variables and methods of `self`: `def excerpt(text, limit = text.size / 2)`. Referring to a later parameter fails with `NameError` unless a method of that name exists. Passing `nil` explicitly is a value, not an omission, so `log("boot", nil)` leaves `at` as `nil`.
code
ruby · 13 linesdef tag(msg, tags = [])
tags << msg
end
tag(1) # => [1]
tag(2) # => [2] (a new Array each call)
def excerpt(text, limit = text.size / 2)
text[0, limit]
end
excerpt("disk full") # => "disk"
excerpt("disk full", nil) # TypeError: no implicit conversion from nil to integergo deeper
Recall that defaults are used only when the argument is omitted and are evaluated on every such call.
Explain left-to-right evaluation, defaults that use earlier parameters, the NameError for later ones, and why an explicit nil bypasses the default.
Watch for costly or side-effecting defaults and nil-versus-omitted ambiguity in shared APIs, and use a sentinel or an explicit nil contract where it matters.
Define how public methods treat nil versus omitted arguments so the contract is consistent across libraries and does not depend on each author's habits.
## Defaults are expressions run per call A **default value** in Ruby is not a stored constant: it is an expression the method runs at **call time**, and only when the caller did not supply that argument. Parameters are handled **left to right**, so each default can see what came before it. ```ruby class AuditLog def log(msg, at = Time.now, tags = []) tags << :audit "#{at.strftime('%H:%M:%S')} #{msg} #{tags.inspect}" end end audit = AuditLog.new audit.log("boot") # a fresh time and tags = [:audit] audit.log("ready") # a new time again, tags = [:audit] again ``` Consequences of per-call evaluation: - **Time-based defaults are current.** `at = Time.now` records when each call happened, not when the class was loaded. - **Mutable defaults are fresh.** `tags = []` allocates a new Array for every call that omits it, so `tags << :audit` never accumulates across calls. - **Defaults can be expensive.** A default that queries a database or reads a file runs every time the argument is omitted; move such work into the body behind a condition if it is not always needed. ## The contrast interviewers ask about Python evaluates default expressions **once**, when the function is defined, and every call that omits the argument shares that single object. That is why a Python function with a list default can accumulate values across calls. Ruby's model is the opposite, so the equivalent Ruby method returns `[1]` for the first call and `[2]` for the second, not `[1, 2]`. | Question | Ruby | |---|---| | When is the default evaluated? | on each call that omits the argument | | Is a `[]` default shared between calls? | no, each call gets a new Array | | Can it reference an earlier parameter? | yes, left to right | | Can it reference a later parameter? | no, `NameError` unless a method has that name | | Does an explicit `nil` trigger it? | no, `nil` is bound as passed | ## Using earlier parameters, and the limits Because parameters bind in order, a default can depend on anything bound before it: 1. **Earlier parameters:** `def excerpt(text, limit = text.size / 2)`. 2. **Chains of defaults:** `def box(width, height = width, depth = height)`. 3. **Instance state and methods:** `def log(msg, level = default_level)`, where `default_level` is a method of the same object. The reverse direction does not work. In `def span(a = b, b = 1)`, when `a`'s default runs, `b` is not yet a local variable, so Ruby treats `b` as a method call and raises `NameError` for an undefined local variable or method, unless the object happens to have a method called `b`, which is worse because it silently calls it. ## What a default may contain A default is any Ruby expression, not only a literal: - **Method calls:** `at = Time.now`, `level = default_level`. - **Conditionals:** `limit = text.size > 80 ? 40 : text.size`. - **Raising:** `def fetch(key, fallback = raise(ArgumentError, "fallback required"))` makes an apparently optional parameter fail loudly with a custom message when omitted. - **Constants and frozen values:** `tags = DEFAULT_TAGS` shares one object across calls deliberately, so the method must not mutate it; freezing the constant enforces that. Whatever the expression is, it runs in the method's own scope, with `self` set to the receiver and the earlier parameters already bound. ## Explicit nil is not an omission A default fires only when the **argument is missing**. A caller who passes `nil` has supplied a value: - `audit.log("boot", nil)` binds `at = nil`, and `at.strftime` then raises `NoMethodError`. - If `nil` should mean "use the default", write the parameter as `at = nil` and resolve it in the body with `at ||= Time.now`. - To distinguish "omitted" from "passed nil" exactly, use a private sentinel object as the default, `NOT_GIVEN = Object.new`, and compare with `equal?`. ## Key points - Defaults run at call time, only for omitted arguments, left to right. - Mutable and time-based defaults are fresh per call. - Earlier parameters are visible to later defaults; later ones are not. - `nil` passed explicitly is bound as `nil`.
- In Ruby, with `def box(width, height = width, depth = height)`, what does `box(3)` bind?`width = 3`, `height = 3` and `depth = 3`. Each default runs left to right after the parameters before it are bound, so `height` sees `width` and `depth` sees `height`. `box(3, 5)` gives `depth = 5`.
- How can a Ruby method tell a caller who omitted an argument from one who passed nil?Use a private sentinel as the default, for example `NOT_GIVEN = Object.new` and `def log(msg, at = NOT_GIVEN)`, then check `at.equal?(NOT_GIVEN)`. A `nil` default cannot make the distinction, because both cases arrive as `nil`.
saying these in an interview costs you the question
- Ruby evaluates a default once, when the method is defined.
- A tags = [] default is shared by every call, so values pile up.
- Passing nil explicitly makes Ruby use the default instead.
- A default can refer to any parameter, including later ones.
- Defaults must be literals, not method calls or expressions.