Variable-Length Arguments (splat)
Most methods you write take a fixed, known number of arguments — but sometimes you don’t know in advance how many values a caller will pass, or you want to accept an open-ended set of named options. Ruby’s splat operator (*) and double splat operator (**) solve exactly this problem: they let a method gather any number of extra positional or keyword arguments into a single Array or Hash, and let a caller do the reverse — spread a collection out into individual arguments. Once you’re comfortable with them, splats show up everywhere in idiomatic Ruby, from core methods like Array.new to option-heavy APIs in Rails and RSpec.
Overview: How Splat Arguments Work
In a method definition, a parameter prefixed with a single asterisk — commonly written *args — is called a splat parameter. It tells Ruby: bind the required parameters first, and whatever positional arguments are left over, collect them into an Array and assign that Array to this name. A parameter prefixed with two asterisks, **kwargs, is a double splat parameter: it collects any keyword arguments that weren’t explicitly named elsewhere into a Hash.
The reverse trick works at the call site. If you already have values sitting in an Array, you can splat that Array with *my_array when calling a method, and Ruby unpacks it into separate positional arguments — exactly as if you had typed them out by hand. Likewise, **my_hash at a call site unpacks a Hash into individual keyword arguments. This symmetry — gather when defining, spread when calling — is the same operator doing complementary jobs depending on context.
Under the hood there’s nothing magical: a splat parameter really is an ordinary Array instance (its class is Array, and it responds to every Array and Enumerable method), and a double splat parameter really is an ordinary Hash. If no extra arguments are passed, Ruby still creates an empty Array or Hash rather than nil, so you never need to guard against nil before calling .each or .size on them. The same mechanism appears outside method calls too — in multiple assignment (first, *rest = [1, 2, 3, 4] makes rest equal to [2, 3, 4]) and in block parameters — because it’s a general destructuring feature, not something invented just for methods.
Ruby also allows an anonymous splat (*) and anonymous double splat (**) with no name attached, which accept and discard extra positional or keyword arguments without you needing to name — or use — them. This is handy when a method just forwards some arguments to super or another method and doesn’t care about the rest itself.
Syntax
The general shape of a method definition using both kinds of splat, alongside the other parameter kinds, looks like this:
def method_name(required, optional = "default", *splat_args, keyword:, keyword_opt: "value", **double_splat, &block)
# method body
end
Ruby requires parameters to appear in this order — you cannot put a keyword argument before the splat, and you cannot write more than one splat or double-splat in the same list:
| Position | Example | Meaning |
|---|---|---|
| 1. Required positional | required |
Must always be supplied, in order |
| 2. Optional positional | optional = "default" |
Has a default value, filled left to right |
| 3. Splat | *splat_args |
Collects remaining positional args into an Array (at most one per method) |
| 4. Required keyword | keyword: |
Must be passed by name, in any order |
| 5. Optional keyword | keyword_opt: "value" |
Named argument with a default |
| 6. Double splat | **double_splat |
Collects remaining keyword args into a Hash (at most one per method) |
| 7. Block | &block |
Captures a block passed to the method as a Proc |
At a call site, the same symbols do the opposite job — they spread a collection into arguments instead of gathering arguments into a collection:
some_method(*an_array)— unpacks each element ofan_arrayas a separate positional argument.some_method(**a_hash)— unpacks each entry ofa_hashas a separate keyword argument (the hash’s keys must be Symbols).
Examples
Example 1: Collecting any number of positional arguments
def sum_all(*numbers)
numbers.reduce(0) { |total, n| total + n }
end
puts sum_all(1, 2, 3)
puts sum_all(10, 20)
puts sum_all
Output:
6
30
0
The splat parameter *numbers gathers every positional argument passed to sum_all into a single Array, no matter how many there are — three, two, or none at all. reduce(0) { ... } then folds that Array down to a single sum, starting from 0. Because a splat parameter defaults to an empty Array rather than nil, calling sum_all with zero arguments still works safely and returns 0 instead of raising an error.
Example 2: Mixing a required argument with a splat
def greet(greeting, *names)
names.map { |name| "#{greeting}, #{name}!" }.join(" ")
end
puts greet("Hello", "Alice", "Bob", "Carol")
puts greet("Hi", "Dave")
Output:
Hello, Alice! Hello, Bob! Hello, Carol!
Hi, Dave!
Here the first argument always fills the required greeting parameter, and *names mops up everything after it into an Array, however many names are passed. Inside the method, names is an ordinary Array, so .map and .join work exactly as they would on any Array you built yourself. This is the classic use case for a positional splat: a fixed “header” argument followed by an open-ended list of items to process.
Example 3: Collecting keyword arguments with a double splat
def build_profile(name:, **details)
puts "Name: #{name}"
details.each { |key, value| puts "#{key}: #{value}" }
end
build_profile(name: "Ada", age: 36, occupation: "Programmer")
Output:
Name: Ada
age: 36
occupation: Programmer
This time the double splat handles keyword arguments instead of positional ones. name: is required and must always be supplied, but **details quietly collects every other keyword argument the caller supplies — age: and occupation: in this call — into a Hash, in the order they were passed. This pattern suits methods that accept one required core value plus an open-ended set of optional attributes, without forcing every caller to know the full attribute list in advance.
Example 4: Splatting an Array and a Hash at the call site
def describe(name, age:, city: "Unknown")
"#{name} is #{age} years old and lives in #{city}."
end
person_args = ["Grace"]
person_opts = { age: 34, city: "Boston" }
puts describe(*person_args, **person_opts)
Output:
Grace is 34 years old and lives in Boston.
This example shows splats working in the opposite direction — at the call site rather than in the method definition. person_args is an Array and person_opts is a Hash; splatting each with * and ** spreads their contents into the positional and keyword arguments describe expects, exactly as if you’d typed describe("Grace", age: 34, city: "Boston") by hand. This is especially useful when the values you need to pass already live in a collection — say, config values read from a file — and you don’t want to destructure them manually.
How It Works, Step by Step
Take the call greet("Hello", "Alice", "Bob", "Carol") against def greet(greeting, *names):
- Ruby looks at the parameter list and sees one required parameter (
greeting) followed by one splat parameter (names). - It assigns required parameters first, left to right, from the front of the argument list:
greetinggets"Hello". - Everything left over —
"Alice","Bob","Carol"— is packaged into a new Array and assigned tonames, so inside the methodnames == ["Alice", "Bob", "Carol"]. - The method body then runs exactly as if you’d written it against that Array directly.
Keyword arguments and double splats follow a related but separate process: Ruby first identifies any trailing keyword arguments (written as key: value at the call site, which it treats specially rather than as a plain trailing Hash), matches them against declared keyword parameters by name, and stuffs whatever names it doesn’t recognize into the double-splat Hash. In build_profile(name: "Ada", age: 36, occupation: "Programmer") against def build_profile(name:, **details), Ruby matches name: to the required keyword parameter and puts the remaining two pairs, in the order they were passed, into details.
Unpacking at a call site happens at the same moment the argument list is assembled, but in the opposite direction: before Ruby tries to match arguments to parameters, it first expands any *array or **hash it finds into a flat sequence of positional and keyword arguments, and binding then proceeds exactly as described above.
Common Mistakes
Mistake 1: Passing an Array where individual arguments are expected
A method with fixed positional parameters does not automatically unpack an Array you hand it — you must splat it explicitly:
def total(a, b, c)
a + b + c
end
numbers = [1, 2, 3]
puts total(numbers)
This raises ArgumentError: wrong number of arguments (given 1, expected 3), because Ruby sees a single Array argument, not three separate numbers. The fix is to splat the Array at the call site so its elements spread out into the three positional slots:
def total(a, b, c)
a + b + c
end
numbers = [1, 2, 3]
puts total(*numbers)
Output:
6
Mistake 2: Double-splatting a Hash with String keys
Keyword arguments in Ruby are matched by Symbol name, so double-splatting a Hash whose keys are Strings raises a TypeError rather than silently working:
def show_info(**opts)
opts.each { |key, value| puts "#{key}: #{value}" }
end
data = { "name" => "Grace", "role" => "Engineer" }
show_info(**data)
This raises TypeError: wrong argument type String (expected Symbol), because **data tries to turn each entry into a keyword argument, and keyword argument names must be Symbols. Convert the keys first — Hash#transform_keys is the idiomatic way:
def show_info(**opts)
opts.each { |key, value| puts "#{key}: #{value}" }
end
data = { "name" => "Grace", "role" => "Engineer" }
show_info(**data.transform_keys(&:to_sym))
Output:
name: Grace
role: Engineer
Mistake 3: Trying to use more than one splat in a parameter list
Ruby allows at most one splat and one double-splat per method definition — writing two of either is a SyntaxError, not something that quietly does what you might expect:
def broken_method(*first, *second)
first + second
end
This fails to parse at all. If you need two independently variable-length groups of arguments, redesign the method to accept explicit Arrays instead, e.g. def combine(first_group, second_group), and pass an Array for each rather than trying to splat both directly into the parameter list.
Best Practices
- Give splat parameters descriptive, pluralized names (
*numbers,*names) instead of the generic*args, so readers immediately know what the collected values represent. - Prefer keyword arguments with a double splat (
**options) over a positional Hash for optional settings — it documents valid option names at the call site and lets Ruby enforce required ones for you. - Remember a splat parameter is always an Array and a double-splat parameter is always a Hash, never
nil— you can call.each,.empty?, or.sizeon them without a nil check. - Use the anonymous
*or**when a method needs to accept — and usually forward — extra arguments it never inspects itself, such as a wrapper that just callssuper. - Don’t reach for splat as a substitute for good API design; if a method genuinely needs five or six distinct, always-present pieces of data, model them as explicit keyword arguments or a
Structinstead of an unstructured splat. - When splatting a Hash into keyword arguments with
**, make sure its keys are Symbols first (transform_keys(&:to_sym)) — Ruby will not coerce String keys for you.
Practice Exercises
- Write a method
average(*numbers)that returns the arithmetic mean of any number of numbers passed to it, and returns0if none are given. (Hint: check for an empty Array before dividing, or you’ll trigger aZeroDivisionError.) - Write a method
build_query(base, **params)that appends keyword arguments to a base URL string as a query string, so thatbuild_query("https://example.com", q: "ruby", page: 2)returns"https://example.com?q=ruby&page=2". - Given
scores = [88, 92, 79, 95]anddef total(*nums) = nums.sum, predict whattotal(*scores)prints before you run it — then verify by running it yourself.
Summary
*paramin a method definition gathers any extra positional arguments into an Array.**paramgathers any extra keyword arguments into a Hash.*arrayand**hashat a call site do the reverse: they unpack a collection into individual arguments.- Splat parameters always default to an empty collection, never
nil, so no nil checks are needed. - Ruby enforces a fixed order for parameter kinds: required, optional, splat, keyword, double-splat, block.
- Anonymous
*and**accept and discard extra arguments when you don’t need to reference them. - Prefer explicit keyword arguments over ad hoc positional splats for readability when the argument list is really just a set of named options.
