skip to content

Inside a generic function `fun <T> makeBuffer(size: Int)`, why can't you just write `Array<T>(size) { ... }` to create a generic array, and what are the idiomatic Kotlin ways around this limitation?

level: seniorimportance: should knowfreq 35%

answer

  1. Arrays are reified; List is erased
  2. No runtime class for T -> can't build T[]
  3. reified inline + Array(size){...}
  4. Class<T> + java.lang.reflect.Array.newInstance
  5. arrayOfNulls<Any?> + unchecked cast, or use a List

basics

~20 s

Arrays need to know their exact element type at runtime, but T is erased, so the runtime has no real type to build. Workarounds: use a reified inline function, pass a Class<T> token, build an Array<Any?> and cast, or just use a List/ArrayList.

solid answer

~40 s

Unlike `List<T>` (a plain object whose payload is erased), a JVM **array is reified at runtime** — `Array<String>` is genuinely `String[]` and stores its element type. To allocate one the runtime must know the real component type, but inside `fun <T> f()` the `T` is erased, so there's no concrete class to instantiate. Hence `Array<T>(size) { ... }` is rejected. Idiomatic escapes: (1) make the function `inline` with a `reified T` so `arrayOfNulls<T>(size)` / `Array(size){...}` resolves to the concrete type; (2) accept a `Class<T>` token and call `java.lang.reflect.Array.newInstance(clazz, size)`; (3) allocate `arrayOfNulls<Any?>(size)` (an `Array<Any?>`) and `@Suppress("UNCHECKED_CAST") as Array<T>`, accepting an unchecked cast and possible ArrayStoreException later; or (4) avoid arrays and use a `MutableList<T>` / `ArrayList<T>`, which has no such limit because lists are fully erased.

code

kotlin · 12 lines
kotlin
// Won't compile: T is erased, no runtime component type
// fun <T> bad(n: Int): Array<T> = Array(n) { TODO() }

inline fun <reified T> good(n: Int, init: (Int) -> T): Array<T> = Array(n, init)

fun <T> viaToken(type: Class<T>, n: Int): Array<T> {
    @Suppress("UNCHECKED_CAST")
    return java.lang.reflect.Array.newInstance(type, n) as Array<T>
}

val a = good(3) { "v$it" }          // Array<String>, real String[]
val b = viaToken(Int::class.javaObjectType, 4) // Array<Integer>

go deeper

for a junior

Knows you can't directly create Array<T> and would reach for a List.

for a middle

Uses a reified inline function or arrayOfNulls + cast and recognizes the cast is unchecked.

for a senior

Explains arrays are reified vs generics erased and contrasts all four workarounds with their safety tradeoffs.

for a principal

Reasons about ArrayStoreException risk, interop/performance reasons to need a real T[], and API design to hide the unsafe cast.

## Why arrays are special Most generics on the JVM are **erased**: a `List<String>` is just a `List` at runtime. But **arrays are reified** — `Array<String>` compiles to a real `String[]` that *remembers* its component type and enforces it (storing a non-String triggers `ArrayStoreException`). Creating an array therefore needs the **runtime component type**. Inside `fun <T> makeBuffer(size: Int)` the parameter `T` is erased — there is no runtime `Class` for it — so the compiler cannot emit `new T[size]` and **rejects `Array<T>(size){...}`**. This is the 'no generic array creation' limit (the same reason Java forbids `new T[]`). ## Workaround 1 — reified inline (best when feasible) ```kotlin inline fun <reified T> makeBuffer(size: Int, init: (Int) -> T): Array<T> = Array(size) { init(it) } // T is concrete at each call site ``` Inlining specializes `T`, so a real component type is known. `arrayOfNulls<T>(size)` likewise needs `reified T`. ## Workaround 2 — pass a Class<T> token + reflection ```kotlin fun <T> makeBuffer(type: Class<T>, size: Int): Array<T> { @Suppress("UNCHECKED_CAST") return java.lang.reflect.Array.newInstance(type, size) as Array<T> } ``` The token supplies the runtime component type a non-inline function lacks. ## Workaround 3 — Array<Any?> + unchecked cast ```kotlin fun <T> makeBuffer(size: Int): Array<T> { @Suppress("UNCHECKED_CAST") return arrayOfNulls<Any?>(size) as Array<T> } ``` The backing array is really `Object[]`, so the cast is **unchecked** and the array's runtime type is wrong — fine if it never escapes as a typed `T[]`, but it can surface `ClassCastException`/`ArrayStoreException` at the boundary. This is exactly how `ArrayList`-style structures store elements internally. ## Workaround 4 — use a List instead ```kotlin fun <T> makeBuffer(size: Int): MutableList<T?> = MutableList(size) { null } ``` Lists are erased, so there's no generic-creation restriction at all. Usually the cleanest choice unless you specifically need a primitive/typed array for interop or performance. ## Summary of the limit 'No generic array creation' is a direct consequence of arrays being reified while type parameters are erased: you must reintroduce the component type (via `reified` or a `Class` token) or sidestep arrays entirely.

  • Why does the arrayOfNulls<Any?>() + cast approach risk ArrayStoreException, but the reified approach doesn't?
    The cast approach leaves the real runtime array as Object[], so its declared T[] type is a lie; the reified approach allocates a genuine T[] with the correct component type, so JVM store checks pass.
  • Does Kotlin's MutableList(size){...} have the same generic-creation restriction?
    No. Lists are erased reference objects with no runtime component type, so there is no generic-array-creation rule to violate.

A List is a generic crate that holds anything; an array is a custom-cut mold stamped with one material — you must tell the foundry which material before it can pour, and erasure hid that answer.

saying these in an interview costs you the question

  • Saying arrays are erased like other generics
  • Believing Array<T>(size){...} compiles in a non-inline generic function
  • Ignoring the unchecked cast / ArrayStoreException risk of the Any? approach
  • Not mentioning reified inline or a Class token as the safe routes
  • Confusing why List<T> works but Array<T> creation doesn't

context