skip to content

Interpreters & Releases

Which engine runs your code, which release you are on, how the ruby command starts a script and how require finds files. Interviewers probe upgrade hazards and load-path bugs.

on this pageshow

explore

questions

20

In Ruby, what does the `if __FILE__ == $0` guard do, and what do ARGV and $PROGRAM_NAME hold?

level: juniorimportance: must knowfreq 55%

answer

  1. current file vs launched file
  2. $0 is an alias of $PROGRAM_NAME
  3. ARGV excludes the script name
  4. require runs top-level code once
  5. __END__ makes DATA in the main script

basics

~20 s

FILE is the current file's path and $0 (alias $PROGRAM_NAME) names the script Ruby launched. They match only in that launched file, so guarded code runs when executed directly, not when required. ARGV holds the arguments after the script name.

solid answer

~50 s

`__FILE__` is a keyword that evaluates to the path of the source file it appears in, while `$0` (same variable as `$PROGRAM_NAME`) names the program that `ruby` was started with, or `"-e"` for a one-liner. In the file you launch they are equal; in a file loaded by `require` or `require_relative` they differ, so `main if __FILE__ == $0` lets one file be both a library and a command. `ARGV` is an array of strings holding only what follows the script name: interpreter flags placed before the script are consumed by `ruby`, and the script name itself is not in it. `$0` is writable (assigning it changes the process title where the platform supports that), and a file ending in `__END__` exposes the text after it through the `DATA` constant, but only for the main script.

code

bash · 4 lines
bash
$ ruby -e 'p [__FILE__, $0, ARGV]' a b
["-e", "-e", ["a", "b"]]
$ ruby rotate_logs.rb /var/log/app -v
# ARGV inside rotate_logs.rb: ["/var/log/app", "-v"]

go deeper

for a junior

Recall the three names: FILE is this file, $0 is the launched script, ARGV is the argument list without the script name. State plainly when the guarded block runs and when it is skipped.

for a middle

Explain why the guard is false under require, what $0 is under ruby -e, and that flags after the script name land in ARGV. Mention DATA and END as a main-script-only feature.

for a senior

Show how you keep CLI files testable: a thin guard calling an entry method that takes an argv array and returns a status. Point out that wrappers and binstubs change $0 and break naive guards.

for a principal

Frame the idiom as a packaging decision: small scripts can stay dual-use, but tools shared across teams deserve an explicit executable plus a library entry point, so launch style never changes behaviour.

