Kotlin has both arrays and a separate collections framework (List, Set, Map), and for most application code, the collections framework is the better default. Here’s the actual difference, not just “use collections instead.”
Arrays: Fixed Size, Direct Memory Layout
An Array<T> has a size fixed at creation. You can change what’s stored at each index, but you can’t grow or shrink it — there’s no add() or remove():
val numbers = arrayOf(1, 2, 3)
numbers[0] = 10 // fine, mutating an existing slot
// numbers.add(4) // doesn't compile, no such method
Arrays map closely to how the JVM represents arrays natively, which is why they exist at all in a language that otherwise favors collections — they’re the right choice when you’re interfacing with Java APIs that expect an array, or in narrow performance-critical code where avoiding boxing matters (which is also why Kotlin has IntArray, ByteArray, etc. — dedicated array types for primitives that avoid the overhead of boxing each element as an object).
List: Kotlin’s Default for Sequences of Data
val names: List<String> = listOf("Alice", "Bob") // read-only
val mutableNames: MutableList<String> = mutableListOf("Alice", "Bob")
mutableNames.add("Carol")
Unlike Java, Kotlin’s List is read-only by default — listOf() gives you something with no add/remove methods at all. You have to explicitly ask for mutableListOf() or MutableList to get a modifiable one. This distinction is one of Kotlin’s most useful safety features: a function that takes a plain List parameter is guaranteed not to mutate it, which the type system enforces rather than just a convention.
List also comes with a large set of built-in operations that arrays don’t have directly — map, filter, sortedBy, groupBy, and dozens more, all without mutating the original.
val adults = users.filter { it.age >= 18 }.map { it.name }
Set and Map
The same read-only-by-default pattern applies to the other two collection types: Set for a group of unique, unordered values, and Map for key-value pairs.
val uniqueTags: Set<String> = setOf("kotlin", "flutter", "kotlin") // duplicates dropped
val scores: Map<String, Int> = mapOf("Alice" to 90, "Bob" to 85)
Which One Should You Use?
Default to List (or Set/Map) for application code — the built-in operations and the mutable/read-only distinction are worth having in almost every case. Reach for an array specifically when a Java API requires one, when you need a fixed-size primitive array for performance (IntArray and friends), or when the collection’s size is genuinely fixed for the lifetime of the program and you want that guaranteed by the type rather than just by convention.