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

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 it

2️⃣ 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)

Pro Tips

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

TypeSyntax
Required positionaldef f(a, b)
Required keyworddef f(name:)
Optional keyworddef f(role: "member")
Splat (extra positionals)def f(*args)
Double splat (extra keywords)def f(**opts)
Capture blockdef f(&block)
Forward everythingdef f(...); g(...); end

🎉 Lesson Complete!

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

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.