Strings and string templates
Text is most of what programs handle — names, addresses, messages, JSON. Kotlin's strings are pleasant to work with, and two features in particular — templates and raw strings — remove the noise that makes string handling ugly in older languages. This lesson covers them and the methods you will reach for daily.
String templates — stop gluing strings together
You met $name in the first program. Here it is in full. Inside a string, $ inserts a value:
val name = "Kavita"
val age = 33
println("$name is $age years old") // Kavita is 33 years old
For anything more than a plain variable — a property, a method call, an expression — wrap it in
${...}:
val price = 199.99
val qty = 3
println("Total: ₹${price * qty}") // Total: ₹599.97
println("Name has ${name.length} letters") // Name has 6 letters
println("${name.uppercase()} from Pune") // KAVITA from Pune
Compare with the string concatenation you would write in older Java —
name + " is " + age + " years old" — and the template is obviously clearer. Prefer templates
over + for building strings. They read like the sentence they produce, and there is nowhere to
misplace a space or a +.
To print a literal dollar sign, escape it: "\$5" prints $5.
Raw strings — for paths, JSON, and anything with quotes or newlines
A normal string needs escaping for quotes, backslashes and newlines, which gets ugly fast:
val ugly = "She said \"namaste\"\nand left" // escapes everywhere
A raw string, in triple quotes, takes the text exactly as written — no escaping, real newlines:
val greeting = """
She said "namaste"
and left
""".trimIndent()
Raw strings are perfect for multi-line text, file paths ("""C:\Users\Kavita""" — no doubled
backslashes), JSON, SQL, and regular expressions. trimIndent() removes the common leading
indentation so the string lines up with your code without carrying the code's indentation into the
value — you will use it on almost every raw string. Templates work inside raw strings too:
val name = "Kavita"
val json = """
{ "name": "$name", "city": "Pune" }
""".trimIndent()
The methods you will use constantly
Strings carry a large, useful API. The ones worth knowing from the start:
val s = " Namaste, Pune "
println(s.length) // 17 (includes the spaces)
println(s.trim()) // "Namaste, Pune"
println(s.uppercase()) // " NAMASTE, PUNE "
println(s.lowercase()) // " namaste, pune "
println(s.trim().split(", ")) // [Namaste, Pune] — a List<String>
println("Pune".contains("un")) // true
println("Pune".startsWith("Pu")) // true
println("Pune".replace("P", "T")) // "Tune"
println("a,b,c".split(",")) // [a, b, c]
println("Pune".reversed()) // "enuP"
Two things to internalise. split returns a List<String>, not another string — it is your
bridge from text to a collection you can process (the collections module lives on this). And
every one of these returns a new string; the original is untouched, because strings are
immutable. s.uppercase() does not change s — it hands you a new string. If you want the
result, you must keep it:
val name = "kavita"
name.uppercase() // computed and thrown away!
val proper = name.uppercase() // kept — this is what you want
Forgetting to keep the result is a real beginner bug, and it is silent — no error, the change just does not happen.
Indexing and characters
A string is a sequence of Chars, accessible by index (from zero):
val s = "Pune"
println(s[0]) // P — a Char, in single quotes when written literally
println(s.first()) // P
println(s.last()) // e
println(s.indexOf("n")) // 2
for (c in "Pune") print("$c ") // P u n e
Indexing out of range throws — s[99] on a four-character string is a
StringIndexOutOfBoundsException. Prefer first(), last(), getOrNull(index) and iteration over
manual indexing when you can; they are harder to get wrong.
Comparing and building
Compare strings with == (value equality, from the types lesson — it does what you mean):
println("Pune" == "Pune") // true
println("Pune".equals("pune", ignoreCase = true)) // true — case-insensitive when you ask
When you genuinely need to assemble a string in a loop, StringBuilder avoids creating a new string
each time — but reach for it only when you have measured a need; for ordinary work, templates and
joinToString are clearer:
val cities = listOf("Pune", "Mumbai", "Nagpur")
println(cities.joinToString(", ")) // Pune, Mumbai, Nagpur
println(cities.joinToString(prefix = "[", postfix = "]")) // [Pune, Mumbai, Nagpur]
joinToString is the clean way to turn a list back into text, and it beats a hand-written loop with
a StringBuilder for readability every time.
Check your work
How a string template inserts a plain variable, and an expression. $name for a variable;
${...} for a property, call or expression.
Why prefer templates over +. They read like the sentence they produce, with nowhere to
misplace a space or +.
What a raw string (triple quotes) is for. Multi-line text, paths, JSON, SQL, regex — no escaping, real newlines.
What trimIndent() does. Removes the common leading indentation so the value does not carry the
code's indentation.
What split returns. A List<String> — the bridge from text to collections.
Why s.uppercase() without keeping the result does nothing useful. Strings are immutable; every
method returns a new string and leaves the original untouched.
What happens on s[99] for a short string. A StringIndexOutOfBoundsException — prefer
first/last/getOrNull.
How to compare strings, including case-insensitively. == for value equality;
equals(other, ignoreCase = true) when case should not matter.
The clean way to turn a list into text. joinToString, not a hand-written loop.
Practice
- Build a sentence with a template using a name and an age. Then rewrite it with
+and decide which you prefer. - Use
${...}to embed a calculation and a method call in one string. - Write a multi-line raw string with quotes in it, apply
trimIndent(), and print it. - Call
uppercase()on a string without keeping the result, print the original, and confirm it is unchanged. Then keep the result and print that. splita comma-separated string and confirm you get aList. Print its size.- Index a string at position 0 and at a position past its end. Read the exception for the second.
- Compare two strings that differ only in case, first with
==(false) then withequals(..., ignoreCase = true)(true). - Turn
listOf("Pune", "Mumbai", "Nagpur")into"Pune | Mumbai | Nagpur"withjoinToString.
Official documentation
- Kotlin — Strings — Templates, raw strings, and the string API.
- Kotlin — String templates —
$and${...}in detail. - Kotlin standard library — String — Every method, with signatures.
- Kotlin standard library — joinToString — The list-to-text tool.
Next: if, when, and expressions that return values.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship