72 Ruby Interview Questions and Answers (2026)

Ruby still powers a huge share of production web apps and developer tooling, and its focus on expressive, readable code keeps it a favorite for fast-moving teams. Interviewers can tell within minutes whether you actually understand it — walk in shaky on the object model, blocks, or metaprogramming and you will lose the offer to someone who does.
This is your fix: 72 questions with tight, interview-ready answers and code where it counts. They're ordered Junior to Mid to Senior, so you build from fundamentals up to metaprogramming, concurrency, and performance. Work through them and you'll speak Ruby like you mean it.
Q1.What does 'everything is an object' mean in Ruby, and are there any exceptions?
In Ruby almost every value is an object: an instance of some class that responds to methods, including things that are primitives in other languages like integers, nil, true, and even classes themselves.
Literals are objects: 42.even? works because 42 is an instance of Integer; nil.nil? works because nil is the single instance of NilClass.
Classes are objects too: A class is an instance of Class, so you can call methods on it and assign it to variables.
The real exceptions:
Methods, blocks, and keywords (if, def, while) are not objects by themselves.
You can reify some of them: a block becomes an object via Proc, and a method via Object#method, but in raw form they are syntax, not values.
Q2.What is duck typing and how does Ruby implement it?
Duck typing means an object's suitability is determined by what methods it responds to, not by its class: "if it walks like a duck and quacks like a duck, it's a duck." Ruby implements this through dynamic method dispatch rather than declared interfaces.
Behavior over type: Code only cares that an object responds to the methods it calls, so any object with a each or to_s can be passed in.
No interface declarations: Unlike Java/C#, Ruby has no implements; compatibility is checked at call time when the method is dispatched.
Tools that support it: respond_to? to check capability, and method_missing to respond dynamically.
Trade-off: Flexibility and easy polymorphism, but errors surface at runtime rather than compile time, so tests matter more.
Q3.What is the difference between a Class and a Module? When would you choose one over the other?
Class and a Module? When would you choose one over the other?A class is a blueprint for objects that can be instantiated and inherited; a module is a bundle of methods/constants that cannot be instantiated but can be mixed into classes or used as a namespace. Use a class for "is-a" entities and a module to share behavior or organize names.
Class:
Can be instantiated with .new and supports single inheritance via <.
Models a thing with identity and state (a User, an Order).
Module:
Cannot be instantiated and has no inheritance.
Two jobs: a namespace (Math::PI) and a mixin of shared behavior via include or extend.
Choosing:
Need objects with state and an "is-a" relationship: class.
Need to share capabilities across unrelated classes ("has-a" / "can-do"): module mixin.
Ruby has single inheritance, so mixins are how it gets multiple-inheritance-like reuse.
Q4.What is the Enumerable module and why is it so powerful?
Enumerable module and why is it so powerful?Enumerable is a mixin module that gives any collection class a huge library of iteration methods (map, select, reduce, sort, etc.) in exchange for defining a single method: each.
One method buys dozens: Define each and include Enumerable, and you get the whole traversal/transform/query toolkit for free.
Consistent API everywhere: Array, Hash, Range, and Set all share the same methods, so knowledge transfers across collections.
Functional, chainable style: Methods return new collections you can chain, encouraging declarative code over manual loops.
Sorting and comparison: min, max, and sort rely on elements implementing <=>.
Q5.What is the difference between a Class and an Object in Ruby?
Class and an Object in Ruby?A class is the template that defines structure and behavior; an object (instance) is a concrete thing created from that template with its own state. The class says what something can do; the object is an actual one that does it.
Class:
Defines instance methods and the shape of data (instance variables).
Created once; used to stamp out many instances.
Object:
Produced by Class.new; each holds its own copy of instance variable values.
Two objects of the same class share methods but have independent state.
Ruby twist: A class is itself an object (an instance of Class), so the two concepts are layered, not opposed.
Q6.What is the difference between attr_accessor, attr_reader, and attr_writer?
attr_accessor, attr_reader, and attr_writer?They are macros that generate accessor methods for instance variables: attr_reader makes a getter, attr_writer makes a setter, and attr_accessor makes both. They exist to avoid hand-writing repetitive getter/setter methods.
attr_reader :x: Generates x returning @x; read-only.
attr_writer :x: Generates x= to set @x; write-only.
attr_accessor :x: Generates both getter and setter.
Practical notes:
Default to the least access needed (often attr_reader) to protect internal state.
Inside the object, assigning to a writer needs self.x = ... since a bare x = ... creates a local variable.
Q7.What values are considered truthy and falsy in Ruby, and how do nil and false differ?
nil and false differ?In Ruby only two values are falsy: false and nil. Everything else is truthy, including 0, empty strings, and empty arrays.
Falsy values: Only nil and false evaluate as false in conditionals.
Truthy values: Everything else, including 0, "", and [] (unlike many other languages).
How nil and false differ:
nil is an instance of NilClass representing absence of value; false is an instance of FalseClass representing a real boolean.
Use .nil? to test specifically for nil, since both look falsy in an if.
Practical tip: To distinguish "missing" from "explicitly false", compare with == or use nil? rather than relying on truthiness.
Q8.What is a Range in Ruby and how is it used?
Range in Ruby and how is it used?A Range represents an interval between a start and end value, created with .. (inclusive) or ... (exclusive of the end). It works with any objects that are comparable and can be sequenced.
Two literal forms: 1..5 includes 5; 1...5 stops at 4.
Common uses:
Iteration: (1..5).each.
Membership tests: (1..10).include?(7) or cover?.
Slicing: array[2..4], "hello"[0..1].
As a case condition: when 1..10.
Works beyond integers: Any objects supporting <=> (and succ for iteration), e.g. ('a'..'e') or dates.
Endless and beginless ranges: (1..) and (..10) are valid and handy for slicing and comparisons.
Q9.How do you define a class method versus an instance method in Ruby?
Instance methods are defined with a plain def and operate on instances; class methods are defined on the class itself (commonly def self.name) and are called on the class without an object.
Instance method:
Defined with def greet; called on an object: obj.greet.
Has access to that instance's instance variables.
Class method:
Defined with def self.create; called on the class: Klass.create.
Often used for factories, helpers, or operations not tied to one instance.
Other ways to declare class methods:
class << self opens the singleton class to group several class methods.
Inside the class body, def self.x works because self is the class there.
Q10.What is the difference between require, require_relative, and load?
require, require_relative, and load?All three load Ruby code, but differ in path resolution and whether they re-run a file. require loads once from the load path, require_relative loads once relative to the current file, and load runs the file every time it is called.
require:
Searches the load path ($LOAD_PATH) and gems; needs an absolute path or a name it can resolve.
Loads a file only once: tracks it in $LOADED_FEATURES and returns false on repeat calls.
require_relative:
Resolves the path relative to the location of the current source file, not the working directory.
Also loads only once; ideal for requiring files within your own project.
load:
Executes the file every time it is called (no once-only tracking); useful for reloading changed code.
Requires the full filename including .rb, and resolves via the load path.
Rule of thumb: Use require for gems/libraries, require_relative for project files, and load only when you deliberately need re-execution.
Q11.What does the safe navigation operator (&.) do and when would you use it?
&.) do and when would you use it?The safe navigation operator &. calls a method on a receiver only if it isn't nil: if the receiver is nil it short-circuits and returns nil instead of raising NoMethodError.
What it replaces: Turns user && user.address && user.address.city into user&.address&.city.
How it differs from .: a&.b only guards against nil, not against other falsey-ish or missing methods.
When to use it: Optional values that may legitimately be nil (API results, optional associations).
When NOT to use it: Don't chain it everywhere to silence bugs: it can hide a nil that should never occur and mask the real error.
Q12.What is the difference between a Symbol and a String in terms of memory and usage?
Symbol and a String in terms of memory and usage?A Symbol is an immutable, interned identifier stored once and reused, while a String is a mutable sequence of characters where each literal usually creates a new object. Use symbols for fixed names/identifiers and strings for text data.
Memory: :name referenced 1000 times is the same single object (same object_id); "name" written 1000 times can be 1000 objects (unless frozen/deduped).
Mutability: Symbols are immutable and frozen; strings are mutable (<<, upcase!).
Comparison speed: Symbol equality is an identity check (fast); string equality compares contents.
Usage:
Symbols: hash keys, method names, fixed labels/states.
Strings: user input, text manipulation, anything that changes or is displayed.
Historical note: Symbols are garbage-collected since Ruby 2.2, so dynamically creating them is far less dangerous than it once was.
Q13.Why would you use freeze on a Ruby object?
freeze on a Ruby object?freeze makes an object immutable: any attempt to modify it raises FrozenError. You use it to protect shared state, declare true constants, and gain small performance/memory wins.
Enforce immutability: Constants in Ruby aren't truly immutable; CONFIG = {...}.freeze prevents accidental in-place changes.
Safe sharing: A frozen object can be shared across threads/objects without fear of one party mutating it underneath another.
Performance / memory: Frozen strings can be deduplicated and used as efficient hash keys.
Caveat: shallow freeze: freeze only freezes the object itself, not nested objects. A frozen array can still have mutable elements; deep-freeze each element if needed.
Q14.Why are symbols often preferred over strings as hash keys?
Symbols are preferred as hash keys because they are immutable and interned: the same symbol is one shared object, so it hashes consistently and compares quickly, making lookups fast and memory-efficient.
Single shared object: :id used as a key everywhere is the same object, while "id" string keys could allocate many objects.
Immutable and stable hash: A key's hash must not change. Symbols can't be mutated; a mutable string key could be altered and "lost" in the hash.
Faster comparison: Resolving collisions compares keys via identity for symbols rather than char-by-char for strings.
Idiomatic: Ruby/Rails conventions (keyword args, options hashes) use symbol keys; { name: "Ann" } is symbol-keyed sugar.
Note: String literal keys are auto-frozen when # frozen_string_literal: true is on, narrowing the gap, but symbols remain clearer and conventional.
Q15.What is the difference between to_s and inspect?
to_s and inspect?to_s produces a human-readable string for display, while inspect produces a developer-facing, debug-oriented representation that ideally shows the object's internal state.
to_s is for users: Used by string interpolation and puts; meant to be clean and readable.
inspect is for developers: Used by p, the IRB/console, and arrays printed with p; shows quotes, structure, ivars.
Concrete differences: "hi".to_s is hi, but "hi".inspect is "hi" (with quotes); nil.to_s is empty while nil.inspect is "nil".
Override both for your classes: Default inspect lists instance variables; a custom one makes debugging much clearer.
Q16.Explain the difference between public, private, and protected visibility in Ruby.
public, private, and protected visibility in Ruby.Visibility controls who may call a method. public methods can be called by anyone, private methods only from within the same instance (no explicit receiver, with a small exception), and protected methods can be called with an explicit receiver but only from objects of the same class or subclass.
public: The default; part of the object's external API, callable from anywhere.
private: Callable only without an explicit receiver, i.e. implicitly on self. Modern Ruby allows self.setter= calls as an exception.
protected: Callable with an explicit receiver as long as the caller is the same class or a relative; ideal for comparing two instances' internals.
Key nuance: Ruby visibility is about the receiver syntax, not the calling location, which differs from many other languages.
Q17.Explain how the yield keyword works.
yield keyword works.yield invokes the block that was implicitly passed to the current method, optionally passing it arguments and using its return value. It lets a method hand control back to caller-supplied code.
Calls the implicit block: Any block attached to the method call ({ } or do...end) is run where yield appears.
Passes and receives values: yield x passes x to the block; the expression evaluates to the block's return value.
Guard with block_given?: Calling yield without a block raises LocalJumpError, so check first if the block is optional.
Q18.Explain the difference between a block and a Proc.
block and a Proc.A block is an anonymous chunk of code passed to a method; it is not an object on its own. A Proc is a block wrapped in an object that you can store in a variable, pass around, and call multiple times.
A block is syntax, not a value: Written with do...end or { }, a method may receive only one block, invoked with yield or a &block parameter.
A Proc is a first-class object: Created with Proc.new, proc { }, or lambda; stored, passed, and called with .call.
Conversion between them: Prefixing a parameter with & turns a passed block into a Proc inside the method, and & on a Proc turns it back into a block at the call site.
Two flavors of Proc: Lambdas check argument count strictly and return only from themselves; non-lambda procs are lenient and return from the enclosing method.
Q19.Explain the difference between map and each. When would you use map!?
map and each. When would you use map!?each iterates purely for side effects and returns the original collection, while map transforms each element and returns a new array of the results. Use map! when you want that transformation applied in place, mutating the original array.
each is for side effects: Run code per element (print, accumulate); the block's return value is ignored and each returns the receiver unchanged.
map is for transformation: Collects each block return value into a new array of the same length; the original is untouched.
map! mutates in place: Replaces each element with the block's result on the original array; use only when mutation is intended and safe.
Choosing: Need a new transformed collection: map. Just doing work: each. Want to overwrite the source: map!.
Q20.Explain the roles of begin, rescue, else, and ensure in Ruby exception handling.
begin, rescue, else, and ensure in Ruby exception handling.These keywords structure exception handling: begin marks the protected region, rescue handles raised errors, else runs only when no error occurred, and ensure always runs for cleanup.
begin: Opens the block whose code is monitored for exceptions (often implicit in a method/class body).
rescue:
Catches matching exceptions; you can specify a class and bind it: rescue ArgumentError => e.
Multiple rescue clauses are tried top to bottom; most specific first.
else: Runs only if the begin body raised nothing; keeps the no-error path out of the protected region.
ensure: Always executes (error or not, even on return): use for closing files, releasing locks, cleanup.
Q21.Why is the Enumerable module considered the heart of Ruby, and what is the role of the spaceship (<=>) operator?
Enumerable module considered the heart of Ruby, and what is the role of the spaceship (<=>) operator?Enumerable is called the heart of Ruby because it provides a single, consistent iteration vocabulary that nearly every collection shares, built on just each. The spaceship operator <=> is what lets those methods order elements: it answers "how do two objects compare?"
Why it's central:
Idiomatic Ruby favors expressing intent (select, group_by) over hand-written loops.
It standardizes behavior across all enumerable types, so the same code reads the same everywhere.
The <=> operator:
Returns -1, 0, or 1 (or nil if not comparable).
sort, min, max, and sort_by all delegate ordering to it.
Pairs with Comparable: Define <=> and include Comparable to get <, >, ==, and between? for free.
Q22.What does self refer to in different contexts within a Ruby program?
self refer to in different contexts within a Ruby program?self is the current object: the implicit receiver of method calls and the default for instance variable access. What it points to depends on where the code is executing.
Inside an instance method: self is the instance the method was called on.
Inside a class/module body (but outside any method): self is the class or module object itself.
Inside a class method: self is the class (e.g. defined with def self.foo).
At the top level: self is main, a special instance of Object.
Why it matters: self.attr = x is needed to call a setter, since a bare attr = x creates a local variable instead.
Q23.What does respond_to? do and how does it relate to duck typing?
respond_to? do and how does it relate to duck typing?respond_to? asks whether an object has (and can respond to) a given method by name, returning true or false. It lets you check capability rather than class, which is the essence of duck typing.
Basic usage:
obj.respond_to?(:each) returns true if obj can receive each.
A second argument true also checks private/protected methods.
Relation to duck typing:
Duck typing cares what an object can do, not what it is: "if it quacks like a duck...".
respond_to? lets you branch on behavior instead of using is_a? / class checks, keeping code flexible.
Caveat: Methods caught by method_missing won't show up unless you also implement respond_to_missing?.
Q24.How does Pattern Matching (case...in) differ from a standard case...when statement?
case...in) differ from a standard case...when statement?case...when tests a value against conditions using ===, while case...in (pattern matching, stable in Ruby 3.0) deconstructs a value's structure and can bind parts of it to variables.
case...when matches by ===:
Each when calls clause === value (class, range, regex, etc.).
Good for simple value/type/range branching.
case...in matches by structure:
Destructures arrays and hashes and binds matched parts to local variables.
Supports find patterns, guards (if), and pinning (^) of existing values.
Failure behavior differs: case...when returns nil if nothing matches; case...in raises NoMatchingPatternError unless you add an else.
Q25.Explain pattern matching in Ruby using case...in.
case...in.Pattern matching with case...in checks a value against structural patterns, deconstructing arrays and hashes and binding their parts to variables in one step. It is ideal for processing nested data like JSON or API responses.
Array patterns: in [a, b, *rest] matches by shape and binds elements, using * to capture the remainder.
Hash patterns: in {name:, age:} matches keys and binds their values to name and age.
Extra features:
Type checks: in Integer => n.
Guards: in [x, y] if x > y.
Pin operator: ^expected matches against an existing variable's value.
No-match handling: Add else to avoid a NoMatchingPatternError; there is also a one-line => / in form for single matches.
Q26.What is the difference between ==, ===, eql?, and equal?
==, ===, eql?, and equal?All four test equality but at different levels: == is general value equality, === is case/membership equality, eql? is strict value-and-type equality (used by hashes), and equal? is object identity.
==: Value equality, overridden per class. 1 == 1.0 is true.
===: "Case subsumption" used by case/when. For a class it means is_a?, for a Range it means inclusion, for a Regexp it means match.
eql?: Stricter value equality used together with hash for Hash keys. 1.eql?(1.0) is false (different types).
equal?: Identity: same object (same object_id). Should never be overridden.
Q27.What is the purpose of the # frozen_string_literal: true magic comment?
# frozen_string_literal: true magic comment?The # frozen_string_literal: true magic comment makes every string literal in that file frozen (immutable), which saves memory by reusing identical strings and prevents accidental mutation.
Where it goes: On the first line of the file (after a shebang if present); it is file-scoped.
Benefits:
Performance/memory: identical literals like "hello" can be deduplicated into one object instead of allocating per evaluation.
Safety: mutating a literal raises FrozenError, catching unintended shared-state bugs.
Gotcha: If you need a mutable string, build it explicitly with String.new, +"str", or dup.
Future direction: Frozen string literals are the planned default in future Ruby, so adding it now eases migration.
Q28.Hash vs. Array: which is faster for lookups and why?
Hash vs. Array: which is faster for lookups and why?For looking up a value by key, a Hash is much faster: it offers roughly O(1) access, while finding a value in an Array by scanning is O(n). Arrays only win when you access by known integer index, which is also O(1).
Hash lookup is O(1) average: It hashes the key to compute a bucket, so size barely affects lookup time.
Array search is O(n): include? or find must walk elements one by one until a match.
Array index access is O(1): arr[5] is instant because it jumps to a memory offset; this is positional, not value-based, lookup.
Rule of thumb: Need membership/keyed lookup, use a Hash (or Set). Need ordered, index-addressed data, use an Array.
Q29.What is the difference between dup and clone in Ruby?
dup and clone in Ruby?Both create a shallow copy of an object, but clone preserves more of the original's state: it copies the frozen status and the singleton class, while dup does not.
Frozen state: clone copies the frozen status (a clone of a frozen object is frozen); dup always returns an unfrozen copy.
Singleton class / singleton methods: clone copies the singleton class (so singleton methods carry over); dup does not.
Both are shallow: Instance variables are copied by reference, so nested objects are shared. Use a deep-copy technique (e.g. Marshal) if you need full independence.
Customization: Both call initialize_copy; clone accepts freeze: false to override frozen copying.
Q30.How does memoization work in Ruby, and what does the ||= operator do?
||= operator do?Memoization caches the result of an expensive computation so it runs once and later calls reuse the stored value. The ||= operator is the idiomatic tool: it assigns only if the variable is currently nil or false.
What ||= expands to: a ||= b means a || (a = b): evaluate the right side only when a is falsy.
How memoization uses it: @value ||= compute stores the result on first call and returns the cached ivar afterward.
The falsy trap: If the real result is nil or false, it recomputes every time. Use defined? or an explicit key check instead.
Multi-line bodies: Wrap in begin ... end or use @value ||= (a; b) to memoize a block of work.
Q31.What do the tap and then (yield_self) methods do and when would you use them?
tap and then (yield_self) methods do and when would you use them?Both come from Kernel and aid method chaining, but they differ in what they return: tap yields the object and returns the same object, while then (aliased yield_self) yields the object and returns the block's result.
tap: side effects, returns self: Great for inspecting/logging or mutating an object mid-chain without breaking the chain.
then / yield_self: transform, returns block value: Wraps a value in a transformation so you can pipe it through expressions left-to-right.
then and nil handling: Combined with the safe-navigation idiom, it helps express "do this only if present" pipelines cleanly.
Q32.Explain the difference between include, extend, and prepend.
include, extend, and prepend.All three mix a module into a class, but they differ in where the module lands in the ancestors chain and which methods it affects. include adds instance methods, prepend adds them ahead of the class, and extend adds them as singleton (class-level) methods.
include: Inserts the module just above the class in the ancestors chain, so its methods become instance methods (overridable by the class).
prepend: Inserts the module below the class, so its methods take precedence and can wrap the original via super (useful for decorators).
extend: Adds the module's methods to a single object's singleton class; on a class it makes them class methods.
Quick mental model: include = below the receiver in lookup, prepend = above it, extend = onto the object itself.
Q33.What does the super keyword do, and how does it differ when called with vs. without parentheses?
super keyword do, and how does it differ when called with vs. without parentheses?super calls the method of the same name in the next ancestor up the method resolution chain. The argument passing differs critically based on whether you use parentheses.
super (bare, no parentheses):
Forwards the same arguments the current method received, automatically.
Reflects the original arguments even if local variables were reassigned (it uses the method's parameters).
super() (with empty parentheses): Calls the parent method with no arguments explicitly.
super(a, b) (with explicit arguments): Passes exactly the arguments you specify.
Follows the ancestor chain: Resolves through ancestors, so it can hit an included module's method, not just a superclass.
Q34.How are modules used as namespaces in Ruby?
A module acts as a namespace by wrapping classes, methods, and constants so their fully qualified names are unique and don't collide with code elsewhere.
Grouping related code: Define classes/constants inside a module; access them via ModuleName::ClassName.
Avoiding name collisions: Two libraries can both define a Parser class if each is namespaced under its own module.
Nesting matters for constant lookup: Code written inside the module body sees sibling constants without the prefix.
Modules differ from classes here: A namespace module isn't instantiated; it just organizes. (Modules also serve as mixins, a separate role.)
Q35.What is the Comparable module and how does it work with the spaceship operator?
Comparable module and how does it work with the spaceship operator?Comparable is a mixin that gives a class a full set of comparison methods (<, <=, ==, >=, >, between?, clamp) for free, all derived from one method you define: the spaceship operator <=>.
You implement <=>: It returns -1 if self is less, 0 if equal, 1 if greater, and nil if not comparable.
Comparable derives the rest: Each comparison method calls <=> and interprets its result.
Bonus methods: You also get between? and clamp automatically.
Q36.What is the difference between a Proc and a Lambda?
Proc and a Lambda?Both are callable closures, but lambdas behave more like methods: they check argument counts strictly and their return exits only the lambda, whereas a proc is lenient and its return returns from the enclosing method.
Argument checking: Lambdas raise ArgumentError on wrong arity; procs ignore extras and fill missing args with nil.
Return behavior:
return in a lambda returns from the lambda itself.
return in a proc returns from the method that defined the proc (can raise LocalJumpError if that method already returned).
Both are Proc objects: lambda? distinguishes them; a lambda is just a Proc with stricter semantics.
Q37.Explain how yield works and how it differs from calling a block via &block.call.
yield works and how it differs from calling a block via &block.call.Both run a caller's block, but yield works with an implicit block and is faster, while capturing it as &block turns it into a named Proc object you can store, pass on, or call with block.call.
yield uses the implicit block:
No need to name it in the parameter list; lighter weight (no Proc object allocated).
Cannot be stored or forwarded to another method.
&block reifies the block as a Proc:
The & in the parameter converts the block into a Proc bound to block.
You can pass it onward (other_method(&block)), store it, or check it as a truthy value.
Practical guidance: Use yield for the common case; use &block when you must capture or forward the block.
Q38.What is a closure, and how does Ruby maintain the scope of variables inside a block?
block?A closure is a function-like object that captures the variables from the lexical scope where it was defined, keeping them alive even after that scope has returned. In Ruby, blocks, Procs, and lambdas are closures: they remember the binding (local variables, self) in effect when they were created.
Captures by reference, not by copy: A block sees and can mutate the same local variables that existed where it was written, so changes persist after the block runs.
Lexical (static) scoping: What a block can access is determined by where it is written, not where it is called.
The binding keeps variables alive: Ruby stores the surrounding environment in a Binding object attached to the closure, so the variables aren't garbage collected while the closure exists.
Blocks don't introduce a new scope for outer variables: Variables defined outside remain visible inside; a variable first assigned inside the block stays local to it.
Q39.When would you use a Proc instead of a block?
Proc instead of a block?Use a Proc (or lambda) when you need to treat the behavior as data: store it, reuse it, pass several callables, or capture a block now and run it later. A plain block is fine for the common case of passing one piece of code to a single method call.
You need more than one callable: A method can take only one block, so pass extra behavior as Proc arguments.
You want to reuse the same logic: Assign it to a variable once and pass it to many methods instead of repeating a block.
You need to store or defer execution: Keep a callback in an instance variable or collection and invoke it later with .call.
You want strict argument or return semantics: Reach for a lambda when you want arity checking and a local return.
Q40.What does the &:symbol idiom (symbol-to-proc) do, and how does it work under the hood?
&:symbol idiom (symbol-to-proc) do, and how does it work under the hood?The &:symbol idiom is shorthand for calling a single method on each element: map(&:upcase) is equivalent to map { |x| x.upcase }. It works because & coerces its operand into a block, and Symbol defines to_proc.
& triggers to_proc: When you write &object at a call site and the object isn't already a Proc, Ruby calls object.to_proc to build one.
Symbol#to_proc builds the proc: It returns a proc that takes a receiver and sends the symbol to it, roughly proc { |x, *args| x.public_send(:upcase, *args) }.
Then it becomes the block: That proc is passed to the method as its block, so map yields each element into it.
Limitation: It only calls a method with no arguments on each element; needing arguments or multiple operations requires a real block.
Q41.How do splat (*) and double splat (**) operators work in method arguments?
*) and double splat (**) operators work in method arguments?The splat * collects any number of positional arguments into an Array, and the double splat ** collects keyword arguments into a Hash. Both also work in reverse to expand a collection back into arguments at a call site.
* in a definition gathers positionals: def f(*args) makes args an Array of all positional arguments.
** in a definition gathers keywords: def f(**opts) makes opts a Hash of all keyword arguments.
They also splat at the call site: *array spreads an Array into positional args; **hash spreads a Hash into keyword args.
Forwarding everything: Combine *args, **kwargs, &block (or ... in Ruby 2.7+) to pass all arguments through to another method.
Q42.What is the difference between positional arguments and keyword arguments in Ruby methods?
Positional arguments are matched to parameters by their order, while keyword arguments are matched by name. Since Ruby 3.0 the two are fully separated: a Hash is no longer silently converted into keywords.
Positional: order matters: def f(a, b) requires callers to remember which value goes where; easy to mix up with many params.
Keyword: name matters: def f(a:, b:) lets callers pass f(b: 2, a: 1) in any order; self-documenting at the call site.
Defaults and required-ness: Both can have defaults; a keyword with no default (a:) is required by name.
Ruby 3 separation: Passing a Hash where keywords are expected now needs explicit **; positional and keyword args no longer auto-convert.
Q43.Explain the new Data class introduced in Ruby 3.2 and how it differs from Struct.
Data class introduced in Ruby 3.2 and how it differs from Struct.Data is a class introduced in Ruby 3.2 for defining simple, immutable value objects. It is conceptually a modern, locked-down Struct: instances cannot be mutated after creation, which makes it safer for representing plain values.
Immutable by design: There are no setters; to "change" a value you create a copy with with, e.g. point.with(y: 5).
Flexible construction: Instances can be built with positional or keyword arguments via new, with keywords reading clearly.
How it differs from Struct:
Struct is mutable (has setters); Data is frozen-like and read-only.
Struct supports index access (s[0]) and acts somewhat like an array; Data focuses on named attributes only.
Data expresses intent: "this is a value object", avoiding accidental mutation bugs.
Shared features: Both auto-generate ==, to_h, and accessors, and support a block to add custom methods.
Q44.When would you use a Struct or Data (Ruby 3.2+) instead of a standard Class?
Struct or Data (Ruby 3.2+) instead of a standard Class?Reach for Struct or Data when you mainly need a lightweight value object that bundles a few named attributes, getting accessors, ==, and a readable inspect for free. Use a full class when behavior and invariants dominate the design.
Struct: mutable value object:
Generates a class with accessors, ==, to_a, and keyword/positional init.
Attributes are writable, so use it when state may change after creation.
Data (Ruby 3.2+): immutable value object:
Built via Data.define(:x, :y); instances are frozen, with no setters.
Update with #with(x: 1) which returns a new copy; ideal for thread-safe, hashable values.
When a full class wins: Rich behavior, validation in initialization, complex invariants, or deep inheritance hierarchies.
Rule of thumb: Immutable data holder, Data; mutable simple bundle, Struct; behavior-heavy, plain class.
Q45.How does inject (reduce) work in Ruby, and how do you use it?
inject (reduce) work in Ruby, and how do you use it?inject (aliased reduce) folds a collection into a single value by repeatedly applying a binary operation, carrying an accumulator from one element to the next. Each iteration the block returns the new accumulator.
How the accumulation flows:
The block receives (memo, element) and its return value becomes the next memo.
The final memo is the return value.
Initial value handling:
With an argument, inject(0) starts memo at 0.
Without one, the first element becomes the initial memo and iteration starts at the second.
Symbol shorthand: inject(:+) applies an operator/method between elements; combine with a seed: inject(1, :*).
Gotcha: On an empty collection with no initial value it returns nil; pass a seed to be safe.
Q46.What is the difference between throw/catch and raise/rescue?
throw/catch and raise/rescue?raise/rescue is for error handling: signaling and recovering from exceptional conditions. throw/catch is a control-flow mechanism (a labeled, non-local jump) for breaking out of deeply nested loops or blocks, not for representing errors.
raise / rescue:
Raises an Exception object representing a real error condition.
Carries a class, message, and backtrace; meant for things that went wrong.
throw / catch:
Use a symbol (or any object) as a label to jump out: catch(:done) { ... throw :done, value }.
Optionally returns a value from the catch block; no error semantics, no backtrace cost.
When to choose which:
Genuine error or failure: raise.
Escaping nested iteration cleanly (rare): throw/catch.
Q47.How do you define a custom exception in Ruby, and why should you inherit from StandardError rather than Exception?
StandardError rather than Exception?Define a custom exception by subclassing StandardError (directly or via a domain base class). Inherit from StandardError rather than Exception because a bare rescue only catches StandardError and its descendants, so your error gets handled normally instead of slipping through.
How to define one:
Subclass and optionally add custom data or a default message.
A common pattern: one app-wide base error, with specific errors inheriting from it so callers can rescue broadly or narrowly.
Why not Exception:
Exception is the root and includes things you should not normally catch: SignalException, NoMemoryError, SystemExit.
Catching at that level can swallow Ctrl-C and interpreter shutdown, hiding serious problems.
Payoff: rescue => e (which defaults to StandardError) and rescue YourError both behave as expected.
Q48.What does the retry keyword do in exception handling?
retry keyword do in exception handling?retry is used inside a rescue clause to re-run the begin block from the top, giving the operation another attempt. It is the standard way to handle transient failures like network timeouts.
What it does:
Jumps back to the start of the protected block and executes it again from scratch.
Only valid within a rescue; using it elsewhere raises a SyntaxError.
Must have a stop condition:
Without a counter or limit it loops forever on a persistent error.
Track attempts and re-raise once exhausted; add a backoff (sleep) for external services.
Q49.What is 'Monkey Patching' (Open Classes) and why is it considered dangerous?
Monkey patching exploits Ruby's open classes: any class (even core ones like String or Array) can be reopened at runtime to add or redefine methods. It's powerful but dangerous because changes are global and invisible.
Open classes: Reopening a class with class String again merges new methods into the existing class instead of replacing it.
Why it's dangerous:
Global scope: a patch applied anywhere affects every object of that class across the whole program.
Silent collisions: two libraries patching the same method silently clobber each other; last load wins.
Hard to debug: the method's definition isn't where the class is declared, so source-jumping fails.
Brittle: a future stdlib/gem update can add a real method with the same name and break assumptions.
Use sparingly: prefer it for genuine cross-cutting fixes, and isolate patches in clearly named files.
Q50.How does define_method differ from using the def keyword?
define_method differ from using the def keyword?Both create methods, but define_method is a runtime metaprogramming call that takes a block (a closure), while def is a keyword that defines a method with its own fresh scope.
Scope handling:
def opens a new scope: local variables outside it are NOT visible inside.
define_method takes a block that closes over the surrounding scope, so it can capture local variables.
Dynamic naming: define_method accepts a method name as a symbol/string computed at runtime, perfect for generating many methods in a loop.
Performance: def is marginally faster (no closure), and is the default for normal code.
Q51.What is 'Monkey Patching,' and what are the safer alternatives provided by modern Ruby?
Monkey patching is reopening an existing class to add or change methods at runtime; because the change is global and silent, modern Ruby offers more contained alternatives: refinements, modules with prepend, and plain composition.
The core problem: Patches are program-wide and can collide with other gems or future library versions.
Safer alternatives:
Refinements: scope a change with refine and activate it only where using is called, so it's lexically contained.
Module + prepend: wrap an existing method and call super, instead of overwriting it, preserving the original.
Composition/decoration: wrap the object in your own class rather than altering the original at all.
Q52.How does the send method work and when would you use it?
send method work and when would you use it?send invokes a method by its name given as a symbol or string, passing along any arguments and block. It's the dynamic-dispatch tool: useful when the method to call is decided at runtime.
How it works:
obj.send(:method_name, arg1, arg2) is equivalent to obj.method_name(arg1, arg2).
It bypasses access control: send can call private and protected methods.
Use public_send to respect visibility.
When to use:
Dynamic dispatch: choosing a method from a variable, config value, or user-mapped action.
Metaprogramming and DSLs where method names are generated.
Q53.How does the send method work and what are its security implications?
send method work and what are its security implications?send calls a method named by a symbol/string at runtime, forwarding args and blocks. Its security concern is that it ignores method visibility (it can invoke private methods), so passing user input to it can expose internals.
How it works: obj.send(:name, *args, &block) performs dynamic dispatch equivalent to calling that method directly.
Security implications:
It bypasses private/protected access control.
Passing an untrusted string (e.g. obj.send(params[:method])) lets attackers invoke arbitrary methods like destroy or internal helpers.
Mitigations:
Prefer public_send, which respects visibility.
Whitelist allowed method names before dispatching on user input.
Q54.Explain the difference between send and public_send.
send and public_send.Both invoke a method dynamically by name, but send bypasses method visibility while public_send respects it, only calling public methods.
send ignores access control: Can call private and protected methods, which is powerful but breaks encapsulation if misused.
public_send enforces visibility: Raises NoMethodError when you try to call a private/protected method, matching normal dot-call rules.
Rule of thumb: Prefer public_send for dispatching on user/external input; reserve send for deliberate internal access to non-public methods (tests, metaprogramming).
Q55.What is the difference between Object, Kernel, and BasicObject?
Object, Kernel, and BasicObject?Q56.How does Ruby's Garbage Collector work?
Q57.What is a singleton class (or eigenclass) in Ruby, and when is it created?
Q58.How does Ruby's method lookup work? Walk me through the ancestors chain.
ancestors chain.Q59.How does constant lookup and scoping work in Ruby?
Q60.Why does Ruby favor mixins over multiple inheritance, and how do modules enable this?
Q61.What are Lazy Enumerators, and in what scenario would they prevent a memory overflow?
Q62.What are the tradeoffs of using method_missing for dynamic dispatch?
method_missing for dynamic dispatch?Q63.Compare instance_eval and class_eval. When would you use each?
instance_eval and class_eval. When would you use each?Q64.When would you use method_missing vs. define_method?
method_missing vs. define_method?Q65.What are Refinements and how do they solve the problems of Monkey Patching?
Q66.Explain the difference between Threads, Fibers, and Ractors. When is each appropriate?
Threads, Fibers, and Ractors. When is each appropriate?Q67.What is the Global VM Lock (GVL), and how does it impact multi-threaded Ruby applications?
Global VM Lock (GVL), and how does it impact multi-threaded Ruby applications?Q68.What is M:N scheduling in Ruby 3.3+, and how does it improve thread performance?
Q69.How does Ruby's Generational Garbage Collector work? Explain the Mark-and-Sweep process.
Generational Garbage Collector work? Explain the Mark-and-Sweep process.Q70.What is YJIT, and how does it optimize Ruby code at runtime?
YJIT, and how does it optimize Ruby code at runtime?Q71.How do you ensure thread safety in Ruby?
Q72.How does a Ractor achieve true parallelism in Ruby 3?
Ractor achieve true parallelism in Ruby 3?