Methods in Ruby

· 9 min read · Updated March 26, 2026 · beginner
ruby methods fundamentals

What Are Methods?

Methods are the fundamental unit of reusable code in Ruby. They let you package a sequence of operations under a name, then call that name whenever you need those operations performed. Without methods, you’d copy and paste the same code everywhere — a maintenance nightmare that Ruby spares you from.

Think of a method as a named action your program can perform on demand. “Greet the user,” “calculate a total,” “validate an email address” — each of these is a perfect candidate for a method.

Defining a Method

You define a method with the def keyword, followed by the method name, an optional parameter list, and closed with end:

def greet(name)
  puts "Hello, #{name}!"
end

greet("Alice")
# => Hello, Alice!

```ruby

Method names in Ruby follow a convention: lowercase letters with underscores (snake_case). Ruby does not enforce this, but it is the community standard and reading code that deviates from it will surprise other developers.

You can also define methods that take no parameters:

```ruby
def hello
  "Hello, world!"
end

hello
# => "Hello, world!"

```ruby

Ruby allows method names to end with `?` (predicates), `!` (bang methods), or `=` (setter methods). These are syntactic conventions that signal intent to other developers — the language itself treats them as part of the name.

## Parameters and Default Values

When you need a method to accept input, you define parameters — variables that receive values when the method is called:

```ruby
def power(base, exponent)
  base ** exponent
end

power(2, 3)
# => 8

```ruby

Default values make parameters optional. Ruby evaluates default values left-to-right at the point of the method call:

```ruby
def greet(name = "World")
  "Hello, #{name}!"
end

greet
# => "Hello, World!"

greet("Alice")
# => "Hello, Alice!"

```ruby

Be careful with mutable default values like arrays or hashes. If you write `def add_item(item, list = [])`, all calls that skip the `list` argument share the same array object. This is a classic Ruby gotcha:

```ruby
def append(item, list = [])
  list << item
  list
end

append("a") << "b"  # this mutates the shared default array
append("c")         # => ["a", "b", "c"]  — surprising!

```ruby

The safer approach is to use `nil` as the default and initialize inside the method:

```ruby
def append(item, list = nil)
  list ||= []
  list << item
  list
end

append("a")
# => ["a"]
append("b")
# => ["b"]

```ruby

## Variable-Length Arguments

Ruby lets you collect multiple arguments into a single parameter using the splat operator `*`. The method receives an array containing all the extra arguments:

```ruby
def sum(*numbers)
  numbers.inject(0, :+)
end

sum(1, 2, 3)
# => 6
sum(1, 2, 3, 4, 5)
# => 15
sum
# => 0

```ruby

For keyword arguments, the double splat `**` collects them into a hash:

```ruby
def configure(**options)
  options.each { |key, value| puts "#{key}: #{value}" }
end

configure(host: "localhost", port: 3000)
# => host: localhost
# => port: 3000

```ruby

Ruby 3 introduced a trailing comma feature that lets you add new parameters without touching existing call sites:

```ruby
def greet(name:, age:,)
  "#{name} is #{age}"
end
```ruby

## Returning Values

Every Ruby method returns a value — the result of the last expression evaluated in the method body. You do not need an explicit `return` statement in most cases:

```ruby
def add(a, b)
  a + b
end

add(2, 3)
# => 5

```ruby

The explicit `return` keyword is useful when you want to exit a method early:

```ruby
def find_first_even(numbers)
  numbers.each do |n|
    return n if n.even?
  end
  nil
end

find_first_even([1, 3, 5, 6, 7])
# => 6

```ruby

If you use `return` without a value, the method returns `nil`.

One thing to watch: setter methods (methods ending in `=`) always return the argument that was passed, not the assigned value:

```ruby
def val=(x)
  @x = x
end

result = (self.val = 42)
result
# => 42  — not the value of @x

```ruby

## Predicate Methods

Methods whose names end with `?` are called predicates. They are a naming convention signaling that the method returns a boolean value:

```ruby
def empty?(string)
  string.length == 0
end

empty?("")
# => true
empty?("hi")
# => false

```ruby

Ruby has many built-in predicates:

```ruby
[1, 2, 3].empty?
# => false

"hello".include?("lo")
# => true

Hash.new.respond_to?(:keys)
# => true

```ruby

The `?` suffix is a convention only. Ruby does not enforce that predicates return `true` or `false`, but idiomatic Ruby predicates always do. If a predicate can return `nil` or some other truthy/falsy value, callers may get unexpected behavior.

## Bang Methods

Methods ending with `!` are called bang methods. They signal a "dangerous" or "mutating" variant — typically one that changes the receiver in place rather than returning a new object:

```ruby
original = "hello"
loud = original.upcase!

original
# => "HELLO"  — original was mutated
loud
# => "HELLO"  — returns the mutated string

```ruby

Compare with the non-bang version:

```ruby
original = "hello"
loud = original.upcase

original
# => "hello"  — unchanged
loud
# => "HELLO"  — new string

```ruby

The `!` is a naming convention only. Ruby does not treat bang methods specially — you can define a bang method that does anything. The convention exists so developers can tell at a glance which version of a method mutates state and which one does not.

If a method has only a bang variant (like `exit!`), the bang typically indicates it terminates the process without running finalizers, as opposed to `exit` which does run them.

Do not rely on the bang to communicate danger — name your methods so the intent is clear from the name itself.

## Setter Methods

Setter methods allow assignment syntax. You define one with `def name=(value)`:

```ruby
class Person
  def name=(value)
    @name = value
  end
