9 Essential Ruby on Rails Interview Questions *

question badge

What is the difference between Ruby’s Hash and ActiveSupport’s HashWithIndifferentAccess?

answer badge

The Hash class in Ruby’s core library retrieves values by doing a standard == comparison on the keys. This means that a value stored for a Symbol key (e.g. :my_value) cannot be retrieved using the equivalent String (e.g. ‘my_value’). On the other hand, HashWithIndifferentAccess treats Symbol keys and String keys as equivalent so that the following would work:

h = HashWithIndifferentAccess.new
h[:my_value] = 'foo'
h['my_value'] #=> will return "foo"
question badge

Please note: This interview question only applies to legacy, pre-Ruby-2.2 projects.

What’s the problem with the following controller code? What would be the consequence of leaving this code in a production app? How would you fix it?

    class MyController < ApplicationController
      def options
        options = {}
        available_option_keys = [:first_option, :second_option, :third_option]
        all_keys = params.keys.map(&:to_sym)
        set_option_keys = all_keys & available_option_keys
        set_option_keys.each do |key|
          options[key] = params[key]
answer badge

It’s dangerous to convert user supplied parameters to symbols, since Symbol objects in Ruby are not garbage collected. An attacker could send a series of requests with random keys that would be turned into symbols, quickly exhausting your server’s available memory and taking down your site.

There are two ways that this could be fixed. The first would be to use slice to eliminate values from the params hash that are not valid option keys. This would look something like:


An alternative, some would argue better, option would simply be to use String keys for your options. Unless you have an extremely large number of possible option keys, you won’t actually save that much memory by using Symbol keys instead.

question badge

What’s the issue with the controller code below? How would you fix it?

class CommentsController < ApplicationController
  def users_comments
    posts = Post.all
    comments = posts.map(&:comments).flatten
    @user_comments = comments.select do |comment|
      comment.author.username == params[:username]
answer badge

This is a classic example of the notorious “n+1” bug. The first line will retrieve all of the Post objects from the database, but then the very next line will make an additional request for each Post to retrieve the corresponding Comment objects. To make matters worse, this code is then making even more database requests in order to retrieve the Author of each Comment.

This can all be avoided by changing the first line in the method to:

posts = Post.includes(comments: [:author]).all

This tells ActiveRecord to retrieve the corresponding Comment and Author records from the database immediately after the initial request for all Posts, thereby reducing the number of database requests to just three.

Please note that the above answer is only one of a few ways that it is possible to avoid incurring an “n+1” penalty, and each alternative will have its own caveats and corner cases. The above answer was selected to be presented here since it requires the smallest change to the existing code and makes no assumptions regarding the reverse association of Comment to Post.

Incidentally, there’s another issue here (although not what we’re focused on in this question and answer); namely, erforming a query in Ruby that could instead be done in the database (and which would very likely be faster there!). A relatively complex query like this can instead be constructed in ActiveRecord pretty easily, thus turning a 3 database query operation (plus some Ruby code executing) into a single database query.

Apply to Join Toptal's Developer Network and enjoy reliable, steady, remote freelance Ruby on Rails jobs.

Apply to Toptal's Freelance Ruby on Rails jobs
question badge

What is CSRF? How does Rails protect against it?

answer badge

CSRF stands for Cross-Site Request Forgery. This is a form of an attack where the attacker submits a form on your behalf to a different website, potentially causing damage or revealing sensitive information. Since browsers will automatically include cookies for a domain on a request, if you were recently logged in to the target site, the attacker’s request will appear to come from you as a logged-in user (as your session cookie will be sent with the POST request).

In order to protect against CSRF attacks, you can add protect_from_forgery to your ApplicationController. This will then cause Rails to require a CSRF token to be present before accepting any POST, PUT, or DELETE requests. The CSRF token is included as a hidden field in every form created using Rails’ form builders. It is also included as a header in GET requests so that other, non-form-based mechanisms for sending a POST can use it as well. Attackers are prevented from stealing the CSRF token by browsers’ “same origin” policy.

question badge

How would you define a Person model so that any Person can be assigned as the parent of another Person (as demonstrated in the Rails console below)? What columns would you need to define in the migration creating the table for Person?

irb(main):001:0> john = Person.create(name: "John")
irb(main):002:0> jim = Person.create(name: "Jim", parent: john)
irb(main):003:0> bob = Person.create(name: "Bob", parent: john)
irb(main):004:0> john.children.map(&:name)
=> ["Jim", "Bob"]

And for a more advanced challenge: Update the Person model so that you can also get a list of all of a person’s grandchildren, as illustrated below. Would you need to make any changes to the corresponding table in the database?

irb(main):001:0> sally = Person.create(name: "Sally")
irb(main):002:0> sue = Person.create(name: "Sue", parent: sally)
irb(main):003:0> kate = Person.create(name: "Kate", parent: sally)
irb(main):004:0> lisa = Person.create(name: "Lisa", parent: sue)
irb(main):005:0> robin = Person.create(name: "Robin", parent: kate)
irb(main):006:0> donna = Person.create(name: "Donna", parent: kate)
irb(main):007:0> sally.grandchildren.map(&:name)
=> ["Lisa", "Robin", "Donna"]
answer badge

Normally, the target class of an ActiveRecord association is inferred from the association’s name (a perfect example of “convention over configuration”). It is possible to override this default behavior, though, and specify a different target class. Doing so, it is even possible to have relationships between two objects of the same class.

This is how it is possible to set up a parent-child relationship. The model definition would look like:

class Person < ActiveRecord::Base
  belongs_to :parent, class: Person
  has_many :children, class: Person, foreign_key: :parent_id

It’s necessary to specify the foreign_key option for the has_many relationship because ActiveRecord will attempt to use :person_id by default. In the migration to create the table for this model, you would need to define, at minimum, a column for the name attribute as well as an integer column for parent_id.

Self-referential relationships can be extended in all the same ways as normal two-model relationships. This even includes has_many ... :through => ... style relationships. However, because we are circumventing Rails’ conventions, we will need to specify the source of the :through in the case of adding a grandchild relationship:

class Person < ActiveRecord::Base
  belongs_to :parent, class: Person
  has_many :children, class: Person, foreign_key: :parent_id
  has_many :grandchildren, class: Person, through: :children, source: :children

Consequently, since this is still just using the parent_id defined in the first case, no changes to the table in the database are required.

question badge

What paths (HTTP verb and URL) will be defined by the following snippet in config/routes.rb?

resources :posts do
  member do
    get 'comments'
  collection do
    post 'bulk_upload'
answer badge

Using the resource method to define routes will automatically generate routes for the standard seven restful actions:

  1. GET /posts
  2. POST /posts
  3. GET /posts/new
  4. GET /posts/:id/edit
  5. GET /posts/:id
  6. PATCH/PUT /posts/:id
  7. DELETE /posts/:id

Note that Rails also supports the (relatively) new URL verb PATCH for partial updates to records. (In theory, a PUT request should only be valid if the entire record is included in the request.)

The extra routes defined inside of the block passed to resources will generate one route valid for individual posts (GET /posts/:id/comments) as well as one defined for the top-level resource (POST /posts/bulk_upload).

question badge

Create a route to be able to display pages with different information about different types of beer. The route should recognize URL paths like /beer/<beer_type> and should use the same controller action for each type of beer with the actually beer type passed into the controller action as a parameter. The valid beer types are:

  • IPA
  • brown_ale
  • pilsner
  • lager
  • lambic
  • hefeweizen

Any other type of beer specified should generate a 404 status code.

answer badge

One option would be to generate a simple get route that specifies the controller action to call and passes the kind of beer as a parameter:

get 'beers/:kind' => 'beers#kind'

Then, within the context of the controller action, if the kind parameter is not included in the list of valid kinds, the action can raise a ActionController::RoutingError, which will redirect to 404 in production.

Alternatively, a simpler solution is to check against the list of valid kinds in the definition of the route. This can be accomplished using the constraints option as follows:

kinds = %w|IPA brown_ale pilsner lager lambic hefweizen|
get 'beers/:kind' => 'beers#kind', constraints: {kind: Regexp.new(kinds.join('|'))}

This code calls the BeersController#kind action method with params['kind'] set to a string representing the beer type given in the URL path. The key is using the constraints option for the route to specify a regular expression to use to verify the route is correct. In this case, the lambda checks to see that the kind parameter is included in the list of valid beer types.

Or perhaps an even better solution would be to use resource routing. This has the added benefit of providing URL generation helpers, but at the cost of requiring that the parameter name for the beer be passed as :id. This would look something like:

kinds = %w|IPA brown_ale pilsner lager lambic hefweizen|
resource :beer, only: [:show], constraints: {id: Regexp.new(kinds.join('|'))}
question badge

Suppose we have a Student with id=”4”. If we delete the Student with id=”4”, what will be the result of the following queries:

  • Student.find(4)
  • Student.find_by_id(4)
answer badge


  • Student.find(4) will raise an error: ActiveRecord::RecordNotFound: Couldn't find Student with id=4
  • Student.find_by_id(4) will return nil and will not raise an error.
question badge

Given this input:

x = [{"a" => 10},{"b" => 20},{"c" => 30}]

How can you obtain the following?

  1. one array containing all keys
  2. another containing all values
  3. the sum of all the values
answer badge

This works:

y = x[0].merge(x[1]).merge(x[2])

y.keys                   # will return all keys
y.values                 # will return all values
y.values.inject(:+)      # will return the sum of all values

But a better first line would be this:

y = x.reduce(:merge)

…because it would work on an array of any size, not just the exact input given.

* There is more to interviewing than tricky technical questions, so these are intended merely as a guide. Not every “A” candidate worth hiring will be able to answer them all, nor does answering them all guarantee an “A” candidate. At the end of the day, hiring remains an art, a science — and a lot of work.
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.
All fields are required
Thanks for submitting your question.
Our editorial staff will review it shortly. Please note that 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.
Work with leading US companies on Ruby on Rails freelance jobs. Design your career as a Ruby on Rails developer with Toptal.
Quang Van
United States
Quang enjoys creating beautiful software with equally impressive code. Along with possessing a talent for understanding and refactoring large codebases, he believes in best practices, small functions, and white space. Quang has over 15 years of experience working for marketing, SaaS, healthcare, and crypto companies. He's also founded companies and created social networks, SPAs, membership sites, and marketing tools.