Toolmingo
Guides21 min read

50 Ruby Interview Questions (With Answers)

Top Ruby interview questions with clear answers and code examples — covering blocks, procs, lambdas, modules, metaprogramming, concurrency, and common gotchas.

Ruby interviews test language fundamentals, object-oriented design, metaprogramming, and Rails-adjacent patterns. This guide covers 50 of the most common questions — with concise answers and runnable code examples.

Quick reference

Topic Most asked questions
OOP & objects Everything is an object, class vs module, extend/include
Blocks, procs, lambdas Difference, yield, &, -> syntax
Metaprogramming method_missing, define_method, send, open classes
Modules & mixins include vs extend vs prepend
Symbols & strings :symbol vs "string", immutability, memory
Iterators & enumerables each, map, select, reduce, lazy enumerators
Exceptions rescue, ensure, raise, custom errors
Concurrency GIL, threads, fibers, Ractors (Ruby 3)

Basics

1. What is Ruby and what makes it unique?

Ruby is a dynamic, object-oriented, interpreted language created by Yukihiro Matsumoto in 1995. Key traits:

  • Everything is an object — integers, nil, true, classes themselves
  • Principle of Least Surprise — behaviour matches programmer intuition
  • Open classes — any class can be reopened and modified at runtime
  • Blocks, procs, lambdas — first-class closures baked into the language
  • Expressive syntax — reads like natural language
# Everything is an object
1.class        # => Integer
nil.class      # => NilClass
true.respond_to?(:class) # => true

# Integers have methods
3.times { print "Hi " }   # Hi Hi Hi
-5.abs                     # => 5

2. What is the difference between nil, false, and falsy values in Ruby?

In Ruby only nil and false are falsy. Everything else — 0, "", [], {} — is truthy.

puts "falsy" if nil    # falsy
puts "falsy" if false  # falsy
puts "truthy" if 0     # truthy  ← different from Python/JS
puts "truthy" if ""    # truthy
puts "truthy" if []    # truthy

nil means "no value"; false means "boolean false". Use nil? to check for nil explicitly.


3. Explain ==, equal?, eql?, and === in Ruby.

Method Checks
== Value equality (overrideable)
equal? Object identity (same object_id)
eql? Value equality with type check (used in Hash keys)
=== Case equality (used by case/when)
a = "hello"
b = "hello"

a == b       # => true   (same value)
a.equal?(b)  # => false  (different objects)
a.eql?(b)    # => true   (same value + type)

(1..10) === 5   # => true   (range includes 5)
/\d+/ === "42"  # => true   (regex match)
String === "hi" # => true   (is-a check)

4. What are Ruby symbols and how do they differ from strings?

Symbols are immutable, memory-efficient identifiers prefixed with :.

:name.class   # => Symbol
"name".class  # => String

:name.object_id == :name.object_id  # => true  (same object every time)
"name".object_id == "name".object_id  # => false (new object each time)
Symbol String
Mutable No Yes
Memory One instance ever New object each literal
Use case Hash keys, method names, identifiers Text data, manipulation

Use symbols for hash keys and method options; strings for text content.

# Prefer symbol keys in hashes
person = { name: "Alice", age: 30 }  # { :name => "Alice", :age => 30 }

5. What is the difference between puts, print, and p?

puts "hello"   # prints "hello\n" — calls .to_s, adds newline
print "hello"  # prints "hello" — no newline
p "hello"      # prints "\"hello\"\n" — calls .inspect, useful for debugging
p [1, nil, ""]  # => [1, nil, ""]  shows nil and empty string clearly

p returns the object; puts/print return nil. Use p for debugging.


6. What are freeze and frozen??

freeze makes an object immutable. frozen? checks if it is.

str = "hello"
str.freeze
str << " world"   # RuntimeError: can't modify frozen String

str.frozen?  # => true

# Ruby 3+ string literals can be globally frozen with:
# frozen_string_literal: true