## Three names a running script can see When `ruby rotate_logs.rb /var/log/app --keep 7` starts, Ruby exposes three related pieces of information, and confusing them is the root of most bugs in small command-line tools. | Name | Kind | Holds in `rotate_logs.rb` | Holds in a file it requires | |---|---|---|---| | `__FILE__` | keyword | `"rotate_logs.rb"` | that file's own path | | `$0` / `$PROGRAM_NAME` | global variable | `"rotate_logs.rb"` | still `"rotate_logs.rb"` | | `ARGV` (alias `$*`) | constant, an `Array` of `String` | `["/var/log/app", "--keep", "7"]` | the same array | - **`__FILE__`** is resolved at parse time for each file. In the launched script it is the path exactly as it was given on the command line; in a file loaded through `require` it is the path Ruby resolved, usually absolute. - **`$0`** and **`$PROGRAM_NAME`** are two names for the same variable. For `ruby -e '...'` its value is `"-e"`. - **`ARGV`** never contains the interpreter's own flags or the script name. Anything after the script name, even `-v`, is passed to the script untouched. ## How the guard works ```ruby # rotate_logs.rb module LogRotator def self.run(dir, keep:) # ...rename app.log to app.log.1 and prune old files... true end end if __FILE__ == $PROGRAM_NAME dir = ARGV.fetch(0) { abort "usage: #{$PROGRAM_NAME} DIR" } exit LogRotator.run(dir, keep: 7) end ``` 1. Run directly, `__FILE__` and `$PROGRAM_NAME` are both `"rotate_logs.rb"`, so the block reads `ARGV` and runs. 2. Required from a test (`require_relative "rotate_logs"`), the file's top-level code still executes once, defining `LogRotator`, but `$PROGRAM_NAME` names the test runner's script, so the command-line block is skipped. The guard does not stop Ruby from loading the file twice; that is the job of `require`'s once-only rule, a separate mechanism. It only decides whether the command part runs. ## When the comparison surprises you - **Wrappers and binstubs.** If a wrapper script calls `load "rotate_logs.rb"`, then `$0` names the wrapper and the guard is false. Keep the real entry point in a method (`LogRotator::CLI.start(ARGV)`) that the wrapper can call directly. - **Different spellings of one path.** Code that compares against a path built elsewhere should expand both sides: `File.expand_path(__FILE__) == File.expand_path($PROGRAM_NAME)`. - **One-liners.** Under `ruby -e`, both `__FILE__` and `$0` are `"-e"`. ## `$0` is writable `$0` is a hooked variable: assigning `$0 = "rotate_logs: pruning"` updates the process title where the platform allows it, so a long job can report its phase to tools that list processes. `Process.argv0` still returns the original name after such an assignment. ## `__END__` and `DATA` A line containing only `__END__` stops parsing. If the **main script** contains it, Ruby defines the constant `DATA`, a `File` opened on that script and positioned just after the `__END__` line: ```ruby DATA.each_line { |pattern| puts "would rotate #{pattern.chomp}" } __END__ *.log *.err ``` - A required file may also contain `__END__`, which ends its code, but `DATA` is only set for the program Ruby launched. - `DATA` is unrelated to the core `Data` class that builds value objects with `Data.define`. ## Testing the command part Move the command logic into a method that takes an array (`def self.start(argv)`) and call it with `ARGV` inside the guard. A test then calls `start(["tmp/logs"])` without touching the real `ARGV` or the process title, and asserts on the returned value. ## `ARGV` is an ordinary, mutable array - `ARGV` and `$*` refer to the same `Array`; every element is a `String`, so numbers must be converted (`Integer(ARGV[1])`). - `ARGV.shift` removes and returns the first argument, a common way to read a subcommand. - `OptionParser#parse!` from the `optparse` library removes the options it recognises from `ARGV` in place, leaving only the positional arguments. - Because it is global and mutable, code deep inside a library should not read `ARGV`; pass the values in instead. ## Interview traps in one list 1. Expecting the script name at `ARGV[0]`, as in a C program's `argv`. 2. Believing the guard prevents a file from being loaded twice. 3. Reading `DATA` from a required file. 4. Forgetting that under `ruby -e` there is no script file, so `__FILE__` is `"-e"` and paths built from it are meaningless.

  • Why can `__FILE__ == $0` be false even though you meant to run the file as a program?
    `$0` names whatever Ruby launched. If a wrapper, binstub or another script uses `load` or `require` on your file, `$0` is the wrapper's name and the guard is false. The robust design is an explicit entry method, such as `CLI.start(ARGV)`, that the wrapper calls, with the guard kept only as a convenience for direct runs.
  • What is `DATA`, and why does a required file with its own `__END__` not get one?
    `DATA` is a `File` opened on the main script and positioned after its `__END__` line, so the script can carry a small embedded data section. Ruby defines it only for the program it launched; `__END__` in a required file just ends that file's code and creates nothing, so `DATA` there is either the main script's or a `NameError`.
  • How would you make the command-line branch of such a file testable?
    Keep the branch to one line, `exit CLI.start(ARGV)`, and put the work in `CLI.start(argv)`, which takes an array and returns a boolean or a status. Tests call `CLI.start(["tmp/logs"])` directly, so they neither mutate the real `ARGV` nor trigger `exit` inside the test process.

