Organizing Code with require
Ruby programs rarely live in a single file for long. As soon as you split logic into classes, modules, or reusable libraries across multiple files, you need a way to load those files into your running program — that’s the job of require. Ruby also gives you require_relative for loading your own project files by their location on disk, and load for re-running a file’s code from scratch. Understanding how these three work, and how Ruby keeps track of what’s already been loaded, is essential for organizing any Ruby project larger than a script.
Overview: How require Works
Ruby has no import keyword or per-file namespace system like some other languages. require simply finds a file on disk and executes its top-level Ruby code exactly once, as if you had typed that code directly into your program at the point of the require call. Whatever classes, modules, constants, or methods that file defines at the top level become globally visible afterward — there is no "export list" to opt into.
$LOAD_PATH (also readable as $:) is an array of directories Ruby searches when you call require with a bare name like require "json". Ruby’s standard library directories are on $LOAD_PATH automatically, and installed gems add their lib/ folders to it once activated (Bundler handles this for you in a real project). Since Ruby 1.9, the current directory is not on $LOAD_PATH by default, which is exactly why plain require is the wrong tool for your own project files.
Ruby remembers every file it has successfully required in a global array called $LOADED_FEATURES (aliased as $"). Before loading anything, require checks whether the file’s absolute path is already in that list. If it is, require does nothing and returns false. If it isn’t, Ruby loads and executes the file, adds its path to $LOADED_FEATURES, and returns true. This is what makes it safe to require the same file from ten different places in a codebase — it only ever runs once.
require_relative is require‘s sibling for your own code. Instead of searching $LOAD_PATH, it resolves the given path relative to the directory of the file that calls it, using the special __FILE__ constant internally. That means require_relative "greeter" always finds greeter.rb next to the file that requires it, no matter what directory you happen to run your program from. The convention is: use require for gems and the standard library, and require_relative for files that belong to your own project.
load is different again: it re-executes a file’s code every time you call it, completely ignoring $LOADED_FEATURES. It returns true or raises LoadError, never false. It’s mostly used for interactive reloading (re-running a script after you’ve edited it, without restarting your process) rather than for everyday code organization.
Syntax
require "library_name" # search $LOAD_PATH; for gems & stdlib
require_relative "path" # relative to the current file's directory
load "path.rb" # re-executes every time, needs .rb extension
$LOAD_PATH.unshift(dir) # add a directory to the search path
__dir__ # absolute directory of the current file
__FILE__ # absolute path of the current file
| Form | Resolves against | Runs again if called twice? | Typical use |
|---|---|---|---|
require |
$LOAD_PATH |
No (returns false) |
Gems, standard library (json, set, date) |
require_relative |
Calling file’s directory | No (returns false) |
Your own project’s files |
load |
$LOAD_PATH or a full path |
Yes, always | Interactive reloading, one-off scripts |
Examples
Example 1: Loading a standard library file
The standard library ships with Ruby but still has to be explicitly loaded. Here, require "json" makes the JSON module available.
require "json"
data = { name: "Ruby", year: 1995, mutable: true }
json_text = JSON.generate(data)
puts json_text
parsed = JSON.parse(json_text)
puts parsed["name"]
puts parsed.class
Output:
{"name":"Ruby","year":1995,"mutable":true}
Ruby
Hash
Before this require, calling JSON.generate would raise NameError: uninitialized constant JSON — the module simply doesn’t exist in your program until its file has been loaded. Note also that JSON.parse returns a plain Hash with string keys by default, not the symbol keys you passed in.
Example 2: Splitting code across files with require_relative
This example writes a small library file to disk next to the script and then loads it with require_relative, exactly the pattern you’d use with real project files.
lib_path = File.join(__dir__, "greeter.rb")
File.write(lib_path, <<~RUBY)
class Greeter
def initialize(name)
@name = name
end
def greet
"Hello, #{@name}! Welcome to Ruby."
end
end
RUBY
require_relative "greeter"
greeter = Greeter.new("Alex")
puts greeter.greet
puts defined?(Greeter)
Output:
Hello, Alex! Welcome to Ruby.
constant
require_relative "greeter" resolves to greeter.rb in the same directory as the running script, regardless of the shell’s current working directory. Once loaded, Greeter is a normal global constant — defined?(Greeter) confirms it exists and reports its kind as "constant".
Example 3: require only loads a file once
first_load = require "json"
second_load = require "json"
puts first_load
puts second_load
puts $LOADED_FEATURES.any? { |path| path.end_with?("json.rb") }
Output:
true
false
true
The first require "json" actually loads the library and returns true. The second call finds json.rb‘s path already sitting in $LOADED_FEATURES, skips loading it again, and returns false. This return value is genuinely useful — some libraries use require "foo" and do_something to run setup code only the first time a file is loaded.
How require Works Step by Step
When Ruby evaluates a require (or require_relative) call, roughly this happens:
- Ruby resolves the argument to an absolute file path — searching
$LOAD_PATHforrequire, or resolving relative to__FILE__forrequire_relative. - It checks whether that absolute path is already recorded in
$LOADED_FEATURES. If so, it stops here and returnsfalse. - If not, Ruby immediately adds the path to
$LOADED_FEATURES(before running the file’s code — this is what prevents infinite loops in circular requires). - Ruby then reads and executes the file’s top-level Ruby code from top to bottom, exactly as if it were pasted in at the call site. Any
class,module, constant, or top-level method definitions take effect globally. - If the file raises an error partway through, the
requirecall propagates that error, and the path is removed from$LOADED_FEATURESso a laterrequirecan retry. - On success,
requirereturnstrue. If the file can’t be found at all, Ruby raisesLoadError.
Common Mistakes
Mistake 1: Using require for your own project files
Plain require searches $LOAD_PATH, which does not include your project’s own folders. A relative-looking path passed to require often works by accident while you’re in a particular directory, then breaks the moment someone runs the script from elsewhere.
# main.rb (sits in the same folder as greeter.rb)
require "greeter"
puts Greeter.new("Sam").greet
Output:
main.rb:1:in 'require': cannot load such file -- greeter (LoadError)
The fix is to use require_relative, which always resolves against main.rb‘s own directory regardless of the current working directory:
require_relative "greeter"
puts Greeter.new("Sam").greet
Output:
Hello, Sam! Welcome to Ruby.
Mistake 2: Circular require_relative between two files
When file A requires file B, and file B also requires file A, Ruby’s loop guard prevents infinite recursion — but it does so by marking a file as "loaded" the moment it starts, not when it finishes. If one file references a constant from the other at the top level (outside a method body), that constant may not exist yet.
# a.rb
require_relative "b"
class A
GREETING = "hi from A"
end
puts "In a.rb, B::MESSAGE = #{B::MESSAGE}"
# b.rb
require_relative "a"
class B
MESSAGE = "hi from B, and A::GREETING = #{A::GREETING}"
end
Output (running b.rb):
b.rb:4:in '<class:B>': uninitialized constant A (NameError)
Running b.rb triggers require_relative "a", which triggers require_relative "b" again — Ruby sees b.rb is already being loaded and skips it silently, so a.rb continues and finishes defining A. But by the time control returns to finish loading b.rb, the order in which the constant is actually needed can easily flip depending on which file is the entry point, and you get an intermittent NameError. The real fix is to remove the circular dependency: pull the shared piece into its own file that both sides require independently.
lib_dir = __dir__
File.write(File.join(lib_dir, "a2.rb"), <<~RUBY)
class A2
VERSION = "1.0"
end
RUBY
File.write(File.join(lib_dir, "b2.rb"), <<~RUBY)
class B2
def report
"A2::VERSION is #{A2::VERSION}"
end
end
RUBY
require_relative "a2"
require_relative "b2"
puts B2.new.report
Output:
A2::VERSION is 1.0
b2.rb only references A2::VERSION inside a method body, so it’s not evaluated until report is actually called — by which point both files have finished loading in a predictable order.
Mistake 3: Expecting require to pick up edited code
Because require checks $LOADED_FEATURES first, calling it again after editing a file does nothing — the file is already marked as loaded, so your changes are silently ignored.
irb> require "./greeter"
=> true
irb> # ...edit greeter.rb to change the greeting message...
irb> require "./greeter"
=> false
irb> Greeter.new("Sam").greet
=> "Hello, Sam!" # still the OLD code -- require skipped the file
irb> load "./greeter.rb"
=> true
irb> Greeter.new("Sam").greet
=> "Hi there, Sam!" # now it picks up the edited code
Use load with an explicit path when you deliberately want a file re-executed, such as reloading code in a long-running console session.
Best Practices
- Use
requireonly for gems and the standard library; userequire_relativefor anything inside your own project. - Put all
require/require_relativecalls at the very top of a file so a reader can see its dependencies at a glance. - Name files after the constant they define —
greeter.rbdefinesGreeter— sorequire_relativecalls read predictably. - Avoid mutual
require_relativecalls between two files; if two classes genuinely need each other, extract the shared piece into a third file that both require. - Reach for
loadonly when you deliberately want a file’s top-level code to re-run; it’s rarely the right tool in application code. - In a real application, let Bundler (
Bundler.require) or a single loader file manage gem requires in the correct order rather than requiring every gem by hand in every file. - Remember there’s no per-file namespace: everything a required file defines at the top level lands in the same global constant table, so wrap your code in modules or classes to avoid naming collisions.
Practice Exercises
1. Create two files, animal.rb (a class Animal with a speak method) and main.rb that loads it with require_relative and calls speak. Confirm it still works when you run it from a different working directory.
2. Take a script that defines three unrelated classes in one file and split it into three separate files plus one entry file that requires all three. The program’s output should be identical before and after the split.
3. Deliberately create two files that require_relative each other and reference one another’s constants at the top level (not inside a method). Run it, observe the NameError, then fix it by extracting the shared constant into its own file.
Summary
requireloads a file by searching$LOAD_PATHand is meant for gems and the standard library.require_relativeloads a file relative to the calling file’s own directory and is the right choice for your own project code.- Ruby tracks every loaded file’s absolute path in
$LOADED_FEATURES; a repeatedrequireof the same file is a no-op that returnsfalse. loadre-executes a file’s code every time it’s called and ignores$LOADED_FEATURESentirely.- Required files have no private namespace — everything they define at the top level becomes a global constant.
- Circular
require_relativecalls that reference each other’s constants outside a method body are fragile and order-dependent; break the cycle by extracting shared code into its own file.
