What Are Kotlin Varargs and How to Use Them

Summarize this article with:

Every time you call listOf(), setOf(), or mapOf() in Kotlin, you’re using varargs. Most developers do this dozens of times a day without thinking about it.

But what are Kotlin varargs beyond that surface-level usage? The vararg keyword controls how functions accept a variable number of arguments, how those arguments compile to JVM bytecode, and how the spread operator copies arrays behind the scenes.

This article covers vararg syntax, the spread operator’s performance cost, how varargs differ from array parameters, generic type behavior, constructor patterns, common pitfalls, bytecode compilation across JVM and non-JVM targets, and practical use cases in real Kotlin projects.

What Is varargs in Kotlin

maxresdefault What Are Kotlin Varargs and How to Use Them

The vararg keyword in Kotlin is a modifier that lets a function accept a variable number of arguments of the same type. You place it before the parameter name in a function declaration, and Kotlin handles the rest.

Inside the function body, Kotlin treats the vararg parameter as an array. So if you write vararg names: String, you’re actually working with an Array<String> behind the scenes.

That’s the whole trick. You get the convenience of passing comma-separated values at the call site, but the flexibility of array operations inside the function.

Why varargs Exist in Kotlin Functions

Fixed-parameter functions force you to know the exact number of inputs at compile time. That works fine for most cases. But sometimes you need a function that handles two items today and twelve tomorrow.

The Kotlin standard library uses this pattern everywhere. Functions like listOf(), setOf(), and mapOf() all accept variable arguments. You’ve probably called them dozens of times without thinking about it.

Google’s own data shows that 70% of the top 1,000 apps on the Play Store are written in Kotlin, and JetBrains reports that 95% of top Android apps include some Kotlin code. That means vararg-based standard library calls run in practically every major Android app on the market.

Why is Kotlin becoming the new Java?

Discover Kotlin statistics: Android adoption, multiplatform growth, developer satisfaction, and the modern language evolution from JetBrains.

Explore Kotlin Data →

Basic vararg Syntax

The declaration looks like this:

fun printAll(vararg messages: String) { ... }

You call it with individual values separated by commas:

printAll("hello", "world", "test")

Or with zero arguments. That’s valid too.

One rule to know right away: only one vararg parameter per function. The Kotlin compiler won’t let you declare two. Took me a while to internalize that, but once you do, function signatures stay clean.

How varargs Syntax Works in Function Declarations

maxresdefault What Are Kotlin Varargs and How to Use Them

The vararg modifier goes directly before the parameter name and type. Unlike Java, where the three dots (...) follow the type, Kotlin puts the modifier first.

fun calculate(vararg numbers: Int): Int

This is one of those small syntax differences that trips up Java developers switching to Kotlin. The JetBrains Developer Ecosystem Survey 2024 found that 75% of Kotlin users are satisfied with the language, but the transition from Java idioms still catches people off guard.

Parameter Position Rules

Here’s where Kotlin actually gives you more freedom than Java. In Java, the varargs parameter must be last. Kotlin doesn’t have that restriction.

You can put vararg anywhere in the parameter list:

fun createUser(vararg roles: String, username: String, age: Int)

But there’s a catch. When the vararg isn’t the last parameter, you have to use named arguments for everything that comes after it. Otherwise the compiler can’t figure out where the variable arguments end and the named ones begin.

So calling the function above looks like:

createUser("admin", "editor", username = "jdoe", age = 30)

Most developers put vararg last anyway. It’s just easier to read. But knowing you have the option matters when designing APIs.

Calling a Function with varargs

Zero arguments: Perfectly valid. The function receives an empty array internally.

Single argument: Works exactly like a regular function call. No special syntax needed.

Multiple arguments: Pass them separated by commas. The Kotlin compiler checks each value against the declared type at compile time, so you get type safety for free.

When someone compares Kotlin to Java for Android work, this call-site simplicity is one of the first things that comes up. Android apps that use Kotlin are 20% less likely to crash, according to Google’s Android team, and features like type-safe varargs contribute to that.

The Spread Operator and varargs

maxresdefault What Are Kotlin Varargs and How to Use Them

You have an existing array. You want to pass its contents to a function that expects vararg. Kotlin won’t let you just hand over the array directly.

That’s what the spread operator () is for.

val tags = arrayOf("kotlin", "android", "jvm") processTags(tags)

