Blocks

Reviewed & published by Brayan K

Ruby is a dynamic, beginner-friendly programming language, and blocks — chunks of code you hand to a method — are the feature that makes Ruby feel uniquely expressive.

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.

By the end of this lesson you'll pass blocks with do...end and { }, and write your own methods that run them with yield.

What You'll Learn in This Lesson

1️⃣ Passing Blocks to Methods

Many built-in methods accept a block. You write the block right after the call, using do...end for several lines or { } for one. The method feeds values to your block through parameters named between |pipes|.

# A block is a chunk of code you hand to a method.
# Two syntaxes: do...end (multi-line) and { } (one-line).

# 3.times runs the block 3 times. |i| is the block parameter.
3.times do |i|
  puts "Line #{i}"        # i goes 0, 1, 2
end

# The curly-brace form is common for short blocks:
[1, 2, 3].each { |n| puts n * 10 }   # -> 10, 20, 30

# each_with_index gives both the item and its position:
["a", "b"].each_with_index do |letter, index|
  puts "#{index}: #{letter}"
end

2️⃣ Writing Methods that Take Blocks (yield)

Your own methods can accept blocks too. Call yield to run the block — as often as you like — and pass arguments with yield(value). To make a block optional, check block_given? first so you never yield into thin air.

# You can write your OWN method that takes a block, using "yield".
def twice
  yield        # run the block...
  yield        # ...and run it again
end

twice { puts "Hello!" }    # -> Hello! (twice)

# yield can pass values INTO the block:
def with_tax(price)
  yield(price * 1.2)       # add 20% tax, hand it to the block
end

with_tax(100) { |total| puts "You pay #{total}" }  # -> You pay 120.0

# block_given? lets a method work with OR without a block:
def maybe
  if block_given?
    yield
  else
    puts "No block this time"
  end
end

maybe                       # -> No block this time
maybe { puts "Got a block" } # -> Got a block

Your turn. A repeat method is sketched for you — it yields the current count. Run it as-is, then customise the block message.

# 🎯 YOUR TURN — replace each TODO, then run with: ruby repeat.rb

# 1) Write a method "repeat(n)" that yields n times,
#    passing the current count (starting at 1) into the block.
def repeat(n)
  count = 1
  while count <= n
    yield(count)        # 👉 hand the count to the block
    count += 1
  end
end

# 2) Call it with a block that prints a countup line.
repeat(3) do |i|
  puts "Step #{i} of 3"   # 👉 TODO: customise this message if you like
end

# ✅ Expected output:
#    Step 1 of 3
#    Step 2 of 3
#    Step 3 of 3

Mini-Challenge: A Banner Wrapper

Write a method that prints a line before and after running a block. This "do something around a block" pattern appears all over Ruby (think File.open). Run with ruby banner.rb.

# 🎯 MINI-CHALLENGE: a simple timer wrapper.
# Save as banner.rb and run with: ruby banner.rb
#
# 1. Write a method "banner" that takes a block.
# 2. It should puts "====" before yielding, and "====" after.
# 3. Call it with a block that prints a message in the middle.
#    (This pattern — do something around a block — is everywhere in Ruby.)
#
# ✅ Example output:
#    ====
#    Hello from inside!
#    ====

# your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Blocks

ConceptRuby Syntax
Multi-line blockdo |x| ... end
One-line block{ |x| ... }
Repeat n times3.times { ... }
Run a blockyield(value)
Block present?block_given?
Index tooeach_with_index

🎉 Lesson Complete!

Practice quiz

What does yield do inside a method?

  • Returns from the method immediately
  • Runs the block that was passed to the method
  • Pauses execution for one second
  • Defines a new block

Answer: Runs the block that was passed to the method. yield invokes the block attached to the current method call.

Which two syntaxes can you use to write a block?

  • [ ] and ( )
  • do...end and { }
  • begin...end and { }
  • << and >>

Answer: do...end and { }. Blocks use do...end (usually multi-line) or curly braces { } (usually one-line).

By convention, when do you use { } instead of do...end?

  • For short, single-line blocks
  • Only inside classes
  • When the block takes no parameters
  • For multi-line blocks

Answer: For short, single-line blocks. Convention reserves { } for short one-liners and do...end for multi-line blocks.

What does block_given? return?

  • The block's return value
  • true only when a block was passed to the current method
  • The number of block parameters
  • Always true inside a method

Answer: true only when a block was passed to the current method. block_given? is true exactly when a block was attached to the call, so you can yield safely.

Calling yield when no block was passed raises which error?

  • NoMethodError
  • ArgumentError
  • LocalJumpError
  • FrozenError

Answer: LocalJumpError. yield with no block raises LocalJumpError; guard with block_given? to avoid it.

Where do block parameters go?

  • In parentheses after the method name
  • Between vertical bars (pipes), like |n|
  • Inside square brackets
  • After the keyword yield only

Answer: Between vertical bars (pipes), like |n|. Block parameters are named between pipes: { |n| n * 2 } or do |item, i| ... end.

Given def twice; yield; yield; end, how many times does the block run?

  • Zero
  • Once
  • Twice
  • It raises an error

Answer: Twice. Each yield runs the block once, so two yields run it twice.

each_with_index yields how many values to its block?

  • One (the element)
  • Two (the element and its index)
  • Three
  • Zero

Answer: Two (the element and its index). each_with_index yields the element and its position, so name two parameters |item, i|.

With def with_tax(p); yield(p * 1.2); end, what does with_tax(100) { |t| puts t } print?

  • 100
  • 120.0
  • 20.0
  • 1.2

Answer: 120.0. yield passes 100 * 1.2 = 120.0 into the block, which prints it.

How many times does [1, 2, 3].each { |n| puts n } run the block?

  • Once
  • Twice
  • Three times
  • Zero times

Answer: Three times. each runs the block once per element — three times for a three-element array.

Continue this course

Frequently asked questions

What exactly is a block in Ruby?

A block is an anonymous chunk of code that you attach to a method call, written either with do...end or curly braces { }. It isn't an object on its own (that's what procs and lambdas are for) — it's a parcel of behaviour the method can run via yield. Blocks are the heart of idiomatic Ruby: methods like each, map, times, and open all take a block so they can run your code at the right moment.

When do I use do...end versus curly braces { }?

They do the same thing, but convention guides the choice. Use { } for short, single-line blocks, like [1,2,3].map { |n| n * 2 }. Use do...end for multi-line blocks that span several statements. There's also a subtle precedence difference: { } binds more tightly than do...end, which can matter in chained calls, but for everyday code the readability convention is what to follow.

What does 'yield' do?

yield runs the block that was passed to the current method. If you call yield(x), it runs the block and passes x in as the block's parameter. A method can yield as many times as it likes — that's how each loops. If you yield but no block was given, Ruby raises a LocalJumpError; guard against that with block_given?, which returns true only when a block was attached to the call.

Why are blocks so important in Ruby?

Blocks let a method handle the boilerplate while you supply just the interesting part. File.open opens a file, runs your block, and guarantees the file is closed afterward — you never write the cleanup. Collection methods like each and map walk the data while your block decides what to do with each element. This 'method controls the loop, block controls the logic' split is what makes Ruby so concise and expressive.