Altcademy - a Forbes magazine logo Best Coding Bootcamp 2023

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 like each, map, and select.

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:

  1. each: The each 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 ```

  1. map: The map 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] ```

  1. select: The select 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:

  1. 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.

  1. 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 the private 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.

  1. 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 the protected 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:

  1. ==: The == method is used to compare the values of two objects. By default, it checks if the two object references are the same (similar to equal?). However, this method can be overridden in a class to provide custom value-based equality logic. For example, Ruby's String, Array, and Hash 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 ```

  1. equal?: The equal? 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.