In Ruby, what does reading an instance variable that was never assigned return, and how can that hide a typo?
answer
- no error, no exception
- unlike an undefined local
- silent even under ruby -w since 3.0
- failure surfaces later on nil
- reader methods fail loudly on typos
basics
~20 sAn instance variable that was never assigned reads as nil, with no exception and, since Ruby 3.0, no warning even under -w. A misspelled @balnce therefore yields nil and fails later, far from the typo.
solid answer
~40 sInstance variables spring into existence when first assigned; reading one that never was simply returns `nil`. There is no `NameError`, unlike an undefined local variable, and since Ruby 3.0 there is not even a verbose-mode warning. So a typo such as `@balnce + amount` in a `BankAccount` method evaluates `nil + amount` and raises `NoMethodError` ("undefined method '+' for nil") on that line or, worse, much later if the `nil` is stored first. Defences: set every instance variable in `initialize`, read state through methods so a misspelling raises `NoMethodError` naming the typo, and use `defined?(@balance)`, which returns `nil` when unset and `"instance-variable"` when set, if you must tell "never assigned" from "assigned nil".
code
ruby · 22 linesclass BankAccount
def initialize(owner)
@owner = owner
@balance = 0
end
def deposit(amount)
@balance = @balnce + amount # typo: @balnce reads nil
end
end
BankAccount.new("Ana").deposit(50)
# NoMethodError: undefined method '+' for nil
class Probe
def check
before = defined?(@x)
@x = nil
[before, defined?(@x)]
end
end
Probe.new.check # => [nil, "instance-variable"]go deeper
Recall that an unassigned instance variable reads nil with no error, and that a misspelled one therefore fails somewhere later on nil.
Explain the contrast with locals and class variables, the Ruby 3.0 removal of the verbose warning, and defined?(@x) returning nil versus "instance-variable".
Show how you keep this from reaching production: all state assigned in initialize, readers used inside the class, static checks that catch undeclared instance variables, and tests that run every method.
Weigh how much a team should lean on conventions and static analysis to compensate for a runtime that never reports a misspelled instance variable, and where that effort pays off.
## Instance variables appear on first assignment A Ruby object has no declared fields. An **instance variable** such as `@balance` comes into being the first time some method running on that object assigns it. Before that moment the object simply does not have it, and reading it is still legal: the expression `@balance` evaluates to `nil`. ```ruby class BankAccount def initialize(owner) @owner = owner end def balance @balance # never assigned => nil end end BankAccount.new("Ana").balance # => nil ``` Nothing is raised and, on Ruby 3.0 and later, nothing is printed either. Earlier releases emitted `warning: instance variable @balance not initialized` when run with `-w`; Ruby 3.0 removed that warning, so there is no switch left that reports the read. ## How a typo hides Compare how Ruby treats three kinds of misspelled name: | Misspelling | What Ruby does | |---|---| | a local variable or method name, `balnce` | raises `NameError` (`undefined local variable or method`) at that line | | a method call with a receiver, `self.balnce` | raises `NoMethodError` naming `balnce` | | an instance variable, `@balnce` | returns `nil` silently | The third row is the dangerous one. Suppose `deposit` contains `@balance = @balnce + amount`: 1. `@balnce` reads `nil`. 2. `nil + amount` raises `NoMethodError` with `undefined method '+' for nil`. 3. The message names `+` and `nil`, not the misspelled variable, so the reader must work out which operand was `nil` and why. It gets worse when the `nil` is stored instead of used: `@limit = @limt` succeeds, and the failure appears in another method, or in another request, that finally calls something on `@limit`. ## Defences that work - **Assign every instance variable in `initialize`**, even to `nil`, so the full list of the object's state is written in one place and a reviewer can spot a name that appears nowhere else. - **Read state through methods** inside the class when a value is used in more than one place. A misspelled method name raises `NoMethodError` or `NameError` that names the typo at the line where it happens. - **Let tools look for it.** A static type checker that requires instance variables to be declared reports a misspelled one; the runtime itself never will. - **Tests that exercise each method** catch the `nil` early, because the failure usually shows up the first time the method runs. ## Unset versus explicitly nil Sometimes `nil` is a legitimate value, for example a `@closed_at` timestamp that stays `nil` while an account is open. Code that needs to know whether the variable was ever assigned can use the `defined?` keyword: - `defined?(@closed_at)` returns `nil` when the variable was never assigned. - It returns the string `"instance-variable"` once it has been assigned, even if the assigned value is `nil`. This distinction matters for memoization of values that can legitimately be `nil` or `false`: `@x ||= compute` recomputes every time the stored value is falsy, while `return @x if defined?(@x)` caches any result. ## What this is not - It is not a class-level default. Every object starts with no instance variables; `nil` is what a read of a missing one produces, not a stored value. - It is not the same for class variables: reading an `@@name` that was never assigned raises `NameError`. - It does not create the variable. After reading a missing `@balance`, the object still has no `@balance`; only assignment adds it. ## Interview angle A good answer names the behaviour, the consequence and the habit that neutralises it: - **Behaviour:** an unassigned instance variable reads `nil`, silently, on every current Ruby. - **Consequence:** a misspelling becomes a `nil` that fails somewhere else, with an error message about `nil` rather than about the name you got wrong. - **Habit:** assign all state in `initialize` and read it through methods where it matters, so the state of the object is listed in one place and typos fail loudly.
- In Ruby, why does @total ||= expensive_sum recompute every time when the sum can be nil or false?`@total ||= expr` means `@total || @total = expr`. When the stored value is `nil` or `false` the left side is falsy, so the expression runs again. For values that can be falsy, memoize with `return @total if defined?(@total)` followed by `@total = expensive_sum`, because `defined?` reports assignment rather than truthiness.
- In Ruby, does reading a never-assigned class variable behave like a missing instance variable?No. Reading an `@@name` that was never assigned raises `NameError` (`uninitialized class variable`), so class variables fail loudly where instance variables quietly return `nil`.
saying these in an interview costs you the question
- Reading an unassigned @ivar raises NameError like an undefined local
- Ruby -w still warns about uninitialized instance variables on 4.0
- Reading @balance once creates it with the value nil
- @x.nil? tells you whether @x was ever assigned
- An unset @@var reads nil just like an unset @var