21 Essential Ruby Interview Questions *
Toptal sourced essential questions that the best Ruby developers and engineers can answer. Driven from our community, we encourage experts to submit questions and offer feedback.
Hire a Top Ruby Developer NowA call to super
invokes the parent method with the same arguments that were passed to the child method. An error will therefore occur if the arguments passed to the child method don’t match what the parent is expecting.
A call to super()
invokes the parent method without any arguments, as presumably expected. As always, being explicit in your code is a good thing.
(Thanks to Ruby Gotchas for this question.)
What will val1
and val2
equal after the code below is executed? Explain your answer.
val1 = true and false # hint: output of this statement in IRB is NOT value of val1!
val2 = true && false
Although these two statements might appear to be equivalent, they are not, due to the order of operations. Specifically, the and
and or
operators have lower precedence than the =
operator, whereas the &&
and ||
operators have higher precedence than the =
operator, based on order of operations.
To help clarify this, here’s the same code, but employing parentheses to clarify the default order of operations:
(val1 = true) and false # results in val1 being equal to true
val2 = (true && false) # results in val2 being equal to false
This is, incidentally, a great example of why using parentheses to clearly specify your intent is generally a good practice, in any language. But whether or not you use parentheses, it’s important to be aware of these order of operations rules and to thereby ensure that you are properly determining when to employ and
/ or
vs. &&
/ ||
.
Which of the expressions listed below will result in "false"
?
true ? "true" : "false"
false ? "true" : "false"
nil ? "true" : "false"
1 ? "true" : "false"
0 ? "true" : "false"
"false" ? "true" : "false"
"" ? "true" : "false"
[] ? "true" : "false"
In Ruby, the only values that evaluate to false are false
and nil
. Everything else – even zero (0) and an empty array ([]) – evaluates to true.
This comes as a real surprise to programmers who have previously been working in other languages like JavaScript.
(Thanks to Ruby Gotchas for this question.)
Write a function that sorts the keys in a hash by the length of the key as a string. For instance, the hash:
{ abc: 'hello', 'another_key' => 123, 4567 => 'third' }
should result in:
["abc", "4567", "another_key"]
As is always true in programming, there are in fact multiple ways to accomplish this.
The most straightforward answer would be of the form:
hsh.keys.map(&:to_s).sort_by(&:length)
or:
hsh.keys.collect(&:to_s).sort_by { |key| key.length }
Alternatively, Ruby’s Enumerable
mixin provides many methods to operate on collections. The key here is to turn the hash keys into a collection, convert them all to strings, then sort the array.
def key_sort hsh
hsh.keys.collect(&:to_s).sort { |a, b| a.length <=> b.length }
end
An equivalent call of the collect
method is done with the usual block syntax of:
collect { |x| x.to_s }
Consider the following two methods:
def times_two(arg1);
puts arg1 * 2;
end
def sum(arg1, arg2);
puts arg1 + arg2;
end
What will be the result of each of the following lines of code:
times_two 5
times_two(5)
times_two (5)
sum 1, 2
sum(1, 2)
sum (1, 2)
The first three lines of code will all print out 10, as expected.
The next two lines of code will both print out 3, as expected.
However, the last line of code (i.e., sum (1,2)
) will result in the following:
syntax error, unexpected ',', expecting ')'
sum (1, 2)
^
The problem is the space between the method name and the open parenthesis. Because of the space, the Ruby parser thinks that (1, 2)
is an expression that represents a single argument, but (1, 2)
is not a valid Ruby expression, hence the error.
Note that the problem does not occur with single argument methods (as shown with our timesTwo
method above), since the single value is a valid expression (e.g., (5)
is a valid expression which simply evaluates to 5).
Consider the following code:
VAL = 'Global'
module Foo
VAL = 'Foo Local'
class Bar
def value1
VAL
end
end
end
class Foo::Bar
def value2
VAL
end
end
What will be the value of each of the following:
Foo::Bar.new.value1
Foo::Bar.new.value2
Explain your answer.
Foo::Bar.new.value1
will be equal to 'Foo Local'
and Foo::Bar.new.value2
will be equal to 'Global'
.
Here’s why:
The module
keyword (as well as the class
and def
keywords) will create a new lexical scope for all of its contents. The above module Foo
therefore creates the scope Foo
in which the VAL
constant equal to 'Foo Local'
is defined. Inside Foo
, we declare class Bar
, which creates another new lexical scope (named Foo::Bar
) which also has access to its parent scope (i.e., Foo
) and all of its constants.
However, when we then declare Foo::Bar
(i.e., using ::
), we are actually creating yet another lexical scope, which is also named Foo::Bar
(how’s that for confusing!). However, this lexical scope has no parent (i.e., it is entirely independent of the lexcial scope Foo
created earlier) and therefore does not have any access to the contents of the ‘Foo’ scope. Therefore, inside class Foo::Bar
, we only have access to the VAL
constant declared at the beginning of the script (i.e., outside of any module) with the value 'Global'
.
Is the line of code below valid Ruby code? If so, what does it do? Explain your answer.
-> (a) {p a}["Hello world"]
Yes, it’s valid. Here’s how to understand what it does:
The ->
operator creates a new Proc, which is one of Ruby’s function types. (The ->
is often called the “stabby proc”. It’s also called the “stabby lambda”, as it creates a new Proc instance that is a lambda. All lambdas are Procs, but not all Procs are lambdas. There are some slight differences between the two.)
This particular Proc takes one parameter (namely, a
). When the Proc is called, Ruby executes the block p a
, which is the equivalent of puts(a.inspect)
(a subtle, but useful, difference which is why p
is sometimes better than puts
for debugging). So this Proc simply prints out the string that is passed to it.
You can call a Proc by using either the call
method on Proc, or by using the square bracket syntax, so this line of code also invokes the Proc and passes it the string “Hello World”.
So putting that all together, this line of code (a) creates a Proc that takes a single parameter a
which it prints out and (b) invokes that Proc and passes it the string “Hello world”. So, in short, this line of code prints “Hello World”.
Explain each of the following operators and how and when they should be used: ==
, ===
, eql?
, equal?
.
==
– Checks if the value of two operands are equal (often overridden to provide a class-specific definition of equality).
===
– Specifically used to test equality within the when
clause of a case
statement (also often overridden to provide meaningful class-specific semantics in case statements).
eql?
– Checks if the value and type of two operands are the same (as opposed to the ==
operator which compares values but ignores types). For example, 1 == 1.0
evaluates to true
, whereas 1.eql?(1.0)
evaluates to false
.
equal?
– Compares the identity
of two objects; i.e., returns true
iff both operands have the same object id (i.e., if they both refer to the same object). Note that this will return false
when comparing two identical copies of the same object.
(Thanks to Ruby Gotchas for this question.)
Given:
x = "hello"
Explain the difference between:
x += " world"
and
x.concat " world"
The +=
operator re-initializes the variable with a new value, so a += b
is equivalent to a = a + b
.
Therefore, while it may seem that +=
is mutating the value, it’s actually creating a new object and pointing the the old variable to that new object.
This is perhaps easier to understand if written as follows:
foo = "foo"
foo2 = foo
foo.concat "bar"
puts foo
=> "foobar"
puts foo2
=> "foobar"
foo += "baz"
puts foo
=> "foobarbaz"
puts foo2
=> "foobar"
(Examining the object_id
of foo
and foo2
will also demonstrate that new objects are being created.)
The difference has implications for performance and also has different mutation behavior than one might expect.
In Ruby code, you quite often see the trick of using an expression like array.map(&:method_name)
as a shorthand form of array.map { |element| element.method_name }
. How exactly does it work?
When a parameter is passed with &
in front of it (indicating that is it to be used as a block), Ruby will call to_proc
on it in an attempt to make it usable as a block. Symbol#to_proc
quite handily returns a Proc
that will invoke the method of the corresponding name on whatever is passed to it, thus enabling our little shorthand trick to work.
Write a single line of Ruby code that prints the Fibonacci sequence of any length as an array.
(Hint: use inject
/reduce
)
There are multiple ways to do this, but one possible answer is:
(1..20).inject( [0, 1] ) { | fib | fib << fib.last(2).inject(:+) }
As you go up the sequence fib
, you sum, or inject(:+)
, the last two elements in the array and add the result to the end of fib
.
Note: inject
is an alias of reduce
Yes, with the help of the send
method.
Given the class Test
:
class Test
private
def method
p "I am a private method"
end
end
We can execute the private method using send
:
>> Test.new.send(:method)
"I am a private method"
Consider the following code:
class A
def self.a(b)
if b > 0
b * b
end
end
end
What will be the values of:
var1 = A.a(0)
var2 = A.a(2)
- var1 will be equal to
nil
- var2 will be equal to
4
Understand:
* A conditional statement in Ruby is an expression that returns nil
if the conditional is false
.
* Ruby methods return the last expression in the method body.
In this example:
-
A.a(0)
returnsnil
for unsuccessful conditional -
A.a(2)
returns the square of2
, i.e4
What is the difference between Array#map
and Array#each
?
Note: collect
is an alias of map
-
Array#each
method iterates over the elements of the array and executes the provided block each time. However, it returns the original array unaffected. -
Array#map
will return a new array of elements containing the values returned by the block it is provided. It also does not affect the original array
Example:
# Given
>> arr = [1,2,3,4]
# This will print 2,4,6,8 but will return [1,2,3,4]
>> arr.each {|x| puts x*2; x*2 }
2
4
6
8
=> [1, 2, 3, 4]
# This will also print 2,4,6,8 but returns [2,4,6,8]
>> arr.map {|x| puts x*2; x*2 }
2
4
6
8
=> [2, 4, 6, 8]
For the class ABC
the given as:
class ABC
def xyz
puts "xyz in ABC"
end
end
What is the return value for:
ABC::new::xyz
ABC::new.xyz
ABC.new::xyz
ABC.new.xyz
All the statements for the invocation of the xyz method through the object are valid.
When run through IRB
:
irb(main):001:0> ABC::new::xyz
xyz in ABC
=> nil
irb(main):002:0> ABC::new.xyz
xyz in ABC
=> nil
irb(main):003:0> ABC.new::xyz
xyz in ABC
=> nil
irb(main):004:0> ABC.new.xyz
xyz in ABC
=> nil
What is the value of the variable upcased
in the below piece of code?
upcased = ["one", "two", "three"].map {|n| puts n.upcase }
upcase = [nil, nil, nil]
Let’s take a look at puts
as below:
>> puts "Hi"
Hi
=> nil
Note the nil
at the end: that’s the return value from puts
. After all, puts
is a method
, so it has to return something. As it happens, it always returns nil
. The printing out of the string is an action the method performs.
Similarly, evaluating the code in question we can understand that while it’s a common learner mistake to expect the result to be ["ONE", "TWO", "THREE"]
. In fact, it’s [nil, nil, nil]
. Each time through the block, the block evaluates to the return value of puts
to be nil
.
Given that the following code has been run:
if false
foo = 'test'
end
What will be the value of:
defined? foo
defined? bar
defined? foo
#=> "local-variable"
defined? bar
#=> nil
foo
has been at least read by the interpreter, therefore it is defined and assigned a nil
value. However, since bar
has never been written, it was never defined in the first place.
Object#dup
creates a shallow copy of an object. For example, it will not copy any mixed-in module methods, whereas Object#clone
will. This can be shown with the following code example:
class Klass
attr_accessor :str
end
module Foo
def foo; 'foo'; end
end
s1 = Klass.new #=> #
s1.extend(Foo) #=> #
s1.foo #=> "foo"
s2 = s1.clone #=> #
s2.foo #=> "foo"
s3 = s1.dup #=> #
s3.foo #=> NoMethodError: undefined method `foo' for #
-
include
mixes in specified module methods as instance methods in the target class -
extend
mixes in specified module methods as class methods in the target class
Given the following class definitions:
module ReusableModule
def module_method
puts "Module Method: Hi there! I'm a module method"
end
end
class ClassThatIncludes
include ReusableModule
end
class ClassThatExtends
extend ReusableModule
end
Here’s how ClassThatIncludes
behaves:
# A class method does not exist
>> ClassThatIncludes.module_method
NoMethodError: undefined method `module_method' for ClassThatIncludes:Class
# A valid instance method exists
>> ClassThatIncludes.new.module_method
Module Method: Hi there! I'm a module method
=> nil
Here’s how ClassThatExtends
behaves:
# A valid class method exists
>> ClassThatExtends.module_method
Module Method: Hi there! I'm a module method
=> nil
# An instance method does not exist
ClassThatExtends.new.module_method
NoMethodError: undefined method `module_method' for #<ClassThatExtends:0x007ffa1e0317e8>
We should mention that object.extend ExampleModule
makes ExampleModule
methods available as singleton methods in the object.
There are 4 types of variables used in Ruby:
- class variables start with
@@
, e.g.@@my_var
- instance variables start with
@
, e.g.@my_var
- global variables start with
$
, e.g.$my_var
- local variables are not prefixed, but method arguments and block arguments start with
_
, e.g._my_arg
(in fact, local variables that are prefixed with_
are considered by linters like RuboCop to be useless variables, where you don’t care what the value is)
These sample questions are intended as a starting point for your interview process. If you need additional help, explore our hiring resources—or let Toptal find the best developers, designers, marketing experts, product managers, project managers, and finance experts for you.
Submit an interview question
Submitted questions and answers are subject to review and editing, and may or may not be selected for posting, at the sole discretion of Toptal, LLC.
Looking for Ruby Developers?
Looking for Ruby Developers? Check out Toptal’s Ruby developers.
Bruz Marzolf
Bruz is a full-stack developer who has loved building things for the web since he first started doing it over 15 years ago. Bruz has spent a lot of time on the back end building services with Ruby and Elixir and working with databases and infrastructure but has also done substantial work on the front end building apps in JavaScript.
Show MoreJeff Gran
Jeff has been primarily a Ruby and JavaScript developer for the past 10 years. He is familiar with a variety of other languages and frameworks. Most recently, he's spent a year doing iOS development in Swift, and then a year doing DevOps work with Terraform, AWS, Docker, and other technologies.
Show MoreChuck Max Wood
Chuck is a Ruby on Rails developer with over 18 years of experience building web applications. Skilled in utilizing Hotwire and Tailwind for front-end development, he has also distinguished himself as a mentor to development teams. In his career, Chuck has guided diverse organizations in establishing robust processes and leveraging tools that enhance their software's speed, security, and maintainability without compromising quality.
Show MoreToptal Connects the Top 3% of Freelance Talent All Over The World.
Join the Toptal community.