String Interpolation

Published by Programming Line in Ruby. Last updated .

String interpolation is Ruby’s built-in way of embedding the value of a variable, or the result of any expression, directly inside a string, using the #{...} syntax. Instead of gluing pieces of text together with the + operator, you write the string once and drop live Ruby code right where you want its value to appear. It reads naturally, avoids type-conversion headaches, and shows up everywhere in idiomatic Ruby — puts statements, log messages, error text, and templating.

Overview: How String Interpolation Works

Only double-quoted strings (and double-quote-like literals) support interpolation. When Ruby’s parser sees #{ inside a double-quoted string, it treats everything up to the matching } as a nested chunk of Ruby code, not literal text. At runtime, that code is evaluated in the surrounding scope — it can see local variables, call methods, index into arrays and hashes, or run a full conditional expression. Whatever value comes out of that expression then has to_s called on it automatically, and the resulting string is spliced into place.

This works for literally everything because, in Ruby, everything is an object. Integers, arrays, hashes, even nil, are instances of a class (Integer, Array, Hash, NilClass), and every one of them responds to to_s. That’s why #{42} becomes "42" and, importantly, why #{nil} silently becomes "" (an empty string) rather than raising an error — a detail that causes real bugs, covered below.

Single-quoted strings, by contrast, are meant for literal text: Ruby does almost no processing on them (only \\ and \' are recognized as escapes), so #{...} inside single quotes is left completely untouched as plain characters.

Syntax

"some literal text #{expression} more literal text"
String form Interpolates? Notes
"..." Yes The standard double-quoted string
'...' No #{...} is treated as literal characters
%Q(...) or %(...) Yes Behaves like a double-quoted string; handy when the text itself contains many double quotes
%q(...) No Behaves like a single-quoted string
Heredoc <<~NAME / <<-NAME Yes Multi-line strings interpolate just like double-quoted strings
Heredoc with quoted delimiter, e.g. <<~'NAME' No Quoting the delimiter disables interpolation, like single quotes
Symbol :"..." Yes Builds a dynamic symbol from an interpolated string
Regex /.../ Yes #{...} can build a pattern dynamically
%w[...] vs %W[...] No / Yes Lowercase word-array literal is literal text; uppercase interpolates each word

Examples

Example 1: The basics

name = "Ruby"
age = 30
puts "Hello, #{name}! In two years you'll be #{age + 2}."
puts "Name in caps: #{name.upcase}"

Output:

Hello, Ruby! In two years you'll be 32.
Name in caps: RUBY

Notice that #{age + 2} isn’t just substituting a variable — it’s running a full expression (addition) and interpolating the result. #{name.upcase} goes further and calls a method on the object before the result is spliced in. Anything that returns a value is fair game inside #{...}.

Example 2: A realistic calculation

def format_price(cents) = "$%.2f" % (cents / 100.0)

item = { name: "Coffee", price_cents: 350 }
quantity = 2
total = item[:price_cents] * quantity
puts "#{quantity}x #{item[:name]} costs #{format_price(total)}"
puts "Free shipping: #{total > 500 ? 'yes' : 'no'}"

Output:

2x Coffee costs $7.00
Free shipping: yes

This example chains several interpolation tricks: a hash lookup (item[:name]), a call to a custom method (format_price), and a full ternary expression. Because #{...} is already inside a double-quoted string, the ternary’s own strings had to use single quotes ('yes' / 'no') — mixing quote styles like this is a common and perfectly normal way to avoid ending the outer string early.

Example 3: Multi-line strings with a heredoc

name = "Ada"
lines_written = 42

report = <<~TEXT
  Report for #{name}
  Lines written today: #{lines_written}
  Status: #{lines_written > 0 ? "productive" : "idle"}
TEXT

puts report

Output:

Report for Ada
Lines written today: 42
Status: productive

The squiggly heredoc (<<~TEXT) strips the common leading indentation from every line, which is why the source can be indented to match the surrounding code while the output stays flush left. Interpolation works exactly the same inside a heredoc as inside a regular double-quoted string, including nested double quotes for the ternary branches, since the heredoc isn’t delimited by quote characters at all.

How It Works Step by Step

When Ruby encounters "text #{expr} more text", here is what actually happens:

  • The parser splits the literal into segments: the literal text before #{, the Ruby code between #{ and }, and the literal text after.
  • The code inside #{...} is parsed as ordinary Ruby — it can span multiple statements separated by ;, though in practice it’s almost always a single expression.
  • At runtime, that expression is evaluated in the enclosing scope, so it has full access to local variables, instance variables, and any method the surrounding object can call.
  • Ruby calls to_s on whatever value the expression produces, converting it to a String.
  • The literal segments and the converted result are concatenated into one new String object, which is what the string literal evaluates to.

Because this happens through the normal to_s protocol, you can control how your own objects interpolate by defining to_s on a custom class — whatever that method returns is exactly what will appear when an instance is interpolated.

Common Mistakes

Mistake 1: Using single quotes and expecting interpolation

name = "Ruby"
puts 'Hello, #{name}!'

Output:

Hello, #{name}!

This isn’t an error, which makes it sneakier — the code runs fine but prints the literal characters #{name} instead of the value of name, because single-quoted strings don’t evaluate interpolation at all. The fix is to switch to double quotes:

name = "Ruby"
puts "Hello, #{name}!"

Output:

Hello, Ruby!

Mistake 2: Concatenating with + instead of interpolating

count = 3
puts "You have " + count + " items"

This raises TypeError: no implicit conversion of Integer into String at runtime. Unlike interpolation, the + operator on a String requires the other operand to already be a String — it does not call to_s for you. Interpolation sidesteps the problem entirely because it converts every value automatically:

count = 3
puts "You have #{count} items"

Output:

You have 3 items

Mistake 3: Interpolating nil and getting silence instead of an error

user = { name: "Sam", nickname: nil }
puts "Nickname: #{user[:nickname]}"

Output:

Nickname: 

Since nil.to_s is "" (an empty string), a missing value doesn’t raise an exception — it just vanishes, which can hide a bug for a long time. Remember Ruby’s truthiness rule here too: nil and false are the only falsy values, so a plain if user[:nickname] check works correctly for catching this case. Guard against it explicitly with || or a default:

user = { name: "Sam", nickname: nil }
puts "Nickname: #{user[:nickname] || 'N/A'}"

Output:

Nickname: N/A

Best Practices

  • Default to double-quoted strings whenever you need interpolation; reach for single quotes for genuinely static text to signal "nothing dynamic happens here."
  • Prefer interpolation over + concatenation — it’s more readable, handles type conversion for you, and avoids TypeError on non-string values.
  • For numeric formatting (currency, fixed decimals, padding), use format/% or Kernel#format inside the interpolation rather than relying on the default to_s.
  • Guard against nil explicitly with || or the safe navigation operator (&.) when a value might be missing, so it doesn’t silently disappear as an empty string.
  • Pull complex logic out into a well-named local variable or method before interpolating it — #{summarize(order)} reads far better than a sprawling conditional crammed inside #{...}.
  • Never interpolate untrusted user input directly into a shell command, SQL query, or raw HTML; use parameterized APIs (like prepared statements) instead, since interpolation performs no escaping.
  • Use a heredoc (<<~) instead of chained interpolated strings when building multi-line text such as emails or reports.

Practice Exercises

  • Write a method greet(name:, time_of_day:) that returns an interpolated string like "Good morning, Ada!", choosing the greeting word based on time_of_day (:morning, :afternoon, or :evening) with a case expression inside the interpolation or beforehand.
  • Given temperatures = [72, 68, 75], build one interpolated string reporting the average, formatted to one decimal place, e.g. "Average temperature: 71.7". Hint: use sum, size, and %.1f.
  • Predict, then verify by tracing through by hand: what does puts "Result: #{1 == 1.0}" print, and why does interpolating a boolean work the same way as interpolating any other object?

Summary

  • #{...} inside a double-quoted string (or double-quote-like literal) evaluates the enclosed Ruby expression and splices its to_s result into the string.
  • Single-quoted strings never interpolate — #{...} stays literal text.
  • Because everything in Ruby is an object with a to_s method, any expression — a variable, a method call, a hash lookup, even a full conditional — can go inside #{...}.
  • nil.to_s is an empty string, so interpolating a missing value fails silently instead of raising; guard with || when that matters.
  • Interpolation is preferred over + concatenation because it converts types automatically and avoids TypeError.
  • Heredocs, %Q(), dynamic symbols (:"..."), and regex literals all support interpolation using the same #{...} mechanism.