Ruby Methods Explained — def return Implicit Return Splat and Keyword Arguments
In this tutorial, you will learn about Ruby Methods Explained. We cover key concepts, practical examples, and best practices to help you master this topic.
Ruby methods defined with def support implicit returns (last expression evaluated), splat arguments for variable-length parameters, keyword arguments with defaults, and method visibility control with public/private/protected.
What You'll Learn
- Defining methods with def and naming conventions
- Implicit vs explicit returns
- Parameters: required, optional, splat, keyword, and block
- Method visibility and organization
Why It Matters
Methods are how you organize code into reusable, testable units. Durga Antivirus Pro uses methods for each scan phase — file opening, signature matching, reporting. DodaZIP has methods for compression, encryption, and archiving. Well-designed methods make code readable, maintainable, and testable.
Real-World Use
A Rails controller is a collection of methods (actions). Each background job method handles a specific task. A Sinatra API groups endpoints into methods. Ruby's flexible method parameters accommodate everything from simple helpers to complex APIs.
flowchart LR
A["Methods"] --> B["Definition"]
B --> C["Parameters"]
C --> D["Returns"]
D --> E["Visibility"]
E --> F["Classes"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#dbeafe,stroke:#2563eb,color:#1e40af
style F fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Defining Methods
def hello
puts "Hello, World!"
end
hello # Call the method
# Hello, World!
Naming Conventions
- Use snake_case:
calculate_total,find_user - End predicates with
?:valid?,nil?,include? - End dangerous methods with
!:save!,delete! - End assignment methods with
=:name=
Predicate and Bang Methods
# Predicate method (returns boolean)
def even?(number)
number % 2 == 0
end
puts even?(4) # true
# Bang method (modifies in place)
def reverse!(array)
array.replace(array.reverse)
end
arr = [1, 2, 3]
reverse!(arr)
puts arr.inspect # [3, 2, 1]
Parameters and Arguments
Required Parameters
def greet(name)
"Hello, #{name}!"
end
puts greet("Alice") # Hello, Alice!
Optional Parameters (Default Values)
def greet(name, greeting = "Hello")
"#{greeting}, #{name}!"
end
puts greet("Alice") # Hello, Alice!
puts greet("Bob", "Hi") # Hi, Bob!
Keyword Arguments
Modern Ruby (2.0+) supports keyword arguments for clearer APIs:
def create_user(name:, age:, role: "user")
{ name: name, age: age, role: role }
end
user = create_user(name: "Alice", age: 25)
puts user.inspect
# {:name=>"Alice", :age=>25, :role=>"user"}
user = create_user(name: "Bob", age: 30, role: "admin")
puts user.inspect
# {:name=>"Bob", :age=>30, :role=>"admin"}
Required Keyword Arguments
def configure(host:, port:, ssl: true)
"Connecting to #{host}:#{port} (SSL: #{ssl})"
end
configure(host: "localhost", port: 3000)
# "Connecting to localhost:3000 (SSL: true)"
configure() # ArgumentError: missing keywords: host, port
Splat Arguments
Collect variable arguments into an array:
def sum(*numbers)
numbers.reduce(0, :+)
end
puts sum(1, 2, 3) # 6
puts sum(10, 20) # 30
puts sum(1, 2, 3, 4, 5) # 15
Double Splat (Keyword Splat)
Collect keyword arguments into a hash:
def log(message, **options)
timestamp = Time.now
output = "[#{timestamp}] #{message}"
output += " -- #{options.inspect}" unless options.empty?
puts output
end
log("User logged in")
# [2026-06-28 10:00:00] User logged in
log("Payment failed", user: "Alice", amount: 99.99, retry: true)
# [2026-06-28 10:00:00] Payment failed -- {:user=>"Alice", :amount=>99.99, :retry=>true}
Block Arguments
def with_timing
start = Time.now
yield
puts "Took #{Time.now - start}s"
end
with_timing { sleep 0.5 }
# Took 0.5001s
Explicit Block Parameter with &
def repeat(times, &block)
times.times { block.call }
end
repeat(3) { puts "Hello" }
# Hello
# Hello
# Hello
Return Values
Implicit Return
Ruby returns the value of the last expression automatically:
def square(x)
x * x # Implicit return
end
puts square(5) # 25
Explicit Return
Use return to exit early:
def divide(a, b)
return "Cannot divide by zero" if b == 0
a / b
end
puts divide(10, 2) # 5
puts divide(10, 0) # Cannot divide by zero
Multiple Returns
Methods can return multiple values as an array:
def min_max(array)
[array.min, array.max]
end
result = min_max([3, 1, 7, 2, 9])
puts result.inspect # [1, 9]
# Destructure
smallest, largest = min_max([3, 1, 7, 2, 9])
puts smallest # 1
puts largest # 9
Method Visibility
class MyClass
def public_method
"Anyone can call me"
end
private
def private_method
"Only accessible within the class"
end
protected
def protected_method
"Accessible within class and subclasses"
end
end
obj = MyClass.new
puts obj.public_method # Works
# obj.private_method # NoMethodError
# obj.protected_method # NoMethodError (from outside)
Private Setter Methods
class Person
attr_reader :name
def initialize(name)
self.name = name # Must use self for private setters
end
private
attr_writer :name
end
Method Chaining
Methods that return self enable chaining:
class StringBuilder
def initialize
@content = ""
end
def append(text)
@content += text
self
end
def newline
@content += "\n"
self
end
def to_s
@content
end
end
result = StringBuilder.new
.append("Hello")
.newline
.append("World")
.to_s
puts result
# Hello
# World
Deconstructing Method Parameters
# Array deconstruction in parameters
def first_and_last((first, *middle, last))
{ first: first, last: last, middle: middle }
end
puts first_and_last([1, 2, 3, 4, 5]).inspect
# {:first=>1, :last=>5, :middle=>[2, 3, 4]}
# Hash deconstruction in parameters
def extract_name(name:, age: nil, **rest)
{ name: name, age: age }
end
puts extract_name(name: "Alice", age: 25, city: "NYC").inspect
# {:name=>"Alice", :age=>25}
Common Mistakes
1. Forgetting That Methods Return the Last Expression
def broken_greet(name)
greeting = "Hello, #{name}!"
end
puts broken_greet("Alice") # "Hello, Alice!" — actually works because assignment returns the value
def broken_greet2(name)
puts "Hello, #{name}!" # Returns nil (puts returns nil)
end
puts broken_greet2("Alice")
# Hello, Alice!
# nil <-- unexpected return value
2. Using Return Unnecessarily
# Unnecessary return
def square(x)
return x * x
end
# Idiomatic — implicit return
def square(x)
x * x
end
3. Confusing puts and return
def calculate(x, y)
result = x + y
puts "Result is #{result}" # prints but returns nil
end
def calculate(x, y)
result = x + y
result # returns the value without printing
end
4. Forgetting Parentheses in Method Calls
def greet(name)
"Hello, #{name}"
end
puts greet "Alice" # Works but ambiguous to readers
puts greet("Alice") # Clearer
5. Not Using Keyword Arguments for Multiple Parameters
# Hard to remember order
configure("localhost", 3000, true, "admin", "secret")
# Clear with keyword arguments
configure(host: "localhost", port: 3000, ssl: true, user: "admin", password: "secret")
6. Using puts Inside a Method That Should Return a Value
# Wrong — can't use the result
def add(a, b)
puts a + b # prints but returns nil
end
total = add(5, 3) # total is nil!
# Right
def add(a, b)
a + b # returns the result
end
total = add(5, 3) # total is 8
Practice Questions
1. What is implicit return in Ruby?
Ruby automatically returns the value of the last expression evaluated in a method, without needing an explicit return keyword.
2. What's the difference between splat (*) and double splat ()?**
*args collects positional arguments into an array. **kwargs collects keyword arguments into a hash. Use * for variable positional arguments and ** for variable keyword arguments.
3. How do you make a parameter optional?
Provide a default value: def greet(name, greeting = "Hello"). If the caller doesn't provide that argument, the default is used.
4. What does keyword arguments improve over positional arguments?
Keyword arguments make method calls self-documenting (you see the parameter names at the call site), eliminate order-memory errors, and allow missing optional keyword arguments.
Challenge: Write a method create_profile that accepts required keyword arguments (name, email), optional keyword arguments (age, city, bio), and a block that's called after profile creation. Return a hash.
Solution
def create_profile(name:, email:, age: nil, city: nil, bio: nil)
profile = {
name: name,
email: email,
age: age,
city: city,
bio: bio,
created_at: Time.now
}
yield(profile) if block_given?
profile
end
profile = create_profile(
name: "Alice",
email: "alice@example.com",
age: 25,
city: "New York"
) do |p|
puts "Profile created for #{p[:name]}"
end
puts profile.inspect
Expected output:
Profile created for Alice
{:name=>"Alice", :email=>"alice@example.com", :age=>25, :city=>"New York", :bio=>nil, :created_at=>2026-06-28 ...}
FAQ
{{< faq question="Can I call methods without parentheses in Ruby?" >}}
Yes. Ruby allows omitting parentheses for method calls: puts "hello" or greet "Alice". However, use parentheses when there's ambiguity, when chaining methods, or when passing multiple arguments.
{{< /faq >}}
{{< faq question="What's the difference between private and protected in Ruby?" >}}
private methods can only be called without a receiver (implicit self). protected methods can be called by any instance of the same class or its subclasses. Protected methods use explicit receivers within the class.
{{< /faq >}}
{{< faq question="How do I define a class method?" >}}
def self.method_name or def ClassName.method_name. You can also use class << self block to define multiple class methods at once.
{{< /faq >}}
{{< faq question="Can a method accept both positional and keyword arguments?" >}}
Yes, in Ruby 3.0+. Positional arguments come first, then keyword arguments: def method(a, b, *args, key1:, key2:, **kwargs). Ruby 3 separated positional and keyword arguments to prevent ambiguity.
{{< /faq >}}
{{< faq question="What does block_given? do?" >}}
block_given? checks if the method was called with a block. Use it when you want to yield to a block conditionally: yield(value) if block_given?.
{{< /faq >}}
Try It Yourself
# methods_demo.rb
def calculate(operation, *numbers)
case operation
when :sum
numbers.reduce(0, :+)
when :product
numbers.reduce(1, :*)
when :average
numbers.reduce(0, :+) / numbers.length.to_f
else
"Unknown operation"
end
end
def format_result(value, precision: 2, prefix: "Result")
formatted = value.is_a?(Float) ? value.round(precision) : value
"#{prefix}: #{formatted}"
end
puts calculate(:sum, 1, 2, 3, 4, 5)
puts calculate(:product, 2, 3, 4)
puts calculate(:average, 10, 20, 30)
puts format_result(calculate(:average, 10, 20, 30), precision: 4)
Expected output:
15
24
20.0
Result: 20.0
What's Next
Now that you understand methods, learn about classes and objects to organize your code with object-oriented programming.
| Topic | Description | Link |
|---|---|---|
| Ruby Classes | class, initialize, attr_accessor | {{< ref "09-classes" >}} |
| Ruby Modules | module, include, extend, prepend | {{< ref "10-modules" >}} |
| Python Functions | Compare Python function definitions | Python |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro