Ruby Interview Questions
Real Ruby interview questions and answers on blocks, procs, lambdas, duck typing and modules.
A curated set of Ruby interview questions, ordered roughly from fundamentals to more advanced — the kind you'll actually be asked in real screens and on-sites.
Language fundamentals
Q: What does it mean that "everything is an object" in Ruby?
Every value you can work with in Ruby — integers, strings, nil, even classes themselves — is an instance of some class, and you can call methods on any of them (5.times, nil.to_s, Integer.ancestors). There are no separate "primitive" types with special-cased behavior the way there are in Java, which keeps Ruby's mental model small and consistent.
Q: What values are falsy in Ruby, and why does this trip up newcomers?
Only nil and false are falsy — everything else, including 0 and "" (empty string), is truthy. Developers coming from JavaScript or Python (where 0 and empty strings/arrays are falsy) often write a conditional expecting if count to be false when count is 0, and are surprised it evaluates as true in Ruby.
Blocks, procs and lambdas
Q: What's the difference between a Proc and a Lambda?
A Lambda checks its argument count strictly (like a normal method call) and its return exits only the lambda itself. A Proc is lenient about argument count (extra arguments are ignored, missing ones become nil) and a return inside it exits the enclosing method, not just the Proc — a subtle but important difference when a Proc is used as a stored callback.
Q: Why would a method accept a block instead of a regular array/callback argument?
Blocks give a clean, low-ceremony syntax for a method to invoke caller-supplied code without the caller needing to instantiate an object first — it's how Ruby's whole iterator style (each, map, select) reads naturally. It also lets a method control the iteration itself (e.g., retry logic, resource cleanup) while still handing control back to caller-provided code at the right moment.
OOP design
Q: How does Ruby handle the lack of multiple inheritance?
Through modules — a module groups reusable methods that can be mixed into any number of classes via include, inserting the module into the class's method lookup chain. This gives you most of the benefit of multiple inheritance (sharing behavior across otherwise-unrelated classes) without the diamond-inheritance ambiguity problems that come with true multiple class inheritance.
Q: What is duck typing and why does the Ruby community favor it over explicit type/interface checks? Duck typing means code cares only whether an object responds to the methods it needs, not what class the object actually is — "if it walks like a duck and quacks like a duck, treat it as a duck." The community favors it because it keeps code flexible and decoupled from specific class hierarchies; a method written this way works with any object that implements the right behavior, known ahead of time or not.
Practical / nil handling
Q: What are some idiomatic ways to safely handle a value that might be nil?
The safe navigation operator (user&.profile&.bio) short-circuits to nil instead of raising NoMethodError if any link in the chain is nil. .nil? explicitly checks for nil. Combinators like value || default or value.to_s (which returns "" for nil) provide sensible fallbacks. In general, idiomatic Ruby prefers handling the nil case explicitly and locally rather than letting it propagate into a confusing NoMethodError deep in a call chain.
Exceptions and tooling in practice
Q: What's the difference between rescue, else, and ensure in a Ruby begin/rescue block?
rescue runs only when a matching exception is raised. else runs only when the begin block completes with no exception at all. ensure always runs, whether an exception occurred or not — the right place for cleanup like closing a file or connection, since it fires regardless of how the block actually exits.
Q: In RSpec, what's the difference between describe and context, and how do you test that a method raises an error?
describe and context are functionally identical blocks for grouping examples; convention uses describe for the subject under test and context for a particular situation, purely to make output read more like a specification. To test a raised error, wrap the call in a block: expect { obj.risky_call }.to raise_error(SomeError, "message") — the block form is required so RSpec can invoke the code itself and catch the exception, rather than it being raised before the assertion ever runs.
Q: What does the pessimistic version constraint ~> mean in a Gemfile, and why is it the conventional default?
~> 7.1.0 allows only patch-level updates (>= 7.1.0, < 7.2.0); ~> 7.1 allows minor-level updates too (>= 7.1, < 8.0). It's the conventional default because it lets routine bug fixes flow in automatically via bundle update while still refusing a major version bump, which semantic versioning reserves for breaking changes.