end

person = Person.new
person.name = "Alice"
```ruby

Ruby provides `attr_writer` as a shortcut for single-attribute writers:

```ruby
class Person
  attr_writer :name
end

person = Person.new
person.name = "Alice"
```ruby

The `=` in a setter method name requires a space between `def` and the name:

```ruby
def val=(x)   # correct
  @x = x
end

def val = x   # wrong — Ruby parses this differently
  @x = x
end
```ruby

## Method Visibility

Ruby gives you control over where methods can be called from. There are three visibility levels:

### Public

Public methods are callable from anywhere. This is the default in Rubyif you do not specify a visibility modifier, your method is public:

```ruby
class Calculator
  def add(a, b)
    a + b
  end
end

Calculator.new.add(1, 2)
# => 3

```ruby

### Private

Private methods cannot be called with an explicit receiver. They are only callable on `self`, which in instance method context means the current instance:

```ruby
class Greeter
  def greet
    "#{format_message}!"
  end

  private

  def format_message
    "Hello"
  end
end

g = Greeter.new
g.greet
# => "Hello!"
g.format_message
# => NoMethodError (private method called with explicit receiver)

```ruby

Inside the instance, you call `format_message` without any receiver — Ruby implicitly calls it on `self`.

### Protected

Protected methods are callable by `self` and any instance of the same class or its subclasses. This is useful when a method needs to compare two instances without being publicly accessible:

```ruby
class Employee
  attr_reader :employee_id

  def initialize(id)
    @employee_id = id
  end

  protected

  def same_company?(other)
    self.employee_id == other.employee_id
  end
end

class Manager < Employee
  def compare(e1, e2)
    e1.same_company?(e2)  # calling protected method on another instance
  end
end

m = Manager.new(1)
alice = Employee.new(1)
bob = Employee.new(2)

m.compare(alice, bob)
# => false

```ruby

### Changing Visibility

Visibility applies to all methods defined after it, until another modifier changes it:

```ruby
class Example
  def public_method; end

  private

  def private_method_a; end
  def private_method_b; end

  public

  def another_public_method; end
end
```ruby

For class methods, use `public_class_method` and `private_class_method`.

## self — The Current Object

`self` refers to the current object — the receiver of the method call currently executing:

```ruby
class Dog
  attr_accessor :name

  def bark
    puts "#{name} says woof!"
  end

  def introduce
    puts "This is #{self.name}."
  end
end

d = Dog.new
d.name = "Rex"
d.bark
# => Rex says woof!
d.introduce
# => This is Rex.

```ruby

Inside instance methods, `self` is the instance. Inside class methods, `self` is the class itself:

```ruby
class Counter
  @@count = 0

  def self.increment
    @@count += 1
  end

  def self.current
    @@count
  end
end

Counter.increment
Counter.current
# => 1

```ruby

When you call a method without an explicit receiver inside an instance method, Ruby implicitly calls it on `self`. This is why `name` and `self.name` are equivalent inside an instance method when `name` is an accessor.

## Keyword Arguments

Keyword arguments bind by name rather than position, which makes callsites easier to read:

```ruby
def connect(host:, port: 80, ssl: false)
  "Connecting to #{host}:#{port} (ssl=#{ssl})"
end

connect(host: "example.com")
# => "Connecting to example.com:80 (ssl=false)"

connect(host: "example.com", port: 443, ssl: true)
# => "Connecting to example.com:443 (ssl=true)"

```ruby

Ruby 2.1 introduced required keyword arguments using the trailing colon syntax with no default:

```ruby
def create_user(name:, email:, role: "guest")
  { name: name, email: email, role: role }
end

create_user(name: "Alice", email: "alice@example.com")
# => { name: "Alice", email: "alice@example.com", role: "guest" }

```ruby

Ruby 3 made a breaking change to keyword argument handling. In Ruby 2, a positional hash would implicitly fill keyword parameters. In Ruby 3, positional and keyword arguments are strictly separated:

```ruby
def greet(name:, greeting: "Hello")
  "#{greeting}, #{name}!"
end

# Ruby 2: greet({ name: "Alice" }) worked
# Ruby 3: this raises ArgumentError
# You must call it with:
greet(name: "Alice")
# or explicitly unpack:
greet(**{ name: "Alice" })
```ruby

The double splat `**` in a method definition collects keyword arguments into a hash. The double splat at a call site unpacks a hash into keyword arguments. These are two sides of the same mechanism.

## Summary

Methods are where Ruby programs live. You now know how to define them with `def`, accept input through parameters and keyword arguments, return values explicitly or implicitly, and control visibility with `public`, `private`, and `protected`. You have seen the conventions around predicate methods (`?`), bang methods (`!`), and setter methods (`=`), and you understand how `self` refers to the current object.

The conventions Ruby uses for method naming are not enforced by the language — they are a shared vocabulary the community settled on. Following them makes your code immediately familiar to other Ruby developers.

## See Also

- [Ruby Blocks and Iterators](/tutorials/ruby-blocks-and-iterators/) — blocks are the natural companion to methods, letting you pass reusable chunks of code around
- [Ruby Classes and Objects](/tutorials/ruby-classes-and-objects/) — methods live inside classes, and understanding how objects work deepens your appreciation of method dispatch