You have a Kotlin function `fun run(vararg args: String)`. A caller holds an Array<String>. Walk through the calls that compile, the ones that don't, and the allocation cost of each.
answer
- run(*arr) OK; run(arr) compile error
- Each spread = shallow copy / allocation
- Add Array<String> overload to skip the copy
- Resolution: bare array -> array overload, spread -> vararg
- Re-spread inside vararg copies again
basics
~10 srun(*arr) compiles and spreads the array's elements. run(arr) does not compile because an Array<String> isn't a String. Each spread makes a defensive copy of the array, so spreading in a loop allocates repeatedly.
solid answer
~50 sWith `fun run(vararg args: String)` the parameter is `Array<out String>` internally and `String[]` on the JVM. `run(*arr)` is the only direct way to forward an existing `Array<String>`: the `*` spread copies the elements into the array the callee receives. `run(arr)` fails — an `Array<String>` is not a `String`. `run("a", *arr, "b")` works and builds a single merged array. Each spread allocates a fresh backing array (shallow copy), so spreading inside a tight loop has real cost; hoist the array or pass it once. If you also defined an overload `fun run(args: Array<String>)`, then `run(arr)` would resolve to that overload (no spread, no copy) while `run(*arr)` still hits the vararg one — a deliberate way to offer an allocation-free path. Inside the vararg function, `args` is already an array you can pass on (re-spreading it copies again).
code
kotlin · 7 linesfun run(vararg args: String) = realRun(args) // vararg delegates
fun realRun(args: Array<String>) { /* ... */ }
val arr = arrayOf("a", "b")
run(*arr) // copies into a new String[]
realRun(arr) // no copy: passes the array directly
// run(arr) // ERROR: Array<String> is not a Stringgo deeper
Knows run(*arr) works and run(arr) doesn't.
Explains the spread requirement and that varargs is a String[] internally.
Quantifies the per-call copy cost and designs an Array<String> overload to avoid it, explaining resolution.
Weighs API surface (vararg ergonomics vs array perf) for library hot paths and the maintenance cost of dual overloads.
## Setup ```kotlin fun run(vararg args: String) { /* args: Array<out String>, JVM String[] */ } val arr: Array<String> = arrayOf("a", "b", "c") ``` ## Which calls compile ```kotlin run("a", "b") // OK — literal varargs, compiler builds a String[] run(*arr) // OK — spread forwards arr's elements run("head", *arr) // OK — literal + spread merged into one array run(*arr, *arr) // OK — two spreads concatenated run(arr) // ERROR — Array<String> is not a String run() // OK — empty vararg => empty array ``` The single hard error is `run(arr)`: a vararg of `String` expects zero-or-more `String` values, and an `Array<String>` is not a `String`. The spread operator `*` is the bridge. ## Allocation cost Every spread performs a **shallow copy** of the elements into the array the callee actually receives — the callee can mutate its parameter without touching your original. Consequences: - `run(*arr)` in a loop allocates a new `String[]` each iteration. - `run("x", *arr, "y")` allocates one combined array sized to `arr.size + 2`. - The copy is shallow: element references are shared, only the backing array is new. ```kotlin for (i in 0 until 1_000) run(*arr) // 1000 array copies ``` ## Offering an allocation-free path with an overload Because varargs and an explicit array param are different signatures, you can provide both: ```kotlin fun run(vararg args: String) = run(args) // delegates fun run(args: Array<String>) { /* real work */ } run("a", "b") // -> vararg overload run(arr) // -> array overload, NO spread, NO copy run(*arr) // -> vararg overload, copies ``` Resolution: a bare array argument prefers the exact `Array<String>` overload; a spread or literal list selects the vararg overload. This is a common micro-optimization for hot APIs. ## Re-forwarding inside a vararg function ```kotlin fun run(vararg args: String) { log(*args) // re-spreads -> another copy of the elements } ``` Passing `args` onward without `*` to another vararg fails the same way `run(arr)` does; with `*` it copies again. ## Primitive note If the vararg were `vararg n: Int`, the parameter is an `IntArray` and you must spread an `IntArray` (`run(*intArrayOf(1,2))`), not an `Array<Int>`. ## Summary - `run(*arr)` works and copies; `run(arr)` does not compile. - Spreads allocate per call; hoist or add an array overload for hot paths. - Mix literals and multiple spreads freely.
- How would you avoid repeated allocations when calling a vararg API in a hot loop?Expose or call an overload taking Array<String> directly and pass the hoisted array without a spread, eliminating the per-call defensive copy.
- If both a vararg and an Array<String> overload exist, which does run(arr) pick and why?The Array<String> overload — it's a more specific exact match for a single array argument, so the vararg overload isn't chosen unless you spread.
saying these in an interview costs you the question
- Claiming run(arr) compiles by auto-spreading
- Saying spread is zero-cost / aliases the array
- Not realizing each spread copies
- Believing you can't have both vararg and array overloads