skip to content

In Ruby, what arguments and block does a bare super pass to the overridden method, and how do super() and super(a) differ from it?

level: middleimportance: must knowfreq 70%

answer

  1. parentheses change everything
  2. current values, not the caller's
  3. super() sends zero arguments
  4. the block tags along regardless
  5. &nil switches the block off

basics

~20 s

A bare super re-sends all of the current method's parameters, using their current values, plus its block. super() sends no arguments and super(a) sends exactly a; both still pass the block unless you pass &nil or another block.

solid answer

~40 s

A bare `super` (no parentheses, no arguments) is special: Ruby re-sends every parameter of the current method (positional, optional, splat and keyword) using the variables' **current** values, so reassigning `mode` before `super` sends the new value. `super()` calls the parent with zero arguments, and `super(a, b)` with exactly those. In all three forms the block the current method received is passed along implicitly; `super(a, &nil)` suppresses it and `super(a, &other)` replaces it. The practical trap is a signature mismatch: an override that takes more parameters than the parent and calls bare `super` raises `ArgumentError`, so it has to pick the parent's arguments explicitly.

code

ruby · 14 lines
ruby
class Vehicle
  def initialize(name)
    @name = name
  end
end

class ElectricCar < Vehicle
  def initialize(name, battery_kwh)
    super(name)          # bare super would send 2 arguments: ArgumentError
    @battery_kwh = battery_kwh
  end
end

ElectricCar.new("Volt", 60)

go deeper

for a junior

Know that super with no parentheses forwards the same arguments and that super() forwards none; both are called from inside an overriding method.

for a middle

Explain that bare super reads the parameters' current values, that the block is passed by every form, and that &nil is the only way to withhold it.

for a senior

Diagnose the ArgumentError from a subclass initialize that adds parameters and calls bare super, and the RuntimeError from bare super inside define_method.

for a principal

Argue for a team convention: bare super only where signatures match, explicit arguments everywhere else, so signature changes in a base class fail loudly and locally.

## Three spellings, three argument lists Inside an overriding method, `super` calls the method of the same name further up the class chain. What it sends depends on the spelling: | Spelling | Arguments sent | Block sent | |---|---|---| | `super` | all of the current method's parameters, current values | the current block | | `super()` | none | the current block | | `super(a, b)` | exactly `a` and `b` | the current block | | `super(a, &nil)` | exactly `a` | none | | `super(a, &blk)` | exactly `a` | `blk` | The bare form is sometimes called **zsuper** (zero-argument super). It is the only form whose argument list is not written at the call site. ## Bare super reads the parameters now, not at entry Zsuper does not replay what the caller originally passed. It reads the method's **parameter variables** at the moment `super` runs: - a reassigned positional parameter sends its new value; - an optional parameter that was not passed sends the default that was filled in; - a splat such as `*args` sends its current contents, even if you replaced the array; - keyword parameters are sent as **keywords**, again with their current values. ```ruby class Vehicle def drive(mode = :eco) [mode, block_given?] end end class ElectricCar < Vehicle def drive(mode) mode = :sport [super, super(), super(mode, &nil)] end end ElectricCar.new.drive(:boost) { } # => [[:sport, true], [:eco, true], [:sport, false]] ``` The bare `super` sent `:sport`, the reassigned value; `super()` sent nothing, so the parent used its default `:eco`; and `super(mode, &nil)` sent `:sport` without the block. ## Keyword parameters Keyword parameters follow the same rule. An override declared `def drive(mode:, **opts)` that calls bare `super` sends `mode:` and the contents of `opts` as **keyword arguments**, using their current values, so a reassigned `mode` reaches the parent as the new value. An explicit call has to spell them out: `super(mode: mode, **opts)`, or with hash shorthand `super(mode:, **opts)`. Leaving `**opts` out of an explicit call is a quiet way to lose options the parent expected, which is one reason teams keep matching signatures on bare `super`. ## The block always tags along A very common misconception is that the parentheses in `super()` also mean "no block". They do not. Every form of `super` passes the current method's block implicitly, so a parent that calls `yield` or checks `block_given?` still sees it. There are only two ways to change that: 1. `super(args, &nil)` passes **no** block. 2. `super(args) { ... }` or `super(args, &other)` passes a **different** block. ## Where bare super breaks Because zsuper sends the current method's parameters, it assumes the parent accepts them: - **Signature mismatch.** If `ElectricCar#initialize(name, battery_kwh)` calls bare `super` and `Vehicle#initialize(name)` takes one argument, Ruby raises `ArgumentError (given 2, expected 1)`. Write `super(name)` instead. - **Parent takes nothing.** When the parent is `Object`, the `initialize` it inherits from `BasicObject` accepts no arguments, so bare `super` from an `initialize(name)` raises `ArgumentError` too; `super()` is correct there. - **Methods made by `define_method`.** Implicit argument passing is not supported inside them; bare `super` raises `RuntimeError` and asks you to specify all arguments explicitly. The reverse mistake also happens: writing `super()` when you meant to forward everything silently drops arguments and lets the parent fall back to its defaults, as the `:eco` result above shows. ## Rules of thumb 1. Use bare `super` when the override has **the same signature** as the parent and should pass everything through. 2. Use `super(explicit, args)` whenever the signatures differ, which is the normal case for `initialize` in a subclass that adds fields. 3. Use `super()` only when the parent takes no arguments, and remember that the block still goes. 4. Add `&nil` only when the parent must not see the caller's block.

  • What happens if you call bare super inside a method created with define_method?
    It raises `RuntimeError` with the message that implicit argument passing of super from a method defined by `define_method()` is not supported and that you must specify all arguments explicitly. Write `super(mode)` or `super(*args, **opts)` instead; the explicit forms work there.
  • Why does a parent method that yields still work when the override calls super() with empty parentheses?
    Because the parentheses only control arguments. The current method's block is passed implicitly by every form of `super`, so the parent's `yield` finds it and `block_given?` returns `true`. To hide the block, write `super(&nil)`.
  • Does bare super work in a method whose parameters are anonymous, such as def drive(*) or def drive(...)?
    Yes. Bare `super` re-sends anonymous splats as well. Inside a method declared with `(...)` you can also forward everything explicitly with `super(...)`, which reads the same but makes the forwarding visible.

saying these in an interview costs you the question

  • super and super() are the same call; the parentheses are only style.
  • Bare super sends the caller's original arguments, ignoring any reassignment in the method.
  • super() passes no block, so a parent that yields raises LocalJumpError.
  • super(a) sends a plus the rest of the current method's arguments.
  • Bare super quietly drops any arguments the parent does not accept.