How is Ruby Used in Technical Interviews?
Ruby Interview Stats
We've hosted over 100k interviews on our platform. Ruby was the language of choice in those interviews 2% of the time, and engineers who interviewed in Ruby passed their interviews 47% of the time.
Below is a distribution of programming languages and their popularity in technical interviews as well as success rates in interviews, by language.
According to Stack Overflow's 2023 Developer Survey, Ruby comes in the four highest-paid languages with a median income of 98.5k USD. GitHub's Octoverse 2022 report ranks Ruby in the top ten used programming languages globally.
What does this mean for you as a candidate? If Ruby is your strongest language, there's no reason to avoid using it in technical interviews. While less common, Ruby is still a viable and respected choice among interviewers.
Ruby Idioms & Idiosyncrasies
As a language, Ruby is favored by several leading companies, such as Airbnb, GitHub, Shopify, and 37Signals, due to its readable syntax and the powerful Rails framework. While originally designed and developed by Yukihiro "Matz" Matsumoto, today there are several implementations available, such as MRI (Matz's Ruby Interpreter), JRuby (which runs on the JVM), and Rubinius, each with their unique features and performance characteristics.
Among these, MRI is the reference implementation of Ruby and is most commonly used. It is also the assumed default in most coding interviews unless specified otherwise, given its status as the original and most widespread version of Ruby. JRuby allows for integration with Java, leveraging the power and ecosystem of the JVM, and Rubinius emphasizes concurrency, using a bytecode virtual machine much like the JVM or .NET CLR.
When preparing for a Ruby interview, understanding the idioms and idiosyncrasies of the language is essential. Here are the key ones to remember.
Everything is an Object
In many programming languages, primitives like integers and booleans are not objects. However, Ruby is a pure object-oriented language, meaning everything in Ruby is an object, even literals like numbers, booleans, and nil
. This means that every piece of data has methods and instance variables, which can be very powerful.
For instance, you're given an array of integers and asked to return an array of even numbers. Because everything is an object, you can call the select
method directly on the array object and pass it a block of code to execute for each element:
numbers = [1, 2, 3, 4, 5]
evens = numbers.select(&:even?)
puts evens # Outputs: [2, 4]
1numbers = [1, 2, 3, 4, 5]
2evens = numbers.select(&:even?)
3puts evens # Outputs: [2, 4]
In this example, select
is a built-in method provided by the Array
class, and even?
is a built-in method provided by the Integer
class. The &:
syntax is a shorthand that converts :even?
to a Proc
object, which is then passed to select
.
Truthy and Falsy Values
In Ruby, only two things are falsy — false
and nil
. Everything else is considered to be truthy, including 0
, 0.0
, ""
(empty string), and []
(empty array). This differs from other languages where 0
, ""
, or []
could be considered falsy.
Blocks, Procs, Lambdas
Ruby is known for its blocks, procs, and lambdas, which are chunks of code that can be passed around like objects. They are similar to Python's lambda functions, Java's lambda expressions, and JavaScript's first-class functions and arrow functions. However, they offer greater flexibility and power that defines Ruby's flavor of functional programming.
words = ["apple", "fig", "cherry", "banana", "grape"]
sorted_by_length = words.sort { |a, b| a.length <=> b.length }
# sorted_by_length is now ["fig", "apple", "grape", "cherry", "banana"]
1words = ["apple", "fig", "cherry", "banana", "grape"]
2sorted_by_length = words.sort { |a, b| a.length <=> b.length }
3# sorted_by_length is now ["fig", "apple", "grape", "cherry", "banana"]
In this example, a block is passed to the sort
method to define custom sorting logic. The spaceship operator <=>
is used to compare the lengths of the strings.
Dynamic Typing
Ruby, similar to Python and JavaScript, is a dynamically typed language, which means that you don't have to declare the type of a variable when you define it. Ruby will figure it out for you. Variables are just names for containers that hold references to objects. The type of variable is simply the type of the object they reference.
In addition, Ruby is strongly typed (unlike JavaScript), which enforces type-checking during runtime. While you don't have to explicitly define a variable's type, once the type is assigned, Ruby won't automatically convert one type to another without explicit instruction.
Here's an example:
x = "Hello, Interviewing.io!" # x is a String
x = 42 # Now x is an Integer
# Ruby won't automatically convert a string into a number
y = "5"
z = y + 2 # This will raise an error because Ruby is strongly typed
1x = "Hello, Interviewing.io!" # x is a String
2x = 42 # Now x is an Integer
3
4# Ruby won't automatically convert a string into a number
5y = "5"
6z = y + 2 # This will raise an error because Ruby is strongly typed
Singleton Methods and Metaprogramming
Metaprogramming in Ruby is a technique where a program can treat its code as data and manipulate it accordingly, generating and defining new methods dynamically at runtime. Singleton methods are part of this toolkit, allowing for specific methods to be defined for individual objects.
Metaprogramming is commonly used in Ruby libraries and frameworks such as Ruby on Rails. One of the most well-known examples is how Rails adds methods to ActiveRecord models based on the column names in the associated database table.
Suppose you have a User
model with first_name
and last_name
attributes. When you retrieve a User
from the database, Rails dynamically adds methods to that instance for getting and setting these attributes.
user = User.find(1) # Retrieves a User from the database.
# Rails uses metaprogramming to define singleton methods on the user instance:
def user.first_name
self[:first_name]
end
def user.first_name=(value)
self[:first_name] = value
end
# You can now use these dynamically generated methods:
user.first_name = 'Brian'
puts user.first_name # Outputs: "Brian"
1user = User.find(1) # Retrieves a User from the database.
2
3# Rails uses metaprogramming to define singleton methods on the user instance:
4def user.first_name
5 self[:first_name]
6end
7
8def user.first_name=(value)
9 self[:first_name] = value
10end
11
12# You can now use these dynamically generated methods:
13user.first_name = 'Brian'
14puts user.first_name # Outputs: "Brian"
Rails internally uses a technique called method_missing
to achieve this. When you call a method that does not exist, method_missing
is invoked, and Rails defines these attribute methods on-the-fly.
Implicit Returns
Methods implicitly return the value of the last statement executed in Ruby, which differs from many languages that require an explicit return
statement. This can make your code more concise and help in coding interviews where you want to write shorter, cleaner methods.
def array_sum(array)
# no explicit return statement used
array.reduce(0, :+)
end
numbers = [1, 2, 3, 4, 5]
puts array_sum(numbers) # Outputs: 15
1def array_sum(array)
2 # no explicit return statement used
3 array.reduce(0, :+)
4end
5
6numbers = [1, 2, 3, 4, 5]
7puts array_sum(numbers) # Outputs: 15
8
Bang Methods
In Ruby, methods ending with an exclamation mark, often called "bang" methods, usually indicate that the method will modify the object it's called on directly rather than returning a new object. This is a convention, not a rule, and it's up to the developer to adhere to this convention when defining their methods.
For example, for reversing a string, Ruby has reverse
and reverse!
methods. reverse
returns a new string that is the reverse of the original, whereas reverse!
modifies the original string in place.
While using built-in methods or creating your own, it's essential to be aware of this convention and to use it consistently.
Common Ruby Interview Mistakes
Not Knowing the Difference Between ==
and eql?
Operators
In Ruby, both ==
and eql?
check for equality, but they are used for different types of comparisons. ==
checks if two objects have the same value. It returns true
even if numbers of different types (integer and float) have the same numerical value Conversely, eql?
checks both the value and the type of objects. It returns true
only if both match, hence being more restrictive.
# Array with mixed integer and float
nums = [1, 2.0, 3, 2]
# '==' counts both integer 2 and float 2.0
puts nums.count { |num| num == 2 }
# Output: 2
# 'eql?' counts only integer 2
puts nums.count { |num| num.eql? 2 }
# Output: 1
1# Array with mixed integer and float
2nums = [1, 2.0, 3, 2]
3
4# '==' counts both integer 2 and float 2.0
5puts nums.count { |num| num == 2 }
6# Output: 2
7
8# 'eql?' counts only integer 2
9puts nums.count { |num| num.eql? 2 }
10# Output: 1
arr = []
puts arr.nil? # Output: false
puts arr.empty? # Output: true
1arr = []
2puts arr.nil? # Output: false
3puts arr.empty? # Output: true
Not Knowing the Difference Between nil?
and empty?
nil?
is a method available on all objects and returns true
if the object it's called on is the special nil
object. nil?
is particularly useful when you want to check if a variable has been initialized or not. On the other hand, empty?
is a method that isn't universally available to all objects but is primarily used with collections such as Arrays, Hashes, or Strings. This method checks whether the collection contains any elements (for arrays or hashes) or characters (for strings).
Assuming Rails Methods Are Built-in Ruby Methods
Rails extends Ruby with several helpful methods. Mistaking these for built-in Ruby methods can lead to unexpected NoMethodError
exceptions. For instance, the methods blank?
and present?
are commonly used in Rails applications to check if an object is empty, a whitespace string, or nil
. These methods are convenient, but they're not available in a non-Rails Ruby script.
# This code will work in Rails, but not in plain Ruby
puts " ".blank? # In Rails, Output: true
# Use built-in Ruby methods instead
puts " ".empty? # Output: false
1# This code will work in Rails, but not in plain Ruby
2puts " ".blank? # In Rails, Output: true
3
4# Use built-in Ruby methods instead
5puts " ".empty? # Output: false
When to Use Symbols vs. Strings
Symbols and strings may look similar, but they are very different and serve different purposes. Symbols are immutable and often used as Hash keys, while strings are mutable and used when the content matters. A common mistake in Ruby is confusing the two, especially when accessing Hash values.
hash = { a: 1, b: 2, c: 3 }
puts hash[:a] # Correct, Output: 1
puts hash['a'] # Incorrect, Output: nil
1hash = { a: 1, b: 2, c: 3 }
2puts hash[:a] # Correct, Output: 1
3puts hash['a'] # Incorrect, Output: nil
Not Using Instance Variables Correctly
In Ruby, a variable's scope is denoted by its prefix.
Instance variables are declared with an @
symbol. Without it, Ruby treats the declaration as a local variable, limiting its scope, which won't have the desired effect.
class Person
def name=(new_name)
# Creates a local variable instead of an instance variable
name = new_name
end
end
class Person
def name=(new_name)
# Creates an instance variable
@name = new_name
end
end
1class Person
2 def name=(new_name)
3 # Creates a local variable instead of an instance variable
4 name = new_name
5 end
6end
7
8
9class Person
10 def name=(new_name)
11 # Creates an instance variable
12 @name = new_name
13 end
14end
Forgetting @
changes a variable's scope from instance-wide to method-specific, causing potential issues in your program.
When to Employ and
/ or
vs. &&
/ ||
Ruby has two sets of logical operators: and
/ or
and &&
/ ||
. Both sets perform logical operations but have different operator precedences, which can lead to different results if not used carefully. &&
and ||
have higher precedence than and
and or
. Moreover, and
and or
have lower precedence than the assignment operator =
. This can lead to unexpected outcomes if and
or or
are used with assignments.
result = nil or true
puts result # Output: nil
1result = nil or true
2puts result # Output: nil
To avoid such issues, use &&
and ||
for logical operations and reserve and
/ or
for control flow, where the right-hand side is only evaluated if needed.
# Control flow using 'or'
file = File.open('exists.txt') or die "Can't open file"
# Logical operations using '||'
is_tall = is_adult || is_over_six_feet
1# Control flow using 'or'
2file = File.open('exists.txt') or die "Can't open file"
3
4# Logical operations using '||'
5is_tall = is_adult || is_over_six_feet
6
Parentheses are Optional
In Ruby, parentheses are optional for method calls. However, leaving them out is more nuanced than it may seem and can lead to unexpected behavior if not used carefully. It is recommended to use a community-accepted style guide such as RuboCop to avoid such issues.
- Use parentheses for method invocations, except for DSL methods, methods with "keyword" status, and attribute access methods.
- Always use parentheses in expressions involving
&&
or||
operators. - Avoid parentheses for methods without arguments.
# Rails migration method, an example of an internal DSL
add_column :users, :email, :string
# Ruby keyword-like methods
puts "Hello, world!" # Output method
gets # Input method
class Person
attr_accessor :name, :age # Defines getter and setter methods for name and age
end
person = Person.new
person.name = "John" # Setter method for name
person.age = 30 # Setter method for age
# Method invocation when the first argument begins with an open parenthesis
x = Math.sin(y)
# Always use parentheses in expressions involving logical operators
result = (a && b) || c
# Avoid parentheses when the method doesn't accept any arguments
array.empty? # No argument method
1# Rails migration method, an example of an internal DSL
2add_column :users, :email, :string
3
4# Ruby keyword-like methods
5puts "Hello, world!" # Output method
6gets # Input method
7
8class Person
9 attr_accessor :name, :age # Defines getter and setter methods for name and age
10end
11
12person = Person.new
13person.name = "John" # Setter method for name
14person.age = 30 # Setter method for age
15
16# Method invocation when the first argument begins with an open parenthesis
17x = Math.sin(y)
18
19# Always use parentheses in expressions involving logical operators
20result = (a && b) || c
21
22# Avoid parentheses when the method doesn't accept any arguments
23array.empty? # No argument method
Concatenating Strings / Strings are Mutable in Ruby
Ruby strings are mutable objects, meaning they can be changed after creation. The <<
and concat
methods take advantage of this by appending to the existing string, resulting in fewer objects and better performance.
On the other hand, the +
operator creates a new string object that combines the original strings, which can result in more memory usage and slower performance when concatenating large strings or performing the operation many times.
str1 = "Hello, "
str2 = "Ruby!"
# This creates a new string
puts str1 + str2 # Output: "Hello, Ruby!"
# These modify the original string
str1 << str2
puts str1 # Output: "Hello, Ruby!"
str1.concat(str2)
puts str1 # Output: "Hello, Ruby!Ruby!"
1str1 = "Hello, "
2str2 = "Ruby!"
3
4# This creates a new string
5puts str1 + str2 # Output: "Hello, Ruby!"
6
7# These modify the original string
8str1 << str2
9puts str1 # Output: "Hello, Ruby!"
10
11str1.concat(str2)
12puts str1 # Output: "Hello, Ruby!Ruby!"
Confusion between include
and extend
The include
method mixes the module methods into an instance of a class, meaning the methods become instance methods. On the other hand, the extend
method mixes the module methods into the class directly, making them class methods.
A common mistake is to use include
when you meant to use extend
, or vice versa, which will not provide the expected functionality.
module Greeting
def hello
"Hello, Ruby!"
end
end
class Person
include Greeting
end
class Robot
extend Greeting
end
person = Person.new
puts person.hello # Correct, Output: "Hello, Ruby!"
puts Robot.hello # Correct, Output: "Hello, Ruby!"
robot = Robot.new
puts robot.hello # Incorrect, will raise a NoMethodError
1module Greeting
2 def hello
3 "Hello, Ruby!"
4 end
5end
6
7class Person
8 include Greeting
9end
10
11class Robot
12 extend Greeting
13end
14
15person = Person.new
16puts person.hello # Correct, Output: "Hello, Ruby!"
17
18puts Robot.hello # Correct, Output: "Hello, Ruby!"
19
20robot = Robot.new
21puts robot.hello # Incorrect, will raise a NoMethodError
Misunderstanding throw
/catch
and raise
/rescue
Ruby error handling is often done with raise
and rescue
, which are used to throw and handle exceptions, respectively. throw
and catch
are used for control flow and are not directly related to exception handling.
A common mistake is confusing throw
with raise
and catch
with rescue
. While their names suggest similar functionality, they serve different purposes in Ruby.
def faulty_method
begin
raise "An error occurred!"
rescue => e
puts "Rescued from error: #{e}"
end
end
faulty_method # Output: "Rescued from error: An error occurred!"
catch :done do
10.times do |i|
throw :done if i > 5
puts i
end
end
# Output: 0 1 2 3 4 5
1def faulty_method
2 begin
3 raise "An error occurred!"
4 rescue => e
5 puts "Rescued from error: #{e}"
6 end
7end
8
9faulty_method # Output: "Rescued from error: An error occurred!"
10
11
12catch :done do
13 10.times do |i|
14 throw :done if i > 5
15 puts i
16 end
17end
18# Output: 0 1 2 3 4 5
19
Not Using Idiomatic Ruby
Ruby is renowned for its elegance and expressiveness, which are rooted in the language's idioms. The Ruby community largely follows the guidelines put forth by the Ruby Style Guide, which is enforced by linting tools like RuboCop. Here are some common mistakes made when not utilizing idiomatic Ruby, along with their corrections and guidelines.
Mistake | Correction | Guideline |
---|---|---|
def sum(a, b) (a + b) end | def sum(a, b) a + b end | Omit parentheses for the returned expression. |
{'one' => 1, 'two' => 2} | {one: 1, two: 2} | Use the Ruby hash syntax. |
if some_condition then do_something end | do_something if some_condition | Avoid the use of `then` for single-line `if/unless`. |
result = if some_condition then something else something_else end | result = some_condition ? something : something_else | Favor the ternary operator(`?:`) over `if/then/else/end` constructs. |
some_method (some_arg) | some_method(some_arg) | Use `def` with parentheses when there are parameters. |
"This string contains #{'interpolation'}" | 'This string contains interpolation' | Prefer single-quoted strings when you don't need string interpolation or special symbols. |
my_array.map do |x| x * 2 end.sum | my_array.map { |x| x * 2 }.sum | Prefer `{...}` over `do...end` for single-line blocks. Avoid `do...end` when chaining. |
Ruby Interview Replays
Below you can find replays of mock interviews conducted on our platform in Ruby. The questions asked in these interviews tend to be language-agnostic (rather than asking about language-specific details and idiosyncrasies), but in these cases, the interviewee chose Ruby as the language they would work in.

About interviewing.io
interviewing.io is a mock interview practice platform. We've hosted over 100K mock interviews, conducted by senior engineers from FAANG & other top companies. We've drawn on data from these interviews to bring you the best interview prep resource on the web.