skip to content

Explain how Kotlin's String stores characters: is it mutable, how do you read a character at a position, and what happens when you 'modify' a String?

level: middleimportance: must knowfreq 65%

answer

  1. Immutable CharSequence of Char
  2. s[i] is the get operator → Char
  3. Transforms return a NEW String
  4. StringBuilder / buildString for loops
  5. == structural vs === reference

basics

~20 s

A String is an immutable sequence of characters. You read a character with square brackets, like s[0]. You can't change a String in place; any 'change' creates a brand-new String and leaves the original untouched.

solid answer

~50 s

String in Kotlin is immutable: once created its characters never change. It's a sequence of Char, so it implements CharSequence and you index it with the get operator: s[i] returns a Char (throwing IndexOutOfBoundsException for bad indices, or use getOrNull/getOrElse). length is a property, and you can iterate (for (ch in s)) because String is Iterable<Char>. Operations like uppercase(), replace(), plus (+), substring(), and trim() all return new String instances — the receiver is never mutated. To build strings efficiently in a loop, use StringBuilder (or the buildString { } helper) which IS mutable, then call toString(). Because Strings are immutable they're safe to share across threads and usable as map keys; literal equal strings are also interned by the JVM, so == (which calls equals/structural equality) is the right comparison, not === (reference identity).

code

kotlin · 7 lines
kotlin
val s = "hello"
println(s[1])           // e
println(s.length)       // 5
val up = s.uppercase()  // HELLO
println(s)              // hello  (unchanged!)
// s[0] = 'H'           // does NOT compile
val built = buildString { append("ab"); append('c') } // abc

go deeper

for a junior

States String is immutable and indexed with [], and that transforms return new strings.

for a middle

Explains the O(n²) concatenation trap, StringBuilder/buildString, and == vs ===.

for a senior

Connects immutability to thread-safety, map-key safety, and JVM string interning; mentions getOrNull/CharSequence.

for a principal

Reasons about memory/allocation cost, when a CharArray or rope-like builder matters, and UTF-16 length vs code-point count nuances.

## What 'immutable sequence of Chars' means A Kotlin `String` is a read-only ordered collection of `Char` values. "Immutable" means **the contents can never be modified after construction** — there is no `s[0] = 'x'` (that line does not compile, because `String.set` does not exist). "Sequence of Chars" means it implements `CharSequence` and exposes: - `length: Int` — number of `Char` (UTF-16) units. - `get(index: Int): Char` via the `[]` operator — `s[2]`. - iteration: `for (ch in s) { ... }` since String is `Iterable<Char>`. ## Indexing ```kotlin val s = "hello" val c: Char = s[1] // 'e' (operator get) val safe = s.getOrNull(99) // null instead of throwing val safe2 = s.getOrElse(99) { '?' } ``` An out-of-range `s[i]` throws `StringIndexOutOfBoundsException` (a subtype of `IndexOutOfBoundsException`). Valid indices are `0 until s.length`. ## 'Modifying' creates a new String Every transformation returns a **new** object; the original is unchanged: ```kotlin val a = "abc" val b = a.uppercase() // "ABC" — a is still "abc" val d = a.replace('a', 'X') // "Xbc" val e = a + "d" // "abcd" val f = a.substring(1) // "bc" ``` This is why repeatedly concatenating in a loop (`result += x`) is O(n²) — each `+=` allocates a new String. Use a mutable builder instead: ```kotlin val sb = StringBuilder() for (x in items) sb.append(x) val result = sb.toString() // or the idiomatic helper: val r = buildString { for (x in items) append(x) } ``` `StringBuilder` is the *mutable* counterpart; `CharArray` is another mutable option you can convert to/from a String (`s.toCharArray()`, `String(charArray)`). ## Consequences of immutability - **Thread-safe to share** — no defensive copies needed. - **Safe as map keys / set elements** — hash code is stable. - **Equality:** use `==` (structural, calls `equals`) for value comparison; `===` checks reference identity. Two separately built equal strings are `==` but may not be `===`. ## Quick API reference `first()`, `last()`, `take(n)`, `drop(n)`, `substring(a, b)`, `indexOf`, `contains` (and the `in` operator), `split`, `lines`, `chars`/`forEach`. All non-mutating. ## Key takeaways - String = immutable `CharSequence`; index with `[]`, length via `.length`. - No in-place edit; transforms return new Strings. - Loop-building → `StringBuilder` / `buildString`. - Compare with `==`, not `===`.

  • Why is building a String with += in a loop a bad idea?
    Each += allocates a new String and copies all prior characters, giving O(n²) work and garbage. Use StringBuilder/buildString for O(n).
  • Should you compare strings with == or ===?
    Use == for value equality (it calls equals). === checks reference identity and can be false for equal-but-distinct String objects.

A String is like a printed page: to 'edit' it you photocopy onto a fresh page with changes — the original print stays exactly as it was.

saying these in an interview costs you the question

  • Believing s[0] = 'x' compiles / mutates the String
  • Thinking uppercase()/replace() change the original
  • Using === to compare string contents
  • Concatenating in a loop with += and calling it efficient
  • Confusing length (property) with a method call length()

context