Ruby Interview Questions
Introduction
Ruby is a dynamic, object-oriented programming language known for its elegant syntax and developer-friendly features. Created by Yukihiro Matsumoto (Matz) in the mid-1990s, Ruby emphasizes the principle of "least surprise" and prioritizes developer happiness.
As you prepare for Ruby-focused technical interviews, understanding core concepts and common questions will significantly boost your confidence. This guide covers essential Ruby interview questions, from basic syntax to more advanced concepts, with practical examples and clear explanations to help you succeed.
Basic Ruby Questions
1. What is Ruby and what are its key features?
Answer: Ruby is a dynamic, interpreted, object-oriented programming language created by Yukihiro Matsumoto in the mid-1990s.
Key features include:
- Pure Object-Oriented: Everything in Ruby is an object
- Dynamic Typing: Variable types are determined at runtime
- Flexible Syntax: Offers multiple ways to accomplish tasks
- Metaprogramming: Ability to write code that writes code
- Garbage Collection: Automatic memory management
- Rich Standard Library: Comes with extensive built-in functionality
- Open Source: Free to use and modify
2. How do you declare variables in Ruby?
Answer: In Ruby, variables are declared without specifying types:
# Local variables
name = "John"
age = 30
# Instance variables (belong to object instances)
@count = 0
# Class variables (shared across all instances of a class)
@@total_users = 0
# Global variables (accessible throughout the program)
$app_name = "MyRubyApp"
# Constants (should not be changed)
PI = 3.14159
3. What are the different data types in Ruby?
Answer: Ruby has several built-in data types:
# Numeric types
integer_number = 42
float_number = 3.14
complex_number = 1 + 2i
# Boolean
is_valid = true
is_expired = false
# String
name = "Ruby"
multiline_string = %Q{
This is a
multiline string
}
# Symbol (immutable, reusable identifiers)
status = :active
# Array
fruits = ["apple", "banana", "orange"]
# Hash (key-value pairs)
person = { "name" => "Alice", "age" => 25 }
person_symbols = { name: "Bob", age: 30 } # Alternate syntax
# Range
one_to_ten = 1..10 # Inclusive range
alphabet = 'a'...'z' # Exclusive range
# Nil (Ruby's version of null)
result = nil
4. Explain the difference between nil
and false
in Ruby.
Answer: Both nil
and false
are considered "falsy" in conditional expressions, but they are different:
nil
is a special value representing the absence of a value or a null referencefalse
is a boolean value representing logical falseness
# Testing nil and false in conditions
if nil
puts "This won't print"
else
puts "nil is falsy" # This will print
end
if false
puts "This won't print"
else
puts "false is falsy" # This will print
end
# nil and false are not equal
puts nil == false # Outputs: false
# Checking for nil
puts nil.nil? # Outputs: true
puts false.nil? # Outputs: false
# nil is an object of NilClass
puts nil.class # Outputs: NilClass
puts false.class # Outputs: FalseClass
Object-Oriented Programming in Ruby
5. How do you define a class in Ruby?
Answer: Classes in Ruby are defined using the class
keyword:
class Person
# Constructor method
def initialize(name, age)
@name = name # Instance variable
@age = age # Instance variable
end
# Instance method
def introduce
"Hi, I'm #{@name} and I'm #{@age} years old."
end
# Getter methods (could also use attr_reader)
def name
@name
end
def age
@age
end
# Setter methods (could also use attr_writer)
def name=(new_name)
@name = new_name
end
def age=(new_age)
@age = new_age
end
end
# Creating an instance
person = Person.new("Ruby", 25)
puts person.introduce # Outputs: Hi, I'm Ruby and I'm 25 years old.
puts person.name # Outputs: Ruby
# Modifying attributes
person.name = "Crystal"
puts person.name # Outputs: Crystal
6. Explain attribute accessors in Ruby.
Answer: Ruby provides convenient macros for creating getter and setter methods:
class Product
# attr_reader creates getter methods
attr_reader :id
# attr_writer creates setter methods
attr_writer :price
# attr_accessor creates both getter and setter methods
attr_accessor :name, :description
def initialize(id, name, description, price)
@id = id
@name = name
@description = description
@price = price
end
end
product = Product.new(1, "Laptop", "Powerful computer", 999.99)
# Using getter methods
puts product.id # Outputs: 1
puts product.name # Outputs: Laptop
# Using setter methods
product.name = "Desktop"
product.price = 1299.99
# Can't use product.price (no getter created with attr_writer)
7. What is inheritance in Ruby? How is it implemented?
Answer: Inheritance allows a class to inherit attributes and methods from another class:
class Animal
def initialize(name)
@name = name
end
def speak
"Some generic animal sound"
end
def name
@name
end
end
class Dog < Animal # Dog inherits from Animal
def speak
"Woof!" # Overriding the speak method
end
def fetch
"#{@name} fetches the ball!" # New method specific to Dog
end
end
# Using the parent class
animal = Animal.new("Generic Animal")
puts animal.name # Outputs: Generic Animal
puts animal.speak # Outputs: Some generic animal sound
# Using the child class
dog = Dog.new("Rex")
puts dog.name # Outputs: Rex (inherited method)
puts dog.speak # Outputs: Woof! (overridden method)
puts dog.fetch # Outputs: Rex fetches the ball! (new method)
8. How do you implement multiple inheritance in Ruby?
Answer: Ruby doesn't support true multiple inheritance, but it provides modules and mixins to achieve similar functionality:
module Swimmable
def swim
"#{self.class} is swimming!"
end
def dive
"#{self.class} is diving deep!"
end
end
module Flyable
def fly
"#{self.class} is flying high!"
end
end
class Bird
def speak
"Tweet!"
end
end
class Duck < Bird
include Swimmable # Mixin the Swimmable module
include Flyable # Mixin the Flyable module
end
duck = Duck.new
puts duck.speak # Outputs: Tweet! (from Bird class)
puts duck.swim # Outputs: Duck is swimming! (from Swimmable module)
puts duck.fly # Outputs: Duck is flying high! (from Flyable module)
Ruby Specifics
9. What are blocks, Procs, and lambdas in Ruby?
Answer: These are all ways to create and use closures in Ruby, but with subtle differences:
# Block (cannot be saved to a variable)
[1, 2, 3].each do |num|
puts num * 2
end
# Alternative block syntax with curly braces
[1, 2, 3].each { |num| puts num * 2 }
# Proc (more relaxed about parameters)
square = Proc.new { |x| x * x }
puts square.call(5) # Outputs: 25
puts square.call(5, 2) # Outputs: 25 (extra arguments ignored)
puts square.call # Outputs: nil (missing arguments become nil)
# Lambda (strict about parameters)
cube = lambda { |x| x * x * x }
puts cube.call(3) # Outputs: 27
# puts cube.call # Would raise ArgumentError
# Alternative lambda syntax (stabby lambda)
cube = ->(x) { x * x * x }
puts cube.call(3) # Outputs: 27
# Differences in return behavior
def proc_return_test
my_proc = Proc.new { return "Returned from proc!" }
my_proc.call
return "This line never executes"
end
def lambda_return_test
my_lambda = -> { return "Returned from lambda!" }
my_lambda.call
return "This line executes!"
end
puts proc_return_test # Outputs: Returned from proc!
puts lambda_return_test # Outputs: This line executes!
10. Explain Ruby's method visibility: public, private, and protected.
Answer: Ruby offers three levels of method visibility:
class BankAccount
def initialize(name, balance)
@name = name
@balance = balance
end
# Public methods (default) - accessible from anywhere
def account_summary
"Account: #{@name}, Balance: $#{@balance}"
end
# Protected methods - accessible by instances of the same class or subclasses
protected
def balance
@balance
end
def compare_balance(other_account)
if self.balance > other_account.balance
"#{@name}'s account has more funds"
else
"#{other_account.instance_variable_get(:@name)}'s account has more funds"
end
end
# Private methods - only accessible within the class itself
private
def generate_account_number
"AC-#{rand(100000..999999)}"
end
end
# Usage example
account1 = BankAccount.new("Alice", 5000)
account2 = BankAccount.new("Bob", 3000)
puts account1.account_summary # Accessible (public)
# puts account1.balance # Error! Protected method
# puts account1.generate_account_number # Error! Private method
# Defining a method to demonstrate protected method usage
class BankAccount
def compare_with(other_account)
compare_balance(other_account) # Can call protected method
end
end
puts account1.compare_with(account2) # Works through public interface
Advanced Ruby Concepts
11. What is metaprogramming in Ruby?
Answer: Metaprogramming is writing code that writes or manipulates code at runtime. Ruby has powerful metaprogramming capabilities:
# Example 1: define_method to dynamically create methods
class Product
ATTRIBUTES = [:name, :price, :category]
attr_accessor *ATTRIBUTES
def initialize(attributes = {})
attributes.each do |key, value|
send("#{key}=", value) if ATTRIBUTES.include?(key)
end
end
# Dynamically define methods for each attribute
ATTRIBUTES.each do |attribute|
define_method("#{attribute}_info") do
value = send(attribute)
"The #{attribute} is #{value}"
end
end
end
laptop = Product.new(name: "MacBook", price: 1299, category: "Electronics")
puts laptop.name_info # Outputs: The name is MacBook
puts laptop.price_info # Outputs: The price is 1299
# Example 2: method_missing to handle nonexistent methods
class DataRecord
def initialize(data = {})
@data = data
end
def method_missing(name, *args)
if name.to_s =~ /^find_by_(.+)$/
key = $1.to_sym
value = args.first
@data.select { |record| record[key] == value }
else
super
end
end
def respond_to_missing?(name, include_private = false)
name.to_s.start_with?('find_by_') || super
end
end
records = DataRecord.new([
{id: 1, name: "Alice", role: "Developer"},
{id: 2, name: "Bob", role: "Designer"},
{id: 3, name: "Charlie", role: "Developer"}
])
puts records.find_by_role("Developer").inspect
# Outputs: [{:id=>1, :name=>"Alice", :role=>"Developer"},
# {:id=>3, :name=>"Charlie", :role=>"Developer"}]
12. What are Ruby Gems? How do you create one?
Answer: Ruby Gems are packaged Ruby applications or libraries that can be distributed and installed easily.
Creating a simple gem involves:
# File structure for a gem named "hello_world"
# hello_world/
# ├── lib/
# │ └── hello_world.rb
# ├── test/
# │ └── test_hello_world.rb
# ├── README.md
# ├── LICENSE.txt
# └── hello_world.gemspec
# Content of lib/hello_world.rb
module HelloWorld
class Greeter
def self.hello(name = "World")
"Hello, #{name}!"
end
end
end
# Content of hello_world.gemspec
Gem::Specification.new do |s|
s.name = 'hello_world'
s.version = '0.0.1'
s.summary = "Hello World!"
s.description = "A simple hello world gem"
s.authors = ["Your Name"]
s.email = '[email protected]'
s.files = ["lib/hello_world.rb"]
s.homepage = 'https://rubygems.org/gems/hello_world'
s.license = 'MIT'
end
# Build and install the gem
# $ gem build hello_world.gemspec
# $ gem install ./hello_world-0.0.1.gem
# Using the gem
# require 'hello_world'
# puts HelloWorld::Greeter.hello("Ruby") # Outputs: Hello, Ruby!
13. Explain Ruby's garbage collection.
Answer: Ruby uses automatic memory management through garbage collection:
- Ruby's garbage collector identifies and frees memory occupied by objects that are no longer referenced
- It uses a mark-and-sweep algorithm (with incremental and generational improvements in newer versions)
- Developers rarely need to manually manage memory in Ruby
- GC can be triggered manually with
GC.start
but is generally handled automatically
# Example showing garbage collection
def create_many_objects
1000.times do |i|
# Create temporary objects that will be eligible for garbage collection
"String #{i}" * 1000
end
end
# Check memory usage before
memory_before = `ps -o rss= -p #{Process.pid}`.to_i
puts "Memory before: #{memory_before} KB"
# Create objects
create_many_objects
# Force garbage collection
GC.start
# Check memory after garbage collection
memory_after = `ps -o rss= -p #{Process.pid}`.to_i
puts "Memory after: #{memory_after} KB"
puts "Difference: #{memory_after - memory_before} KB"
Ruby on Rails Questions
14. What is Ruby on Rails?
Answer: Ruby on Rails (often just "Rails") is a popular web application framework written in Ruby that follows the Model-View-Controller (MVC) architectural pattern. It emphasizes convention over configuration and includes tools that make common development tasks easier.
Key components and principles include:
- MVC Architecture: Separates business logic, data, and user interface concerns
- Convention over Configuration: Sensible defaults reduce the need for explicit configuration
- DRY (Don't Repeat Yourself): Encourages code reuse
- Active Record: ORM (Object-Relational Mapping) for database interactions
- Action View: Template system for generating HTML
- Action Controller: Handles incoming web requests
- Active Support: Utility classes and extensions to Ruby
- Bundled Tools: Includes testing frameworks, database migrations, and scaffolding
15. What is the difference between include
and extend
in Ruby?
Answer: Both include
and extend
incorporate modules into classes, but in different ways:
module Greetings
def say_hello
"Hello from #{self}!"
end
end
# Using include (adds methods as instance methods)
class Person
include Greetings
end
# Using extend (adds methods as class methods)
class Company
extend Greetings
end
# Instance method demonstration
person = Person.new
puts person.say_hello # Outputs: Hello from #<Person:0x...>!
# puts Person.say_hello # Error! Not a class method
# Class method demonstration
puts Company.say_hello # Outputs: Hello from Company!
# company = Company.new
# puts company.say_hello # Error! Not an instance method
# You can use both together
class Team
include Greetings # For instances
extend Greetings # For the class itself
end
team = Team.new
puts Team.say_hello # Outputs: Hello from Team!
puts team.say_hello # Outputs: Hello from #<Team:0x...>!
Real-World Problem Solving
16. How would you remove duplicates from an array in Ruby?
Answer: Ruby offers several ways to remove duplicates:
# Using the built-in uniq method
array = [1, 2, 3, 2, 1, 4, 5, 4]
unique_array = array.uniq
puts unique_array.inspect # Outputs: [1, 2, 3, 4, 5]
# In-place modification
array = [1, 2, 3, 2, 1, 4, 5, 4]
array.uniq!
puts array.inspect # Outputs: [1, 2, 3, 4, 5]
# Using a Set
require 'set'
array = [1, 2, 3, 2, 1, 4, 5, 4]
unique_array = array.to_set.to_a
puts unique_array.inspect # Outputs: [1, 2, 3, 4, 5]
# Custom implementation (for demonstration)
def remove_duplicates(array)
result = []
seen = {}
array.each do |item|
unless seen[item]
result << item
seen[item] = true
end
end
result
end
array = [1, 2, 3, 2, 1, 4, 5, 4]
puts remove_duplicates(array).inspect # Outputs: [1, 2, 3, 4, 5]
17. Write a method to check if a string is a palindrome.
Answer: A palindrome reads the same backward as forward:
# Simple palindrome checker
def palindrome?(string)
# Normalize the string: lowercase and remove non-alphanumeric characters
normalized = string.downcase.gsub(/[^a-z0-9]/, '')
# Compare with its reverse
normalized == normalized.reverse
end
# Test cases
puts palindrome?("racecar") # Outputs: true
puts palindrome?("A man, a plan, a canal: Panama") # Outputs: true
puts palindrome?("hello") # Outputs: false
# Alternative one-liner
def palindrome_oneliner?(string)
s = string.downcase.gsub(/[^a-z0-9]/, '')
s == s.reverse
end
18. How would you implement a simple API client in Ruby?
Answer: Here's a basic implementation using Ruby's standard libraries:
require 'net/http'
require 'uri'
require 'json'
class ApiClient
def initialize(base_url, options = {})
@base_url = base_url
@headers = options[:headers] || {}
@timeout = options[:timeout] || 30
end
def get(endpoint, params = {})
uri = build_uri(endpoint, params)
request = Net::HTTP::Get.new(uri)
add_headers(request)
perform_request(uri, request)
end
def post(endpoint, body = {})
uri = build_uri(endpoint)
request = Net::HTTP::Post.new(uri)
request.body = body.to_json
request.content_type = 'application/json'
add_headers(request)
perform_request(uri, request)
end
private
def build_uri(endpoint, params = {})
uri = URI.join(@base_url, endpoint)
if params && !params.empty?
uri.query = URI.encode_www_form(params)
end
uri
end
def add_headers(request)
@headers.each do |key, value|
request[key] = value
end
end
def perform_request(uri, request)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
http.open_timeout = @timeout
http.read_timeout = @timeout
response = http.request(request)
if response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body) rescue response.body
else
{ error: response.message, code: response.code, body: response.body }
end
end
end
# Example usage:
weather_client = ApiClient.new('https://api.weather.example',
headers: { 'API-Key' => 'your-api-key' })
# GET request
forecast = weather_client.get('/forecast', { city: 'New York', days: 3 })
puts forecast.inspect
# POST request
new_user = weather_client.post('/users', { name: 'John', email: '[email protected]' })
puts new_user.inspect
Performance and Optimization
19. How would you profile a Ruby application?
Answer: Ruby offers several tools for profiling applications:
- Using built-in Benchmark module:
require 'benchmark'
def slow_method
sleep(0.1)
"result"
end
def fast_method
"result"
end
# Simple benchmarking
Benchmark.bm do |x|
x.report("slow:") { 10.times { slow_method } }
x.report("fast:") { 10.times { fast_method } }
end
# Memory profiling with memory_profiler gem
# require 'memory_profiler'
# report = MemoryProfiler.report do
# 100.times { slow_method }
# end
# report.pretty_print
- Using Ruby's built-in profiler:
require 'profile'
def complex_calculation
result = 0
1000.times do |i|
result += i ** 2
end
result
end
# This will run with profiling enabled
complex_calculation
- Using external tools:
- ruby-prof gem
- stackprof gem
- rack-mini-profiler for web applications
20. Explain the difference between Symbols and Strings in Ruby.
Answer: Symbols and Strings are both text representations but have different characteristics:
# Memory efficiency
require 'benchmark'
str_time = Benchmark.realtime do
100_000.times do |i|
"string_#{i}".object_id
end
end
sym_time = Benchmark.realtime do
100_000.times do |i|
:"symbol_#{i}".object_id
end
end
puts "String time: #{str_time}"
puts "Symbol time: #{sym_time}"
# Immutability
string = "hello"
symbol = :hello
string.upcase!
puts string # Outputs: HELLO (strings are mutable)
# Symbols are immutable
# symbol.upcase! # Would raise NoMethodError
# Object Identity
string1 = "hello"
string2 = "hello"
puts string1.object_id == string2.object_id # Outputs: false (different objects)
symbol1 = :hello
symbol2 = :hello
puts symbol1.object_id == symbol2.object_id # Outputs: true (same object)
# Memory Usage
require 'objspace'
strings = []
symbols = []
1000.times do
strings << "hello"
symbols << :hello
end
puts "1000 strings size: #{ObjectSpace.memsize_of(strings)} bytes"
puts "1000 symbols size: #{ObjectSpace.memsize_of(symbols)} bytes"
Key differences:
- Symbols are immutable, strings are mutable
- Identical symbols are a single object in memory, identical strings are different objects
- Symbols are more memory-efficient when reused
- Strings are better for text manipulation
- Symbols are commonly used for hash keys, method names, and identifiers
Summary
Ruby is a versatile, elegant programming language that emphasizes developer productivity and happiness. In this guide, we've covered essential Ruby interview questions ranging from basic syntax to advanced concepts like metaprogramming and performance optimization.
Key takeaways:
- Ruby is a pure object-oriented language where everything is an object
- Ruby provides powerful features like blocks, procs, lambdas, and metaprogramming
- Understanding Ruby's approach to OOP, including inheritance and modules, is crucial
- Ruby offers elegant solutions for common programming tasks
- Performance considerations and optimization techniques are important for production applications
Additional Resources
To continue learning Ruby for interview preparation:
- Official Ruby Documentation: ruby-lang.org
- "Programming Ruby" (the Pickaxe book) by Dave Thomas
- "Eloquent Ruby" by Russ Olsen
- "Practical Object-Oriented Design in Ruby" by Sandi Metz
- Ruby Koans: rubykoans.com
- Exercism Ruby Track: exercism.io/tracks/ruby
- Ruby Weekly Newsletter: rubyweekly.com
Practice Exercises
To reinforce your Ruby knowledge:
- Implement a simple class hierarchy representing different shapes with methods to calculate area and perimeter
- Create a module for sorting algorithms and benchmark their performance
- Build a simple web scraper using Ruby's standard libraries
- Implement a basic version of the
map
andselect
methods - Create a DSL (Domain-Specific Language) for describing recipes or other structured data
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)