Copilot Kotlin: Filter arrays
Video: Copilot Kotlin: Filter arrays by Taught by Celeste AI - AI Coding Coach
Kotlin: Filter an Array
arrayOf(...).filter { ... }returns aList<T>, not anArray<T>. The filter family lives onIterable, and arrays opt in but always produce lists.
A small puzzle that exposes a subtle Kotlin quirk: arrays and lists are different types, and most functional operations on arrays return lists.
The Copilot prompt
fun main() {
val numbers = arrayOf(1, 2, 3, 4, 5, 6)
// Filter to keep only even numbers
Copilot generates:
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // [2, 4, 6]
Walkthrough
Array<Int>.filter { ... } returns a List<Int>. To filter and stay an array:
val evenNumbers: Array<Int> = numbers.filter { it % 2 == 0 }.toTypedArray()
// Or for IntArray specifically:
val evenInts: IntArray = (numbers.toIntArray()).filter { it % 2 == 0 }.toIntArray()
toTypedArray() for Array<T>. toIntArray() / toLongArray() / etc. for primitive variants.
For most code, the List<T> result from filter is what you want. Arrays are mostly there for Java interop and primitive specialization.
Array vs List
| Feature | Array<T> |
List<T> |
|---|---|---|
| Mutable size | No (fixed length) | MutableList yes; List no |
| Mutable elements | Yes (arr[i] = x) |
MutableList yes; List no |
| Functional ops | Returns List<T> (mostly) |
Returns List<T> |
| Primitive specialization | Yes (IntArray, etc.) |
No (boxed List<Int>) |
| Java interop | T[] directly |
java.util.List<T> |
| Pattern matching | No | No |
| Equality (==) | Reference (uses contentEquals for content) |
Content equality |
Use Array<T> (or IntArray, etc.) when:
- Java interop requires it.
- You need fixed-size, primitive-specialized storage.
- Performance profiling shows the boxing is the bottleneck.
Otherwise, prefer List<T> / MutableList<T>.
Filter operations
Same as on List — episode 24 covers them all:
arr.filter { it > 0 } // returns List<T>
arr.filterNot { it > 0 }
arr.filterIndexed { i, v -> ... }
arr.filterIsInstance<String>() // for Array<Any>
All return List<T>. Chain them, then toTypedArray() at the end if you need an array back.
IntArray vs Array
val a: Array<Int> = arrayOf(1, 2, 3) // boxed Integer[] under the hood
val b: IntArray = intArrayOf(1, 2, 3) // primitive int[] under the hood
Array<Int> boxes each element. IntArray is the JVM int[] — same memory layout as Java.
For numeric performance, IntArray is significantly faster. For interop with generic Java APIs, Array<Int> may be needed.
Sequence for huge arrays
val huge = IntArray(1_000_000) { it }
val firstTenEvens = huge.asSequence()
.filter { it % 2 == 0 }
.take(10)
.toList()
asSequence() makes the operation lazy. Without it, huge.filter { it % 2 == 0 } allocates a 500,000-element list before .take(10) could short-circuit.
Mapping back to an array
val numbers = arrayOf(1, 2, 3, 4, 5)
val doubled: Array<Int> = numbers.map { it * 2 }.toTypedArray()
val doubledInt: IntArray = numbers.map { it * 2 }.toIntArray()
toTypedArray() and toIntArray() are the round-trip back.
For IntArray operations that stay as IntArray:
val ints = intArrayOf(1, 2, 3, 4, 5)
val mapped: IntArray = IntArray(ints.size) { ints[it] * 2 } // primitives all the way
The IntArray(size) { initializer } constructor lets you build a fresh IntArray element by element — staying primitive throughout.
Common mistakes
Expecting filter to return an Array. It returns List<T>. Add .toTypedArray() (or .toIntArray() for primitives) if you need an array.
Comparing arrays with ==. arrayOf(1, 2) == arrayOf(1, 2) is false (reference equality). Use arr1.contentEquals(arr2) for content comparison.
Using Array<Int> when IntArray is appropriate. Array<Int> boxes. For tight numeric loops, IntArray is faster.
Calling filter on IntArray and getting List<Int>. Yes, that's what happens. Boxing happens at the conversion. To stay primitive, use IntArray(...) constructor patterns.
Forgetting Array<T> is fixed-size. No add or removeAt. To resize, build a new array.
What's next
Episode 34: Add a method to a class. A small OOP puzzle — define a class, add a method that uses its state.
Recap
Array<T>.filter { ... } returns List<T>. Convert back with toTypedArray() (or toIntArray() for primitives). Arrays are fixed-size, primitive-specializable, but lose the functional polish of lists. Use IntArray for numeric performance; Array<T> for generic interop with Java; List<T> / MutableList<T> for everything else. arr.contentEquals(other) for content comparison; == is reference equality.
Next episode: add a method to a class.