Question

IntArray vs Array<Int> in Kotlin: Primitive Arrays and Boxing

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 typeTypical JVM typeWhat is stored
IntArrayint[]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.

Take Quiz

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:

  1. intArrayOf(12, 18, 25) creates an IntArray backed by a JVM int[].
  2. total starts with the primitive Int value 0.
  3. The loop reads 12, then adds it to total, making total equal to 12.
  4. It reads 18, making total equal to 30.
  5. It reads 25, making total equal to 55.
  6. println(total) prints 55.

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.

Take Quiz

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

FeatureIntArrayArray<Int>List<Int>
JVM representationint[]Integer[]Usually a collection of boxed Integer values
Stores primitive integers directlyYesNo
Elements may be nullNoOnly with Array<Int?>Only with List<Int?>
Generic typeNoYes, Array<T>Yes, List<T>
Useful collection operationsBasic array operationsBasic array operationsRich read-only collection API
Best fitNumeric processing and Java int[] interopFixed-size object/nullable integer arrays and Integer[] interopGeneral 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.

Take Quiz

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(...) for IntArray.
  • Use arrayOf(...) for Array<Int>.
  • IntArray cannot contain null.
  • IntArray and Array<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
Take Quiz

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

  1. Create an IntArray containing at least four temperature readings.
  2. Create an Array<Int?> containing manual corrections, including at least one null value.
  3. Write a function that calculates the average of an IntArray.
  4. Write a function that counts non-null values in an Array<Int?>.
  5. Print both results.
Take Quiz

Keep learning

Related questions

View more