Symbols and integers are frozen by default. Use freeze for constants you never want mutated.


Blocks, Procs, and Lambdas

7. What is a block in Ruby?

A block is anonymous code passed to a method, delimited by do...end or { }.

[1, 2, 3].each do |n|
  puts n * 2
end

[1, 2, 3].each { |n| puts n * 2 }

Blocks are not objects — you can't assign them to variables directly. Use yield to call the block inside a method.


8. What is yield and how does it work?

yield transfers control from the method to the block passed to it.

def greet
  puts "Before"
  yield "World" if block_given?
  puts "After"
end

greet { |name| puts "Hello, #{name}!" }
# Before
# Hello, World!
# After

block_given? safely checks whether a block was provided.


9. What is the difference between a Proc and a Lambda?

Both are callable objects (closures), but they differ in two key ways:

Proc Lambda
Created with Proc.new { } or proc { } lambda { } or ->() { }
Return behaviour return exits the enclosing method return exits only the lambda
Arity check Lenient (extra args = nil, fewer = ok) Strict (wrong arg count raises ArgumentError)
# Return difference
def test_proc
  p = Proc.new { return "from proc" }
  p.call
  "after proc"  # never reached
end

def test_lambda
  l = lambda { return "from lambda" }
  l.call
  "after lambda"  # this IS reached
end

test_proc    # => "from proc"
test_lambda  # => "after lambda"

# Arity difference
p = Proc.new { |a, b| [a, b] }
p.call(1)        # => [1, nil]  (no error)

l = lambda { |a, b| [a, b] }
l.call(1)        # ArgumentError: wrong number of arguments

10. What does the & (ampersand) operator do?

& converts between blocks and procs:

# Convert proc to block when calling a method
double = proc { |n| n * 2 }
[1, 2, 3].map(&double)  # => [2, 4, 6]

# Convert symbol to proc (shorthand)
[1, 2, 3].map(&:to_s)   # => ["1", "2", "3"]
# equivalent to: [1,2,3].map { |n| n.to_s }

# Capture block as proc in method definition
def capture(&block)
  block.call(10)
end
capture { |n| n * 3 }  # => 30

11. What is a closure?

A closure is a function that captures the environment (variables) where it was defined.

def make_counter
  count = 0
  increment = -> { count += 1; count }
  decrement = -> { count -= 1; count }
  [increment, decrement]
end

inc, dec = make_counter
inc.call  # => 1
inc.call  # => 2
dec.call  # => 1

The lambda captures count from make_counter's scope and persists it.


Object-Oriented Programming

12. How does Ruby's object model work?

Every object has a class, every class is an object (instance of Class), and Class itself inherits from Module which inherits from Object.

1.class          # => Integer
Integer.class    # => Class
Class.class      # => Class
Class.superclass # => Module
Module.superclass # => Object
Object.superclass # => BasicObject
BasicObject.superclass # => nil

Method lookup walks: object's singleton class → class → superclass chain → Kernel → BasicObject.


13. What is the difference between include, extend, and prepend?

All three add a module's methods to a class, but differently:

Method Adds module methods as Target
include Instance methods class instances
extend Class-level (singleton) methods the class itself
prepend Instance methods (before class in lookup) class instances
module Greetable
  def hello
    "Hello from #{self.class}"
  end
end

class Person
  include Greetable
end
Person.new.hello   # => "Hello from Person"

class Robot
  extend Greetable
end
Robot.hello        # => "Hello from Robot"  (class method)

module Logging
  def hello
    puts "Calling hello"
    super
  end
end

class Cat
  prepend Logging
  def hello
    "Meow"
  end
end
Cat.new.hello  # "Calling hello" then "Meow"

14. What is a singleton method?

A method defined on a specific object instance, not its class.

dog = Object.new

def dog.speak
  "Woof!"
end

dog.speak  # => "Woof!"
# Other Object instances don't have this method

Class methods are singleton methods on the class object:

class Cat
  def self.species  # self is the Cat class object
    "Felis catus"
  end
end
Cat.species  # => "Felis catus"

15. What are attr_reader, attr_writer, and attr_accessor?

Macros that auto-generate getter/setter instance methods:

class Person
  attr_reader :name       # generates: def name; @name; end
  attr_writer :age        # generates: def age=(v); @age = v; end
  attr_accessor :email    # generates both reader and writer

  def initialize(name, age, email)
    @name  = name
    @age   = age
    @email = email
  end
end

p = Person.new("Alice", 30, "alice@example.com")
p.name         # => "Alice"
p.age = 31     # sets @age
p.email        # => "alice@example.com"
p.email = "new@example.com"

16. What is the difference between public, protected, and private?

Visibility Accessible from Can be called with explicit receiver?
public Anywhere Yes
protected Inside class and subclasses Yes (on self or same-class instance)
private Inside class only No (implicit self only)
class BankAccount
  def balance_greater_than?(other)
    balance > other.balance  # protected method, called on another instance
  end

  protected

  def balance
    @balance
  end

  private

  def secret_pin
    1234
  end
end

17. What is self in Ruby?

self refers to the current object in context:

class Dog
  def self.kingdom     # self = Dog class
    "Animalia"
  end

  def name             # self = instance
    self.class.name
  end

  def bark
    puts self.inspect  # self = current Dog instance
  end
end

Inside instance methods, self is the receiver. Inside class methods (and class << self blocks), self is the class.


Modules and Mixins

18. What is a module in Ruby and when do you use it?