The guard is like a speech that begins only when the speaker is the person the audience came to see: invited as a guest (required), the same person sits quietly and just lends their expertise.

saying these in an interview costs you the question

  • ARGV[0] is the script name, as argv[0] is in C
  • __FILE__ and $0 are always equal in every file
  • The guard stops require from loading the file a second time
  • Flags placed after the script name are consumed by ruby itself
  • DATA reads the __END__ section of whichever file mentions it
open as a page

In Ruby, what are CRuby (MRI), JRuby and TruffleRuby, and how does CRuby's YARV virtual machine run your source code?

level: juniorimportance: must knowfreq 45%

basics

~20 s

CRuby, also called MRI, is the reference Ruby written in C: it parses source with Prism, compiles it to YARV bytecode and interprets that bytecode. JRuby runs Ruby on the JVM; TruffleRuby runs it on GraalVM.

open as a page

In Ruby, how do require, require_relative and load differ in how they find a file and how often they run it?

level: juniorimportance: must knowfreq 68%

basics

~20 s

require searches $LOAD_PATH and runs a file once, returning true then false; require_relative resolves the path from the calling file's directory with the same once-only rule; load runs the named file on every call and returns true.

open as a page

In Ruby, how do you check which version is running, and why is comparing RUBY_VERSION strings with < unreliable?

level: juniorimportance: must knowfreq 55%

basics

~10 s

RUBY_VERSION holds the running version as a String such as "4.0.7". Comparing it with < compares characters, so "3.4.10" < "3.4.9" is true; wrap both sides in Gem::Version, which compares numeric segments.

open as a page

In Ruby, how do exit, exit! and abort differ in exit status, SystemExit, ensure clauses and at_exit handlers?

level: middleimportance: must knowfreq 45%

basics

~20 s

exit raises SystemExit (default status 0), so ensure clauses and at_exit handlers run and it can be rescued. abort prints its message to $stderr and raises SystemExit with status 1. exit! ends the process at once (default status 1), skipping ensure and at_exit.

open as a page

In Ruby, how does require search $LOAD_PATH, and how does $LOADED_FEATURES enforce its once-only rule?

level: middleimportance: must knowfreq 42%

basics

~20 s

require tries each $LOAD_PATH directory in order and loads the first match. After a successful load it records the file's absolute path in $LOADED_FEATURES and skips any later require resolving to that same file; a load that raised is not recorded.

open as a page

In Ruby 3.x and 4.0, why are deprecation warnings hidden by default, and how do you surface them with -W:deprecated or Warning[:deprecated]?

level: middleimportance: must knowfreq 45%

basics

~20 s

Since Ruby 2.7.2, deprecation warnings are off by default so end users are not flooded by library internals. Developers turn them on with ruby -W:deprecated, -w, RUBYOPT or Warning[:deprecated] = true, ideally in tests and CI.

open as a page

In Ruby, in what order do at_exit handlers run, and how can a handler read or change the exit status?

level: middleimportance: should knowfreq 33%

basics

~20 s

at_exit handlers run in reverse order of registration when the program ends. Inside one, $! holds the exception that ended the program (a SystemExit after exit, nil after a normal finish), and calling exit there replaces the final status.

open as a page

In Ruby's command line, what do the -e, -n, -p, -l and -a flags do in a one-liner over a log file?

level: middleimportance: should knowfreq 28%

basics

~20 s

-e runs code given on the command line; -n wraps it in a while gets loop with each line in $; -p does the same and prints $ after every pass; -l chops line endings and sets $; -a splits each line into $F.

open as a page

In Ruby, how do RUBY_ENGINE, RUBY_ENGINE_VERSION and RUBY_PLATFORM tell code which engine and platform it runs on?

level: middleimportance: should knowfreq 35%

basics

~10 s

