In Kotlin, what is the difference between accessing a list element with `list[i]`, `list.getOrNull(i)`, and `list.getOrElse(i) { default }`? When does each fail or succeed?
answer
- [] throws, getOrNull → null, getOrElse → computed default
- getOrElse lambda receives the bad index
- map[key] is null-safe; getValue throws
- Pair getOrNull with ?: Elvis
basics
~10 slist[i] throws an error if the index is out of range. getOrNull(i) returns null instead of throwing. getOrElse(i) { ... } returns a value you compute when the index is out of range.
solid answer
~40 s`list[i]` calls `get(index)` and throws `IndexOutOfBoundsException` when the index is negative or `>= size`. `getOrNull(i)` is a safe variant returning `T?` — it gives `null` for any out-of-bounds index instead of throwing. `getOrElse(i) { idx -> ... }` returns a computed fallback (the lambda receives the bad index) and never throws. Use `[]` when the index is provably valid (e.g. from `indices`), `getOrNull` when you want to branch with `?:` or `?.let`, and `getOrElse` when you have a meaningful default. None of these mutate the list. For maps the analogue is `map[key]` (returns `V?`, no throw) versus `getValue(key)` (throws `NoSuchElementException`) and `getOrDefault`/`getOrElse`.
code
kotlin · 5 linesval xs = listOf(10, 20, 30)
val a = xs[2] // 30
val b = xs.getOrNull(9) // null
val c = xs.getOrElse(9) { it * 0 } // 0 (lambda gets index 9)
val d = xs.getOrNull(9) ?: -1 // -1go deeper
Knows [] throws and getOrNull returns null; can pick the right one for a bounds-uncertain index.
Uses getOrElse with the index parameter and composes getOrNull with Elvis/let; knows the map analogues.
Explains O(1) vs O(n) for List vs Sequence access and argues against exception-driven control flow.
Sets team conventions: prefer total accessors at boundaries, reserve [] for provably-valid indices from indices/lastIndex.
## The three access forms Kotlin's `List` exposes element access through the indexed-access operator and several safe helpers. - **`list[i]`** — sugar for `list.get(i)`. The `get` member is defined on `List<E>` and throws `IndexOutOfBoundsException` when `i < 0` or `i >= size`. Use it only when the index is guaranteed valid. - **`list.getOrNull(i)`** — an extension on `List<T>` returning `T?`. It performs a bounds check (`if (index in indices) get(index) else null`) and returns `null` for any out-of-range index. Great with the Elvis operator: `list.getOrNull(i) ?: fallback`. - **`list.getOrElse(i) { idx -> ... }`** — an extension returning `T`. When in bounds it returns the element; otherwise it invokes the lambda, **passing the requested index**, and returns its result. Never throws. ```kotlin val xs = listOf("a", "b", "c") xs[1] // "b" xs.getOrNull(5) // null (no throw) xs.getOrElse(5) { "?" } // "?" xs.getOrElse(5) { idx -> "missing@$idx" } // "missing@5" ``` ## Why not just catch the exception? Using `getOrNull`/`getOrElse` expresses intent and avoids exception-driven control flow, which is slower and obscures logic. The `?:` and `?.let { }` operators compose cleanly with the nullable result. ## Maps are different For `Map<K, V>`: - `map[key]` returns `V?` and never throws (absent key → `null`). - `map.getValue(key)` throws `NoSuchElementException` if the key is absent. - `map.getOrElse(key) { default }` and `map.getOrDefault(key, default)` supply fallbacks. ## Edge cases - Negative indices are out of bounds for all of these. - `getOrNull`/`getOrElse` are O(1) on `List` (random access) but on a `Sequence` or `Iterable` the analogous `elementAtOrNull` is O(n) because it iterates.
- What does the lambda passed to getOrElse receive?The requested (out-of-bounds) index, so you can build a message or compute an index-dependent default.
- How do you do the same safe access on a Map?Use map[key] (returns V?, never throws) or map.getOrElse(key) { default } / getOrDefault; getValue throws on a missing key.
Like asking for a book by shelf number: [] crashes if the slot is empty, getOrNull hands you nothing, getOrElse hands you a substitute you chose.
saying these in an interview costs you the question
- Claiming list[i] returns null when out of bounds (it throws)
- Saying getOrNull throws an exception
- Wrapping every access in try/catch instead of using the safe accessors
- Thinking getOrElse's lambda receives the element rather than the index
- Confusing map.getValue (throws) with map[key] (null-safe)