# Kotlin Empty List: Why `emptyList()` Returns a Singleton and How It Differs from Other Initializations

> Discover why Kotlin emptyList() returns a singleton for efficiency. Learn how this immutable list differs from other initializations and saves memory.

- Repository: [JetBrains/kotlin](https://github.com/jetbrains/kotlin)
- Tags: deep-dive
- Published: 2026-02-21

---

**`emptyList()` in Kotlin returns a single, shared instance of an immutable list that is reused across the entire program, eliminating memory allocation overhead on every call.**

When working with collections in the JetBrains/kotlin standard library, choosing the right initialization method impacts both performance and memory usage. Unlike other list factories that instantiate new objects, the `kotlin empty list` function leverages a singleton pattern defined in [`libraries/stdlib/src/kotlin/collections/Collections.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/src/kotlin/collections/Collections.kt). Understanding this implementation detail helps developers write more efficient code, particularly when using empty lists as default arguments or sentinel values.

## The Singleton Implementation of EmptyList

The `emptyList()` function does not allocate new memory. Instead, it returns a reference to a single `object` declaration that exists for the lifetime of the application:

```kotlin
public fun <T> emptyList(): List<T> = EmptyList

```

This implementation appears at **line 75** of [`libraries/stdlib/src/kotlin/collections/Collections.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/src/kotlin/collections/Collections.kt). The `EmptyList` object itself is defined at **line 25** as an internal singleton implementing `List<Nothing>`:

```kotlin
internal object EmptyList : List<Nothing>, Serializable, RandomAccess {
    override val size: Int get() = 0
    override fun isEmpty(): Boolean = true
    override fun get(index: Int): Nothing = 
        throw IndexOutOfBoundsException("Empty list doesn't contain element at index $index.")
    // … other overrides …
}

```

Because `EmptyList` is a Kotlin `object`, the JVM creates exactly **one instance** at class-initialization time. Every call to `emptyList()` returns this same instance, making identity comparisons (`===`) return `true` regardless of the call site or generic type parameter.

## How kotlin empty list Differs from Other List Factories

The key differentiator of `kotlin empty list` is that it avoids allocation overhead entirely, while other factory functions create new backing storage:

**`emptyList()`** returns the single `EmptyList` object with zero allocation. This makes it ideal for default parameter values and high-frequency operations where you need a read-only empty list.

**`listOf()`** with no arguments delegates internally to `emptyList()`, returning the same singleton. However, when provided with elements, `listOf(vararg)` creates a new `ArrayList` or optimized specialized list.

**`mutableListOf()`** and **`arrayListOf()`** always allocate a new `ArrayList` instance, even when initialized with no elements. These return mutable collections with independent backing arrays.

**`listOf(element)`** returns a tiny, optimized read-only list that stores the single element directly, but this is still a new allocation rather than a singleton.

## Practical Code Examples

The following examples demonstrate the singleton behavior and identity equality of `emptyList()`:

```kotlin
// Example 1 – Reusing the same instance across types
val a: List<Int> = emptyList()
val b: List<String> = emptyList()
println(a === b)          // true – both reference the same EmptyList object

```

When used as a default parameter, `emptyList()` avoids creating new objects on every function call:

```kotlin
// Example 2 – Zero-allocation default parameter
fun <T> filterNonNull(items: List<T?> = emptyList()): List<T> =
    items.filterNotNull()

println(filterNonNull())  // prints [] without allocating a new list

```

The compiler optimizes `listOf()` without arguments to use the same singleton, but mutable variants create distinct instances:

```kotlin
// Example 3 – Comparison with listOf()
val c = listOf<Int>()    // compiled to a call to emptyList()
println(c === emptyList()) // true

// Example 4 – Mutable counterpart creates new objects
val mutable = mutableListOf<Int>()
println(mutable === emptyList()) // false – mutableListOf creates a new ArrayList

```

## Serialization and Equality Guarantees

The `EmptyList` object implements **Serializable** and **RandomAccess**, ensuring it can be safely written to object streams while preserving its singleton nature upon deserialization. The `serialVersionUID` guarantees compatibility across Kotlin versions.

Equality comparisons treat any empty list as equal regardless of implementation:

```kotlin
val empty1 = emptyList<String>()
val empty2 = listOf<String>()
val empty3 = mutableListOf<String>()

println(empty1 == empty2)  // true – structural equality
println(empty1 == empty3)  // true – structural equality
println(empty1 === empty2) // true – referential equality (same singleton)

```

The `toString()` implementation yields `"[]"`, providing consistent string representation across all empty list types.

## Summary

- **`emptyList()`** returns a pre-created singleton instance defined in [`libraries/stdlib/src/kotlin/collections/Collections.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/src/kotlin/collections/Collections.kt), eliminating per-call allocation overhead.
- The **EmptyList object** implements `List<Nothing>`, `Serializable`, and `RandomAccess`, providing immutable, thread-safe behavior.
- **`listOf()`** without arguments delegates to `emptyList()`, while **`mutableListOf()`** always creates a new `ArrayList` instance.
- Identity comparisons (`===`) between any two `emptyList()` calls return **true**, confirming the singleton pattern.
- The implementation supports safe serialization and maintains structural equality with any other empty list implementation.

## Frequently Asked Questions

### Is `emptyList()` the same as `listOf()` with no arguments?

Yes. When you call `listOf()` without arguments, the compiler generates a call to `emptyList()`, returning the same singleton instance. Both expressions result in identical bytecode and reference the shared `EmptyList` object.

### Can I modify a list created with `emptyList()`?

No. The `EmptyList` object is immutable, and any attempt to modify it—such as calling `add()`, `remove()`, or `set()`—will result in an `UnsupportedOperationException`. For mutable empty lists, use `mutableListOf()` instead.

### Why does `emptyList()` return `List<Nothing>` instead of `List<T>`?

The `EmptyList` object implements `List<Nothing>` because it contains no elements, and `Nothing` is the bottom type in Kotlin's type hierarchy. The generic function `emptyList<T>()` casts this single instance to `List<T>` safely at the call site, allowing type inference to work while maintaining the singleton pattern.

### Is `emptyList()` thread-safe?

Yes. Because `emptyList()` returns an immutable singleton object with no mutable state, it is inherently thread-safe. Multiple threads can safely reference the same `EmptyList` instance without synchronization concerns, making it ideal for concurrent applications and default parameter values.