ZetCode

Ruby BEGIN and END Hooks - Startup and Shutdown Code

last modified October 18, 2026

This tutorial explores Ruby's BEGIN and END keywords. BEGIN blocks run before the main program, and END blocks run when it exits.

The BEGIN keyword defines code that executes before the rest of the file. The END keyword registers code that runs at exit. These uppercase keywords are not the lowercase begin ... end block used for exception handling.

BEGIN blocks are useful for setup, while END blocks handle cleanup. END runs on normal termination, on exit, and when an uncaught exception terminates the program. It is skipped by exit!, exec, and fatal signals. Multiple BEGIN blocks run in definition order, and multiple END blocks run in reverse order.

Basic BEGIN and END Example

This simple example demonstrates the execution order of BEGIN and END blocks. Notice they run outside the normal program flow.

basic_begin_end.rb
BEGIN {
  puts "This runs first"
}

END {
  puts "This runs last"
}

puts "This runs in between"
# Output:
# This runs first
# This runs in between
# This runs last

BEGIN runs before any other statement, and END runs after the main program finishes. The output therefore appears in three distinct phases.

Multiple BEGIN/END Blocks

Ruby allows multiple BEGIN and END blocks. BEGIN blocks run in definition order, while END blocks execute in reverse order (like a stack).

multiple_blocks.rb
BEGIN {
  puts "BEGIN 1"
}

BEGIN {
  puts "BEGIN 2"
}

END {
  puts "END 1"
}

END {
  puts "END 2"
}

puts "Main program"
# Output:
# BEGIN 1
# BEGIN 2
# Main program
# END 2
# END 1

BEGIN blocks are queued in the order they appear. END blocks behave like a stack, so the last one defined runs first.

BEGIN with Variable Initialization

BEGIN blocks can initialize variables used in the main program. This example sets up configuration before the main code runs.

initialization.rb
BEGIN {
  $app_name = "My Ruby Program"
  $start_time = Time.now
}

puts "Welcome to #{$app_name}"  # prints "Welcome to My Ruby Program"
puts "Started at #{$start_time}"

END {
  puts "Program ran for #{Time.now - $start_time} seconds"
}

Global variables are available everywhere, including inside methods and END blocks. A local variable created in a BEGIN block is visible at the top level but not inside a method. The END block uses the saved start time to report the runtime.

Error Handling in END Blocks

END blocks execute even when the program exits early or raises exceptions. This makes them ideal for cleanup tasks.

error_handling.rb
BEGIN {
  puts "Starting risky operation"
}

END {
  puts "Cleanup always happens"
}

fail_now = true
raise "Something went wrong!" if fail_now

puts "This might not run"

Here fail_now is true, so the raise terminates the main program. END still executes, and the final puts is skipped. Change fail_now to false to exercise the normal path.

Remember that exit!, exec, and fatal signals bypass END blocks.

File Operations with BEGIN/END

A common use case is file handling - opening in BEGIN and closing in END. This keeps the file open for the whole run and closes it when the program terminates normally or with an uncaught exception.

file_operations.rb
BEGIN {
  $log_file = File.open("output.log", "w")
  $log_file.puts "Log started: #{Time.now}"
}

def log(message)
  $log_file.puts message
end

log("Processing started")

END {
  $log_file.puts "Log ended: #{Time.now}"
  $log_file.close
  puts "Log file closed"  # prints "Log file closed"
}

log("Processing completed")

The example opens output.log before any logging and closes it in END. Even if the main program raises, the file is closed. The script writes the log to the working directory.

BEGIN with Command-line Arguments

BEGIN blocks can process ARGV before the main program. This example handles early argument parsing.

argv_processing.rb
# Run: ruby argv_processing.rb --verbose

BEGIN {
  $verbose = ARGV.delete("--verbose")
  puts "Verbose mode enabled" if $verbose
}

puts "Remaining arguments: #{ARGV.inspect}"

END {
  puts "Verbose logging complete" if $verbose
}
# Output:
# Verbose mode enabled
# Remaining arguments: []
# Verbose logging complete

BEGIN can edit ARGV before the main program reads it. With --verbose, the script prints both verbose messages; without it, $verbose is nil and those messages are omitted.

Complex Program Structure

This example shows BEGIN/END in a larger program with multiple components. The blocks help organize setup and teardown.

program_structure.rb
BEGIN {
  require 'json'
  $config = { "api_endpoint" => "https://example.com" }
}

BEGIN {
  $stats = {
    start: Time.now,
    requests: 0
  }
}

def make_request(url)
  $stats[:requests] += 1
  "response from #{url}"
end

puts make_request($config["api_endpoint"])  # prints "response from https://example.com"

END {
  duration = Time.now - $stats[:start]
  puts "Made #{$stats[:requests]} requests in #{duration.round(6)} seconds"
  File.write("stats.json", JSON.dump($stats))
}

The two BEGIN blocks load JSON support and initialize counters. The END block reports the request count and writes stats.json. The request is simulated, so no network connection is needed.

Source

Ruby Keywords Documentation

This tutorial covered Ruby's BEGIN and END keywords with practical examples showing startup, cleanup, and exit handling.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Ruby tutorials.