In Ruby, what do a `**opts` parameter and a `**nil` parameter each do with the keyword arguments a caller passes?
answer
- catch-all for unlisted names
- a new Hash on every call
- String keys are allowed too
- typos stop raising
- no keywords accepted
basics
~20 sA **opts parameter gathers every keyword the method does not name into a new Hash, so unknown keywords stop raising. A **nil parameter declares that the method takes no keywords: passing any raises ArgumentError (no keywords accepted).
solid answer
~40 s`**opts`, the keyword splat, collects all keywords not matched by an explicit keyword parameter into a **new Hash** on every call: `{}` when there are none, and never the caller's own Hash object, so mutating `opts` cannot affect the caller. Its keys may even be Strings, as in `send_email(to: x, "X-Priority" => "1")`. The price is that a typo such as `sbuject:` lands in `opts` instead of raising `unknown keyword`. `**nil` is the opposite marker: the method explicitly accepts no keywords, so any keyword or non-empty keyword splat raises `ArgumentError` with `no keywords accepted`. Without it, a method that declares no keywords quietly turns `name: value` pairs into a trailing positional Hash; `**nil` forbids that and keeps room to add real keywords later.
code
ruby · 16 linesdefaults = { track_opens: true }
def send_email(to:, **opts)
opts.delete(:track_opens)
opts
end
send_email(to: "[email protected]", **defaults) # => {}
defaults # => {track_opens: true}
def archive(message, **nil)
message
end
archive({ id: 7 }) # => {id: 7}
archive(id: 7) # ArgumentError: no keywords acceptedgo deeper
Recall that **opts collects unlisted keywords into a Hash and that **nil makes a method refuse keywords outright.
Explain that the splat is a fresh Hash each call, may hold String keys, and silently absorbs typos, and why a method without keywords converts them into a positional Hash.
Judge when an open-ended splat is justified in an API such as a mailer, how to restore unknown-name checks by hand, and when **nil protects a signature you plan to extend.
Set a codebase-wide stance on catch-all splats versus closed keyword lists, balancing forwarding convenience against errors that surface only in production.
## The keyword splat: `**opts` A method's parameter list may end its keyword section with one **keyword splat** (also called keyword rest), written `**name`. It changes what happens to keywords the method does not declare by name: - Every keyword that no explicit keyword parameter claims goes into the splat as a `key => value` pair. - The splat is always a **Hash**, `{}` when the caller passed nothing extra, never `nil`. - It is a **new Hash for each call**. Even `send_email(**defaults)` hands the method a copy, so `opts.delete(:cc)` inside the method leaves the caller's `defaults` untouched. - Its keys may be **non-Symbols**. A call such as `send_email(to: "[email protected]", "X-Priority" => "1")` puts `"X-Priority"` into `opts`. - A method has at most one keyword splat, after its explicit keywords and before any `&block` parameter. ```ruby def send_email(to:, subject: "(no subject)", **headers) { to: to, subject: subject, headers: headers } end send_email(to: "[email protected]", "X-Priority" => "1", list_id: "news") # => {to: "[email protected]", subject: "(no subject)", # headers: {"X-Priority" => "1", list_id: "news"}} ``` ## What the splat costs The splat removes Ruby's automatic check for unknown names. With `def send_email(to:, subject: nil, **headers)`, a caller writing `sbuject: "Invoice"` gets no error: the typo becomes a header and the email goes out with no subject. When the extras are meant for a closed set of settings, check them yourself: 1. Keep a list of the names the method understands. 2. Compute `headers.keys - ALLOWED`. 3. Raise `ArgumentError` naming the leftovers if that list is not empty. A splat is the right tool when the extras are genuinely open-ended and are passed on untouched to something else, such as arbitrary mail headers or options forwarded to a lower layer. ## `**nil`: refusing keywords explicitly A method that declares **no** keyword parameters at all does not reject keywords. Ruby converts them into a single positional Hash: ```ruby def log_delivery(entry) entry end log_delivery(id: 42) # => {id: 42} ``` Writing `**nil` in the parameter list switches that conversion off and makes the refusal explicit: ```ruby def log_delivery(entry, **nil) entry end log_delivery({ id: 42 }) # => {id: 42} a braced Hash is positional, so allowed log_delivery(id: 42) # ArgumentError: no keywords accepted ``` Why bother? A method that today treats `id: 42` as a positional Hash cannot later gain a real keyword parameter without changing what existing calls mean. Declaring `**nil` forces callers to pass a Hash with braces now, so a keyword can be added later without silently reinterpreting their calls. Ruby's own documentation gives exactly that reason for the syntax. ## Side by side | Signature | `m(id: 1)` | `m({ id: 1 })` | |---|---|---| | `def m(entry)` | `entry` is `{id: 1}` | `entry` is `{id: 1}` | | `def m(entry, **nil)` | `ArgumentError` (no keywords accepted) | `entry` is `{id: 1}` | | `def m(**opts)` | `opts` is `{id: 1}` | `ArgumentError` (wrong number of arguments) | | `def m(id:)` | `id` is `1` | `ArgumentError` (wrong number of arguments) | The last two rows are the keyword separation made in Ruby 3.0: a braced Hash is a positional argument and is never turned into keywords. ## `**nil` at a call site Since Ruby 3.4, `**nil` may also appear in a **call**, where it passes no keywords, just like `**{}`. This makes conditional splats read naturally: `send_email(to: x, **(tracking if track?))` passes the tracking keywords only when `tracking if track?` returns a Hash, and nothing when it returns `nil`. This is separate from the `**nil` parameter above, which rejects keywords rather than passing none. ## Common mistakes - Treating `opts` as `nil` when no extras were passed and guarding with `opts&.fetch`; it is always a Hash. - Assuming `opts` is the caller's Hash and mutating it to "consume" options for the caller. - Adding a splat to a method with a fixed set of settings, then losing typo detection without replacing it. - Believing a method without keywords rejects `name: value` calls; it converts them to a positional Hash unless `**nil` is declared.
- How do you keep typo detection while still accepting a few pass-through options?Declare the settings you understand as explicit keywords, and collect only the genuinely open-ended part, for example `headers: {}` as one keyword whose value is a Hash. If you must use `**opts`, compare `opts.keys` against a list of allowed names and raise `ArgumentError` for the rest, since Ruby no longer does it for you.
- What does `send_email(to: x, **nil)` do in Ruby 3.4 and later?It passes no extra keywords, exactly like `**{}`, and calls no conversion method on `nil`. Its practical use is a conditional splat such as `**(tracking if track?)`, which forwards the Hash when the condition holds and nothing otherwise. Before 3.4, splatting `nil` in a call was an error, so code written for older versions used `**(tracking || {})`.
saying these in an interview costs you the question
- When no extra keywords are passed, **opts is nil.
- **opts is the very Hash object the caller splatted in.
- Keys collected by **opts must be Symbols.
- A method with no keyword parameters rejects name: value arguments.
- **nil in a def means the method's keywords default to nil.