Modules serve two purposes:

  1. Namespace — group related constants/methods, avoid name clashes
  2. Mixin — share behaviour across unrelated classes (Ruby's answer to multiple inheritance)
# Namespace
module Payments
  class Invoice; end
  class Receipt; end
end

Payments::Invoice.new

# Mixin
module Serializable
  def to_json
    instance_variables.each_with_object({}) do |var, hash|
      hash[var.to_s.delete("@")] = instance_variable_get(var)
    end.to_json
  end
end

class User
  include Serializable
end

19. What is the method lookup path (MRO)?

Ruby uses a linear lookup chain you can inspect with .ancestors:

module A; end
module B; end
class C
  include A
  include B
end

C.ancestors  # => [C, B, A, Object, Kernel, BasicObject]

prepend inserts the module before the class in the chain.


Metaprogramming

20. What is method_missing and when should you use it?

method_missing is called when Ruby can't find a method. Override it to intercept undefined calls.

class DynamicClass
  def method_missing(name, *args)
    if name.to_s.start_with?("say_")
      word = name.to_s.sub("say_", "")
      puts word
    else
      super  # important: delegate unknown methods up
    end
  end

  def respond_to_missing?(name, include_private = false)
    name.to_s.start_with?("say_") || super
  end
end

obj = DynamicClass.new
obj.say_hello   # "hello"
obj.say_world   # "world"

Always define respond_to_missing? alongside method_missing. Use sparingly — it's slow and hides errors.


21. What is define_method?

define_method creates methods dynamically at runtime:

class Report
  ["html", "pdf", "csv"].each do |format|
    define_method("export_#{format}") do
      "Exporting as #{format.upcase}"
    end
  end
end

r = Report.new
r.export_html  # => "Exporting as HTML"
r.export_pdf   # => "Exporting as PDF"

Prefer define_method over eval for dynamic method creation — it's safer and scoped correctly.


22. What does send do?

send calls a method by name (string or symbol), bypassing visibility:

"hello".send(:upcase)    # => "HELLO"
"hello".send("length")   # => 5

class Secret
  private
  def hidden; "shhh"; end
end

Secret.new.send(:hidden)          # => "shhh"  (bypasses private)
Secret.new.public_send(:hidden)   # NoMethodError (respects private)

Use public_send when you don't intentionally need to bypass visibility.


23. What are open classes (monkey patching)?

Ruby allows reopening any class — including built-ins — to add or override methods.

class Integer
  def factorial
    return 1 if self <= 1
    self * (self - 1).factorial
  end
end

5.factorial  # => 120

Powerful but risky — can break third-party code. Prefer refinements (scoped monkey patches) in Ruby 2+:

module FactorialRefinement
  refine Integer do
    def factorial
      return 1 if self <= 1
      self * (self - 1).factorial
    end
  end
end

using FactorialRefinement
5.factorial  # => 120  (only in this file/scope)

24. What is eval, instance_eval, and class_eval?

Method Context (self) Use
eval(str) Current context Execute string as Ruby — avoid
obj.instance_eval The object Access instance variables
Klass.class_eval The class Define methods dynamically
class Person
  def initialize(name); @name = name; end
end

alice = Person.new("Alice")
alice.instance_eval { @name }   # => "Alice"

Person.class_eval do
  def greet
    "Hi, I'm #{@name}"
  end
end

alice.greet  # => "Hi, I'm Alice"

Iterators and Enumerables

25. What is the Enumerable module?

Enumerable provides collection iteration methods to any class that defines each:

class NumberBag
  include Enumerable

  def initialize(*nums)
    @nums = nums
  end

  def each(&block)
    @nums.each(&block)
  end
end

bag = NumberBag.new(3, 1, 4, 1, 5, 9)
bag.sort      # => [1, 1, 3, 4, 5, 9]
bag.max       # => 9
bag.select(&:odd?)  # => [3, 1, 1, 5, 9]
bag.map { |n| n * 2 }  # => [6, 2, 8, 2, 10, 18]

26. What is the difference between map, select, reject, and reduce?

nums = [1, 2, 3, 4, 5]

nums.map    { |n| n * 2 }        # => [2, 4, 6, 8, 10]   transform each
nums.select { |n| n.even? }      # => [2, 4]              keep matching
nums.reject { |n| n.even? }      # => [1, 3, 5]           drop matching
nums.reduce(0) { |sum, n| sum + n }  # => 15              fold to value

# reduce shorthand
nums.reduce(:+)   # => 15
nums.reduce(:*)   # => 120

inject is an alias for reduce.


27. What is each_with_object vs inject/reduce?

# inject — accumulator is return value of block
[1,2,3].inject([]) { |acc, n| acc << n * 2; acc }  # note: must return acc

# each_with_object — accumulator is always the passed object
[1,2,3].each_with_object([]) { |n, acc| acc << n * 2 }
# => [2, 4, 6]  (cleaner — no need to return acc)

# Building a hash
[:a, :b].each_with_index.each_with_object({}) do |(el, i), hash|
  hash[el] = i
end
# => { a: 0, b: 1 }

28. What is a lazy enumerator?

lazy defers computation until values are needed — useful for infinite sequences:

# Without lazy — would loop forever
(1..Float::INFINITY).select(&:odd?).first(5)  # hangs!

# With lazy — stops after finding 5
(1..Float::INFINITY).lazy.select(&:odd?).first(5)
# => [1, 3, 5, 7, 9]

# Chain multiple operations without creating intermediate arrays
(1..Float::INFINITY).lazy
  .map    { |n| n ** 2 }
  .select { |n| n % 3 == 0 }
  .first(3)
# => [9, 36, 81]

Strings and Data Structures

29. What are the main ways to create strings in Ruby?

# Double quotes — interpolation + escape sequences
name = "Alice"
"Hello, #{name}!"    # => "Hello, Alice!"
"Line\nBreak"        # \n is interpreted

# Single quotes — literal, no interpolation
'Hello, #{name}!'    # => "Hello, \#{name}!"  (literal)
'Line\nBreak'        # \n is NOT interpreted (except \\ and \')

# Heredoc
text = <<~HEREDOC
  Multi-line
  string here
HEREDOC

# %w and %i — word/symbol arrays
%w[apple banana cherry]   # => ["apple", "banana", "cherry"]
%i[foo bar baz]           # => [:foo, :bar, :baz]

30. What is the difference between Array#each and Array#map?

# each — returns original array, used for side effects
[1, 2, 3].each { |n| puts n }
# returns [1, 2, 3]

# map — returns NEW array with transformed values
[1, 2, 3].map { |n| n * 10 }
# => [10, 20, 30]

Use each for side effects (printing, updating external state). Use map when you need a new transformed collection.


31. What is the difference between Hash#merge and Hash#merge!?

a = { x: 1, y: 2 }
b = { y: 10, z: 3 }

a.merge(b)   # => { x: 1, y: 10, z: 3 }  — returns new hash, a unchanged
a.merge!(b)  # => { x: 1, y: 10, z: 3 }  — modifies a in place

# With block to resolve conflicts
a.merge(b) { |key, old, new_val| old + new_val }
# => { x: 1, y: 12, z: 3 }

The ! (bang) convention means "modifies receiver in place".


Exception Handling

32. How does exception handling work in Ruby?

begin
  result = 10 / 0
rescue ZeroDivisionError => e
  puts "Error: #{e.message}"
rescue ArgumentError, TypeError => e
  puts "Type problem: #{e.message}"
rescue => e           # catches StandardError and subclasses
  puts "Unknown: #{e}"
else
  puts "No error, result = #{result}"  # only runs if no exception
ensure
  puts "Always runs (like finally)"   # cleanup, always executes
end

33. What is the exception hierarchy?

Exception
├── SignalException (Interrupt, etc.)
├── ScriptError (SyntaxError, LoadError, NotImplementedError)
├── SystemExit
└── StandardError  ← rescue catches this by default
    ├── RuntimeError
    ├── ArgumentError
    ├── TypeError
    ├── NameError → NoMethodError
    ├── IOError → EOFError
    ├── IndexError → KeyError, StopIteration
    ├── ZeroDivisionError
    └── ...

Never rescue Exception — it catches Interrupt (Ctrl+C) and SignalException. Rescue StandardError or specific subclasses.


34. How do you define custom exceptions?

class AppError < StandardError; end

class ValidationError < AppError
  attr_reader :field

  def initialize(field, message = "is invalid")
    @field = field
    super("#{field} #{message}")
  end
end

begin
  raise ValidationError.new(:email, "is not a valid format")
rescue ValidationError => e
  puts e.message  # => "email is not a valid format"
  puts e.field    # => :email
end

35. What does retry do in a rescue block?

retry re-runs the begin block from the start:

attempts = 0

begin
  attempts += 1
  result = flaky_api_call
rescue NetworkError => e
  retry if attempts < 3
  raise  # re-raise if all retries exhausted
end

Concurrency

36. What is the Global Interpreter Lock (GIL) in Ruby?

MRI Ruby (the standard implementation) has a GIL (Global VM Lock): only one thread executes Ruby code at a time, even on multi-core machines.

  • I/O-bound work: threads still help — GIL is released during I/O
  • CPU-bound work: use fork (separate processes) or JRuby/TruffleRuby (no GIL)
# Threads — useful for I/O concurrency
threads = 5.times.map do
  Thread.new { sleep 1; "done" }
end
threads.map(&:value)  # all sleep concurrently — ~1 second total

37. What are fibers?

Fibers are cooperative coroutines — lightweight, manually controlled context switches.

fiber = Fiber.new do
  puts "Step 1"
  Fiber.yield
  puts "Step 2"
  Fiber.yield
  puts "Step 3"
end

fiber.resume  # Step 1
fiber.resume  # Step 2
fiber.resume  # Step 3

# Fibers as generators
counter = Fiber.new do
  n = 0
  loop { Fiber.yield(n += 1) }
end

counter.resume  # => 1
counter.resume  # => 2
counter.resume  # => 3

38. What are Ractors (Ruby 3)?

Ractors are actor-like concurrent units that run in parallel without sharing mutable state:

ractor = Ractor.new(5) do |n|
  n * 2
end

ractor.take  # => 10

# True parallel execution
results = 4.times.map do |i|
  Ractor.new(i) { |n| n ** 2 }
end.map(&:take)
# => [0, 1, 4, 9]  (computed in parallel)

Ractors enforce isolation — objects must be shareable (frozen or Ractor-safe) to cross boundaries.


Functional Patterns

39. What is method chaining and how does Ruby support it?

Method chaining returns self (or a new object) from each method, enabling pipelines:

"  Hello World  "
  .strip
  .downcase
  .split
  .map(&:capitalize)
  .join(" ")
# => "Hello World"

# then/yield_self for value transformation chains
"hello"
  .then { |s| s.upcase }
  .then { |s| "#{s}!" }
# => "HELLO!"

40. What is tap?

tap yields self to a block and returns self — useful for debugging chains:

[1, 2, 3]
  .tap { |a| puts "before: #{a}" }
  .map { |n| n * 2 }
  .tap { |a| puts "after: #{a}" }
  .select(&:odd?)
# before: [1, 2, 3]
# after: [2, 4, 6]
# => []

Practical Patterns

41. What is the difference between require, require_relative, and load?

require "json"          # loads from $LOAD_PATH, loads once
require_relative "util" # loads relative to current file, loads once
load "script.rb"        # loads every time called, executes top-level code

Use require for gems/stdlib, require_relative for project files, load for scripts you want re-executed.


42. What is Comparable and how do you use it?

Include Comparable and define <=> to get all comparison operators for free:

class Temperature
  include Comparable
  attr_reader :degrees

  def initialize(degrees)
    @degrees = degrees
  end

  def <=>(other)
    degrees <=> other.degrees
  end
end

temps = [Temperature.new(30), Temperature.new(15), Temperature.new(22)]
temps.min.degrees  # => 15
temps.max.degrees  # => 30
temps.sort.map(&:degrees)  # => [15, 22, 30]
Temperature.new(25).between?(Temperature.new(20), Temperature.new(30))  # => true

43. What is the Struct class?

Struct auto-generates a value object class with accessors, ==, and to_a:

Point = Struct.new(:x, :y) do
  def distance_to(other)
    Math.sqrt((x - other.x)**2 + (y - other.y)**2)
  end
end

p1 = Point.new(0, 0)
p2 = Point.new(3, 4)
p1.distance_to(p2)  # => 5.0
p1 == Point.new(0, 0)  # => true

44. What is Comparable vs Enumerable?

Module Purpose Required method
Comparable Ordering between objects <=>
Enumerable Collection iteration each

Both are mixins. Comparable gives <, >, <=, >=, between?, clamp. Enumerable gives map, select, sort, min, max, reduce, etc.


45. What is Object#freeze vs Ractor.make_shareable?

# freeze — prevents mutation, object stays in current Ractor
str = "hello".freeze
str << " world"  # RuntimeError

# Ractor.make_shareable — deep freezes for Ractor sharing
data = { name: "Alice", scores: [1, 2, 3] }
Ractor.make_shareable(data)
# deep-freezes data and all nested objects

Common Gotchas

46. What is the difference between ||= and &&=?

# ||= — assign if nil or false
a = nil
a ||= 10   # a = 10
a ||= 20   # a still 10 (already truthy)

# &&= — assign only if currently truthy
b = 5
b &&= b * 2  # b = 10
c = nil
c &&= 10     # c still nil (&&= skips when falsy)

hash[:key] ||= default is the classic memoization idiom.


47. What is the "frozen string literal" comment?

# frozen_string_literal: true

When placed at the top of a file, all string literals in that file are frozen, improving memory and performance (strings are deduplicated). Modifying them raises FrozenError.

Ruby 3+ makes this opt-in; a future version may make it the default.


48. What is * (splat) and ** (double splat)?

# Single splat — collects extra positional args
def greet(first, *rest)
  puts "#{first} and #{rest.join(", ")}"
end
greet("Alice", "Bob", "Carol")  # Alice and Bob, Carol

# Array unpacking
first, *middle, last = [1, 2, 3, 4, 5]
# first=1, middle=[2,3,4], last=5

# Double splat — keyword arguments / hash unpacking
def configure(host:, port: 80, **options)
  puts options.inspect
end
configure(host: "localhost", debug: true)
# => { debug: true }

# Hash unpacking
defaults = { timeout: 30, retries: 3 }
configure(host: "example.com", **defaults)

49. What are common Ruby anti-patterns?

Anti-pattern Problem Fix
Rescuing Exception Catches Interrupt, hides signals Rescue StandardError or specific
Using method_missing for everything Slow, hides NoMethodError Use define_method or real methods
Monkey patching without refinements Breaks gems, hard to debug Use refine for scoped patches
eval(string) Security risk, slow Use send, define_method, blocks
Mutable default arguments Shared state across calls Use nil default, assign inside
Not defining respond_to_missing? respond_to? lies after method_missing Always pair them
Over-using globals ($var) Hard to test and reason about Use dependency injection
Ignoring frozen string literals Memory waste, unintended mutation Add # frozen_string_literal: true

50. Ruby vs Python vs JavaScript — quick comparison

Dimension Ruby Python JavaScript
Typing Dynamic, duck-typed Dynamic, duck-typed Dynamic
Falsy values nil, false only None, 0, "", [], etc. Many (0, "", null, undefined, NaN)
Blocks/closures First-class blocks + procs/lambdas Lambdas (1-expr only), functions Functions, arrow functions
Mixins include/extend/prepend Multiple inheritance Prototypes, mixins via Object.assign
Metaprogramming Very strong (open classes, method_missing) Moderate (__getattr__, decorators) Proxies, Reflect
Main use case Web (Rails), scripting Data science, ML, scripting Web frontend + backend (Node)
Concurrency GIL (MRI), Ractors (Ruby 3) GIL (CPython), asyncio Event loop, Workers
Package manager Bundler + RubyGems pip + venv/poetry npm/yarn/pnpm

FAQ

Q: Is Ruby strictly object-oriented?
A: Yes — every value in Ruby is an object, including integers, nil, true, and false. There are no primitive types. Even classes are objects (instances of Class).

Q: What is the difference between nil in Ruby and null in other languages?
A: nil is an object (NilClass.new would fail because there's only one nil). It has methods like nil.to_i # => 0, nil.to_s # => "", nil.nil? # => true. In Java/C, null is just the absence of a pointer.

Q: When should I use a Struct vs a plain class?
A: Struct is ideal for value objects — small data containers with few/no methods where equality is based on field values. Use a full class when you need complex initialization, inheritance, or many methods.

Q: What is duck typing?
A: Ruby doesn't check an object's class — it checks whether it responds to the methods you call. "If it quacks like a duck, it's a duck." Use respond_to?(:method_name) instead of is_a?(SomeClass) for flexible, decoupled code.

Q: What is the difference between throw/catch and raise/rescue in Ruby?
A: raise/rescue is for exceptions and errors. throw/catch is for non-local jumps (e.g., breaking out of nested loops or early exit from a computation) without the overhead of exception handling.

result = catch(:done) do
  [1,2,3,4,5].each do |n|
    [10,20,30].each do |m|
      throw :done, n * m if n * m > 50
    end
  end
end
# => 60  (3 * 20)

Q: What is Bundler and why do you use it?
A: Bundler manages gem dependencies for a project. Gemfile declares what gems and versions you need; Gemfile.lock pins exact versions for reproducible installs. Run bundle exec to ensure the correct gem versions are used. Essential for any non-trivial Ruby project.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools