String Interpolation in Scala
Use string interpolation in Scala — Scala for Python Developers.
Focus: use string interpolation in scala
You've been writing %s-style format strings in Python for years, and now Scala throws s"..." at you and expects you to just... get it. The pain is real: your first attempt at injecting a variable into a Scala string either prints the literal $name or blows up with a compiler error about an unclosed string literal. But string interpolation in Scala isn't just a syntax quirk — it's a type-safe, composable feature that beats Python's f-strings in several important ways. By the end of this lesson, you'll not only use s, f, and raw interpolators fluently, but you'll also know when to reach for a custom interpolator to keep your code clean and safe.
The problem this lesson solves
Python developers reaching for f"{name}" in Scala hit a wall. The $ syntax looks familiar but behaves differently, and the %-style formatting you rely on with %s or .format() doesn't exist in Scala's standard library. Worse, simple mistakes — like forgetting to escape a $ in regex or SQL — produce silent runtime bugs or cryptic compiler errors. Without a solid mental model, you'll find yourself concatenating strings with +, which works but is ugly, error-prone, and slow. This lesson gives you the exact tools to stop fighting the syntax and start writing idiomatic Scala that's safer and more maintainable.
Core concept / mental model
Think of string interpolation in Scala as a macro-powered template engine built into the compiler. Unlike Python's f-strings, which are a special syntax that the interpreter evaluates, Scala interpolators are just ordinary methods that the compiler rewrites into a series of string concatenations or formatting calls. The prefix before the string (like s, f, or raw) isn't magic — it's a method call on StringContext.
Here's the mental shift: in Python, you write f"Hello {name}" and the language handles it. In Scala, you write s"Hello $name" and you're actually calling StringContext("Hello ", "").s(name). The compiler parses the string, splits it into literal parts and expressions, then invokes the interpolator method with each expression's value as an argument. This design means:
- Type safety: The compiler checks that every
$expressioncompiles and returns a value. - Extensibility: You can define your own interpolators to do anything — escaping, logging, building SQL queries — just like
sandfdo. - No runtime parsing: The interpolation happens at compile time, so there's no performance hit compared to building the string manually.
For a Python developer, the closest mental model is f-strings, but with an extra layer: you can swap the f for s, raw, or a custom prefix, and each changes how the string is built.
How it works step by step
String interpolation in Scala follows a straightforward three-step process that the compiler performs for you:
- Parse — The compiler splits the string literal at each
$or$identifierand$ {expression}. Everything between those points becomes a literal string part. - Generate — It constructs a
StringContextwith the literal parts and calls the interpolator method (e.g.,.s(...)) with each expression's value as an argument. - Evaluate — The interpolator method runs, building the final string. For
s, it just callstoStringon each value. Forf, it appliesprintf-style formatting.
Let's see this in action with a simple example. When you write s"Hello $name", the compiler internally does something like StringContext("Hello ", "").s(name). The s method on StringContext takes the values and joins them with the literal parts.
Here are the three built-in interpolators and what they do:
s(simple) — Replaces$varand${expr}with the string representation of the value. No escaping of special characters.f(formatted) — Works like Python's%formatting. You include format specifiers like%d,%f,%safter the expression, and it appliesString.format-style logic.raw(raw) — Similar tos, but doesn't process escape sequences like\nor\t. Everything is displayed literally.
Pro tip: Think of
sas your default,fwhen you need precision formatting, andrawwhen you're dealing with regex patterns or file paths where backslashes matter.
Hands-on walkthrough
Let's get practical. We'll start with the s interpolator, then move to f and raw, and finally build a small custom interpolator. All examples are complete and runnable in the Scala REPL or a Scala 3 project.
1. The basic s interpolator
// Basic variable interpolation
val name = "Ada"
val age = 36
val greeting = s"Hello, $name! You are $age years old."
println(greeting)
// Output: Hello, Ada! You are 36 years old.
// Expressions with ${...}
val x = 10
val y = 20
println(s"Sum: ${x + y}")
// Output: Sum: 30
Notice you can embed any valid Scala expression inside ${...}. The compiler type-checks it, so a typo like s"$name" when name is undefined fails at compile time.
2. The f interpolator for formatted output
val pi = 3.14159265359
val count = 42
// Simple formatting
val formatted = f"Pi is approximately $pi%.2f"
println(formatted)
// Output: Pi is approximately 3.14
// Multiple placeholders with widths
val table = f"${'A'}%-5s${100}%5d"
println(table)
// Output: A 100
The f interpolator uses the same format specifiers as Java's String.format. For Python developers, think %0.2f becomes %.2f (no % before the expression — the % is part of the specifier).
3. The raw interpolator for escaping
// Without raw, escape sequences are processed
val path = s"C:\\Users\\John"
println(path)
// Output: C:\Users\John
// With raw, backslashes stay as-is
val rawPath = raw"C:\Users\John"
println(rawPath)
// Output: C:\Users\John (same, because \n isn't here, but see next)
val newline = "\n"
val sValue = s"Line1$newlineLine2"
println(sValue)
// Output: Line1
// Line2
val rawValue = raw"Line1\nLine2"
println(rawValue)
// Output: Line1\nLine2 (backslash-n stays literal)
This is crucial for regex patterns. In Python, you'd use r"\d+". In Scala, use raw"\d+" to avoid double escaping.
4. Custom interpolator: a mini SQL builder
import scala.util.Try
extension (sc: StringContext)
def sql(args: Any*): String =
val parts = sc.parts
val escaped = args.map {
case s: String => s.replace("'", "''")
case other => other.toString
}
parts.head + escaped.zip(parts.tail).map { case (arg, part) => s"'$arg'$part" }.mkString
val table = "users"
val id = 42
val query = sql"SELECT * FROM $table WHERE id = $id"
println(query)
// Output: SELECT * FROM users WHERE id = 42
val name = "O'Reilly"
val safeQuery = sql"SELECT * FROM $table WHERE name = $name"
println(safeQuery)
// Output: SELECT * FROM users WHERE name = 'O''Reilly'
This shows how you can intercept values and apply escaping logic automatically — something Python f-strings can't do without helper functions.
Compare options / when to choose what
| Interpolator | Python equivalent | Use when | Example |
|---|---|---|---|
s |
f-string |
Default for any dynamic string | s"Hello $name" |
f |
% formatting |
Need precise numeric formatting, alignment | f"Pi = $pi%.2f" |
raw |
r"..." raw string |
Regex, file paths, Windows paths | raw"\d+" |
| Custom | Helper function / template | Need validation, escaping, or domain-specific logic | sql"..." |
When to choose what:
- Use
sfor 95% of cases — it's concise and readable. - Use
fwhen you need to control decimal places, padding, or locale-specific formatting. - Use
rawwhen backslashes would otherwise be interpreted as escape sequences. - Build a custom interpolator when you need automatic escaping (SQL, HTML), logging, or internationalization — it keeps your business logic clean.
Pro tip: For SQL queries, never use string concatenation or simple
s""— always use a custom interpolator or a prepared statement library. It's your first line of defense against injection attacks.
Troubleshooting & edge cases
1. $ inside regex or SQL strings
If you write s"SELECT * FROM t WHERE name = '$name'" and name contains a quote, you get broken SQL or worse — a SQL injection. Always escape or use a custom interpolator.
For regex, s"\\d+" is a nightmare. Use raw"\d+" instead.
// Wrong
val pattern = s"\\d+" // two backslashes in source
// Right
val pattern = raw"\d+" // one backslash
2. $ in currency amounts or template literals
If you need a literal $ in your string, you must escape it with $$. This is a common gotcha:
val price = 19.99
println(s"Price: $$$price") // Wrong! Compile error
println(s"Price: $$${price}") // Wrong too
println(s"Price: $$${price}") // Actually, this prints "Price: $19.99"
// To properly escape:
println(s"Price: \$$price") // Escapes the dollar
// Or use: s"Price: {price}"
// In Scala 3, use s"Price: $$price"
In Scala 2, use \$; in Scala 3, use $$. Always test in your version.
3. Compile error: unclosed string literal
This happens when your interpolation expression contains unmatched braces or quotes. Use ${...} for complex expressions and avoid nesting quotes inside.
// Wrong
s"${someMap("key")}" // may work, but careful with braces
// Better
val value = someMap("key")
s"$value"
4. Null values
s"$nullValue" prints null, not an empty string. If you want empty, use Option and getOrElse.
val maybeName: Option[String] = None
println(s"Hello ${maybeName.getOrElse("")}")
// Output: Hello
What you learned & what's next
You've mastered the three built-in interpolators (s, f, raw), learned how to create custom interpolators for safety, and can debug common pitfalls like escaping $ and dealing with nulls. You can now:
- Explain the core idea behind string interpolation in Scala — that interpolators are compile-time macros, not runtime magic.
- Substitute
f"{name}"in Python withs"$name"in Scala automatically. - Choose the right interpolator for formatting, escaping, and domain-specific needs.
- Apply best practices for security and readability.
Next up in the track, you'll learn about pattern matching — a supercharged version of Python's if/elif that works beautifully with interpolated strings for parsing and validation. Get ready to match against structured data with confidence.
Practice recap
Open the Scala REPL and create a small function that takes a user's name and age, then uses f interpolator to print a formatted bio with the name left-aligned and age as a 3-digit number. Then, extend it with a custom sql interpolator that escapes quotes in a sample query. Test edge cases like a name with an apostrophe.
Common mistakes
- Forgetting to escape a literal
$— use\$in Scala 2 or$$in Scala 3, otherwise you get a compile error. - Using
s"..."for regex patterns with backslashes — always useraw"..."to avoid double escaping. - Thinking that interpolation handles SQL escaping automatically — it doesn't; use a custom interpolator or a prepared statement.
- Assuming
s"$null"produces an empty string — it printsnull; useOptionandgetOrElse.
Variations
- Use
String.formatfrom Java for complex locale-aware formatting if you prefer a static method. - Define your own interpolator with
extension(Scala 3) or implicit classes (Scala 2) for domain-specific logic. - Use the
spinterpolator from thescalapackage (Scala 2.13+) for pretty-printed string representations of values.
Real-world use cases
- Generating dynamic SQL queries safely by using a custom
sqlinterpolator that escapes input values automatically. - Creating user-facing log messages with structured data — use
s"User $id at ${timestamp} action $action"for readable logs. - Building URL paths or REST API requests with
f"$base/api/$resource?id=${id}"for precise query string formatting.
Key takeaways
- Scala's interpolation is compile-time — the compiler generates a
StringContextcall, giving type safety. - Use
sfor simple substitution,ffor formatted output,rawfor regex/paths, and custom interpolators for safety. - Always escape literal
$as\$(Scala 2) or$$(Scala 3) to avoid errors. - Custom interpolators let you intercept and transform arguments — perfect for SQL escaping or HTML encoding.
- For null-safe interpolation, use
OptionandgetOrElseto avoid printingnull.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.