skip to content

Value / Inline Classes

Value classes wrap a single value to give it a real type without paying for an object allocation in the common case. The interview value is knowing exactly when that promise holds and when the compiler has to box after all.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

explore

questions

15

What does it mean for a Kotlin @JvmInline value class to be "boxed," and when does the compiler keep it inlined (unboxed) instead?

level: juniorimportance: must knowfreq 55%

answer

  1. Unboxed = wrapper erased, raw value passed directly
  2. Boxed = real object on heap
  3. Boxes on: nullable, generics, supertype/Any, collections
  4. Stays unboxed when static type is exactly the value class, non-null
  5. Boxing is correct but costs an allocation

basics

~20 s

Normally the value class disappears and only its single wrapped value is used directly, which is fast. Sometimes Kotlin must wrap it back into a real object (boxing), like when it is null or stored generically.

solid answer

~40 s

A @JvmInline value class wraps one property. In the common case the compiler erases the wrapper and passes the underlying value directly (inlined/unboxed), so there is no extra allocation. Boxing means the compiler creates an actual instance of the wrapper class on the heap. This happens when the value must be treated as an object reference: used as a nullable type, used through a generic type parameter, stored in a collection like List<T>, or used as Any or a supertype/interface it implements. As a rule of thumb: a value class stays unboxed when its static type is exactly the value class and non-null; it boxes whenever it has to flow through a slot that expects an object. Boxing is correctness-preserving but reintroduces the allocation the class was meant to avoid.

code

kotlin · 11 lines
kotlin
@JvmInline
value class UserId(val raw: Long)

fun direct(id: UserId): Long = id.raw          // unboxed: param is a long

fun demo() {
    val id = UserId(7)                          // unboxed
    val nullable: UserId? = id                  // boxed (nullable)
    val anyRef: Any = id                        // boxed (Any)
    val ids: List<UserId> = listOf(id)          // boxed (generic List)
}

go deeper

for a junior

Can state the unboxed-by-default idea and name at least nullable and collections as boxing triggers.

for a middle

Lists all four trigger categories (nullable, generics, supertype/Any, collections) and explains the static-type rule.

for a senior

Ties boxing back to the performance intent and reasons about keeping hot paths unboxed.

for a principal

Frames value classes as a zero-cost abstraction whose guarantee leaks at object-reference boundaries, and weighs that when designing public APIs.

## What a value class is A `@JvmInline value class` wraps exactly one read-only property: ```kotlin @JvmInline value class UserId(val raw: Long) ``` The point is zero-overhead typing: at runtime, in the good case, there is no `UserId` object — the JVM just sees a `long`. ## Inlined (unboxed) vs boxed - **Inlined / unboxed**: the wrapper is erased and the underlying value (`raw`) is used directly. No heap allocation. This is the default the compiler tries hard to keep. - **Boxed**: the compiler allocates a real instance of the generated class (it has a `box-impl`/`unbox-impl` pair under the hood) so the value can be handled as an object reference. ## When boxing happens The value must be represented as an `Object` reference, so it boxes when: - **Nullable use** — `UserId?`. `null` can't be a primitive `long`, so the type becomes the boxed class. - **Generics** — used as a type argument, e.g. `List<UserId>`, `Pair<UserId, UserId>`, or any generic function parameter `T`. Generic slots erase to `Object`. - **Supertype / interface / Any** — assigned to `Any` or to an interface the value class implements; that requires an object reference. - **Collections / arrays-of-Object** — `listOf(userId)` boxes each element. (A dedicated `Array<UserId>` is also boxed; only the primitive `LongArray` etc. would be unboxed but that loses the type.) ## When it stays unboxed When the **static type is exactly the value class and non-null** and it's used directly — local variables, parameters typed `UserId`, return type `UserId`. Example: ```kotlin fun create(): UserId = UserId(42) // returns a long, unboxed fun length(id: UserId): Int = ... // takes a long, unboxed val id: UserId = create() // stays unboxed val maybe: UserId? = id // BOXED (nullable) val list = listOf(id) // BOXED (generic) ``` ## Why it matters The entire value of an inline value class is avoiding allocation while keeping a distinct type. Knowing what triggers boxing lets you keep hot paths allocation-free.

  • Does putting a value class into a List<UserId> box it?
    Yes. List is generic, its element slot erases to Object, so each element is boxed.
  • Is a non-null UserId local variable boxed?
    No. Its static type is exactly UserId and non-null, so it stays as the underlying value (unboxed).

Like shipping a single book: usually you hand it over bare (unboxed); but if it must go into a mixed parcel of 'any item' or be marked 'maybe empty', you put it in a box first.

saying these in an interview costs you the question

  • Claiming value classes are 'never' allocated under any circumstance
  • Thinking nullable UserId? stays unboxed
  • Confusing value class with data class (data class always allocates)
  • Assuming listOf(id) keeps it inlined

context

open as a page

What is a Kotlin value class, and why must it be annotated with @JvmInline on the JVM?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A value class wraps one value (like a String or Int) in a new type without the cost of a real object. On the JVM you add @JvmInline so Kotlin can store just the wrapped value instead of a wrapper object.

open as a page

Why would you wrap a String or Long in a value class such as UserId or Email instead of using the raw primitive type directly?

level: juniorimportance: must knowfreq 70%

basics

~20 s

To make the type meaningful and safe. A plain String could be anything; a UserId can only be a user id, so the compiler stops you from mixing up a user id with an order id or an email.

open as a page

At runtime, how does the Kotlin compiler represent a @JvmInline value class, and what does the underlying property's type have to be?

level: middleimportance: must knowfreq 55%

basics

~10 s

Where possible the compiler throws away the wrapper and uses the wrapped value directly, so there's no extra object. The single property can hold almost any type, including another value class.

open as a page

What are the structural constraints on a value class — how many properties can it have, and can it have ordinary properties with backing fields?

level: middleimportance: must knowfreq 60%

basics

~10 s

A value class must have exactly one value (one constructor property). It can't have extra stored properties — only computed ones. That single value is what it really is underneath.

open as a page

Explain why `value class Color(val rgb: Int)` boxes when its type is `Color?` but not when it's `Color`, and how this interacts with a value class whose underlying type is already a reference type.

level: middleimportance: should knowfreq 45%

basics

~20 s

A non-null value uses the raw underlying value directly. A nullable value needs to represent 'no value', which a primitive can't, so Kotlin wraps it in an object. If the underlying type is already an object, nullability is cheaper.

open as a page

When would you choose a @JvmInline value class over a single-field data class? What members can a value class still have?

level: middleimportance: should knowfreq 50%

basics

~20 s

Use a value class when you want a typed wrapper around one value with no runtime cost. A single-field data class always creates a real object. Value classes can still have functions, computed properties, init checks, and implement interfaces.

open as a page

Can a value class participate in inheritance — extend another class, be extended, or implement an interface? Why or why not?

level: middleimportance: should knowfreq 45%

basics

~10 s

A value class can't extend another class and can't be a parent of others — it's effectively final with no superclass. It can still implement interfaces.

open as a page

You have `@JvmInline value class Meters(val v: Double) : Comparable<Meters>`. Walk through which of these box: `meters.compareTo(other)`, `listOf(a, b).sorted()`, and passing `meters` to `fun <T> id(x: T): T`. How can boxing reintroduce the allocation cost the value class was meant to avoid?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Calling its own methods directly stays cheap. But the moment values flow through generic code or an interface (like sorting a list), Kotlin wraps them into objects, so you pay allocations exactly where you hoped to save them.

open as a page

Why does the Kotlin compiler mangle function names that take or return @JvmInline value class parameters on the JVM, and what practical consequences does this have for overloads and Java interop?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Because the wrapper disappears, two different Kotlin functions could end up with identical JVM signatures and clash. Kotlin renames (mangles) the methods with a hash suffix to keep them distinct, which makes them awkward to call from Java.

open as a page

How are equals/hashCode generated for a @JvmInline value class, and what are the implications of a private wrapped val plus init-block validation?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Equality and hashCode come from the one wrapped value, so two wrappers are equal when their values are equal. You can make the value private and validate it in an init block so invalid instances can never exist.

open as a page

How do you enforce that an Email or Percentage value class only ever holds a valid value, and what are the limits of doing so?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Put a check in the value class's init block so construction fails for bad input. But it can't guarantee validity if the value is created by deserialization or unsafe casts that bypass the constructor.

open as a page

You need a Money type and a Coordinates(lat, lng) type. For each, decide between a value class and a data class and justify the choice.

level: seniorimportance: should knowfreq 50%

basics

~10 s

Money is one value, so a value class fits and stays cheap. Coordinates has two fields, which a value class can't hold, so use a data class.

open as a page

When designing a public Kotlin library API, what boxing- and mangling-related trade-offs should drive whether you expose a @JvmInline value class versus a plain type or a data class — especially considering Java consumers and identity-sensitive operations like equals/hashCode and `===`?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Value classes give type safety with no allocation in the common case, but they box at many boundaries and produce ugly, Java-unfriendly names. Weigh those costs against the safety benefit, especially for Java users and code that compares object identity.

open as a page

What are the hard constraints on a @JvmInline value class declaration, and what interop/design consequences follow from name mangling?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

A value class must have exactly one immutable property, can't store extra state, can't extend a class, and can't be inner. Because Kotlin renames its functions, calling them from Java is awkward and you usually need @JvmName.

open as a page