Question
In Kotlin, what is the difference between List and Array?
They seem to support many of the same operations, such as looping and using functions like filter. Is there any real difference in behavior, performance, or typical usage?
val names1 = listOf("Joe", "Ben", "Thomas")
val names2 = arrayOf("Joe", "Ben", "Thomas")
for (name in names1)
println(name)
for (name in names2)
println(name)
When should I use a List instead of an Array, and vice versa?
Short Answer
By the end of this page, you will understand how List and Array differ in Kotlin, even though they often look similar in simple examples. You will learn how they differ in size behavior, mutability, APIs, performance characteristics, interoperability, and common real-world usage so you can choose the right one confidently.
Concept
List and Array in Kotlin are both ordered collections, but they are not the same kind of type.
- An
Arrayis a fixed-size container that stores elements by index. - A
Listis a collection interface designed for working with ordered data at a higher level.
The key difference
The biggest practical difference is this:
Arrayhas a fixed size after creation.Listrepresents a read-only list interface in Kotlin.
That means:
val arr = arrayOf("A", "B", "C")
arr[0] = "Z" // allowed
// arr.add("D") // not allowed
val list = listOf("A", "B", "C")
// list[0] = "Z" // not allowed
// list.add("D") // not allowed
If you want a resizable list, Kotlin provides MutableList:
val items = mutableListOf("A", "B", "C")
items.add("D")
items[0] = "Z"
Why they can feel similar
They both support:
- iteration with
for - indexing
- many standard library functions like
filter,map, andforEach
This is why they often look interchangeable in beginner examples.
Why the distinction matters
In real programs, choosing the right type affects:
- whether the size can change
- whether elements can be replaced
- how your API communicates intent
- Java interoperability
- performance in specific cases
Important Kotlin design idea
Kotlin encourages developers to program to collection interfaces like List instead of concrete storage types like Array, unless array-specific behavior is actually needed.
That usually makes code easier to read, safer to use, and more flexible.
Mental Model
Think of an Array like a row of numbered lockers.
- The number of lockers is fixed.
- You can open locker
0,1, or2and replace what is inside. - But you cannot suddenly create more lockers in the same row.
Think of a List like a playlist view.
- You usually care about the ordered sequence of items.
- A read-only
Listsays: “Here are the songs you can look at.” - A
MutableListsays: “You may also add, remove, or reorder songs.”
So:
- Use
Arraywhen you need a fixed-size indexed container. - Use
Listwhen you want to represent ordered data in a more general and idiomatic Kotlin way.
Syntax and Examples
Creating each type
val numbersArray = arrayOf(1, 2, 3)
val numbersList = listOf(1, 2, 3)
val mutableNumbers = mutableListOf(1, 2, 3)
Accessing elements
println(numbersArray[0]) // 1
println(numbersList[0]) // 1
Changing elements
val colors = arrayOf("red", "green", "blue")
colors[1] = "yellow"
println(colors.joinToString()) // red, yellow, blue
A read-only List does not allow element replacement:
val names = listOf("Joe", "Ben", "Thomas")
// names[0] = "Sam" // error
A MutableList does:
val names = mutableListOf("Joe", "Ben", "Thomas")
names[0] = "Sam"
names.add("Lina")
println(names) // [Sam, Ben, Thomas, Lina]
Similar operations on both
Both arrays and lists work with many collection-style operations:
val arr = arrayOf("Joe", "Ben", "Thomas")
val list = listOf("Joe", "Ben", "Thomas")
println(arr.filter { it.startsWith("T") }) // [Thomas]
println(list.filter { it.startsWith("T") }) // [Thomas]
Converting between them
val arr = arrayOf("a", "b", "c")
val list = arr.toList()
val list2 = listOf("x", "y", "z")
val arr2 = list2.toTypedArray()
Primitive arrays
Kotlin also has special primitive array types:
val intArray = intArrayOf(1, 2, 3)
These are different from Array<Int> and are useful for performance and Java interop.
Step by Step Execution
Consider this example:
val namesArray = arrayOf("Joe", "Ben", "Thomas")
val namesList = listOf("Joe", "Ben", "Thomas")
println(namesArray[1])
println(namesList[1])
val filteredArray = namesArray.filter { it.length > 3 }
val filteredList = namesList.filter { it.length > 3 }
println(filteredArray)
println(filteredList)
What happens step by step
namesArrayis created as anArray<String>with 3 fixed slots.namesListis created as a read-onlyList<String>with 3 elements.namesArray[1]reads the value at index1, which is"Ben".namesList[1]also reads the value at index1, which is"Ben".filter { it.length > 3 }checks each element."Joe"has length 3, so it is skipped."Ben"has length 3, so it is skipped."Thomas"has length 6, so it is kept.
filteredArraybecomes aList<String>, not an array.filteredListalso becomes aList<String>.
Important detail
Even when you start with an array:
val result = namesArray.filter { it.startsWith("J") }
result is a List<String>, because many Kotlin standard functions return collection types rather than preserving Array.
That is one reason arrays and lists may feel similar at first, but their core types still differ.
Real World Use Cases
When List is commonly used
List is the default choice in most Kotlin application code.
Examples
- Returning a list of users from a service
- Passing a collection of validation errors
- Working with API results
- Rendering ordered UI items
- Filtering and mapping business data
fun getActiveUsers(): List<String> {
return listOf("Ana", "Mila", "Ravi")
}
When Array is commonly used
Array is more common when fixed-size storage or interoperability matters.
Examples
- Interacting with Java APIs that expect arrays
- Storing a fixed number of values
- Working with matrix-like indexed data
- Performance-sensitive code with primitive arrays
- Using vararg-related operations
val rgb = arrayOf(255, 128, 64)
println(rgb[0])
When MutableList is better than both
If you need to add and remove items dynamically, use MutableList.
val tasks = mutableListOf("Write", "Test")
tasks.add("Deploy")
tasks.remove("Test")
Real Codebase Usage
In real Kotlin codebases, developers usually prefer List in public APIs and only use Array when there is a specific reason.
Common pattern: expose List, keep flexibility
fun loadNames(): List<String> {
return listOf("Joe", "Ben", "Thomas")
}
Why this is common:
- callers only need to read the values
- implementation can later change internally
- the API clearly communicates intent
Common pattern: use MutableList during construction
fun collectErrors(input: List<String>): List<String> {
val errors = mutableListOf<String>()
for (item in input) {
if (item.isBlank()) {
errors.add("Blank value found")
}
}
return errors
}
This pattern is common because:
- building data often needs mutation
- returning
Listavoids exposing mutation unnecessarily
Common pattern: arrays for vararg and Java interop
fun printAll(vararg names: String) {
for (name in names) println(name)
}
val arr = arrayOf("Joe", "Ben")
printAll(*arr)
The spread operator * is often used when passing an array to a vararg parameter.
Common pattern: primitive arrays in performance-sensitive code
val scores = intArrayOf(10, 20, 30)
Primitive arrays avoid boxing overhead that can happen with List<Int> or Array<Int> in some cases.
Guard clauses and validation with lists
fun processNames(names: List<String>) {
if (names.isEmpty()) return
val validNames = names.filter { it.isNotBlank() }
println(validNames)
}
This is more idiomatic than requiring arrays unless the function truly needs one.
Common Mistakes
1. Thinking List and MutableList are the same
Broken expectation:
val items = listOf("a", "b")
// items.add("c") // error
Why it happens:
listOf()gives a read-onlyList- it does not support
addorremove
Use this instead:
val items = mutableListOf("a", "b")
items.add("c")
2. Thinking arrays are resizable
Broken code:
val arr = arrayOf(1, 2, 3)
// arr.add(4) // error
Why it happens:
- arrays have fixed size
- you can replace existing elements, but not grow the array directly
3. Assuming filter() on an array returns an array
Broken assumption:
val arr = arrayOf("a", "bb", "ccc")
val result = arr.filter { it.length > 1 }
result is a List<String>, not an Array<String>.
If you need an array afterward:
val resultArray = arr.filter { it.length > 1 }.toTypedArray()
4. Confusing read-only with truly immutable
val list: List<String> = mutableListOf("a", "b")
This reference is read-only, but the underlying object may still be mutable if another reference can change it.
5. Using Array<Int> when a primitive array is better
val a = Array(3) { it }
val b = intArrayOf(0, 1, 2)
For many numeric cases, IntArray is more efficient than Array<Int>.
6. Exposing arrays in APIs unnecessarily
Less idiomatic:
fun getNames(): Array<String> = arrayOf("Joe", "Ben")
Often better:
fun getNames(): List<String> = listOf("Joe", "Ben")
Use arrays only when array behavior is specifically required.
Comparisons
| Feature | List<T> | MutableList<T> | Array<T> |
|---|---|---|---|
| Ordered collection | Yes | Yes | Yes |
| Access by index | Yes | Yes | Yes |
| Replace element by index | No | Yes | Yes |
| Add/remove elements | No | Yes | No |
| Fixed size | Interface does not imply storage details | No, can grow/shrink | Yes |
| Common in Kotlin APIs | Yes | Sometimes internally | Less often |
| Good for Java interop | Sometimes | Sometimes | Yes |
| Primitive specialized versions | No | No | Yes, like IntArray |
Array<T> vs List<T>
- Choose
List<T>when you want to expose ordered data for reading. - Choose
Array<T>when you need fixed-size indexed storage or Java-style array behavior.
List<T> vs MutableList<T>
- Choose
List<T>when callers should not modify the collection. - Choose
MutableList<T>when you need to add, remove, or update elements.
Array<Int> vs IntArray
| Type | Stores | Typical use |
|---|---|---|
Array<Int> | boxed Int objects | general generic APIs |
IntArray | primitive int values | performance-sensitive numeric work |
Cheat Sheet
Quick rules
listOf(...)creates a read-onlyListmutableListOf(...)creates aMutableListarrayOf(...)creates anArrayArrayhas fixed sizeListcannot be modified through its read-only APIMutableListsupportsadd,remove, and element assignment
Common syntax
val list = listOf("a", "b")
val mutable = mutableListOf("a", "b")
val array = arrayOf("a", "b")
Read values
println(list[0])
println(array[0])
Update values
mutable[0] = "x"
array[0] = "x"
Add/remove
mutable.add("c")
mutable.remove("a")
Convert
val listFromArray = array.toList()
val arrayFromList = list.toTypedArray()
Remember
filter,map, and similar operations often returnList- use
Listby default in Kotlin APIs - use
Arraywhen fixed-size storage or interop is needed - use
IntArray,DoubleArray, etc. for primitive performance cases
FAQ
Is Kotlin List the same as Java array?
No. A Kotlin List is a collection interface for ordered elements, while an array is a fixed-size indexed container.
Can I change elements inside a Kotlin List?
Not if it is a read-only List. Use MutableList if you need to replace, add, or remove elements.
Why do Array and List both work in for loops?
Because both are iterable in Kotlin, so the for syntax works naturally with each.
Does filter() return the same type I started with?
Not always. For arrays, filter() usually returns a List, not an array.
Which should I use by default in Kotlin?
Usually List, especially for function parameters and return types, unless you specifically need array behavior.
When should I use Array in Kotlin?
Use it for fixed-size indexed data, Java interoperability, vararg-related operations, or specialized array use cases.
What is the difference between Array<Int> and IntArray?
Array<Int> stores boxed Int values, while IntArray stores primitive integers and is often more efficient.
Mini Project
Description
Build a small Kotlin utility that stores a fixed set of weekday names in an array and manages a changing task list with a mutable list. This demonstrates the real difference between fixed-size data and dynamically changing collections.
Goal
Create a program that uses an Array for fixed data and a MutableList for editable data, then prints useful results from both.
Requirements
- Create an
Arraycontaining the seven weekday names. - Create a
MutableListcontaining at least two tasks. - Update one existing weekday or task by index.
- Add a new task and remove one old task.
- Print all weekdays and final tasks.
- Use at least one collection function such as
filterormap.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.