Kotlin arrays look simple until you realize there are eight specialized types, a generic class, and a handful of performance traps hiding underneath. So what are Kotlin arrays, really?
They are fixed-size, ordered collections backed by the Array class and a set of primitive type variants like IntArray and ByteArray. Picking the wrong one can cost you 5x the memory on the JVM.
This guide covers how to create, access, iterate, and transform arrays in Kotlin. You will also learn the real performance differences between primitive arrays and typed arrays, when to use arrays versus lists, how multidimensional arrays work, and how vararg functions interact with the spread operator.
What Is a Kotlin Array

A Kotlin array is a fixed-size, ordered collection of elements stored in contiguous memory. It holds a set number of values under a single variable name, and each value is accessible by its position (index) starting from zero.
Unlike Java, where arrays are a language-level primitive, Kotlin represents arrays through the Array class. This means every array in Kotlin is an object with methods and properties attached to it, including size, get(), and set().
The indexed access operator array[index] is just syntactic sugar. Under the hood, it calls operator fun get(index: Int) and operator fun set(index: Int, value: T).
One thing that trips up a lot of developers coming from Java: Kotlin arrays are invariant. An Array<String> is not a subtype of Array<Any>. Java arrays are covariant, which leads to runtime exceptions that Kotlin’s type system prevents at compile time.
Google estimates that 70% of the top 1,000 apps on the Play Store are written in Kotlin, and JetBrains reports that 95% of those apps include some Kotlin code (Dice, 2024). So understanding how arrays work in this language is not a niche concern. It is directly relevant to mainstream Android development.
The Kotlin standard library provides the Array class as a generic container, but it also ships specialized classes like IntArray, ByteArray, and CharArray for working with primitive types without the overhead of boxing. We will get into that distinction shortly.
How to Create Arrays in Kotlin

Kotlin gives you several factory functions and constructors for array initialization. Which one you pick depends on whether you already know the values, need a computed pattern, or just want an empty container.
arrayOf() and Typed Factory Functions
Known values at declaration time: use arrayOf().
“ val names = arrayOf("Alice", "Bob", "Charlie") `
For primitive types, Kotlin provides typed factory functions that skip boxing entirely:
- intArrayOf(1, 2, 3)
creates anIntArray
- doubleArrayOf(1.0, 2.5)
creates aDoubleArray
- booleanArrayOf(true, false)
creates aBooleanArray
These compile down to JVM primitive arrays (int[], double[], boolean[]), which matters a lot for performance in tight loops.
The Array Constructor with Lambda
When your values follow a pattern, the Array constructor with a lambda init function is your best option.
` val squares = Array(5) { i -> i i } // Result: [0, 1, 4, 9, 16] `
The lambda receives the index as its argument. Took me a while to appreciate how clean this is compared to allocating an array and then running a separate loop to fill it.
The same pattern works for primitive arrays: IntArray(10) { it 2 }.
Empty and Nullable Arrays
Empty array: emptyArray() gives you a zero-length typed array. Useful when an API requires an array parameter but you have nothing to pass.
Nullable elements: arrayOfNulls(5) creates an array of size 5 where every element starts as null. The return type is Array, which means Kotlin's null safety system tracks these nullable slots for you.
The JetBrains Developer Ecosystem Survey 2024 shows that 75% of Kotlin users express satisfaction with the language. A big reason for that is how these small design choices (like typed factory functions and null-safe arrays) reduce boilerplate compared to what you would write in Java.
Primitive Type Arrays vs. Typed Arrays

