Top 20 Ruby Technical Questions in Coding Interviews
Introduction
Ruby is an object-oriented programming language that is known for its simplicity, readability, and elegance. It has been used extensively in web development, especially with the Ruby on Rails framework. If you're preparing for a coding interview, it's essential to familiarize yourself with the common Ruby technical questions that may come up. In this blog post, we'll go over 20 common Ruby technical questions, and we'll provide in-depth explanations and code examples to help you understand and prepare for your interview.
1. What is the difference between a class and a module in Ruby?
Interview Question: Explain the difference between a class and a module in Ruby.
Answer: In Ruby, a class is an object-oriented construct that allows you to create instances (objects) of that class. A class can inherit from a parent class and can include methods (functions) and instance variables (attributes).
A module, on the other hand, is a collection of methods and constants that can be mixed into a class using the include
or extend
methods. Modules are useful for sharing behavior between classes without using inheritance. Unlike classes, modules cannot be instantiated or have instance variables.
Here's an example of a class and a module in Ruby:
class Animal
def speak
puts "I can speak!"
end
end
module Swimmer
def swim
puts "I can swim!"
end
end
class Fish < Animal
include Swimmer
end
fish = Fish.new
fish.speak # Output: "I can speak!"
fish.swim # Output: "I can swim!"
In this example, the Animal
class has a speak
method. The Swimmer
module has a swim
method. The Fish
class inherits from the Animal
class and includes the Swimmer
module, so instances of the Fish
class can use both the speak
and swim
methods.
2. What are Ruby symbols?
Interview Question: Explain what Ruby symbols are and provide an example of their usage.
Answer: Symbols in Ruby are lightweight, immutable strings that serve as identifiers or keys. They are denoted by a colon (:
) followed by a string, like :symbol_name
. The main advantage of using symbols over strings is that symbols have the same object ID for a given name, which makes them more efficient for memory usage and comparison.
Here's an example comparing symbols and strings:
string1 = "hello"
string2 = "hello"
puts string1 == string2 # Output: true
puts string1.object_id == string2.object_id # Output: false
symbol1 = :hello
symbol2 = :hello
puts symbol1 == symbol2 # Output: true
puts symbol1.object_id == symbol2.object_id # Output: true
In this example, we create two identical strings and two identical symbols. When comparing the strings and symbols for equality, both comparisons return true
. However, when comparing their object IDs, the strings have different object IDs, while the symbols have the same object ID.
Symbols are commonly used as keys in hashes, due to their efficiency and readability:
person = {
name: "John Doe",
age: 30,
occupation: "Software Developer"
}
puts person[:name] # Output: "John Doe"
In this example, we use symbols as keys in a hash representing a person's information.
3. Explain method chaining in Ruby and provide an example.
Interview Question: What is method chaining in Ruby, and how can it be used to simplify code?
Answer: Method chaining in Ruby is the process of calling multiple methods on a single object, one after another. This is possible because most methods in Ruby return an object, allowing you to call another method on the returned object immediately. Method chaining can make your code more concise and easier to read.
Here's an example of method chaining in Ruby:
class Calculator
def initialize(value)
@value = value
end
def add(number)
@value += number
self
end
def subtract(number)
@value -= number
self
end
def multiply(number)
@value *= number
self
end
def result
@value
end
end
calc = Calculator.new(5)
result = calc.add(3).subtract(1).multiply(2).result
puts result # Output: 14
In this example, we create a Calculator
class with methods for addition, subtraction, and multiplication. Each of these methods returns self
, allowing us to chain multiple method calls together. The result
method then returns the final value after the chain of calculations.
4. What is the difference between instance variables and class variables in Ruby?
Interview Question: Explain the difference between instance variables and class variables in Ruby, and provide a code example to demonstrate their usage.
Answer: In Ruby, instance variables are variables that belong to a specific instance (object) of a class. They are denoted by an at symbol (@
) followed by the variable name, like @instance_variable
. Instance variables are used to store data that is unique to each object of a class.
Class variables, on the other hand, are variables that belong to the class itself, not to any specific instance. They are denoted by two at symbols (@@
) followed by the variable name, like @@class_variable
. Class variables are used to store data that is shared by all instances of a class.
Here's an example demonstrating the usage of instance variables and class variables:
class BankAccount
@@total_accounts = 0
def initialize(balance)
@balance = balance
@@total_accounts += 1
end
def deposit(amount)
@balance += amount
end
def withdraw(amount)
@balance -= amount
end
def self.total_accounts
@@total_accounts
end
end
account1 = BankAccount.new(1000)
account2 = BankAccount.new(2000)
account1.deposit(500)
account2.withdraw(300)
puts BankAccount.total_accounts # Output: 2
In this example, the BankAccount
class has an instance variable @balance
and a class variable @@total_accounts
. The @balance
variable is unique to each instance, while the @@total_accounts
variable is shared by all instances, keeping track of the total number of accounts created.
5. What are blocks, procs, and lambdas in Ruby?
Interview Question: Explain the difference between blocks, procs, and lambdas in Ruby, and provide a code example for each.
Answer: Blocks, procs, and lambdas in Ruby are all ways to define anonymous functions (functions without a name) that can be passed as arguments to other functions or methods. However, they have some differences in their syntax and behavior.
- Blocks: A block is a piece of code enclosed between
do...end
or curly braces ({}
), which can be passed to a method as an argument. Blocks are not objects and can only appear once in a method call. They are typically used with methods likeeach
,map
, andselect
.
Here's an example of a block:
ruby numbers = [1, 2, 3, 4, 5] squares = numbers.map { |number| number * number } puts squares.inspect # Output: [1, 4, 9, 16, 25]
- Procs: A proc (short for "procedure") is an object that encapsulates a block of code and can be stored in a variable, passed as an argument, or returned from a method. Procs do not check the number of arguments passed to them, and they return from the calling method when they encounter a
return
statement.
Here's an example of a proc:
ruby multiply = Proc.new { |a, b| a * b } result = multiply.call(3, 4) puts result # Output: 12
- Lambdas: A lambda is similar to a proc, but with two key differences: lambdas check the number of arguments passed to them, and they only return from the lambda itself when they encounter a
return
statement, not from the calling method.
Here's an example of a lambda:
ruby add = lambda { |a, b| a + b } result = add.call(3, 4) puts result # Output: 7
6. What is the difference between include
and extend
in Ruby?
Interview Question: Explain the difference between include
and extend
in Ruby, and provide a code example to demonstrate their usage.
Answer: Both include
and extend
in Ruby are used to mix in a module's methods into a class. However, they have different behaviors:
include
: When a module is included in a class, the module's methods become instance methods of the class. This means that instances (objects) of the class can call the module's methods directly.
Here's an example of include
:
```ruby module Greeting def say_hello puts "Hello!" end end
class Person include Greeting end
john = Person.new john.say_hello # Output: "Hello!" ```
extend
: When a module is extended by a class, the module's methods become class methods of the class. This means that the class itself can call the module's methods, not its instances.
Here's an example of extend
:
```ruby module MathOperations def add(a, b) a + b end end
class Calculator extend MathOperations end
result = Calculator.add(3, 4) puts result # Output: 7 ```
In this example, we use include
to make the Greeting
module's methods available as instance methods in the Person
class. We use extend
to make the MathOperations
module's methods available as class methods in the Calculator
class.
7. What is metaprogramming in Ruby?
Interview Question: Explain what metaprogramming is in Ruby, and provide an example of its usage.
Answer: Metaprogramming in Ruby is the practice of writing code that generates or manipulates other code at runtime. In other words, metaprogramming allows you to write code that can modify or extend the behavior of your program while it's running. Ruby's dynamic nature and its support for reflection (introspection) make it well-suited for metaprogramming.
Here's an example of metaprogramming in Ruby:
class Person
ATTRIBUTES = [:name, :age, :email]
ATTRIBUTES.each do |attribute|
define_method("#{attribute}") do
instance_variable_get("@#{attribute}")
end
define_method("#{attribute}=") do |value|
instance_variable_set("@#{attribute}", value)
end
end
end
john = Person.new
john.name = "John Doe"
john.age = 30
john.email = "john.doe@example.com"
puts john.name # Output: "John Doe"
puts john.age # Output: 30
puts john.email # Output: "john.doe@example.com"
In this example, we use metaprogramming to dynamically define getter and setter methods for each attribute in the ATTRIBUTES
array. We use the define_method
method to create the methods at runtime, and the instance_variable_get
and instance_variable_set
methods to access the instance variables corresponding to each attribute.
8. What is duck typing in Ruby?
Interview Question: Explain the concept of duck typing in Ruby and provide an example.
Answer: Duck typing is a programming concept in which you focus on the behavior of an object, rather than its class or type. In Ruby, duck typing relies on the idea that "If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck." This means that, instead of checking the class of an object, you check if the object responds to a particular method or behaves as expected. You can think of duck typing as a more flexible way to achieve polymorphism.
Here's an example of duck typing in Ruby:
class Dog
def speak
"Woof!"
end
end
class Cat
def speak
"Meow!"
end
end
class Duck
def speak
"Quack!"
end
end
def make_animal_speak(animal)
puts animal.speak
end
dog = Dog.new
cat = Cat.new
duck = Duck.new
make_animal_speak(dog) # Output: "Woof!"
make_animal_speak(cat) # Output: "Meow!"
make_animal_speak(duck) # Output: "Quack!"
In this example, we define three different classes (Dog
, Cat
, Duck
) with the same method speak
. Then, we create a make_animal_speak
method that accepts an animal object and calls its speak
method without checking its class. This demonstrates duck typing, as we only care about the behavior (the speak
method) and not the class of the object.
9. What is the difference between nil
, false
, and true
in Ruby?
Interview Question: Explain the difference between nil
, false
, and true
in Ruby, and in which scenarios they are considered as "truthy" or "falsy."
Answer: In Ruby, nil
, false
, and true
are special objects that represent the concepts of "nothing," "falseness," and "truth," respectively.
nil
is an object of the NilClass
and represents the absence of a value or nothingness. It is commonly used to indicate that a variable, method, or expression has no value. In Ruby, nil
is considered "falsy."
false
is an object of the FalseClass
and represents a boolean false value. When a condition evaluates to false
, it means the condition is not met. In Ruby, false
is considered "falsy."
true
is an object of the TrueClass
and represents a boolean true value. When a condition evaluates to true
, it means the condition is met. In Ruby, true
is considered "truthy."
All values in Ruby, other than nil
and false
, are considered "truthy." This means that when used in a condition, they will be treated as true. Here's an example to demonstrate this:
def check_value(value)
if value
puts "The value '#{value}' is considered truthy."
else
puts "The value '#{value}' is considered falsy."
end
end
check_value(nil) # Output: "The value '' is considered falsy."
check_value(false) # Output: "The value 'false' is considered falsy."
check_value(true) # Output: "The value 'true' is considered truthy."
check_value(0) # Output: "The value '0' is considered truthy."
check_value("") # Output: "The value '' is considered truthy."
In this example, we define a check_value
method that checks if a value is considered truthy or falsy. We can see that nil
and false
are considered falsy, while true
, 0
, and empty strings (""
) are considered truthy.
10. What are the different ways to iterate through an array in Ruby?
Interview Question: Describe the different ways to iterate through an array in Ruby and provide examples.
Answer: Ruby provides several methods to iterate through arrays. Some of the most common methods are:
each
: Theeach
method iterates through each element of the array and passes it to a block. The block is executed for every element in the array.
Example:
```ruby numbers = [1, 2, 3, 4, 5]
numbers.each do |number| puts number * 2 end # Output: # 2 # 4 # 6 # 8 # 10 ```
map
: Themap
method iterates through each element of the array, passes it to a block, and returns a new array containing the results of the block's execution for each element.
Example:
```ruby numbers = [1, 2, 3, 4, 5]
doubled_numbers = numbers.map do |number| number * 2 end
puts doubled_numbers.inspect # Output: [2, 4, 6, 8, 10] ```
select
: Theselect
method iterates through each element of the array, passes it to a block, and returns a new array containing the elements for which the block returns a truthy value.
Example:
```ruby numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.select do |number| number.even? end
puts even_numbers.inspect # Output: [2, 4] ```
These are just a few examples of the many ways you can iterate through arrays in Ruby. Other methods include reject
, reduce
, inject
, and more.
11. What is a mixin in Ruby?
Interview Question: Explain what a mixin is in Ruby and provide an example of its usage.
Answer: A mixin is a technique in Ruby that allows you to include reusable code from a module into a class without using inheritance. When you include a module in a class, all the methods defined in the module become instance methods of the class. Mixins provide a way to achieve multiple inheritance-like behavior in Ruby, since a class can include multiple mixins.
Here's an example of a mixin in Ruby:
module Greeting
def hello
"Hello, my name is #{@name}"
end
end
class Person
include Greeting
def initialize(name)
@name = name
end
end
class Robot
include Greeting
def initialize(name)
@name = name
end
end
john = Person.new("John")
puts john.hello # Output: "Hello, my name is John"
robo = Robot.new("Robo")
puts robo.hello # Output: "Hello, my name is Robo"
In this example, we define a Greeting
module with a hello
method. Then, we include the Greeting
module in two different classes: Person
and Robot
. Both classes can now use the hello
method from the Greeting
module, demonstrating the mixin technique.
12. How do you handle exceptions in Ruby?
Interview Question: Explain how to handle exceptions in Ruby and provide examples of using begin
, rescue
, ensure
, and raise
.
Answer: In Ruby, exceptions are used to handle errors and unexpected situations that may occur during the execution of a program. To handle exceptions, you can use the begin
, rescue
, ensure
, and raise
keywords:
begin
: The begin
keyword is used to define a block of code where an exception may occur. If an exception is raised inside the begin
block, the execution jumps to the nearest rescue
block.
rescue
: The rescue
keyword is used to define a block of code that will handle an exception. When an exception is raised, the code inside the rescue
block is executed, allowing you to handle the exception and continue the execution of the program.
ensure
: The ensure
keyword is used to define a block of code that will always be executed, regardless of whether an exception was raised or not. This can be useful for cleanup tasks, like closing a file or releasing resources.
raise
: The raise
keyword is used to explicitly raise an exception. You can raise a specific exception with a message or re-raise the current exception being handled in a rescue
block.
Here's an example of handling exceptions in Ruby:
def divide(a, b)
begin
result = a / b
rescue ZeroDivisionError => e
puts "Error: #{e.message}"
result = nil
ensure
puts "Division operation finished."
end
result
end
puts divide(10, 2) # Output: 5
puts divide(10, 0) # Output: Error: divided by 0
# Division operation finished.
# nil
In this example, we define a divide
method that may raise a ZeroDivisionError
exception. We wrap the division operation in a begin
block and handle the exception in a rescue
block. The ensure
block is used to print a message indicating the division operation has finished.
13. What are the differences between public, private, and protected methods in Ruby?
Interview Question: Describe the differences between public, private, and protected methods in Ruby and provide examples.
Answer: In Ruby, methods can have three different visibility levels: public, private, and protected. These visibility levels determine how the methods can be accessed outside the class they are defined in:
- Public methods: Public methods can be called by any object that has access to the instance of the class they are defined in. By default, all methods in a Ruby class are public, except for the
initialize
method, which is always private.
Example:
```ruby class Dog def bark "Woof!" end end
dog = Dog.new puts dog.bark # Output: "Woof!" ```
In this example, the bark
method is public, so it can be called on an instance of the Dog
class.
- Private methods: Private methods can only be called from within the class they are defined in and cannot be accessed from outside the class. To define a private method, you can use the
private
keyword followed by the method definition, or you can use theprivate
keyword followed by a symbol with the method name.
Example:
```ruby class Dog def greet "#{bark} I'm a dog." end
private
def bark
"Woof!"
end
end
dog = Dog.new puts dog.greet # Output: "Woof! I'm a dog." puts dog.bark # Output: NoMethodError: private method `bark' called for # ```
In this example, the bark
method is private, so it can only be called from within the Dog
class. When we try to call bark
on an instance of the Dog
class, a NoMethodError
is raised.
- Protected methods: Protected methods are similar to private methods, but they can also be called by instances of subclasses or instances of the same class. To define a protected method, you can use the
protected
keyword followed by the method definition, or you can use theprotected
keyword followed by a symbol with the method name.
Example:
```ruby class Animal protected
def speak
"I'm an animal."
end
end
class Dog < Animal def greet "#{speak} I'm a dog." end end
dog = Dog.new puts dog.greet # Output: "I'm an animal. I'm a dog." ```
In this example, the speak
method is protected and defined in the Animal
class. The Dog
class, which is a subclass of Animal
, can call the speak
method.
14. How do you implement a singleton pattern in Ruby?
A singleton pattern is a design pattern that restricts the instantiation of a class to only one instance. This is useful when you need a single object to coordinate actions across the system. In Ruby, you can implement a singleton pattern using the Singleton
module from the Ruby Standard Library.
Here's an example:
require 'singleton'
class Logger
include Singleton
def log(message)
puts "[LOG] #{message}"
end
end
logger1 = Logger.instance
logger2 = Logger.instance
logger1.log("Hello, World!")
logger2.log("Hello, again!")
puts "logger1 and logger2 are the same object: #{logger1 == logger2}"
In this example, we include the Singleton
module in the Logger
class. This ensures that there can only be one instance of the Logger
class. The instance
method is used to get the singleton instance of the class. When we compare logger1
and logger2
, they are the same object.
15. What are Ruby's Enumerable methods?
Enumerable methods are a set of powerful and versatile methods provided by the Ruby Enumerable
module. These methods allow you to work with collections, such as arrays and hashes, in a more efficient and expressive way. Some common Enumerable methods include:
map
: Transforms each element in a collection according to a given block of code.select
: Filters a collection based on a given condition in the block of code.reduce
: Combines the elements of a collection using a binary operation specified by a block of code.sort
: Sorts the elements of a collection based on a given block of code.any?
: Returns true if any element in the collection satisfies a given condition in the block of code.all?
: Returns true if all elements in the collection satisfy a given condition in the block of code.
To use Enumerable methods, you need to include the Enumerable
module in your class and implement the each
method.
Here's an example:
class CustomArray
include Enumerable
def initialize(*elements)
@elements = elements
end
def each
@elements.each do |element|
yield element
end
end
end
custom_array = CustomArray.new(1, 2, 3, 4, 5)
squares = custom_array.map { |x| x * x }
puts "Squares: #{squares.inspect}"
even_numbers = custom_array.select { |x| x.even? }
puts "Even numbers: #{even_numbers.inspect}"
sum = custom_array.reduce(0) { |acc, x| acc + x }
puts "Sum: #{sum}"
In this example, we create a CustomArray
class that includes the Enumerable
module. We implement the each
method to iterate over the elements of the array. We then use various Enumerable methods to perform different operations on the custom array.
16. What is memoization in Ruby, and how can it be implemented?
Memoization is a technique used to optimize expensive or time-consuming operations by caching their results and reusing them when the same inputs are provided. This is particularly useful for functions with expensive calculations or remote API calls that don't change often.
In Ruby, you can implement memoization using instance variables and conditional assignment.
Here's an example:
class ExpensiveCalculation
def calculate(input)
@result ||= {}
@result[input] ||= begin
puts "Performing expensive calculation for #{input}"
# Simulate an expensive operation
sleep(1)
input * 2
end
end
end
calc = ExpensiveCalculation.new
puts calc.calculate(10) # Performs the expensive calculation
puts calc.calculate(10) # Returns the cached result
puts calc.calculate(20) # Performs the expensive calculation
In this example, we use an instance variable @result
to store the results of the expensive calculations. The ||=
operator is used to assign the result only if it hasn't been calculated before. This way, the expensive calculation is performed only once for each unique input.
17. How do you create a custom iterator in Ruby?
In Ruby, you can create a custom iterator by defining a method that takes a block of code as a parameter and uses the yield
keyword to execute the block for each element in the collection. This allows you to create iterators that work with your custom data structures or algorithms.
Here's an example:
class CustomArray
def initialize(*elements)
@elements = elements
end
def custom_iterator
@elements.each do |element|
yield element * 2
end
end
end
custom_array = CustomArray.new(1, 2, 3)
custom_array.custom_iterator do |element|
puts "Element: #{element}"
end
In this example, we create a CustomArray
class with a custom_iterator
method. The custom_iterator
method iterates over the elements of the array, and for each element, it yields the result of multiplying the element by 2. We then use the custom iterator to print the elements of the custom array.
18. How do you use the case
statement in Ruby?
The case
statement in Ruby is used for multi-way conditional branching. It's similar to a series of if-elsif-else
statements but can be more concise and easier to read when dealing with multiple conditions.
A case
statement consists of a case
keyword, an expression to be matched, and one or more when
clauses with expressions to be compared with the result of the case
expression. Optionally, an else
clause can be included to handle cases where none of the when
expressions match.
Here's an example:
def size_description(size)
case size
when 1..10
"Small"
when 11..20
"Medium"
when 21..30
"Large"
else
"Extra Large"
end
end
puts size_description(5) # Output: "Small"
puts size_description(15) # Output: "Medium"
puts size_description(25) # Output: "Large"
puts size_description(35) # Output: "Extra Large"
In this example, we use a case
statement to determine the size description for a given size value. The case
expression is matched against the when
clauses, and the corresponding code block is executed when a match is found.
19. What is the difference between the ==
and equal?
methods in Ruby?
In Ruby, the ==
and equal?
methods are used to compare objects for equality, but they serve different purposes:
==
: The==
method is used to compare the values of two objects. By default, it checks if the two object references are the same (similar toequal?
). However, this method can be overridden in a class to provide custom value-based equality logic. For example, Ruby'sString
,Array
, andHash
classes override the==
method to compare the contents of their objects, rather than their references.
Example:
```ruby str1 = "hello" str2 = "hello"
puts str1 == str2 # Output: true ```
equal?
: Theequal?
method is used to check if two object references are the same, i.e., if they point to the same object in memory. This method cannot be overridden, and it's generally used to test if two variables refer to the same object.
Example:
```ruby str1 = "hello" str2 = "hello" str3 = str1
puts str1.equal?(str2) # Output: false puts str1.equal?(str3) # Output: true ```
In this example, str1
and str2
have the same value but are different objects in memory, so equal?
returns false. However, str1
and str3
refer to the same object in memory, so equal?
returns true.
20. How do you use the yield
keyword in Ruby?
The yield
keyword in Ruby is used to execute a block of code that is passed as an argument to a method. This allows you to create flexible and reusable methods that can be customized with different blocks of code as needed.
When a method containing the yield
keyword is called with a block, the block is executed at the point where yield
is encountered. The method can also pass arguments to the block, and the block can return a value that is used by the method.
Here's an example:
def custom_greeting(name)
greeting = yield name
puts "#{greeting}, #{name}!"
end
custom_greeting("Alice") do |name|
"Hello"
end
custom_greeting("Bob") do |name|
"Bonjour"
end
In this example, we define a custom_greeting
method that takes a name as a parameter and uses the yield
keyword to execute the provided block of code. The block is responsible for generating a greeting based on the name, and the method prints the final greeting message. When we call the method with different blocks, we get different greeting messages for different names.