UtilsDaily

Ruby & Rails Interview Prep

16 essential topics with code examples to ace your backend interview.

16 topics
๐Ÿ”ฎ Metaprogramming Core Ruby โ–พ

Metaprogramming is "code that writes code." Ruby's flexibility at runtime is one of its most powerful features.

1. Dynamic Method Definition (define_method)

class Greeting
  ["english", "french", "spanish"].each do |lang|
    define_method("greet_in_#{lang}") do
      case lang
      when "english" then "Hello!"
      when "french"  then "Bonjour!"
      when "spanish" then "Hola!"
      end
    end
  end
end

Greeting.new.greet_in_french  # => "Bonjour!"

2. Dynamic Dispatch (send)

user.send("name")        # Calls user.name (even private!)
user.public_send("name") # Safe: only public methods

3. Ghost Methods (method_missing)

Intercepts calls to undefined methods โ€” how ActiveRecord's find_by_name works.

class SecretAgent
  def method_missing(name, *args)
    puts "Searching for dossier: #{name}..."
  end
  def respond_to_missing?(name, include_private = false)
    true
  end
end

bond = SecretAgent.new
bond.goldfinger  # => Searching for dossier: goldfinger...

4. class_eval / instance_eval

# Add method to all Strings at runtime
String.class_eval do
  def shout
    self.upcase + "!!!"
  end
end
"hello".shout  # => "HELLO!!!"

5. Introspection

  • obj.methods โ€” list all available methods
  • obj.instance_variables โ€” list all instance variables
  • obj.respond_to?(:method_name) โ€” check before calling
๐Ÿ“ฆ Blocks, Procs & Lambdas Core Ruby โ–พ

All three are closures โ€” pieces of code that can be stored and passed around.

Blocks

Part of method call syntax. Not an object. Triggered inside method via yield.

def run_block
  yield "Aravinth" if block_given?
end
run_block { |name| puts "Hello, #{name}!" }  # => Hello, Aravinth!

Procs โ€” blocks as objects

say_hi = Proc.new { puts "Hello!" }
say_hi.call  # => Hello!

Lambdas โ€” strict Procs

say_hi = -> { puts "Hello!" }
say_hi.call

Key Difference: Proc vs Lambda

FeatureProcLambda
Argument checkIgnores extra/missing argsRaises ArgumentError
return behaviorExits the surrounding methodExits only the lambda

The & Operator

hello_proc = Proc.new { |name| puts "Hello, #{name}!" }
greet(&hello_proc)  # Converts Proc โ†’ block

def capture(&block)  # Converts block โ†’ Proc
  block.call
end

When to use: Blocks for one-off logic, Lambdas for reusable callable objects, Procs rarely (only for early-return behavior).

๐Ÿ”ญ Variable Scope Core Ruby โ–พ
PrefixTypeScopeExample
(none)LocalCurrent block/methodname = "Alice"
@InstanceOne object instance@name = "Alice"
@@ClassClass + all subclasses@@count = 0
$GlobalEverywhere (avoid!)$debug = true
UPPERConstantAccessible by namespaceMAX = 100

Scope Gates

class, module, and def each create a new scope โ€” local variables do NOT cross these boundaries.

x = 10
class MyClass
  puts x  # => NameError: undefined local variable
end

Why @@ is dangerous

Class variables are shared across the entire inheritance hierarchy, causing subtle bugs when subclasses modify them. Prefer class-level instance variables (@var on the class itself) instead.

๐Ÿฆ† Classes, Modules & Duck Typing OOP โ–พ
ClassModule
Instantiable?Yes (.new)No
Inherit from?Yes (single)No
Use as mixin?NoYes (include/extend)

Mixin Methods

  • include M โ†’ M's methods become instance methods
  • extend M โ†’ M's methods become class methods
  • prepend M โ†’ M's methods run before the class's own methods
module Greetable
  def greet
    "Hello, I'm #{name}"
  end
end

class User
  include Greetable
  attr_reader :name
  def initialize(name) = @name = name
end

User.new("Alice").greet  # => "Hello, I'm Alice"

Duck Typing

Ruby has no native interfaces. Instead, it uses duck typing: "if it walks like a duck and quacks like a duck, it's a duck." Check capability with respond_to?, not class.

def process(obj)
  raise "Not quackable" unless obj.respond_to?(:quack)
  obj.quack
end
๐Ÿ”— Method Lookup & Ancestors Chain OOP โ–พ

When Ruby looks up a method, it walks the ancestor chain in order:

  1. Prepended modules (right-to-left)
  2. The class itself
  3. Included modules (right-to-left)
  4. Superclass (repeats up to BasicObject)
  5. method_missing as final fallback
module A; end
module B; end

class MyClass
  prepend A
  include B
end

MyClass.ancestors
# => [A, MyClass, B, Object, Kernel, BasicObject]

Practical: prepend for transparent wrapping

module Logging
  def save
    puts "Saving..."
    super  # calls original MyModel#save
  end
end

class MyModel
  prepend Logging
  def save = puts "Saved!"
end

MyModel.new.save
# => Saving...
# => Saved!
โšก Eager Loading & N+1 Queries ActiveRecord โ–พ

An N+1 problem: 1 query for books, then 1 per book for the author = 11 queries for 10 books.

# BAD (N+1)
Book.limit(10).each { |b| puts b.author.name }

# GOOD (2 queries total)
Book.includes(:author).limit(10).each { |b| puts b.author.name }

Three Methods Compared

MethodQueriesSQLCan filter by assoc?
preloadAlways 2IN (...)No
eager_loadAlways 1LEFT OUTER JOINYes
includes1 or 2Smart switchYes (with .references)

Rails 6.1+: Strict Loading

has_many :books, strict_loading: true
# Raises ActiveRecord::StrictLoadingViolationError on N+1

Rails 7+: Async Loading

@posts = Post.includes(:comments).load_async
do_other_work  # DB query runs in background thread
render @posts  # waits here if needed
๐Ÿ—„๏ธ ActiveRecord Migrations ActiveRecord โ–พ

Magic Naming

# Auto-generates up/down methods:
rails g migration AddEmailToUsers email:string
rails g migration RemoveAgeFromUsers age:integer
rails g migration CreateProducts name:string price:decimal

Data Types to Know

  • integer vs bigint โ€” use bigint for IDs with high volume
  • decimal โ€” always for currency (never float)
  • jsonb โ€” PostgreSQL JSON with indexing support
  • string vs text โ€” string (255 limit) vs text (unlimited)

change vs up/down

class AddIndexToEmails < ActiveRecord::Migration[7.0]
  def change
    add_index :users, :email, unique: true  # reversible
  end
end

# Non-reversible operations need explicit up/down:
def up
  execute "UPDATE users SET role = 'admin' WHERE id = 1"
end
def down
  raise ActiveRecord::IrreversibleMigration
end
๐ŸŒณ Rails Inheritance Patterns ActiveRecord โ–พ

Single Table Inheritance (STI)

One table, type column differentiates subclasses. Simple but can cause sparse columns.

class Animal < ApplicationRecord; end
class Dog < Animal; end
class Cat < Animal; end
# All stored in `animals` table with `type` column

Delegated Types (Rails 6.1+)

Shared base table + separate tables per type. Best of both worlds.

class Entry < ApplicationRecord
  delegated_type :entryable, types: %w[Message Comment]
end
class Message < ApplicationRecord; end  # own table
class Comment < ApplicationRecord; end  # own table

Polymorphic Associations

class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
  has_many :comments, as: :commentable
end
class Video < ApplicationRecord
  has_many :comments, as: :commentable
end
PatternUse WhenDownside
STIFew shared columns, few typesSparse columns
Delegated TypesEach type has unique fieldsMore tables
PolymorphicOne model belongs to many typesNo FK constraints
๐Ÿ—๏ธ Rails Design Patterns Architecture โ–พ
PatternProblem Solved
Service ObjectFat controllers or complex business logic
Form ObjectValidation spanning multiple models
Query ObjectComplex or reusable ActiveRecord scopes
Policy ObjectAuthorization logic (Pundit gem)
Decorator/PresenterView-specific formatting logic
SingletonCentralized state / shared resource

Service Object Example

# app/services/user_registration_service.rb
class UserRegistrationService
  def initialize(params) = @params = params

  def call
    user = User.new(@params)
    if user.save
      UserMailer.welcome_email(user).deliver_later
      true
    else
      false
    end
  end
end

# In controller:
UserRegistrationService.new(user_params).call

Query Object Example

class PopularPostsQuery
  def call
    Post.where('created_at > ?', 1.month.ago)
        .joins(:likes)
        .group('posts.id')
        .order('COUNT(likes.id) DESC')
        .limit(10)
  end
end
โš™๏ธ Background Jobs: Sidekiq, SQS & Kafka Architecture โ–พ
SidekiqAmazon SQSApache Kafka
BackendRedisAWS managedDistributed log
ModelPush + threaded workersPull (visibility timeout)Consumer groups
Best forRails standard async workDecoupled AWS servicesHigh-throughput streaming
OrderingQueue orderNot guaranteedPer-partition

Sidekiq Job Example

class SendEmailJob
  include Sidekiq::Job
  sidekiq_options retry: 3, queue: 'mailers'

  def perform(user_id)
    UserMailer.welcome(User.find(user_id)).deliver_now
  end
end

SendEmailJob.perform_async(user.id)
SendEmailJob.perform_in(5.minutes, user.id)

Dead Letter Queue (DLQ)

Jobs that fail all retries go to a DLQ. Monitor and replay manually. Essential for reliability in production.

๐Ÿ—ƒ๏ธ SQL Fundamentals Database โ–พ

Core Query Pattern

SELECT department, COUNT(*) AS total, AVG(salary) AS avg_salary
FROM employees
WHERE status = 'active'
GROUP BY department
HAVING COUNT(*) > 5
ORDER BY avg_salary DESC
LIMIT 10;

JOINs

-- INNER JOIN: only matching rows
SELECT u.name, o.total FROM users u
INNER JOIN orders o ON o.user_id = u.id;

-- LEFT JOIN: all users, even those without orders
SELECT u.name, o.total FROM users u
LEFT JOIN orders o ON o.user_id = u.id;

CTE (Common Table Expression)

WITH monthly_sales AS (
  SELECT DATE_TRUNC('month', created_at) AS month, SUM(total) AS revenue
  FROM orders GROUP BY 1
)
SELECT * FROM monthly_sales WHERE revenue > 10000;

Window Functions

SELECT name, salary,
  RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank
FROM employees;
๐Ÿ˜ MySQL vs PostgreSQL Database โ–พ
MySQLPostgreSQL
TypeRelationalObject-relational
JSONBasic JSONJSONB (indexed, queryable)
ArraysNo nativeNative array type
MVCCPer-row lockingFull MVCC (better concurrency)
Window functionsMySQL 8+Full support
Best forSimple read-heavy web appsComplex queries, analytics

Rails default: PostgreSQL is preferred in modern Rails for JSONB columns, better performance under concurrent writes, and richer query capabilities.

๐Ÿ”„ ActiveRecord Fundamentals & Yield Rails โ–พ

Associations

class User < ApplicationRecord
  has_many :posts, dependent: :destroy
  has_one  :profile
  belongs_to :company
  has_many :comments, through: :posts
end

Validations

validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :age, numericality: { greater_than: 0, less_than: 150 }

Callbacks Order

before_validation โ†’ after_validation โ†’ before_save โ†’ before_create/update โ†’ (DB write) โ†’ after_create/update โ†’ after_save โ†’ after_commit

Yield in Layouts


<%= yield %>                   
<%= yield :sidebar %>          


<%= content_for :sidebar do %>
  

Sidebar content here

<% end %>
๐Ÿš€ Rails Evolution: Old vs New Features Rails โ–พ
OldNew (Rails 7+)Why changed
Asset Pipeline (Sprockets)Importmaps / PropshaftNo Node.js required
TurbolinksTurbo (Hotwire)Partial page updates, Streams
Encrypted secretscredentials.yml.encNever commit secrets
ActionController::LiveTurbo StreamsReal-time over WebSockets
Webpackeresbuild / ViteFaster, simpler bundling

Hotwire Stack

  • Turbo Drive โ€” SPA-like navigation without JS frameworks
  • Turbo Frames โ€” decompose pages into independent sections
  • Turbo Streams โ€” server-pushed partial page updates over WebSocket
  • Stimulus โ€” minimal JS sprinkles for interactivity
๐Ÿ“ก Action Cable & WebSockets Real-time โ–พ

Action Cable integrates WebSockets into Rails using a pub/sub model backed by Redis.

Key Concepts

  • Connection โ€” one WebSocket per browser tab (authentication here)
  • Channel โ€” logical grouping of streams (like a chat room)
  • Broadcasting โ€” server pushes data to all subscribers
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room]}"
  end
end

# Broadcast from anywhere:
ActionCable.server.broadcast("chat_1", { message: "Hello!" })

Rails 7+: Turbo Streams over Cable

class Message < ApplicationRecord
  after_create_commit -> {
    broadcast_append_to "messages",
      partial: "messages/message",
      locals: { message: self }
  }
end
๐Ÿงต Process Forking vs Threading Core Ruby โ–พ
Forking (Process)Threading
MemorySeparate (copy-on-write)Shared heap
Crash isolationYes โ€” child crash safeNo โ€” one crash kills all
GIL impactEach process has its own GILGIL limits true CPU parallelism
Used byUnicorn, Passenger (prefork)Puma, Sidekiq
pid = fork do
  # Child process โ€” separate memory space
  puts "Child PID: #{Process.pid}"
end
Process.waitpid(pid)
puts "Parent continues"

GIL (Global Interpreter Lock)

CRuby's GIL prevents true parallel thread execution for CPU-bound work. For I/O-bound tasks (web requests, DB queries), threads work well. For CPU-heavy work, use processes or JRuby/TruffleRuby.

Puma: Hybrid Model

Puma uses multiple workers (processes) ร— threads per worker, combining both approaches for web servers.

No topics match your search. Try different keywords.

What is Ruby & Rails Interview Prep?

This is a structured, interactive reference covering the 16 Ruby on Rails topics that appear most frequently in backend engineering interviews โ€” from junior to senior level. Each topic includes a plain-English explanation alongside real, runnable code examples drawn from production Rails applications.

Ruby on Rails is the backbone of thousands of startups and scale-ups (GitHub, Shopify, Basecamp). Companies using Rails expect candidates to understand not just the API surface but the underlying design decisions: why metaprogramming is powerful yet risky, why includes behaves differently from eager_load, and when to reach for a Service Object instead of a fat model.

How Does This Interview Prep Tool Work?

Each of the 16 topics is displayed in an expandable card. All topics are open by default so you can read through everything, or use the search bar to jump to specific areas. Clicking a card header collapses it so you can focus on what you need. The progress bar at the top tracks how many topics remain expanded in your current session.

Use the copy button on every code block to quickly paste examples into your own notes or a REPL for experimentation.

Topics Covered in This Guide

  • Core Ruby: Metaprogramming, Blocks/Procs/Lambdas, Variable Scope, Method Lookup & Ancestors Chain, Process Forking vs Threading
  • OOP: Classes, Modules & Duck Typing
  • ActiveRecord: Eager Loading & N+1 Queries, Migrations, Rails Inheritance Patterns (STI, Delegated Types, Polymorphic)
  • Rails: ActiveRecord Fundamentals & Yield, Rails Evolution (Hotwire, Importmaps), Action Cable & WebSockets
  • Architecture: Rails Design Patterns (Service/Form/Query/Policy Objects), Background Jobs (Sidekiq, SQS, Kafka)
  • Database: SQL Fundamentals (JOINs, CTEs, Window Functions), MySQL vs PostgreSQL

Who Should Use This Guide?

This guide is for backend and full-stack engineers preparing for Rails interviews at any level:

  • Mid-level engineers looking to solidify fundamentals like variable scope, ActiveRecord associations, and migration strategies
  • Senior candidates who need to speak confidently about metaprogramming, eager loading internals, and architectural patterns like Service Objects
  • Career switchers coming to Rails from other frameworks who need a structured overview of Rails-specific conventions

Benefits of Using This Tool

  • Searchable: Find any topic instantly by keyword โ€” no scrolling through long documents
  • Code-first: Every concept is illustrated with a runnable code example, not just theory
  • Interview-focused: Content is curated around what interviewers actually ask, not the full Rails API
  • Free & offline-friendly: No login, no tracking โ€” works immediately in any browser
  • Concise: Deliberately brief โ€” each topic covers the core idea without padding

How to Prepare for a Ruby on Rails Interview

The most common mistake candidates make is only studying the "what" (the API). Interviewers probe the "why" โ€” why does @@class_variable cause issues with inheritance? Why does Sidekiq use Redis instead of a database queue? Why does Rails prefer includes over eager_load by default?

Use this guide alongside hands-on practice: create a small Rails app and deliberately trigger N+1 queries, then solve them. Write a custom method_missing. Implement a Service Object for a real feature. Active recall through coding beats passive reading every time.

Embed This Tool on Your Website

โ–ผ