This is probably the single most important performance detail about Kotlin arrays that most tutorials gloss over.
What Happens on the JVM
When you write Array, the Kotlin compiler generates an Integer[] on the JVM. Every single element gets boxed into a java.lang.Integer object.
When you write IntArray, the compiler generates a raw int[]. No boxing. No wrapper objects. Just primitive values sitting in contiguous memory.
| Type | JVM Representation | Boxing | Memory for 1M Elements |
|---|---|---|---|
| IntArray | int[] | None | ~4 MB |
| Array | Integer[] | Every element | ~20 MB |
| List | List | Every element | ~20 MB |
According to Kotlin Academy’s Effective Kotlin, Array allocates five times more bytes than IntArray for 1 million numbers: 20,000,040 bytes versus 4,000,016 bytes. Calculating the average of those elements is around 25% faster with IntArray.
When to Use Which
Use primitive arrays (IntArray, DoubleArray, FloatArray, LongArray, ShortArray, ByteArray, CharArray, BooleanArray) when you are doing number crunching, processing large datasets, or working in performance-critical paths like game rendering or audio processing.
Use Array when you need nullability (Array) or when you are working with generic code that requires a type parameter.
Baeldung’s JMH benchmarks confirm that Kotlin’s spread operator on arrays introduces roughly a 17% performance penalty because it copies the entire array before passing it to a vararg function. That is another reason to be careful about which array type you pick in hot paths.
Kotlin/Native and Kotlin/JS
On Kotlin/JVM, the distinction between primitive and boxed arrays is real and measurable. On Kotlin Multiplatform targets like Kotlin/Native and Kotlin/JS, the runtime behavior differs.
Kotlin/Native does not use a JVM, so there is no boxing in the Java sense. But the specialized array types still exist for API consistency. Kotlin/JS compiles arrays to JavaScript arrays regardless of type. The performance characteristics change depending on your target platform, so always profile on the actual runtime you are deploying to.
How to Access and Modify Array Elements

Arrays in Kotlin are mutable in content but fixed in size. You can change what sits at index 3, but you cannot add an index 11 to a 10-element array.
Index-Based Access
The bracket syntax is what most developers reach for:
` val colors = arrayOf("red", "green", "blue") val first = colors[0] // "red" colors[2] = "yellow" // replaces "blue" `
Behind the scenes, colors[0] calls colors.get(0), and colors[2] = “yellow” calls colors.set(2, “yellow”). Operator overloading at work.
If you go out of bounds, you get an ArrayIndexOutOfBoundsException. Kotlin does not silently return null or undefined like some other languages.
Safe Access Methods
getOrNull(index) returns null instead of throwing an exception when the index is invalid. Pairs well with Kotlin's elvis operator for providing defaults.
getOrElse(index) { defaultValue } lets you supply a fallback through a lambda.
first() and last() return the first and last elements. They throw NoSuchElementException on empty arrays, so use firstOrNull() and lastOrNull() when the array might be empty.
Destructuring Declarations
Kotlin arrays support destructuring through component1(), component2(), etc.
` val (x, y, z) = arrayOf(10, 20, 30) `
This works for the first five elements. Beyond that, you need to access them by index. Honestly, destructuring works better with Kotlin data classes, but for quick unpacking of small arrays it saves a few lines.
Iterating Over Kotlin Arrays

Looping through array elements is one of those things you do constantly, and Kotlin offers more than one way to do it. Your mileage may vary on which approach reads best.
The for-in Loop
Simplest option. Works exactly how you would expect:
` val items = arrayOf("pen", "notebook", "eraser") for (item in items) { println(item) } `
No index counter, no off-by-one bugs. For most cases, this is all you need.
Index-Aware Iteration
When you need both the index and the value, you have two solid options:
forEachIndexed gives you a lambda with both parameters:
` items.forEachIndexed { index, value -> println("$index: $value") } `
withIndex() returns an IndexedValue object you can destructure in a for loop:
` for ((i, v) in items.withIndex()) { println("$i: $v") } `
One thing worth knowing: forEachIndexed on arrays uses direct index access internally, which avoids the overhead of creating an Iterator object. The withIndex() approach does allocate an iterator. For small arrays this is irrelevant, but on large arrays in hot paths, the difference shows up.
Using indices and Ranges
The indices property returns an IntRange matching the valid index range of your array:
` for (i in items.indices) { println(items[i]) } `
You can also iterate in reverse with items.indices.reversed() or step through with items.indices step 2.
Stack Overflow’s 2024 survey placed Kotlin as the 4th most admired programming language. A lot of that satisfaction comes from small quality-of-life features like these. You write less boilerplate and the code stays readable.
Common Array Operations and Functions

The Kotlin standard library ships dozens of extension functions on arrays. Most of them are inline functions, which means the compiler pastes the lambda body directly into your code at the call site. No lambda object allocation at runtime.
But there is a catch. Transformation functions like map, filter, and flatMap return Lists, not Arrays. If you need an array back, you have to call .toTypedArray() or .toIntArray() on the result.
Transformations
- map { }
transforms each element and returns a List
- filter { }
keeps elements matching a predicate, returns a List
- flatMap { }
maps each element to a collection and flattens the result
Since these produce lists, many developers working with software development in Kotlin just convert arrays to lists up front, do all their processing there, and convert back only when an API specifically demands an array.
Searching and Aggregation
Search functions: find { }, indexOf(), contains(), any { }, all { }, none { }.
Aggregation functions: sum(), average(), max(), min(), reduce { }, fold(initial) { }.
The difference between reduce and fold is that fold takes an initial accumulator value. If your array might be empty, always use fold to avoid exceptions.
Sorting and Copying
In-place sorting: sort() modifies the original array. sortDescending() does the same in reverse order.
New-array sorting: sortedArray() and sortedArrayDescending() return copies without touching the original.
Copying: copyOf() clones the full array. copyOfRange(fromIndex, toIndex) copies a slice. clone() works too, but copyOf() is generally preferred because it is more explicit about what it does.
Converting Arrays to Other Collections
This is where arrays meet the broader Kotlin collection framework:
- toList()
andtoMutableList()for list conversions
- toSet()
andtoHashSet()for removing duplicates
- list.toTypedArray()
for converting back from a list to an array
The Kotlin community style guide generally recommends lists over arrays for everyday work. Arrays exist for interop with Java APIs (especially in Kotlin or Java mixed projects), performance-sensitive code, and low-level operations where you need predictable memory layout.
If you are building apps and not writing a physics engine, List and MutableList will serve you better most of the time. But when you do reach for arrays, knowing these operations saves real debugging time.
Multidimensional Arrays in Kotlin

Kotlin has no built-in 2D array type. What it does have is arrays of arrays, which gets the job done but requires a slightly different mental model than languages like C or Fortran.
Creating a 2D Array
The standard pattern uses the Array constructor with a nested IntArray (or any other array type) inside the lambda:
` val matrix = Array(3) { IntArray(4) } // 3 rows, 4 columns `
Each row is its own IntArray, initialized to zeros by default. You access elements with double indexing: matrix[row][col].
For known values, nest arrayOf calls:
` val grid = arrayOf( intArrayOf(1, 2, 3), intArrayOf(4, 5, 6), intArrayOf(7, 8, 9) ) `
Iterating Over a 2D Array
Nested for loops are the most common approach:
` for (row in grid) { for (value in row) { print("$value ") } println() } `
You can also use forEachIndexed on both dimensions if you need row and column positions. But nested loops are clearer for most grid operations.
Practical Use Cases
Game boards: Tic-tac-toe, chess, Sudoku solvers. A 2D Array works well for representing game state.
Image processing: Pixel data stored as Array where each int holds ARGB values. Common in mobile application development where Bitmap manipulation touches raw pixel arrays.
For anything beyond basic matrix work, JetBrains maintains the Multik library, which provides typed N-dimensional arrays with math operations, linear algebra, and statistics. Think of it as Kotlin’s answer to NumPy.
Arrays vs. Lists in Kotlin

