Ruby Interview Questions and Answers
Blocks, modules, metaprogramming and Ruby on Rails basics.
Whether you are preparing for entry-level Ruby interview questions for freshers or senior software engineer interview questions addressing concurrency, scalability, and system architecture, this track provides peer-reviewed model answers with syntax walkthroughs, edge cases, and practical interview tips.
1 What is the difference between a symbol and a string? Easy
A Symbol is an immutable, interned identifier such as :name. A String is a mutable sequence of characters such as "name".
Symbols are stored once, so repeated use is memory-efficient and comparison is by identity. That makes them ideal as hash keys, state names and method names. Strings are for text you display or manipulate, and each literal can create a new object.
:name.object_id == :name.object_id # true, same object
"name".object_id == "name".object_id # false, different objects
h = { name: "Ada" } # symbol key
Since Ruby 2.2 symbols are garbage-collected, but converting untrusted input with to_sym can still be a memory concern, so avoid it on unvalidated user data. Convert with to_sym and to_s. Prefer frozen string literals reduce allocations for repeated strings. Use symbols for identifiers, strings for data.
2 What do attr_reader, attr_writer and attr_accessor do? Easy
Ruby encapsulates instance variables such as @name, which are private by default. attr_reader, attr_writer and attr_accessor are class macros that define getter and setter methods.
class User
attr_reader :id # def id; @id; end
attr_writer :email # def email=(value); @email = value; end
attr_accessor :name # both getter and setter
end
u = User.new
u.name = "Ada"
puts u.name
attr_accessor :a, :b defines several at once. These are ordinary methods, so they can be overridden or made private with private :name=, restricting external writes.
attr_* accepts an optional boolean second argument to skip instance-variable initialisation warnings. Using accessors internally rather than @name directly lets subclasses override behaviour and keeps the interface consistent. For deeply immutable objects use attr_reader plus freeze.
3 How does truthiness work in Ruby, and what is nil? Medium
In Ruby only nil and false are falsy. Everything else, including 0, "", empty arrays and empty hashes, is truthy. This differs from many languages and is a common source of bugs.
puts "truthy" if 0 # prints, 0 is truthy
puts "yes" if [] # prints, [] is truthy
value = nil
value ||= 10 # assign if nil or false
puts value.inspect # 10
nil is the single instance of NilClass and responds to nil?; Object#nil? returns false for everything else. Test emptiness explicitly with empty? or, in Rails, present?. ||= assigns when the variable is nil or false.
Calling a method on nil raises NoMethodError, but safe navigation short-circuits: user&.profile&.name. nil.to_s is an empty string and nil.to_a is an empty array, which is why Array(nil) and String(nil) are useful conversions.
4 What is the difference between a block, a proc and a lambda? Medium
All three are chunks of code, but they differ in argument checking and how return behaves.
- A
blockis passed to a method withdo...endor braces and is not a standalone object unless converted with&. - A
procdoes not check argument count: missing arguments become nil and extras are dropped. Areturnreturns from the enclosing method and can raiseLocalJumpErrorif that method already returned. - A
lambdachecks arity strictly, andreturnreturns only from the lambda.
def run(callable)
callable.call
:after
end
l = -> { return :from_lambda }
puts run(l) # :after
pr = proc { return :from_proc }
# run(pr) raises LocalJumpError
Lambdas are usually preferred because they behave like methods. Check with lambda?. Blocks are typically yielded once and converted to a Proc through the &block parameter.
5 What are modules and mixins used for? Medium
A module is a collection of methods and constants. It cannot be instantiated, but it can be mixed in to add behaviour, which is Ruby's alternative to multiple inheritance.
includeadds the module's methods as instance methods of the class.extendadds them to the object or class itself as singleton methods.prependinserts the module before the class in the lookup chain, so it can override methods and callsuper.
module Greetable
def greet = "Hello, #{name}"
end
class User
include Greetable
attr_accessor :name
end
Modules are also used for namespacing, as in Admin::User. In Rails, ActiveSupport::Concern manages mixin dependencies and class methods.
Because lookup follows the ancestor chain, Module#ancestors is the key to predicting which method wins. Keep mixins cohesive and relatively state-light to avoid surprising interactions.
6 How does metaprogramming and method_missing work in Ruby? Medium
Ruby programs can define methods and classes at runtime. Common tools include define_method, send, respond_to?, instance_variable_get, class_eval and hooks such as included, inherited and method_added.
method_missing intercepts calls to undefined methods:
class Recorder
def method_missing(name, *args)
if name.to_s.start_with?("record_")
key = name.to_s.sub("record_", "")
puts "recording #{key}: #{args}"
else
super
end
end
def respond_to_missing?(name, include_private = false)
name.to_s.start_with?("record_") || super
end
end
Always call super for names you do not handle, and define respond_to_missing? so respond_to? stays accurate. Metaprogramming enables expressive DSLs such as Rails routes and RSpec, but it hurts readability, tooling and performance. Prefer define_method for known methods.
7 How does Rails follow MVC and convention over configuration? Medium
Rails is an MVC framework: a request hits the router, maps to a controller action, the controller works with models, and renders a view. Models use ActiveRecord for the database, controllers orchestrate and views present.
Convention over configuration replaces boilerplate with defaults. The User model maps to the users table with an id primary key and pluralised naming. UsersController lives in app/controllers, views in app/views/users/, and RESTful routes come from resources :users.
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
end
Other essentials are strong parameters, so you write params.require(:user).permit(:name), database migrations, the asset pipeline or importmap, and per-environment configuration. The trade-off is hidden magic and a learning curve, but productivity and consistency for CRUD applications are high.
8 How does self work, and what is the difference between class and instance methods? Medium
self is the current object. Inside an instance method it is the receiver, inside a class body it is the class, and inside a class method it is the class as well.
class Widget
def initialize(name)
@name = name
end
def self.default
new("default") # class method
end
class << self
def count
@count ||= 0 # class-level instance variable
end
end
end
Widget.default
Defining def self.foo adds a singleton method to the class object, so it is called on the class rather than an instance. Instance variables differ by context: @name in an instance method belongs to the instance, while @count in a class method belongs to the class object.
self is also required to call an attribute writer, as in self.name = value, because a bare name = value would create a local variable instead.
9 How does Ruby resolve a method call through the ancestor chain? Hard
Ruby resolves a call by walking the receiver's singleton class, then the class, then included modules and superclasses, in order. Module#ancestors prints the chain.
module A; def hi = "A"; end
module B; def hi = "B"; end
class C
prepend A
include B
end
C.ancestors # [A, C, B, Object, Kernel, BasicObject]
C.new.hi # "A"
Key rules: prepend places a module before the class, while include places it after. Among included modules the last one included comes first. Singleton methods beat instance methods, and super continues the search from the next entry in the chain.
method_missing is the last resort before NoMethodError, and respond_to? should be kept consistent with it. Refinements can scope overrides lexically. This chain is why prepended modules and concerns are powerful, and why ordering matters when several mixins define the same method.
10 What is the Global VM Lock and how does it affect concurrency? Hard
MRI, the standard Ruby implementation, has a Global VM Lock: only one thread executes Ruby bytecode at a time, so threads do not provide CPU parallelism for pure Ruby code. They do improve concurrency for I/O because the lock is released around blocking operations such as file, socket and database calls.
threads = urls.map do |url|
Thread.new { fetch(url) } # overlaps I/O, not CPU
end
results = threads.map(&:value)
For CPU-bound work you need process-level parallelism: fork, the Process API, multiple server workers under Puma or Unicorn, or a different implementation such as JRuby or TruffleRuby, which have no GVL.
Since Ruby 3.0 the lock is more granular, and Ractors offer actor-style parallelism in limited cases. Concurrency primitives include Mutex, ConditionVariable and Queue. Choose processes for parallelism and threads for overlapping I/O.
Frequently Asked Questions About Ruby Interviews
What do hiring managers evaluate in Ruby technical rounds?
Technical interviewers look for foundational fluency, idiomatic syntax, clarity when communicating complex logic, and awareness of performance trade-offs (e.g. memory footprint, render performance, and network latency) in production environments.
What are the best interview tips for practicing Ruby questions?
Use active recall: summarize each answer in your own words before revealing the model solution. Focus on explaining why a certain approach is chosen rather than just memorizing code syntax.