The asterisk before the array variable tells the compiler to unpack each element and pass them as individual arguments. Without it, you’ll get a type mismatch error.

How the Spread Operator Works Internally

This is where things get interesting (and a little tricky). Under the hood, Kotlin calls Arrays.copyOf() to create a full copy of the array before passing it to the function. Baeldung’s analysis of the bytecode confirms this: the compiler loads the array, duplicates it via Arrays.copyOf, then passes the copy as the vararg parameter.

That means there’s a memory cost. Every spread operation allocates a new array.

For small arrays or infrequent calls, you won’t notice. But inside a tight loop with large arrays? That’s a different story. The detekt static analysis tool flags spread operator usage as a performance rule for exactly this reason.

Mixing Individual Values with Spread Arrays

Kotlin lets you combine individual arguments and spread arrays in the same call:

processTags("first", existingTags, "last")

When you do this, Kotlin uses a SpreadBuilder internally. It gathers all values (individual and spread), computes the final array size, then populates it. That’s an extra object allocation on top of the array copy.

As a practical matter, this mixing feature is genuinely useful for builder patterns and DSL construction. Teams building with Gradle’s Kotlin DSL use it regularly.

Compiler Optimization Since Kotlin 1.1.60

There’s one case where the array copy gets skipped entirely. If you pass an array constructor directly to the spread operator, like arrayOf("a", "b"), the Kotlin compiler (since version 1.1.60) optimizes away the copy.

This only applies when the array is created inline at the call site. If you’re spreading a variable that was declared earlier, the copy still happens.

varargs vs Array Parameters

Both approaches ultimately compile to array-based method signatures on the JVM. The difference is entirely about what happens at the call site.

Featurevararg ParameterArray Parameter
Call syntaxComma-separated valuesWrapped in arrayOf()
Zero-arg callsJust call with empty parensMust pass emptyArray()
Passing existing arraysRequires spread operator (*)Pass directly
IDE displayShows individual paramsShows array type

When to Use vararg Over an Explicit Array

Use vararg when: callers typically pass a small, known number of values by hand. The listOf() and setOf() functions in Kotlin’s standard library are textbook examples. Nobody wants to write listOf(arrayOf(1, 2, 3)).

Use an Array parameter when: callers already have their data in an array or collection. If most callers will be spreading an array anyway, skip the vararg and accept the array directly. Less overhead, cleaner intent.

Netflix, one of the companies that adopted Kotlin early, reportedly focuses on API simplicity across their Android and back-end services. The choice between vararg and array parameters might seem minor, but it shapes how other developers experience your API across the entire codebase.

Bytecode Differences

When vararg is the last parameter, the Kotlin compiler produces a true Java varargs signature (with the ACCVARARGS flag). Java callers can invoke it naturally with comma-separated arguments.

But when vararg sits in any other position, the compiler translates it to a plain array parameter. Java callers then have to construct and pass the array manually.

This matters for software development teams maintaining mixed Kotlin and Java codebases. Your mileage may vary depending on how much Java interop you actually need.

varargs with Generics and Reified Types

maxresdefault What Are Kotlin Varargs and How to Use Them

Kotlin’s type inference handles generic vararg parameters well. You can declare a function like:

fun <T> asList(vararg items: T): List<T> = items.toList()

The compiler infers T from whatever you pass in. This is actually how listOf() works under the hood in the Kotlin standard library.

Type Inference with Mixed Types

When you pass mixed but compatible types, Kotlin resolves to the nearest common supertype. Pass an Int and a Double? You get Array<Number> at the vararg level.

This works predictably most of the time. Where it gets surprising is with nullable types mixed in. Passing "hello" and null together gives you Array<String?>, not Array<String>.

Inline Functions with Reified Type Parameters

Combine vararg with inline and reified, and you get something pretty powerful:

inline fun <reified T> filterByType(vararg items: Any): List<T>

The reified keyword lets you access the actual type at runtime, which normally isn’t possible on the JVM due to type erasure. This pattern shows up in testing frameworks and serialization libraries.

According to Stack Overflow’s 2024 Developer Survey, Kotlin ranked as the 4th most admired programming language with 58.2% developer satisfaction. Features like reified generics paired with varargs are part of what keeps developers coming back.

Standard Library Patterns