This is the question that comes up in every Kotlin codebase review. When do you use an array, and when do you use a list?
Short answer: use lists by default. Reach for arrays only when you have a specific reason.
| Feature | Array | List | MutableList |
|---|---|---|---|
| Size | Fixed at creation | Fixed | Grows dynamically |
| Content mutability | Yes | No | Yes |
| Primitive optimization | Yes (IntArray, etc.) | No | No |
| Generics | Invariant | Covariant | Invariant |
| Kotlin style guide | For interop/performance | Preferred default | When mutation needed |
Why Lists Win for Everyday Code
Read-only by default. List is immutable. You cannot accidentally modify its contents. Arrays are always mutable in content, which means bugs can sneak in when functions modify array elements you expected to stay constant.
Covariance. A List can be passed where a List is expected. An Array cannot be passed where Array is expected. This makes lists far more flexible in generic code.
The Kotlin official style guide and JetBrains documentation both recommend lists over arrays for general use.
When Arrays Still Make Sense
Java API interop: some Android framework methods and Java libraries require arrays as parameters. The varargs system itself is backed by arrays internally.
Performance-critical processing: Kotlin Academy’s Effective Kotlin benchmarks show IntArray.average() runs 25% faster than List.average() for 1 million elements. When processing large datasets in tight loops, that gap matters.
Netflix, which uses Kotlin on the server side, has talked about choosing primitive arrays over lists in data-processing pipelines where garbage collection pressure becomes a concern.
Array Performance and Memory Considerations

Performance differences between array types only matter when you are working at scale. For a list of 50 items, nobody cares. For a million records in a data pipeline, the choice can affect latency and GC pauses.
Memory Layout on the JVM
LogRocket’s analysis of Effective Kotlin data confirms the numbers: storing 1 million integers in an IntArray takes about 4 MB, while Array takes roughly 20 MB. That 5x overhead comes from boxing every integer into a java.lang.Integer object on the heap.
Each boxed integer adds 16 bytes of object header plus 4 bytes for the int value. Multiply that by a million and you are looking at real memory pressure that triggers more frequent garbage collection.
Inline Functions and Array Operations
Kotlin’s standard library array functions (map, filter, forEach, any) are declared as inline. The compiler copies the lambda body directly into the call site.
No lambda object allocation. No virtual method dispatch. This is a genuine advantage over Java’s Stream API, which creates intermediate objects for each pipeline stage.
Baeldung’s JMH benchmarks show that for collection transformations, Kotlin’s inline approach performs comparably to hand-written loops in most scenarios.
When to Optimize and When Not To
| Scenario | Recommended Type | Reason |
|---|---|---|
| App UI state | List | Safety, flexibility |
| Game physics loop | FloatArray | No boxing, contiguous memory |
| JSON parsing results | List | Dynamic size, easy mapping |
| Audio sample buffer | ShortArray | Low latency, minimal GC |
| General business logic | List | Readability, Kotlin conventions |
Profile before optimizing. The Kotlin team provides unit testing and benchmarking tools through the kotlinx-benchmark library, which runs JMH benchmarks on Kotlin/JVM and supports multiplatform targets.
JetBrains reports that the K2 compiler (Kotlin 2.0) delivers up to 94% faster compilation in clean builds for some projects, which also means faster iteration when profiling array-heavy code.
Passing Arrays to Functions and Varargs

