Question
What is the difference between IntArray and Array<Int> in Kotlin, and why are they not interchangeable?
val primitiveNumbers: IntArray = intArrayOf(1, 2, 3)
val boxedNumbers: Array<Int> = arrayOf(1, 2, 3)
I understand that IntArray becomes int[] when Kotlin targets the JVM. What does Array<Int> become?
Java can also have arrays such as String[] and YourObject[]. Why does Kotlin provide special {primitive}Array types when arrays can contain many other kinds of values, not only primitives?
Short Answer
You will learn why Kotlin has both general-purpose Array<T> values and specialized primitive arrays such as IntArray. You will also understand their JVM representations, boxing, nullability, performance trade-offs, and how to choose the correct type.
Concept
Kotlin has two related but different ways to store integers in an array:
val a: IntArray = intArrayOf(10, 20, 30)
val b: Array<Int> = arrayOf(10, 20, 30)
On the JVM, they have different representations:
| Kotlin type | Typical JVM type | What is stored |
|---|---|---|
IntArray | int[] | Primitive int values |
Array<Int> | Integer[] | References to boxed Integer objects |
Int is usually compiled to the JVM primitive type int when Kotlin can do so. A primitive value is stored directly in memory. In contrast, Array<Int> is an instance of Kotlin's generic Array<T> class. JVM generic arrays store references, so every Int must be boxed into a Java Integer object.
Kotlin provides primitive-array types because the JVM has primitive types (int, double, boolean, and so on) as well as object references. A Java int[] is not the same type as an Integer[], and it stores data differently.
Specialized Kotlin types include:
IntArray // int[]
LongArray // long[]
DoubleArray // double[]
BooleanArray // boolean[]
CharArray // char[]
ByteArray // byte[]
Use Array<T> for objects and for a general generic array:
val names: Array<String> = arrayOf("Ava", "Noah")
val users: Array<User> = arrayOf(User("Ava"))
The types are not interchangeable because int[] and Integer[] are distinct JVM types. Converting between them requires creating a new array and, in one direction, boxing or unboxing each element.
Mental Model
Think of IntArray as a tray with numbered slots. Each slot holds a number directly:
[ 10 ][ 20 ][ 30 ]
Think of Array<Int> as a tray of labels that point to separate number objects:
[ reference ] [ reference ] [ reference ]
| | |
Integer(10) Integer(20) Integer(30)
Both trays let you access a number by position, but their slots contain different things. A function that expects the direct-number tray cannot safely accept the reference-label tray without repacking every item.
Syntax and Examples
Create an IntArray with intArrayOf or a size constructor:
val scores = intArrayOf(95, 88, 100)
val counters = IntArray(3) // [0, 0, 0]
val squares = IntArray(5) { index -> index * index }
// [0, 1, 4, 9, 16]
Create an Array<Int> with arrayOf or Array(size) { ... }:
val priorities = arrayOf(1, 2, 3)
val positions = Array(3) { index -> index + 1 }
// [1, 2, 3]
Both support indexed access:
println(scores[0])
println(priorities[0])
scores[1] = 90
priorities[1] = 4
However, their function parameter types must match:
fun total(values: IntArray): Int = values.sum()
val directValues = intArrayOf(1, 2, 3)
val boxedValues = arrayOf(1, 2, 3)
println(total(directValues))
// total(boxedValues) // Does not compile: Array<Int> is not IntArray
Convert explicitly when needed:
val fromBoxed: IntArray = boxedValues.toIntArray()
val fromPrimitive: Array<Int> = directValues.toTypedArray()
These conversions allocate a new array.
Step by Step Execution
Consider this code:
val prices = intArrayOf(12, 18, 25)
var total = 0
for (price in prices) {
total += price
}
println(total)
Execution proceeds as follows:
intArrayOf(12, 18, 25)creates anIntArraybacked by a JVMint[].totalstarts with the primitiveIntvalue0.- The loop reads
12, then adds it tototal, makingtotalequal to12. - It reads
18, makingtotalequal to30. - It reads
25, makingtotalequal to55. println(total)prints55.
Now compare it with a nullable array:
val optionalPrices: Array<Int?> = arrayOf(12, null, 25)
This must be an Array<Int?>, not an IntArray, because a primitive JVM int cannot represent null. Each non-null value is stored as a boxed Integer reference, and one slot can contain a null reference.
Real World Use Cases
Use IntArray when you need a dense sequence of non-null numeric values:
- Pixel color components, coordinates, or audio samples.
- Counts, scores, IDs, and measurements processed in large batches.
- Algorithms that work heavily with numbers, such as sorting, searching, or dynamic programming.
- Java APIs that explicitly require
int[].
fun average(values: IntArray): Double {
return if (values.isEmpty()) 0.0 else values.average()
}
Use Array<Int> when integers must behave like objects or might be absent:
- A fixed-size set of optional values.
- Interoperating with Java APIs that require
Integer[]. - Arrays that use nullable elements.
val monthlyTargets: Array<Int?> = arrayOf(100, null, 150)
for (target in monthlyTargets) {
if (target != null) {
println("Target: $target")
}
}
For many application-level collections, a List<Int> is often more convenient than either array because it has a richer collection API. On the JVM, though, List<Int> also boxes its integers.
Real Codebase Usage
In production Kotlin code, choose the parameter type based on the data contract, not only convenience.
A numeric-processing API commonly accepts IntArray:
fun findLargest(values: IntArray): Int? {
if (values.isEmpty()) return null
return values.maxOrNull()
}
The early return is a guard clause: it handles the empty input before performing the main operation.
For data that may have missing values, use nullable object references:
fun countConfiguredPorts(ports: Array<Int?>): Int {
return ports.count { it != null }
}
When calling Java code, match the Java signature exactly:
void sort(int[] values)
void savePriorities(Integer[] values)
From Kotlin, the matching calls use:
sort(intArrayOf(3, 1, 2))
savePriorities(arrayOf(3, 1, 2))
Avoid unnecessary conversions inside frequently called code. This creates extra arrays and may box or unbox every element:
fun process(values: Array<Int>) {
val primitiveValues = values.toIntArray()
// Use primitiveValues...
}
If the code fundamentally processes primitive numbers, accept IntArray from the beginning instead.
Common Mistakes
Expecting the types to substitute for each other
This does not compile:
fun printFirst(values: IntArray) {
println(values[0])
}
val values = arrayOf(1, 2, 3)
// printFirst(values)
Fix it by using the expected type or converting deliberately:
printFirst(values.toIntArray())
Assuming arrayOf(1, 2, 3) creates an IntArray
arrayOf creates an Array<T>:
val values = arrayOf(1, 2, 3) // Array<Int>
Use intArrayOf when an IntArray is required:
val values = intArrayOf(1, 2, 3)
Trying to store null in an IntArray
This is invalid because primitive int values cannot be null:
// val values: IntArray = intArrayOf(1, null, 3)
Use a nullable object array instead:
val values: Array<Int?> = arrayOf(1, null, 3)
Using == to compare array contents
For arrays, == checks whether the two variables refer to the same array object, not whether elements match:
val left = intArrayOf(1, 2)
val right = intArrayOf(1, 2)
println(left == right) // false
Use contentEquals:
println(left.contentEquals(right)) // true
Converting repeatedly in a loop
Do not call toIntArray() or toTypedArray() repeatedly in performance-sensitive loops. Convert once at a boundary, or use the correct array type throughout the processing path.
Comparisons
| Feature | IntArray | Array<Int> | List<Int> |
|---|---|---|---|
| JVM representation | int[] | Integer[] | Usually a collection of boxed Integer values |
| Stores primitive integers directly | Yes | No | |
Elements may be null | No | Only with Array<Int?> | Only with List<Int?> |
| Generic type | No | Yes, Array<T> | Yes, List<T> |
| Useful collection operations | Basic array operations | Basic array operations | Rich read-only collection API |
| Best fit | Numeric processing and Java int[] interop | Fixed-size object/nullable integer arrays and Integer[] interop | General application data |
IntArray is specifically optimized for primitive integers. Array<String> does not need an equivalent special type because String is already an object reference on the JVM.
Kotlin also keeps Array<T> invariant. For example, Array<String> is not treated as Array<Any> in Kotlin source code. This prevents unsafe writes, even though JVM arrays themselves have covariant behavior.
Cheat Sheet
// Primitive integer array: JVM int[]
val numbers: IntArray = intArrayOf(1, 2, 3)
val zeros = IntArray(4)
// Object-reference array: JVM Integer[] for Int elements
val boxed: Array<Int> = arrayOf(1, 2, 3)
// Nullable integers require boxed references
val optional: Array<Int?> = arrayOf(1, null, 3)
// Convert explicitly; each creates a new array
val primitive: IntArray = boxed.toIntArray()
val objects: Array<Int> = numbers.toTypedArray()
// Compare contents
numbers.contentEquals(intArrayOf(1, 2, 3))
- Use
intArrayOf(...)forIntArray. - Use
arrayOf(...)forArray<Int>. IntArraycannot containnull.IntArrayandArray<Int>are different types and cannot be passed to each other's functions directly.- Use
contentEquals, not==, for element-by-element array comparison.
FAQ
Does Array<Int> always become Integer[] on the JVM?
For an Array<Int> in JVM-targeted Kotlin code, its elements are boxed Integer objects, so it corresponds to Integer[]. Generic arrays hold references rather than primitive values.
Why does Kotlin have IntArray instead of using Array<Int> everywhere?
IntArray lets Kotlin use the JVM's efficient int[] representation. It avoids storing a separate object reference for each integer and avoids boxing each value.
Which is faster: IntArray or Array<Int>?
IntArray is generally preferable for large or heavily processed non-null integer data because it avoids boxing and has a compact primitive representation. Measure real code when performance matters.
Can an IntArray contain null?
No. Use Array<Int?> when each position may contain an integer or null.
Can I convert between IntArray and Array<Int>?
Yes. Use toIntArray() to convert Array<Int> to IntArray, and toTypedArray() to convert IntArray to Array<Int>. Both create a new array.
Should I use an array or a list in Kotlin?
Use an array for fixed-size, indexed storage or Java array interoperation. Use List for most general application data because it provides more collection operations and expresses read-only intent.
Are IntArray values mutable?
Yes. You can replace an element by index, but you cannot resize the array.
val values = intArrayOf(1, 2)
values[0] = 10
Mini Project
Description
Build a small temperature-summary utility. It receives mandatory sensor readings as an IntArray and optional manual corrections as an Array<Int?>. This demonstrates when direct primitive storage is appropriate and when nullable boxed values are necessary.
Goal
Calculate the average sensor temperature and count the manual corrections that were provided.
Requirements
- Create an
IntArraycontaining at least four temperature readings. - Create an
Array<Int?>containing manual corrections, including at least onenullvalue. - Write a function that calculates the average of an
IntArray. - Write a function that counts non-null values in an
Array<Int?>. - Print both results.
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.