Part of Github Copilot with Kotlin

Copilot Kotlin: Filter arrays

Sandy LaneSandy Lane

Video: Copilot Kotlin: Filter arrays by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Kotlin: Filter an Array

arrayOf(...).filter { ... } returns a List<T>, not an Array<T>. The filter family lives on Iterable, 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.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.