skip to content

How do named capturing groups and MatchResult.destructured work in Kotlin? Show extracting fields by name and via destructuring (a, b).

level: middleimportance: should knowfreq 45%

answer

  1. Named group: (?<name>...)
  2. Read by name: groups["name"]?.value
  3. destructured = positional, groups 1..N
  4. destructured skips group 0
  5. too many components -> runtime exception

basics

~10 s

Name a group with (?<name>...) and read it via match.groups["name"]?.value. You can also pull groups positionally with destructuring: val (a, b) = match.destructured.

solid answer

~40 s

Named groups use the (?<name>...) syntax. After a match you read them through the MatchGroupCollection by string key: result.groups["year"]?.value, which is nullable (null if absent). Separately, MatchResult.destructured returns a Destructured object whose component1()..componentN() map to capturing groups 1..N, enabling Kotlin destructuring: val (year, month, day) = result.destructured. Note destructured is positional, NOT by name, and it skips group 0 (the whole match) — the first destructured component is group 1. Naming groups makes patterns self-documenting and robust to reordering, while destructuring is concise for fixed-arity extraction. A common combo: matchEntire(line)?.let { val (a, b) = it.destructured; ... }. Beware: destructuring more components than there are groups throws at runtime.

code

kotlin · 5 lines
kotlin
val rx = Regex("(?<user>\\w+)@(?<host>[\\w.]+)")
val r = rx.matchEntire("[email protected]")!!
r.groups["user"]?.value   // "alice"
r.groups["host"]?.value   // "example.com"
val (user, host) = r.destructured  // positional: groups 1, 2

go deeper

for a junior

Knows (?<name>...) exists and can read it via groups["name"]?.value.

for a middle

Uses both named access and positional destructuring, and knows destructured is positional starting at group 1.

for a senior

Chooses named groups for maintainability/optional groups and is aware of the runtime IndexOutOfBoundsException risk with over-destructuring.

for a principal

Weighs pattern readability/evolution trade-offs and codifies team conventions (named groups for shared regexes) to reduce indexing bugs.

## Named capturing groups Syntax: `(?<name>...)`. The name lets you read the group without counting parentheses: ```kotlin val date = Regex("(?<y>\\d{4})-(?<m>\\d{2})-(?<d>\\d{2})") val m = date.matchEntire("2026-06-23")!! m.groups["y"]?.value // "2026" m.groups["m"]?.value // "06" m.groups["d"]?.value // "23" ``` `groups["name"]` returns `MatchGroup?` — `null` if that named group did not participate. This is more maintainable than positional indices because inserting a new group earlier in the pattern doesn't shift the names. ## Destructuring via .destructured `MatchResult.destructured` returns a `MatchResult.Destructured` whose `component1()`, `component2()`, ... correspond to capturing **groups 1, 2, ...** (group 0, the full match, is skipped). That enables Kotlin's destructuring declaration: ```kotlin val (y, mo, d) = m.destructured // positional: groups 1,2,3 ``` Key facts: - Destructuring is **positional**, not by name — `(?<y>...)` still maps to `component1()` by position. - Requesting more components than the pattern has capturing groups throws an `IndexOutOfBoundsException` at runtime; it is not checked at compile time. - `destructured` supports up to 10 components (`component1..component10`). ## Choosing between them Use **named groups** for readability/robustness and when groups are optional or reordered. Use **destructuring** for compact, fixed-shape extraction where order is stable: ```kotlin Regex("(\\w+):(\\d+)").matchEntire("port:8080")?.let { val (key, value) = it.destructured } ``` The two compose well: name groups for clarity in the pattern, destructure when consuming.

  • Does destructuring use the group names?
    No. It is purely positional: component1() is capturing group 1 regardless of its name.
  • What happens if you destructure 4 values from a pattern with 2 groups?
    It compiles but throws an IndexOutOfBoundsException at runtime when the missing component is accessed.

saying these in an interview costs you the question

  • Assuming destructured maps by group name
  • Thinking the first destructured component is group 0 (it's group 1)
  • Reading a named group expecting non-null without ?. (it is MatchGroup?)
  • Destructuring more components than capturing groups
  • Confusing (?<name>...) with non-capturing (?:...)

context