skip to content

What members and behavior can a `@JvmInline value class` have that a `typealias` cannot, and what are the value class's structural constraints?

level: middleimportance: should knowfreq 40%

answer

  1. Alias = name only: no members, no validation
  2. Value class: methods, computed props, init require, interfaces
  3. Exactly one constructor param, must be val
  4. No class inheritance; interfaces only; implicitly final
  5. @JvmInline required on the JVM

basics

~20 s

A value class is a real type, so it can have methods, computed properties, validation, and implement interfaces. A typealias is only a name and can hold none of those. The value class must wrap exactly one read-only value and can't inherit from a class.

solid answer

~50 s

A `typealias` is purely a name — it carries **no members, no validation, no behavior**; it is erased to the aliased type. A `@JvmInline value class` is a genuine type, so it can declare **functions**, **computed properties** (no backing field), an **`init { require(...) }`** validation block, secondary constructors with bodies, and it can **implement interfaces**. Its structural constraints: the **primary constructor must have exactly one parameter and it must be a `val`** (not `var`); the underlying type cannot be the value class itself; it **cannot extend** another class (no inheritance, it is implicitly final) — only interface implementation is allowed; it cannot have additional state with backing fields beyond that single property; and it cannot be a `data` class with extra fields. It must be annotated `@JvmInline` on the JVM. These constraints are what let the compiler inline the single value while still exposing real behavior — capabilities a transparent alias fundamentally can't offer.

code

kotlin · 8 lines
kotlin
@JvmInline
value class Email(val value: String) {
    init { require('@' in value) }            // validation
    val domain: String get() = value.substringAfter('@')  // computed, no backing field
    fun isFrom(host: String) = domain == host
}
// typealias cannot host any of the above:
typealias EmailAlias = String  // just a name

go deeper

for a junior

Knows a value class can have methods while an alias is just a name.

for a middle

Enumerates allowed members (init, computed props, interfaces) and the one-val constraint.

for a senior

Explains why the constraints (single immutable value, no inheritance) are required for inlining and how interface use forces boxing.

for a principal

Reasons about API design with constrained value types and the immutability/equality guarantees the constraints buy.

## typealias: a name only ```kotlin typealias Predicate<T> = (T) -> Boolean ``` A `typealias` cannot: - declare functions, properties, or an `init` block; - add validation or invariants; - implement interfaces or change the type's behavior. It is **erased** to the aliased type; it exists only for readability/abbreviation. ## value class: a real (but constrained) type ```kotlin @JvmInline value class Percent(val raw: Int) : Comparable<Percent> { init { require(raw in 0..100) { "out of range: $raw" } } // validation val fraction: Double get() = raw / 100.0 // computed prop, no backing field fun clampAdd(other: Percent) = Percent((raw + other.raw).coerceAtMost(100)) override fun compareTo(other: Percent) = raw.compareTo(other.raw) // implements interface } ``` **Allowed members:** - member **functions**; - **computed properties** (must have no backing field — i.e., custom getters); - an **`init`** block for validation (`require`/`check`); - **secondary constructors** (since Kotlin 1.x they may even have bodies in recent versions); - **interface implementation** (e.g., `Comparable`). **Structural constraints (what makes it a value class):** - **Exactly one primary-constructor parameter**, and it must be a **`val`** (read-only). No `var`, no second param. - **No backing-field state** beyond that single property — extra properties must be computed. - **No class inheritance**: a value class cannot have a superclass and is effectively final; it can only **implement interfaces**. - The underlying type **cannot be the value class itself** (no self-recursion). - On the JVM it must be marked **`@JvmInline`** (the bare `value class` keyword reserves the concept; `@JvmInline` selects the inlined representation). - Equality/hashCode are derived structurally from the single value. ## Why the constraints exist Inlining works by replacing the wrapper with its single underlying value at runtime. That is only possible if there is exactly **one** piece of real state and no inherited object identity to preserve. The constraints are precisely what keep the abstraction *zero-cost* while still letting it expose methods and validation — the decisive advantage over a `typealias`, which can express none of this. ## Quick comparison | Capability | typealias | @JvmInline value class | |---|---|---| | New distinct type | No | Yes | | Methods / computed props | No | Yes | | init validation | No | Yes | | Implement interface | No | Yes | | Extend a class | n/a | No (final, interfaces only) | | Runtime cost | none (erased) | none in direct use; boxes sometimes |

  • Can a value class implement an interface, and what is the runtime consequence?
    Yes. But when it is used *through* that interface, it must be boxed into a real object, so you lose the unboxed representation there.
  • Why must the single constructor parameter be a val and not a var?
    Value classes are immutable so the compiler can safely represent them by their underlying value; a mutable var would break that representation and equality.

saying these in an interview costs you the question

  • Thinking a typealias can carry methods or validation
  • Claiming a value class can have multiple stored properties
  • Saying a value class can extend another class
  • Adding a backing-field property beyond the single val
  • Forgetting @JvmInline is required on the JVM

context