coding

Kotlin Arrays and Lists: A Practical Guide for Android Devs

Arrays and lists sit at the heart of every Android application. Whether you're storing user preferences, caching network responses, or managing UI state, you need a reliable way to hold collections of data. Kotlin gives developers two main options: fixed-size arrays created with arrayOf(), and dynamic lists built with mutableListOf(). Getting comfortable with both makes your code safer and more performant. This guide walks through declaring, initializing, and working with these collections, with examples tailored to common Android scenarios.

Declaring Arrays in Kotlin

The arrayOf() function creates a one-dimensional array from a list of values. Here's the simplest case:

Val numbers = arrayOf(1, 2, 3, 4, 5)

Kotlin infers the element type automatically from the values you provide. In this case, it recognizes integers and creates an Array<Int>.

For stronger guarantees, specify the type explicitly using angle brackets:

Val numbers = arrayOf<Int>(10, 20, 30)

Now Kotlin enforces type safety at compile time. If you try to insert a string into this integer array, the code won't even compile. This catches bugs early, before your app reaches users.

Kotlin's standard library also provides shorthand functions for common types: intArrayOf(), doubleArrayOf(), booleanArrayOf(), and so on. These are equivalent to specifying the type manually but read more naturally in code.

Dynamic Collections with mutableListOf

Arrays have a fixed size. Once created, you can't add or remove elements without recreating the entire array. That limitation makes arrays unsuitable for collections that grow and shrink during execution.

Mutable lists solve this problem. The mutableListOf() function creates a dynamic list with built-in methods for modification:

Val transactions = mutableListOf<Double>()

Transactions.add(45.99)

Transactions.add(12.50)

Transactions.removeAt(0)

In this example, the list starts empty and grows as the user adds transactions. The removeAt() method removes an element by index, shrinking the collection.

Type inference works the same way as with arrays. Pass only integers, and Kotlin treats the list as List<Int>. Pass mixed types, and Kotlin infers List<Any>. If you need mixed types but want explicit typing, declare it:

Val mixed = mutableListOf<Any>()

Accessing and Iterating Over Collections

Both arrays and lists use bracket notation for index-based access:

Val third = numbers[2]

Kotlin also provides the indices property, which returns a range of valid index values. This is useful when you need the index position alongside the value:

For (i in numbers.indices) {

Print("${numbers[i]} ")

}

Lists offer additional methods for manipulation: get() and set() for targeted reads and writes, plus() and minus() for creating new lists based on existing ones, and contains() for membership testing. Arrays share most of these capabilities through extension functions in the Kotlin standard library.

Working with Two-Dimensional Arrays

Android apps frequently handle grid-structured data. Game boards, pixel matrices, layout tables — these all map naturally to multidimensional arrays.

Kotlin represents 2D arrays as nested structures. An array of arrays:

Val grid = arrayOf(

ArrayOf(1, 2, 3),

ArrayOf(4, 5, 6),

ArrayOf(7, 8, 9)

)

Access elements with double indexing. The first bracket selects the row, the second selects the column:

Val center = grid[1][1] // Returns 5

This pattern scales to three or more dimensions as needed for more complex data structures.

For grids with unknown dimensions, build them dynamically using nested mutable lists:

Val grid = mutableListOf<MutableList<Int>>()

Repeat(3) { row ->

Val rowList = mutableListOf<Int>()

Repeat(3) { col ->

RowList.add(row * 3 + col + 1)

}

Grid.add(rowList)

}

Our Analysis

The array versus list decision usually comes down to mutability requirements. Use arrays when the collection size is known at creation and won't change — configuration constants, coordinate pairs, fixed enumerations. Use mutable lists when elements get added, removed, or modified during the app's lifetime — user-generated content, API responses, UI state.

Type safety deserves careful attention in Android development. Collections frequently receive data from external sources: JSON responses, database queries, user input. Explicit typing at the collection boundary acts as a validation layer. It fails fast with compile errors rather than crashing at runtime with ClassCastException errors that frustrate users and complicate debugging.

For production Android apps, consider wrapping raw collections in data classes that encapsulate validation logic. This moves type checking out of the collection declaration and into reusable, testable code.

A Worked Example

Imagine you're building a habit tracker app. Users add daily completion records as boolean values.

Val habitRecords = mutableListOf<Boolean>()

HabitRecords.add(true) // Monday completed

HabitRecords.add(false) // Tuesday missed

HabitRecords.add(true) // Wednesday completed

When calculating a streak, you iterate and count consecutive completions. The mutable list handles the variable-length history without any array resizing logic on your part.

Val streak = habitRecords.count { it }

If you used a fixed array instead, you'd need to recreate it every time the user logs a new day. That's extra code, extra memory allocations, and extra opportunities for bugs.

What to Watch Next

Kotlin's collection library extends far beyond basic arrays and lists. Functions like map(), filter(), and reduce() let you transform and aggregate collections without explicit loops. These functional patterns integrate naturally with Android's reactive APIs and coroutines, making your data processing pipelines more concise and expressive.

Key takeaways

  • ArrayOf() creates fixed-size arrays; mutableListOf() creates dynamic lists
  • Specify types explicitly with arrayOf () or mutableListOf () for compile-time safety
  • Access elements with bracket notation: myArray[index]
  • Use indices for index-based iteration; use for-in loops for value iteration
  • Two-dimensional arrays are nested arrays; access with double brackets like grid[row][col]

Frequently asked questions

What's the difference between arrayOf() and intArrayOf() in Kotlin?

Both create integer arrays, but intArrayOf() creates a primitive IntArray while arrayOf () creates a boxed Array . Primitive arrays use less memory and offer slightly better performance for numeric data. Use arrayOf () when you need to store integers as objects, such as in generic collections that require Any types.

Can I modify elements in a Kotlin array after creation?

You can modify the values at existing indices, but you cannot change the array's length. Arrays have a fixed size in Kotlin. If you need to add or remove elements, use a mutableListOf() instead.

How do I declare a list with a specific type in Kotlin?

Use type parameters in angle brackets after the function name. For example, mutableListOf () creates a list that accepts only Double values. Kotlin's type inference will also determine the type automatically if you initialize the list with values of the same type.

Can Kotlin lists contain mixed types?

Yes, but you must declare the type explicitly as List or MutableList . However, mixing types removes the type safety benefits that Kotlin provides, so only use this approach when you genuinely need heterogeneous collections and can handle the casting logic elsewhere.

How do I access elements in a two-dimensional Kotlin array?

Use double bracket notation. For a grid array, the first index selects the row and the second selects the column: grid[0][2] accesses the third element of the first row. This works the same way for arrays with more dimensions.

Related