RUBY_ENGINE names the engine ("ruby" for CRuby, "jruby", "truffleruby"), RUBY_ENGINE_VERSION is that engine's own release, and RUBY_PLATFORM is the build's CPU-OS string, which is just "java" on JRuby.

open as a page

In Ruby, how do Kernel#autoload and Module#autoload defer loading, and what does autoload? report?

level: middleimportance: should knowfreq 30%

basics

~20 s

autoload(:Name, "path") registers a constant whose first reference calls require on the path. Module#autoload registers it inside that module; Kernel#autoload in the current namespace. autoload? returns the pending path, or nil once loaded or never registered.

open as a page

A cron-run Ruby log-rotation script logs its failures, but cron always sees status 0; what in the script decides the exit status?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Only how the program ends: finishing normally, even after a rescued and logged error, returns 0. The script must end with exit(false), exit 1 or abort, nothing may cancel that SystemExit or override it in at_exit, and statuses wrap past 255.

open as a page

A CPU-heavy Ruby worker that renders PDFs keeps one core busy on CRuby with eight threads; when would you move it to JRuby or TruffleRuby, and what breaks?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Move a long-running CPU-bound worker to JRuby or TruffleRuby when one process must use many cores, since their threads run Ruby in parallel; expect broken native gems (JRuby), no fork, slower start-up, warm-up and higher memory.

open as a page

A Ruby static-site generator's plugin loader runs each plugin twice after a rebuild; how do require, load and wrap explain and fix it?

level: seniorimportance: should knowfreq 24%

basics

~20 s

The loader uses load, which re-runs every plugin file on each rebuild, so registration side effects repeat. Either require plugins once, or keep load and reset the registry first, isolating each build's constants with load(path, true) or a fresh module.

open as a page

After moving to Ruby 4.0, why can require "logger" or "ostruct" raise LoadError under Bundler when it worked on 3.3, and what fixes it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Ruby 4.0 turned logger, ostruct, benchmark and others from default gems into bundled gems. Bundled gems ship with Ruby, but under Bundler they load only if the Gemfile or a gemspec lists them; adding them fixes the LoadError.

open as a page

How would you upgrade a Ruby payroll service from 3.1 to 4.0, and which headline changes from 3.2 through 4.0 would you expect to break it?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Fix deprecations on 3.1 first, then move through the versions with CI on each: expect 3.2's removed methods, 3.4's bundled bigdecimal and csv plus new Hash#inspect and error-message formats, and 4.0's bundled logger and removed APIs.

open as a page

With Ruby shipping a new minor version every year, how would you set an upgrade cadence and policy for dozens of Ruby services?

level: principalimportance: should knowfreq 20%

basics

~20 s

Plan one upgrade per year per service instead of multi-version jumps: test every service against the new release early, adopt it within a set window after a patch release, and never let a service fall off security maintenance.

open as a page

In Ruby 3.4 and 4.0, what changed when Prism became CRuby's default parser, and when would you run ruby --parser=parse.y?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

Since Ruby 3.4, CRuby parses code with Prism instead of parse.y; programs should behave the same. Run ruby --parser=parse.y to rule out a Prism bug or to keep MRI-only AST tooling such as RubyVM::AbstractSyntaxTree.of working.

open as a page

In Ruby, what does the RUBYOPT environment variable do, which switches may it carry, and when would you run ruby --disable=rubyopt?

level: seniorimportance: nice to knowfreq 16%

basics

~20 s

RUBYOPT adds command-line options to every ruby process that inherits the environment. Only a safe subset is allowed (-d, -E, -I, -K, -r, -U, -v, -w, -W and some long options); -e, -n or -p raise RuntimeError. --disable=rubyopt ignores it.

open as a page

In Ruby, what happens when several threads reference the same not-yet-loaded autoload constant, and what can still go wrong?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

One thread runs the require while others wait; they see the constant only once the file has loaded. Remaining risks: two threads autoloading mutually dependent files can deadlock, and errors surface in whichever thread arrived first.

open as a page