listOf(), setOf(), mapOf(): All built on generic vararg parameters. They’re the most commonly used vararg functions in any Kotlin project.

arrayOf(), intArrayOf(), longArrayOf(): These handle primitive and reference type Kotlin arrays through varargs.

Understanding how these standard functions use varargs internally makes it easier to design your own APIs that follow the same conventions.

varargs in Constructors and Class Declarations

maxresdefault What Are Kotlin Varargs and How to Use Them

The vararg modifier works in primary constructors the same way it works in regular functions. But there’s a detail most tutorials skip.

When you declare vararg in a primary constructor, the parameter does not automatically become a property. You need to assign it explicitly if you want to store the values:

class TaggedItem(vararg tags: String) { val tagList = tags.toList() } `

Without that assignment, the vararg values exist only during construction.

Primary Constructor Usage

Kotlin constructors with vararg make initialization clean:

val item = TaggedItem(“kotlin”, “jvm”, “android”)

No wrapping in arrays. No builder methods. Just pass the values directly.

This pattern shows up in test fixtures a lot. JUnit 5 parameterized tests and Kotest data-driven tests both use vararg-style initialization to keep test setup readable.

Data Classes and vararg

Here’s where it gets a little annoying. Kotlin data classes don’t play well with vararg in primary constructors.

The auto-generated equals(), hashCode(), and copy() methods work on the array reference, not the array contents. Two data class instances with identical vararg values won't be equal unless you override those methods manually.

Most developers convert the vararg to a List in the constructor body and use that list as the property instead. It's an extra step, but it avoids subtle bugs that are hard to track down.

Secondary Constructors

Secondary constructors can also use vararg. The pattern works for overloading construction paths:

` class Logger(val tag: String) { constructor(vararg tags: String) : this(tags.joinToString("-")) } `

Teams working on Android projects use this for custom View constructors that need to accept multiple style attributes or configuration values.

Kotlin Multiplatform usage jumped from 7% to 18% between 2024 and 2025, according to JetBrains’ Developer Ecosystem data. As more teams share code across platforms, constructor patterns with varargs become part of the shared module design.

Common Mistakes and Pitfalls with varargs

Most vararg bugs fall into two categories: forgetting something the compiler expects, or assuming behavior that doesn’t match how Kotlin actually works.

Forgetting the Spread Operator

This is the single most common vararg mistake. You have an array, you pass it directly to a function expecting vararg, and the compiler throws a type mismatch error.

The fix is always the same: add the prefix. But the error message isn't always obvious, especially for developers coming from Java where arrays pass to varargs automatically.

JetBrains reports that 92% of Kotlin developers previously used Java. That Java habit of passing arrays directly to vararg methods is deeply ingrained, and it’s the first thing that breaks.

Passing vararg to Another vararg Function

The pass-through trap: When you wrap a vararg function inside another vararg function, you must re-spread the parameter. Otherwise Kotlin passes the entire array as a single element.

` fun wrapper(vararg args: Any?) { String.format("%d", args) // WRONG: passes array as one arg String.format("%d", args) // CORRECT: spreads the values } `

The Kotlin Discussions forum has an entire thread dedicated to this exact bug. It’s subtle because there’s no compile-time error. It only fails at runtime.

Multiple vararg Parameters

Kotlin allows exactly one vararg parameter per function. Trying to declare two produces a compile error. No workaround, no exceptions.

If you need two variable-length inputs, accept one as a vararg and the other as a List or Array. That's the standard pattern.

Nullable Types in vararg Declarations

DeclarationAccepts Nulls?Internal Type
vararg items: StringNoArray
vararg items: String?YesArray
vararg items: AnyNoArray
vararg items: Any?Yes, including nullArray

The confusion usually happens when someone assumes vararg items: String can receive null values. It can't. The Kotlin compiler enforces null safety on each individual argument.

Android apps using Kotlin are 20% less likely to crash than Java-based apps, according to Google. Null safety in varargs contributes directly to that number.

How Kotlin varargs Compiles to JVM Bytecode

maxresdefault What Are Kotlin Varargs and How to Use Them

The Kotlin compiler translates vararg parameters into standard Java varargs at the bytecode level. Baeldung's bytecode analysis confirms the compiled output includes the ACCVARARGS flag, which tells the JVM this method accepts a variable-length argument.

Last-Position vararg Compilation

When vararg is the last parameter in the function signature, the Kotlin compiler produces a true Java varargs method.

Kotlin source: fun sum(vararg numbers: Int)

JVM bytecode: public static final int sum(int…)

Java callers can invoke this function naturally, passing comma-separated integers. No spread operator, no arrayOf(). It just works.

Non-Last-Position vararg Compilation

Things change when vararg sits anywhere except the last position. The Kotlin compiler drops the ACC_VARARGS flag and compiles the parameter as a plain array.

fun createUser(vararg roles: String, username: String)

Becomes:

public static final void createUser(String[], String)

Java callers now have to construct and pass a String[] manually. This is a direct consequence of Java requiring varargs to be the last parameter. The Kotlin compiler can't produce valid Java varargs in any other position.

Calling Kotlin vararg Functions from Java

Works seamlessly: Last-position vararg functions behave identically to Java varargs methods.

Requires manual arrays: Non-last-position vararg functions need explicit array construction in Java code.

Teams maintaining mixed development processes with both Kotlin and Java files should keep vararg parameters last whenever possible. It simplifies interop across the entire project.

The Kotlin 2.0 K2 compiler, released as stable in 2024, produces identical vararg bytecode. The K2 compiler runs up to 2x faster during compilation, according to JetBrains, but the runtime output for varargs remains the same.

varargs in Kotlin Multiplatform and Non-JVM Targets

The vararg keyword works the same way across all Kotlin compilation targets. You write it once in shared code, and it compiles correctly for JVM, Native, JavaScript, and WebAssembly.

JetBrains’ Developer Ecosystem data shows Kotlin Multiplatform usage grew from 7% to 18% in a single year. That growth means more developers are writing vararg functions in shared modules that target multiple platforms simultaneously.

Kotlin/Native and Kotlin/JS Behavior

maxresdefault What Are Kotlin Varargs and How to Use Them

Kotlin/Native: Vararg parameters compile to native arrays on each target platform (iOS, macOS, Linux, Windows). The spread operator works identically.

Kotlin/JS: Vararg compiles to JavaScript arrays. Long values in Kotlin/JS are now compiled into JavaScript BigInt as of Kotlin 2.2.20.

In Kotlin 2.3.0, vararg functions in Kotlin/Native are directly mapped to Swift’s variadic function parameters, according to the official release notes. This means iOS developers calling shared Kotlin code can pass variable arguments naturally from Swift.

Kotlin/Wasm Considerations

Safari adopted WasmGC in December 2024, making Kotlin/Wasm apps runnable across 100% of modern browsers.

Vararg behavior in Kotlin/Wasm follows the same semantics as other targets. The spread operator copies arrays the same way. No platform-specific quirks to worry about.

Compose Multiplatform for web uses vararg patterns in its resource API. The stringResource() function, which accepts vararg formatArgs: Any, works identically whether the cross-platform app runs on Android, iOS, or the browser.

Spread Operator Across Platforms

The operator produces the same array-copy behavior on every target. Each platform's compiler backend handles the copy using its own mechanism, but the developer experience stays consistent.

Google Workspace adopted KMP to share logic between Gmail, Docs, and Calendar across platforms. Functions with vararg parameters in shared modules work without platform-specific adjustments.

Practical Use Cases for varargs in Kotlin Projects

Varargs show up in real codebases more often than most developers realize. The feature is embedded in everyday Kotlin patterns, from test setup to build configuration.

DSL Construction

maxresdefault What Are Kotlin Varargs and How to Use Them

Kotlin DSLs rely heavily on vararg parameters. Gradle’s Kotlin DSL, Ktor’s routing configuration, and Jetpack Compose’s modifier chains all use vararg functions internally.

Compose Multiplatform’s stringResource() and pluralStringResource() accept vararg formatArgs for dynamic string templates. This pattern runs across Android, iOS, desktop, and web targets.

Teams building custom DSLs in Kotlin use varargs to create APIs that feel like configuration languages rather than function calls. The Kotlin lambda and vararg combination makes this possible without complex builder hierarchies.

Test Frameworks and Assertion Helpers

JUnit 5: Parameterized tests in Kotlin commonly use vararg for test data initialization.

Kotest: Data-driven testing with forAll(vararg rows: Row) patterns.

Custom assertions: Teams write helper functions like assertAllContain(vararg expected: String) to reduce test boilerplate.

Adopting test-driven development in Kotlin projects becomes cleaner with vararg-based test utilities. You set up fixtures once and reuse them across dozens of test cases.

Logging and Debugging Functions

Most custom logging wrappers use varargs for flexibility:

fun logDebug(tag: String, vararg messages: Any)

This lets developers pass one message or ten without changing the function signature. The vararg parameter gets joined or formatted inside the function body.

Duolingo’s engineering team reported 40% faster feature delivery after adopting KMP. Shared utility functions, including logging helpers with vararg parameters, are part of what makes that speed possible.

When NOT to Use varargs

  • Large argument counts where callers almost always pass arrays (use Array or List instead)
  • Performance-sensitive loops where the spread operator’s array copy becomes measurable
  • Functions called from Java where the vararg isn’t in the last position

The detekt static analysis tool flags spread operator usage inside hot code paths. If your function handles thousands of calls per second, consider accepting a Kotlin Flow or typed collection instead.

Organizations using Kotlin report up to a 30% reduction in development and maintenance costs, according to JetBrains data. Smart use of language features like varargs, where they fit and skipping them where they don’t, is part of that equation.

FAQ on What Are Kotlin Varargs

What does the vararg keyword do in Kotlin?

The vararg modifier lets a function accept a variable number of arguments of the same type. Inside the function body, Kotlin treats the vararg parameter as an array. You can pass zero, one, or many values at the call site.

How many vararg parameters can a Kotlin function have?

Exactly one. The Kotlin compiler rejects any function declaration with two or more vararg parameters. If you need multiple variable-length inputs, accept one as vararg and the other as a List or Array.

What is the spread operator in Kotlin?

The spread operator () unpacks an existing array into individual arguments for a vararg parameter. Without it, passing an array directly causes a type mismatch error. The compiler creates an array copy when spreading.

Does the spread operator affect performance?

Yes. The spread operator calls Arrays.copyOf() to create a full copy of the array before passing it. In tight loops or with large arrays, this adds measurable overhead. Since Kotlin 1.1.60, inline arrayOf() calls skip the copy.

Can vararg be placed anywhere in a function signature?

Unlike Java, Kotlin allows vararg in any position. But when it's not last, all following parameters require named arguments at the call site. Most developers keep vararg last for readability and better Java interop.

How do Kotlin varargs differ from Java varargs?

Kotlin uses the vararg modifier instead of Java's three-dot () syntax. Kotlin also requires the spread operator when passing arrays, while Java accepts arrays directly. Position flexibility is another difference.

Can vararg be used in Kotlin constructors?

Yes. Both primary and secondary constructors support vararg. The parameter doesn't automatically become a class property though. You need to assign it explicitly, typically by converting to a List inside the constructor body.

How do varargs work with Kotlin generics?

Generic vararg parameters use type inference to determine the type from passed arguments. Functions like listOf() in the Kotlin standard library use this pattern. Combining vararg with inline and reified allows runtime type access.

Do varargs work in Kotlin Multiplatform projects?

The vararg keyword works identically across JVM, Native, JavaScript, and Wasm targets. You write it once in shared code and it compiles correctly for each platform. The spread operator behaves the same everywhere.

When should you avoid using varargs in Kotlin?

Avoid varargs in performance-critical loops where array copying matters. Skip them when callers mostly pass existing arrays or collections. Also avoid non-last-position varargs if Java interoperability is a requirement for your project.

Conclusion

Understanding what are Kotlin varargs goes beyond knowing the syntax. It means grasping how the Kotlin compiler handles array creation, how the spread operator copies data at the bytecode level, and where vararg functions fit in your project architecture.

The vararg` modifier powers core Kotlin standard library functions, constructor initialization patterns, and DSL designs used in Gradle, Ktor, and Jetpack Compose. It compiles cleanly across JVM, Native, JS, and Wasm targets.

Position matters. Putting vararg last keeps Java interoperability smooth. Nullable types change the internal array signature. The spread operator has a real performance cost inside hot loops.

Know these details and you’ll write Kotlin function declarations that are flexible without being wasteful. Skip them and you’ll chase runtime bugs that the compiler tried to warn you about.

50218a090dd169a5399b03ee399b27df17d94bb940d98ae3f8daff6c978743c5?s=250&d=mm&r=g What Are Kotlin Varargs and How to Use Them
Related Posts