skip to content

Explain substringBefore, substringAfter, substringBeforeLast, and substringAfterLast, including their delimiter and missingDelimiterValue behavior.

level: middleimportance: should knowfreq 60%

answer

  1. Before = left side, After = right side
  2. Last = last occurrence instead of first
  3. Missing delimiter -> returns WHOLE string by default
  4. Pass "" to opt out of the surprising default
  5. Delimiter can be Char or String, no regex

basics

~20 s

They slice a string around a chosen marker. 'Before' keeps the part to the left of the marker, 'After' keeps the part to the right. The 'Last' versions look for the last occurrence instead of the first.

solid answer

~40 s

These extensions split a String at a delimiter (Char or String) without regex. substringBefore returns everything before the FIRST delimiter; substringAfter returns everything after the FIRST. substringBeforeLast / substringAfterLast use the LAST occurrence. The key subtlety is the missingDelimiterValue parameter: if the delimiter is not found, the function returns missingDelimiterValue, which DEFAULTS to the original (whole) string — not an empty string. So "file".substringAfter('.') returns "file", not "". This default surprises people and is a common bug when, for example, extracting a file extension. You typically pass an explicit default like "" to get safe behavior. They are pure, allocation-light, and ideal for parsing keys, extensions, hostnames — anything where you'd otherwise reach for split() and index access.

code

kotlin · 6 lines
kotlin
val url = "https://example.com:8443/path?q=1"
val scheme = url.substringBefore("://")          // "https"
val hostPort = url.substringAfter("://").substringBefore('/') // "example.com:8443"
val host = hostPort.substringBefore(':')          // "example.com"
val port = hostPort.substringAfter(':', "80")     // "8443", default "80" if absent
val query = url.substringAfterLast('?', "")       // "q=1"

go deeper

for a junior

Knows Before/After slice around a marker and that Last uses the last occurrence.

for a middle

Knows the missingDelimiterValue default is the whole string and passes "" to avoid the file-extension bug.

for a senior

Chooses these over split for clarity/perf, chains them to parse URLs/keys, and reasons about edge cases.

for a principal

Establishes parsing utilities/conventions so the surprising default cannot cause silent data bugs across the codebase.

## The four functions All are `String` extensions that carve a substring around a **delimiter**, which may be a `Char` or a `String`: - `substringBefore(delimiter, missingDelimiterValue = this)` — text before the **first** delimiter. - `substringAfter(delimiter, missingDelimiterValue = this)` — text after the **first** delimiter. - `substringBeforeLast(delimiter, missingDelimiterValue = this)` — text before the **last** delimiter. - `substringAfterLast(delimiter, missingDelimiterValue = this)` — text after the **last** delimiter. ## The critical default: missingDelimiterValue When the delimiter is **not present**, the function does **not** return an empty string. It returns `missingDelimiterValue`, which **defaults to the entire receiver string** (`this`). ```kotlin "a.b.c".substringAfter('.') // "b.c" (after FIRST dot) "a.b.c".substringAfterLast('.') // "c" (after LAST dot) "a.b.c".substringBefore('.') // "a" "a.b.c".substringBeforeLast('.') // "a.b" "noextension".substringAfter('.') // "noextension" <-- whole string! "noextension".substringAfter('.', "") // "" (explicit default) ``` ## Why this matters — file extension bug A naive `path.substringAfterLast('.')` to get an extension returns the **whole filename** when there is no dot — so a file named `README` reports its extension as `README`. Pass an explicit `""`: ```kotlin fun extension(name: String) = name.substringAfterLast('.', "") extension("photo.png") // "png" extension("README") // "" ``` ## First vs Last Choose based on whether the delimiter can repeat. For `user@host@weird`, `substringAfter('@')` gives `host@weird`, while `substringAfterLast('@')` gives `weird`. For a key/value like `Authorization: Bearer x`, `substringAfter(':')` cleanly grabs the value. ## Properties - No regex, no compilation cost — pure character/string search. - Receiver is unchanged (strings are immutable); a new `String` is returned. - Cleaner than `split(...).first()` / index access when you only need one side, and avoids `IndexOutOfBoundsException` on missing parts.

  • What does "hello".substringBefore('z') return and why?
    "hello". The delimiter 'z' is absent, so it returns missingDelimiterValue, which defaults to the whole receiver string.
  • How do you grab both halves around a delimiter cleanly?
    Combine substringBefore and substringAfter, or use split(delimiter, limit = 2) which returns both parts in a list.

saying these in an interview costs you the question

  • Assuming a missing delimiter returns an empty string
  • Confusing first-occurrence vs last-occurrence variants
  • Reaching for regex/split for a simple single-delimiter slice
  • Thinking the function mutates the original string

context