Method Arguments
Reviewed & published by Brayan K
Ruby methods accept arguments in several styles — positional, keyword, splat, double-splat, and block — and choosing the right mix makes your methods both flexible and readable.
Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll combine all five argument types in one signature and forward them effortlessly with the (...) shorthand.
What You'll Learn in This Lesson
- Positional vs keyword arguments
- Required vs optional keyword arguments
- Collecting extras with *args (splat)
- Collecting keywords with **kwargs (double splat)
- Capturing a block with &block
- Forwarding everything with (...)
1️⃣ Positional vs Keyword Arguments
Positional arguments are bound by order; keyword arguments (a name with a trailing colon) are bound by name and can appear in any order. Add a default after the colon — greeting: "Hello" — to make a keyword optional. A keyword with a bare colon and no default is required.
# Positional args are matched by ORDER. Keyword args are matched by NAME.
def positional(name, role)
"#{name} is a #{role}"
end
def keyword(name:, role:) # the colon makes them keyword args
"#{name} is a #{role}"
end
puts positional("Ada", "engineer")
# Keyword args can be passed in ANY order:
puts keyword(role: "engineer", name: "Ada")
# Optional keyword args get a default after the colon:
def greet(name:, greeting: "Hello")
"#{greeting}, #{name}!"
end
puts greet(name: "Sam") # uses default
puts greet(name: "Sam", greeting: "Welcome") # overrides it2️⃣ Splat (*args) and Double Splat (**kwargs)
A single splat *args gathers any number of extra positional arguments into an Array; a double splat **kwargs gathers extra keyword arguments into a Hash. You can combine them with required and keyword parameters in a single signature — Ruby sorts each argument into the right bucket.
# *args collects extra POSITIONAL arguments into an array.
def total(*prices)
prices.sum # prices is a normal Array
end
puts total(10, 20, 30) # => 60
puts total # => 0 (empty array)
# **kwargs collects extra KEYWORD arguments into a hash.
def configure(**options)
options.map { |k, v| "#{k}=#{v}" }.join(", ")
end
puts configure(debug: true, level: 5)
# Mix them: required, splat, keyword, double-splat, all together.
def report(title, *items, format:, **meta)
"#{title} [#{format}]: #{items.join('/')} #{meta.inspect}"
end
puts report("Sales", "Q1", "Q2", format: "csv", author: "Lee")3️⃣ Block Capture (&block) and Forwarding (...)
Add &block as the last parameter to capture a block as a callable Proc you can store or pass on. And when you just want to wrap another method, the (...) shorthand forwards every positional, keyword, and block argument in one stroke.
# &block captures a block as a Proc you can store, inspect, and pass on.
def run_twice(&block)
block.call
block.call
end
run_twice { puts "tick" }
# Argument forwarding with (...) passes EVERYTHING straight through.
def log(*args, **kwargs)
parts = args.map(&:to_s)
parts += kwargs.map { |k, v| "#{k}:#{v}" }
"LOG -> #{parts.join(' ')}"
end
def log_and_count(...)
result = log(...) # forward all args, kwargs, and block
"#{result} (forwarded)"
end
puts log_and_count("saved", id: 7, ok: true)🎯 Your Turn
Make the role keyword optional by supplying a default. Replace the ___ so a call without role: falls back to "member".
# 🎯 YOUR TURN — fill in the ___ so role defaults to "member".
def make_user(name:, role: ___) # 👉 give the keyword arg a default value
"#{name} (#{role})"
end
puts make_user(name: "Kai") # should use the default
puts make_user(name: "Mara", role: "admin")
# ✅ Expected output:
# Kai (member)
# Mara (admin)Common Errors (and the fix)
- "missing keyword: :name" — ❌ A required keyword wasn't passed. ✅ Pass name: or give it a default.
- "unknown keyword: :colour" — ❌ You passed a keyword the method doesn't declare. ✅ Add **opts to accept extras, or fix the spelling.
- "wrong number of arguments" — ❌ Too many/few positionals. ✅ Use *args for a variable count.
- Hash passed but keywords expected — ❌ Ruby 3 won't auto-convert. ✅ Splat it: f(**my_hash).
- Parameters in the wrong order — ❌ def f(**opts, *args) is a syntax error. ✅ Positional/splat first, then keywords/double-splat, then &block.
Pro Tips
- 💡 Booleans love keywords: save(force: true) beats save(true) every time.
- 💡 Anonymous forwarding: Ruby 3.2+ lets you forward with bare *, **, and & too.
- 💡 Don't over-splat: if a method always takes exactly two things, name them — splat is for genuinely variable counts.
Mini-Challenge: A Flexible build_url
Write one method that uses a required positional, a splat for path segments, an optional scheme: keyword, and a double splat for query parameters. Run with ruby url.rb.
# 🎯 MINI-CHALLENGE: a flexible build_url method.
# Save as url.rb and run with: ruby url.rb
#
# Signature: build_url(host, *paths, scheme: "https", **params)
# - host : required positional
# - *paths : extra path segments, joined with "/"
# - scheme: : optional keyword (default "https")
# - **params : leftover keywords become ?key=value&key=value
#
# ✅ Expected output:
# https://example.com
# https://example.com/users/42
# http://api.dev/search?q=ruby&page=2
# your code here📋 Quick Reference — Method Arguments
| Type | Syntax |
|---|---|
| Required positional | def f(a, b) |
| Required keyword | def f(name:) |
| Optional keyword | def f(role: "member") |
| Splat (extra positionals) | def f(*args) |
| Double splat (extra keywords) | def f(**opts) |
| Capture block | def f(&block) |
| Forward everything | def f(...); g(...); end |
🎉 Lesson Complete!
- ✅ Positional args bind by order; keyword args bind by name
- ✅ A keyword with a default is optional; a bare colon makes it required
- ✅ *args gathers positionals into an Array
- ✅ **kwargs gathers keywords into a Hash
- ✅ &block captures a block; (...) forwards everything
Practice quiz
How are positional arguments matched to parameters?
- by name
- alphabetically
- by order
- by type
Answer: by order. Positional arguments bind to parameters by their position.
What syntax marks a keyword argument in a method definition?
- a trailing colon, like name:
- a leading colon
- an asterisk
- an ampersand
Answer: a trailing colon, like name:. A name followed by a colon, def f(name:), declares a keyword argument.
A keyword argument written name: with no default value is:
- optional
- ignored
- a syntax error
- required
Answer: required. A bare keyword with no default must be supplied by the caller.
How do you make a keyword argument optional?
- wrap it in parentheses
- give it a default, like role: "member"
- put it last
- prefix it with *
Answer: give it a default, like role: "member". Supplying a default value makes the keyword optional.
What does *args (single splat) collect?
- extra positional arguments into an Array
- extra keyword arguments into a Hash
- a block
- nothing
Answer: extra positional arguments into an Array. A single splat gathers leftover positional arguments into an Array.
What does **kwargs (double splat) collect?
- positionals into an Array
- a block as a Proc
- extra keyword arguments into a Hash
- the method name
Answer: extra keyword arguments into a Hash. A double splat gathers leftover keyword arguments into a Hash.
What does &block capture in a method's parameter list?
- the return value
- a block as a callable Proc
- all keywords
- the class
Answer: a block as a callable Proc. &block captures a passed block as a Proc you can store or call.
What does the (...) shorthand do when forwarding to another method?
- forwards nothing
- forwards only positionals
- forwards only the block
- forwards every positional, keyword, and block argument
Answer: forwards every positional, keyword, and block argument. (...) forwards all arguments and the block in one stroke.
In Ruby 3, how do you splat a hash into keyword arguments?
- pass it as the last positional
- pass it explicitly as **my_hash
- Ruby converts it automatically
- you cannot
Answer: pass it explicitly as **my_hash. Ruby 3 separated keywords from hashes, so you must use **my_hash.
Which parameter order is valid in a method signature?
- **opts before *args
- &block first
- required, *splat, keywords, **double-splat, &block
- keywords before required positionals only
Answer: required, *splat, keywords, **double-splat, &block. Positionals/splat come first, then keywords/double-splat, then &block.
Continue this course
- Previous: Custom Iterators & Enumerator
- Next: The Comparable Module — Define <=> and get <, >, between?, and clamp for free
- Quick reference: Ruby cheat sheet
Frequently asked questions
What is the difference between positional and keyword arguments?
Positional arguments are matched by their order: in def f(a, b), the first value becomes a and the second becomes b. Keyword arguments, declared with a trailing colon like def f(name:, role:), are matched by name, so you can pass them in any order and the call site reads like documentation. Keyword arguments make calls self-describing and resistant to the 'which boolean was which?' bug that plagues long positional parameter lists.
When should I use *args (splat) versus **kwargs (double splat)?
Use a single splat *args to gather a variable number of positional arguments into an Array — handy for methods like sum(*numbers). Use a double splat **kwargs to gather leftover keyword arguments into a Hash — handy for passing through configuration options. You can combine them: def f(required, *rest, key:, **opts) captures one required positional, any extra positionals, a required keyword, and any extra keywords, each in its own clearly-typed bucket.
How does the (...) argument forwarding shorthand work?
Since Ruby 2.7+, def wrapper(...) and calling inner(...) forwards every positional argument, keyword argument, and block to inner without you naming them. It is the cleanest way to write a method that decorates or delegates to another — logging, timing, or caching wrappers, for example. Before (...), you had to write def wrapper(*args, **kwargs, &block) and forward each piece manually, which was verbose and easy to get subtly wrong.
Why did Ruby separate keyword arguments from hashes in Ruby 3?
In older Ruby, a trailing hash and keyword arguments were quietly interchangeable, which caused confusing bugs when a method expected one but received the other. Ruby 3.0 made the separation strict: keyword arguments are now a distinct part of the method signature, not a disguised final hash. If you want to splat a hash into keyword arguments you pass it explicitly as **my_hash. The change removed a whole class of surprising behaviour at the cost of a one-time migration.