The vararg keyword and the spread operator are where Kotlin arrays interact most directly with function signatures. Getting this right avoids subtle performance pitfalls.
How vararg Parameters Work
A vararg parameter lets a function accept a variable number of arguments. Internally, the compiler treats it as an array.
` fun sum(vararg numbers: Int): Int { return numbers.sum() } `
You can call this with individual values: sum(1, 2, 3). Inside the function body, numbers behaves like an IntArray.
Unlike Java, Kotlin allows the vararg parameter at any position in the parameter list, not just the last. If it is not last, you must use named arguments for the parameters that follow it.
The Spread Operator
When you already have an array and want to pass it into a vararg function, prefix it with :
` val nums = intArrayOf(1, 2, 3) sum(nums) `
Performance note: the spread operator creates a copy of the entire array via Arrays.copyOf() before passing it (Baeldung's bytecode analysis confirms this). In a tight loop, that copy can hurt.
You can mix individual values and spread arrays in the same call:
` sum(0, nums, 4, 5) // passes 0, 1, 2, 3, 4, 5 `
The Kotlin compiler has optimized one case since version 1.1.60: if you create the array inline with arrayOf() and immediately spread it, the copy gets skipped. But for pre-existing arrays, the copy always happens.
Receiving Arrays vs. Collections as Parameters
Use arrays in function signatures when you are writing low-level utility code, working with Java interop, or building performance-sensitive APIs.
Use collections (List) when designing public APIs in pure Kotlin. Lists are covariant, support read-only contracts, and play nicer with Kotlin coroutines and Kotlin flows in async code.
Teams working on cross-platform app development with Kotlin Multiplatform should also keep in mind that array behavior can vary between JVM, Native, and JS targets. Lists provide more consistent behavior across all compilation targets in a shared codebase.
Google’s data showing that 70% of top Play Store apps use Kotlin (Dice, 2024) means these decisions around arrays, lists, and function signatures affect a massive chunk of the Android ecosystem. Getting them right from the start saves code refactoring later.
FAQ on What Are Kotlin Arrays
What is the difference between Array and IntArray in Kotlin?
Array compiles to Integer[] on the JVM, boxing every element. IntArray compiles to primitive int[] with no boxing. IntArray uses about 5x less memory and processes roughly 25% faster for large datasets.
How do you create an array in Kotlin?
Use arrayOf() for known values, Array(size) { init } for computed values, or typed factories like intArrayOf() for primitives. For nullable elements, use arrayOfNulls(size). Each method suits a different initialization scenario.
Are Kotlin arrays mutable?
Yes, array content is mutable. You can change individual elements using index access like array[2] = “new value”. But the array size is fixed at creation. You cannot add or remove elements after initialization.
Should I use arrays or lists in Kotlin?
Use List by default. Lists are covariant, support read-only contracts, and follow the Kotlin style guide. Use arrays for Java interop, vararg parameters, or performance-critical code where primitive arrays reduce GC pressure.
How do you iterate over a Kotlin array?
The simplest way is a for (item in array) loop. For index-aware iteration, use forEachIndexed { index, value -> } or withIndex(). The indices property provides an IntRange for traditional index-based loops.
What is the spread operator in Kotlin arrays?
The prefix unpacks an array into individual arguments for a vararg function. It creates an array copy internally via Arrays.copyOf(), so avoid using it in tight loops where the copy overhead matters.
How do you create a 2D array in Kotlin?
Use Array(rows) { IntArray(cols) } to create a grid initialized to zeros. Access elements with matrix[row][col]. Kotlin has no native 2D array type, so multidimensional arrays are technically arrays of arrays.
Can Kotlin arrays hold null values?
Only Array can hold nulls. Create one with arrayOfNulls(5). Primitive arrays like IntArray and BooleanArray cannot contain null because JVM primitives do not support it. Kotlin's null safety system enforces this at compile time.
How do you convert a Kotlin array to a list?
Call .toList() for a read-only list or .toMutableList() for one you can modify. Going the other direction, use list.toTypedArray(). For primitive arrays, use specific converters like .toIntArray().
Do Kotlin array functions like map and filter return arrays?
No. Functions like map, filter, and flatMap return List objects, not arrays. If you need an array back, chain .toTypedArray() or .toIntArray() onto the result. These functions are inline, so no lambda allocation occurs.
Conclusion
Kotlin arrays are a fixed-size collection type built on the Array class, with specialized variants like IntArray, FloatArray, and LongArray that compile directly to JVM primitive arrays. Understanding what are Kotlin arrays, and specifically the distinction between boxed and unboxed types, is a practical skill that affects memory usage and runtime performance in real projects.
For most application code, List and MutableList are the better default. They are safer, more flexible with generics, and align with Kotlin's official conventions.
But when you are processing large datasets, working with Kotlin lambda functions in hot paths, or passing data through vararg signatures, primitive arrays give you measurable gains. The 5x memory difference between IntArray and Array` is not theoretical. It shows up in profiling.
Pick the right tool based on your context. Profile when it matters. And let the Kotlin type system handle the rest.
- How to Clear All App Data on Android at Once - May 14, 2026
- How to Prep Your Codebase for M&A Due Diligence - May 13, 2026
- TypeScript Cheat Sheet - May 12, 2026



