Method Arguments and Defaults
Ruby methods can accept several flavors of arguments: required positional arguments, optional arguments with default values, a splat argument that soaks up any remaining positional values, keyword arguments (with or without defaults), a double-splat for arbitrary keyword values, and a trailing block. Understanding how Ruby orders and resolves these when a method is called is essential for writing APIs that are both flexible and predictable. Unlike languages that only support fixed positional signatures, Ruby lets you mix these forms freely, which is why so many standard library and Rails methods accept a small pile of options without forcing callers to remember rigid ordering.
Cricket analogy: Ruby's mix of required, optional, splat, keyword, and block arguments is like a team sheet that has fixed required players, optional impact substitutes, a pool of reserves, and named specialist roles, all combinable without a rigid fixed order.
Required and optional positional arguments
A required argument has no default and must be supplied by the caller, or Ruby raises an ArgumentError. An optional argument is given a default expression evaluated at call time (not at method definition time), so defaults can reference earlier parameters or even call other methods. Ruby evaluates default expressions left to right, and any optional parameter must generally come after the required ones unless a splat separates the groups.
Cricket analogy: A required argument raising ArgumentError if missing is like a team sheet being rejected if the captain's name is left blank, while an optional argument's default evaluated at call time is like a reserve player assigned based on who's actually fit that morning.
def greet(name, greeting = "Hello", punctuation: "!")
"#{greeting}, #{name}#{punctuation}"
end
greet("Ada") # => "Hello, Ada!"
greet("Ada", "Hey") # => "Hey, Ada!"
greet("Ada", punctuation: "?") # => "Hello, Ada?"
def total_price(base, tax_rate: 0.07, shipping: base > 50 ? 0 : 5.99)
(base + base * tax_rate + shipping).round(2)
end
total_price(80) # shipping default reads the `base` parameterSplat, double-splat, and keyword arguments
A single splat parameter (*args) collects any extra positional arguments into an array, which is invaluable for variadic methods like a custom logger or a sum function. Keyword arguments, introduced with a name: syntax, must be passed by name at the call site and can have their own defaults; a required keyword argument is written without a default (name:) and raises ArgumentError if omitted. A double-splat parameter (**opts) gathers any keyword arguments not explicitly named into a hash, which is how many Rails methods accept an open-ended options hash while still documenting the common ones explicitly.
Cricket analogy: A splat parameter collecting extra positional arguments is like a scorer's tally sheet absorbing every extra run scored off a single over into one array, while a double-splat is like a post-match stats sheet gathering every named metric not explicitly tracked, like strike rate or dot-ball percentage.
def build_report(title, *sections, author:, tags: [], **metadata)
{
title: title,
sections: sections,
author: author,
tags: tags,
metadata: metadata
}
end
build_report("Q3 Sales", "Summary", "Charts", author: "Priya", region: "EMEA")
# sections => ["Summary", "Charts"], metadata => { region: "EMEA" }Since Ruby 2.7+, calling a method that expects keyword arguments with a plain hash as the last positional argument is no longer automatically converted (the old implicit hash-to-kwargs conversion was removed in Ruby 3.0). Use the double-splat explicitly, e.g. some_method(**options_hash), to forward a hash as keyword arguments.
Mutable default values are a classic pitfall shared with Python: def add_item(item, list = []) looks fine, but because the default expression is re-evaluated on every call (unlike Python's one-time default), Ruby actually sidesteps Python's infamous shared-mutable-default bug. Still, be careful when a default references another parameter that could be nil — the default expression will raise a NoMethodError at call time rather than at definition time.
Argument order and forwarding
The canonical parameter order Ruby enforces is: required positional, optional positional, splat, more required positional (post-splat), keyword arguments, double-splat, and finally an explicit block parameter. Ruby 2.7+ also introduced the anonymous forwarding operator ..., which lets a wrapper method forward all arguments -- positional, keyword, and block -- to another method without naming them individually, which keeps decorator-style methods concise.
Cricket analogy: Ruby's fixed parameter order culminating in a block is like a bowling attack's set sequence: opening bowlers first, then first-change, then part-timers, and finally the specialist spinner brought on last for the tactical finishing touch.
def instrument(...)
start = Time.now
result = perform_work(...)
puts "took #{Time.now - start}s"
result
end- Optional positional arguments use
param = default; default expressions are evaluated fresh on every call. - Keyword arguments (
name:orname: default) must be passed by name and improve readability at call sites. - A splat (*args) collects extra positional arguments into an array; a double-splat (**opts) collects extra keyword arguments into a hash.
- Since Ruby 3.0, positional hashes are no longer auto-converted to keyword arguments -- use
**hashexplicitly. - Parameter order is fixed: required, optional, splat, post-splat required, keywords, double-splat, block.
- The
...forwarding operator (Ruby 2.7+) forwards all arguments and the block to another method in one shot.
Practice what you learned
1. What happens when a method with `def greet(name, greeting = "Hi")` is called as `greet("Sam")`?
2. In Ruby 3.0+, what does `**opts` do in a method parameter list?
3. Which parameter order below is valid in Ruby?
4. What does the `...` operator do when used in a method definition like `def wrapper(...)`?
5. What error is raised if a method defines `def build(name:)` and is called with `build`?
Was this page helpful?
You May Also Like
Defining Methods
How to define methods in Ruby using def, including implicit return values, naming conventions (?, !), visibility, and how methods differ from blocks.
Blocks, Procs, and Lambdas
Explore Ruby's three flavors of closures -- blocks, Procs, and lambdas -- and how they differ in argument strictness, return semantics, and reusability.
yield and block_given?
Understand how `yield` invokes an implicit block passed to a method, and how `block_given?` lets methods branch based on whether a block was supplied.
Common Ruby Pitfalls
A tour of frequent Ruby mistakes — mutable defaults, frozen string surprises, nil handling, and block/proc return semantics — with fixes for each.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics