LaunchKit

Ruby on Rails interview questions, by what they test

September 11, 2026

A Ruby on Rails interview of the technical kind rarely asks for a definition. It puts five lines of Ruby on the table and asks what they return, because reading a short program is the cheapest way to find out whether a candidate has read the language or only used it.

Twenty-three questions of that shape are grouped below by the behaviour each one probes. Most of them are Ruby interview questions rather than framework trivia: twenty-one are about the language itself and two are about Rails, which is roughly where the difficulty lives. Every group states what the code actually does, names the Ruby or Rails documentation that settles it, and links the page holding the runnable answer.

Why these Ruby on Rails interview questions are not sorted by years of experience

Two phrasings of this search attach a number to it: ruby on rails interview questions for 5 years experience, and ruby on rails interview questions for 10 years experience. Both are answered from this one page, and neither gets a list of its own.

A year count is not a property of a question. Hash.new([]) behaves the same way on a first job and on a tenth, and a candidate either knows that the default value is shared or does not. What actually separates two answers to the same question is the layer the candidate reaches for: one answer names the output, a better one names the mechanism that produces it, and the best one names the case where that mechanism costs you a bug in production.

Splitting the same twenty-three questions across a "5 years" page and a "10 years" page would produce two pages of near-identical text competing for near-identical queries, which is a way of ranking for neither. Grouping by subject is also how someone preparing actually navigates: nobody revises "the senior ones", they revise blocks, or they revise Hash defaults.

How to read the groups below

The twenty-three questions fall into eight subjects. Each group opens with the behaviour under test, settles it against a primary source, and ends with links into the quiz pages that hold the executable version. Two subjects carry an extra section of their own, because one question in each comes with a sourcing caveat that is more useful stated than buried. The quiz pages stay separate on purpose: a runnable answer with choices and an explanation is a different thing from a summary, and the summary is what belongs here.

One thing no group claims is frequency. There is no canonical Rails interview book and no survey measuring which questions get asked, so a sentence like "this is the most common Rails interview question" would be an invented statistic. What a question tests is checkable; how often it is asked is not, and it is left out rather than guessed.

Bang methods and mutation in place

Three questions ask one thing in three shapes: does this call change the object, return a copy, or do neither.

The Ruby core documentation for String settles two of them in a line each. chomp! "Removes the trailing record separator, if found; returns self if any changes, nil otherwise", and upcase! "Upcases all characters; returns self if any changes, nil otherwise". A bang method that finds nothing to change returns nil rather than the receiver, so the next call in a chain is sent to nil and the line raises NoMethodError. That is the whole trap in when upcase! returns nil mid-chain and in chomp! returning nil when nothing changes, which are the same behaviour chained two different ways.

The third question inverts the expectation. The Ruby core documentation describes FrozenError as "Raised when there is an attempt to modify a frozen object", which is what most people expect str += "def" to raise when str is frozen. Nothing is raised, and the Ruby syntax documentation on assignment says why: under Abbreviated Assignment it states that a += 2 "is equivalent to" a = a + 2. A += is not a mutation. It builds a new String and binds it to the same name, and the frozen object is never written to. Calling += on a frozen String has the sequence.

What the group tests is whether a candidate separates mutating an object from rebinding a name. That distinction is what decides whether a change is visible to everything else holding a reference to the same object, which is the shape most aliasing bugs take.

Blocks, procs and lambdas

Five questions, one subject: what a block is, what it can see, and what leaving one does.

The Ruby core documentation for Proc states the return rule outright. "In lambdas, return and break means exit from this lambda", while "In non-lambda procs, return means exit from embracing method (and will throw LocalJumpError if invoked outside the method)". So a lambda's return hands a value back to the line that called it, and a proc's return ends the method around it, which is the entire question in return inside a lambda vs inside a proc.

break carries a value of its own. The Ruby syntax documentation on control expressions states that "break accepts a value that supplies the result of the expression it is 'breaking' out of", which is what giving break a return value turns on.

Scope is settled on one documentation section. Under Local Variable Scope, the assignment page states both halves: "Variables defined in an outer scope appear inner scope", and "Since the block creates a new scope, any local variables created inside it do not leak to the surrounding scope". A block therefore reads and writes the locals around it, which is what blocks reaching outer local variables demonstrates. The opt-out is documented on the calling methods page under Block Local Arguments: "Assigning to a block-local argument will not override local arguments outside the block in the caller's scope".

Composition closes the group. Proc#>> "Returns a proc that is the composition of this proc and the given g. The returned proc takes a variable number of arguments, calls this proc with them then calls g with the result", and Proc#<< carries the same sentence with g called first. Composing procs with << and >> chains four of them, so answering it is a test of applying that sentence rather than recalling a diagram.

Two yields and one block

A method that yields twice runs the block twice, and two yields, one block is built on exactly that: n += yield written twice against a block returning 42.

The honest position on the source is worth stating, because this page's standard is that a claim names where it comes from. The Ruby syntax documentation on methods documents yield and recommends it, saying that "if you are only going to call the block and will not otherwise manipulate it or send it to another method, using yield without an explicit block parameter is preferred", and it shows a single-yield example. No sentence was found anywhere in the Ruby documentation stating that yield may be invoked repeatedly and runs the block once per invocation. The behaviour is what every Enumerable method depends on and the runnable answer demonstrates it, so it is written here as observed behaviour rather than as a documented rule with a citation hung on it.

What the question tests is whether a candidate treats a block as a value that can be called many times, rather than as a piece of syntax attached to one call.

Scope and method lookup

Four questions about where a name resolves, and the sharpest of them is settled by a single sentence. The Ruby syntax documentation on assignment states that "the local variable is created when the parser encounters the assignment, not when the assignment occurs". A lastname = "Snow" if false never executes, and still defines lastname as a local for the rest of the method, so the later reference finds a local holding nil instead of calling the method of that name. The same section describes the mirror-image failure: "Since ruby parses the bare a left of the if first and has not yet seen an assignment to a it assumes you wish to call a method". An assignment behind if false still shadows your method is that rule in five lines.

for is the second. The Ruby syntax documentation on control expressions states that "the for loop is similar to using each, but does not create a new variable scope", and adds that "the for loop is rarely used in modern ruby programs". The loop variable is therefore the surrounding method's own variable and keeps its last value after the loop ends, which is why for i in (1...10) clobbers your i.

Argument rebinding is the third, and it needs care with its source. Reassigning a method argument never touches the caller is true, but the one documentation sentence on the subject reads like the opposite: the calling methods page says "all arguments in ruby are passed by reference and are not lazily evaluated". Both are correct because they are about different things. The caller and the method share the object, and a += [4] inside the method does not write to that object: by the assignment page's rule, it binds a new array to the method's own local name. The object is shared; the name is not.

The alias question, and the part of it we cannot source

alias and alias_method look interchangeable until a subclass is involved, which is what alias vs alias_method across inheritance puts on the table.

The Ruby documentation covers the surrounding mechanics well. On method lookup, the calling methods page states that "when you send a message, Ruby looks up the method that matches the name of the message for the receiver. Methods are stored in classes and modules so method lookup walks these, not the objects themselves", and then gives the order: prepended modules in reverse order, a matching method on the class itself, included modules in reverse order, then up the superclass chain. On the keyword, the miscellaneous syntax page states that "the alias keyword is most frequently used to alias methods" and that "you may use alias in any scope". On the method, Module#alias_method "makes new_name a new copy of the method old_name", which "can be used to retain access to methods that are overridden".

What could not be sourced is the contrast itself. No sentence was found anywhere in the Ruby documentation that says how the two constructs resolve differently when a subclass is in the picture. The usual explanation is that the keyword is resolved against its lexically enclosing class at parse time while the method call is dispatched on self at runtime, and that explanation is not written in the documentation read for this page, so it is not asserted here on borrowed authority. The runnable answer shows the difference; sourcing the rule behind it is open work.

Equality and identity

Two questions separate three operations that look like one.

The Ruby core documentation for Object draws the line between == and eql? and gives the example inline: "Numeric types, for example, perform type conversion across ==, but not across eql?", followed by 1 == 1.0 #=> true and 1.eql? 1.0 #=> false. The same page says equal? "should never be overridden by subclasses as it is used to determine object identity", which makes the three a ladder rather than three synonyms: value with conversion, value and type, then the object itself. 1.0 == 1 versus eql? and equal? walks the ladder.

Class membership is the second. Object#instance_of? "returns whether self is an instance of the given class", and the documentation carries the inheritance case as a worked example on the page itself: with class A; end, class B < A; end and class C < B; end, it shows b.instance_of? A #=> false, b.instance_of? B #=> true and b.instance_of? C #=> false. Exact class, no ancestors, which is what separates it from is_a? and kind_of? and what instance_of? ignoring inheritance tests.

Both questions test the same habit. A candidate who reaches for == everywhere and a candidate who picks between ==, eql? and equal? write different Hash keys and different comparison methods, and the difference shows up long before any interview does.

Hash defaults

Two questions, and the Ruby core documentation answers both in its own words.

Under Default Values, the Hash page warns: "Note that the default value is used without being duplicated. It is not advised to set the default value to a mutable object". One array is created when Hash.new([]) is evaluated, and every missing key is handed that same array, so pushing onto h[:a] is visible through h[:b] and the hash itself is still empty because nothing was ever stored under a key. The shared default array in Hash.new([]) is that paragraph as code.

The block form fails in the opposite direction. Under Default Proc, the same page states that "when the default proc for a Hash is set (i.e., not nil), the default value returned by method [] is determined by the default proc alone", and notes that "setting the default proc will clear the default value and vice versa". Hash.new { [] } therefore returns a brand new array on every miss, and because the block never assigns anything, that array is discarded the moment the push finishes. Why Hash.new { [] } stays empty after << is the result.

What both questions test is whether a candidate knows that a default is a fallback for a read, not an insertion. The working form assigns inside the block, as in Hash.new { |hash, key| hash[key] = [] }, and a candidate who can write that has understood the two failures above rather than memorised them.

Precedence and truthiness

Three questions about what binds to what, and the first is answered by the table rather than by a paraphrase of it. The Ruby syntax documentation on precedence lists the operators "from highest to lowest", and in that list && sits one row above ||, both sit above the row for =, +=, -=, etc., which in turn sits above not and above or, and. So x || y && "Rails" groups as x || (y && "Rails"), which is && binding tighter than ||.

Truthiness is the second. The control expressions page states that "for the tests in these control expressions, nil and false are false-values and true and any other object are true-values", so exactly two values are falsy and everything else, including 0 and "", is not. The other half of the question is that ! is a real method: the Ruby core documentation for BasicObject documents BasicObject#! as "boolean negate", which makes !! two method dispatches rather than a syntax form. !! is two calls to BasicObject#! uses the explicit .!@ spelling to make that visible.

Multiple assignment closes the group. The Ruby syntax documentation on assignment states that "you can use multiple assignment to swap two values in-place", under the section that first establishes that "you can assign multiple values on the right-hand side to multiple variables", which is why swapping two variables with a, b = b, a needs no temporary.

Enumerable and laziness

Two questions about when work happens, rather than about what an iterator returns.

The Ruby core documentation describes Enumerator::Lazy as "a special type of Enumerator, that allows constructing chains of operations without evaluating them immediately, and evaluating values on as-needed basis", and adds that "in order to do so it redefines most of Enumerable methods so that they just construct another lazy enumerator". The page states the purpose too: "this class allows idiomatic calculations on long or infinite sequences, as well as chaining of calculations without constructing intermediate arrays". Evaluating per element rather than per stage is the direct consequence, and it is what changes the order of side effects in lazy enumerators running the whole chain per element: each element is carried through every block before the next element starts.

The second question is smaller and reuses the truthiness rule above. Object#itself is documented in one line, "returns self", so select(&:itself) keeps an element when the element is itself truthy. Combined with the control expressions rule that only nil and false are false-values, that means 0, "" and [] all survive the filter, which is the point of filtering truthy values with select(&:itself).

Laziness is the subject in this list with the most direct consequence for a running application, because the documented reason the class exists is chaining "without constructing intermediate arrays". On a large collection the same chain written eagerly materialises one array per stage, which is memory a lazy chain never asks for.

Rails specifics

Two questions leave Ruby for the framework, and both are documented by the Rails Guides and by the framework source at the same time.

Rendering twice is the first. The Rails Guides section on avoiding double render errors names the message, "Can only render or redirect once per action", and shows the exact shape that produces it: a conditional render followed by an unconditional one, with no return between them. The framework source says what is raised. In Rails 8.1.3.1, actionpack/lib/action_controller/metal/rendering.rb opens render with raise ::AbstractController::DoubleRenderError if response_body, and AbstractController::DoubleRenderError is declared in actionpack/lib/abstract_controller/rendering.rb with a default message that spells out the cause: "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action". The Guide quotes the shorter message and the constant carries the longer one, so a reader may meet either text, but the class raised is the same one. Two render calls in one Rails action is that controller.

Key collision is the second. The Rails Guides section on symbolize_keys states that the method "returns a hash that has a symbolized version of the keys in the receiver, where possible", by "sending to_sym to them", and then gives the collision case verbatim: "In case of key collision, the value will be the one most recently inserted into the hash", with the example { "a" => 1, a: 2 }.symbolize_keys # => {:a=>2}. The implementation in activesupport/lib/active_support/core_ext/hash/keys.rb is a one-line transform_keys { |key| key.to_sym rescue key }, which is why the last write wins rather than the first. When symbolize_keys collides :key and "key" puts a symbol and a string key in the same literal.

What this page does not claim

Three gaps are left open on purpose, because the alternative to an honest gap here is an invented fact.

Frequency is the first and the largest. Nothing above says that a question is common, typical, or asked at a particular level of seniority. No survey measures that, and there is no canonical Rails interview book to cite for it, so any such sentence would be a number with nothing behind it.

The alias and alias_method contrast across inheritance is the second, described in its own section above. The documentation covers both constructs and covers method lookup, and does not appear to contrast the two, so the rule behind the runnable answer is stated as unsourced rather than attributed to a page that does not say it.

Repeated yield is the third. A method may yield many times and the block runs once per yield, and that is written above as observed behaviour because no documentation sentence stating it was found. Anyone who locates a primary source for either of the last two should add it to the Sources block below and rewrite the paragraph that currently admits the gap.

Sources

  1. Ruby core documentation, String (Ruby 3.4)
  2. Ruby core documentation, FrozenError (Ruby 3.4)
  3. Ruby core documentation, Proc (Ruby 3.4)
  4. Ruby core documentation, Object (Ruby 3.4)
  5. Ruby core documentation, BasicObject (Ruby 3.4)
  6. Ruby core documentation, Module (Ruby 3.4)
  7. Ruby core documentation, Hash (Ruby 3.4)
  8. Ruby core documentation, Enumerator::Lazy (Ruby 3.4)
  9. Ruby syntax documentation, Assignment (Ruby 3.4)
  10. Ruby syntax documentation, Control Expressions (Ruby 3.4)
  11. Ruby syntax documentation, Calling Methods (Ruby 3.4)
  12. Ruby syntax documentation, Methods (Ruby 3.4)
  13. Ruby syntax documentation, Miscellaneous Syntax (Ruby 3.4)
  14. Ruby syntax documentation, Precedence (Ruby 3.4)
  15. Ruby on Rails Guides, Layouts and Rendering in Rails
  16. Ruby on Rails Guides, Active Support Core Extensions
  17. Rails framework source, actionpack/lib/abstract_controller/rendering.rb (tag v8.1.3.1)
  18. Rails framework source, actionpack/lib/action_controller/metal/rendering.rb (tag v8.1.3.1)
  19. Rails framework source, activesupport/lib/active_support/core_ext/hash/keys.rb (tag v8.1.3.1)

Back to Ruby on Rails jobs: what the data actually says