Type Aliases & Value Classes

Reviewed & published by Brayan K

A type alias is just a second, friendlier name for an existing type, while a value class is a zero-overhead wrapper that turns a single value into a genuinely distinct, type-safe type.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

You'll learn when each helps, how @JvmInline value class stops id mix-ups, its restrictions, and how it differs from a data class.

What You'll Learn in This Lesson

1️⃣ Type Aliases: Friendlier Names

A typealias introduces an alternative name for a type. It creates no new type — the alias and the original are fully interchangeable. Its only job is to make long or repeated types read better, such as turning Map<String, Int> into a meaningful ScoreBoard.

// A typealias is JUST a new name for an existing type.
// It creates NO new type — it's pure readability.
typealias UserName = String
typealias ScoreBoard = Map<String, Int>

fun greet(name: UserName): String = "Hi, $name"

fun main() {
    // UserName and String are interchangeable — same type underneath:
    val n: UserName = "Ada"
    println(greet(n))                 // -> Hi, Ada

    val board: ScoreBoard = mapOf("Ada" to 10, "Bo" to 7)
    println(board["Ada"])             // -> 10
}

2️⃣ Value Classes: Safety Without Overhead

A @JvmInline value class wraps a single val to make a distinct type. At runtime the compiler usually inlines it to the raw value — so no object is allocated — yet the type system treats UserId and ProductId as incompatible, catching mix-ups at compile time.

// A value class wraps ONE value to add type safety with no overhead.
// At runtime it's usually just the underlying Int — no object allocated.
@JvmInline
value class UserId(val raw: Int)

@JvmInline
value class ProductId(val raw: Int)

fun loadUser(id: UserId) = "Loading user #${id.raw}"

fun main() {
    val user = UserId(42)
    println(loadUser(user))           // -> Loading user #42

    // The compiler now BLOCKS mixing up ids:
    // loadUser(ProductId(42))  // ❌ won't compile — wrong type!

    // Access the wrapped value with .raw
    println(user.raw)                 // -> 42
}

Access the wrapped value with the property name you declared (.raw here). This is the idiomatic way to model identifiers, emails, and units.

3️⃣ Methods, Restrictions, and vs data class

Value classes can carry methods and computed (get-only) properties, just not extra stored fields. The key restriction: exactly one property, and it must be a val. When you need several fields, reach for a data class instead, which always allocates a real object.

// Value classes can have methods and computed properties.
@JvmInline
value class Celsius(val degrees: Double) {
    val isFreezing: Boolean get() = degrees <= 0.0
    fun toFahrenheit(): Double = degrees * 9 / 5 + 32
}

fun main() {
    val cold = Celsius(-5.0)
    val warm = Celsius(20.0)

    println(cold.isFreezing)          // -> true
    println(warm.isFreezing)          // -> false
    println(warm.toFahrenheit())      // -> 68.0
}

Your turn. Fill in the ___ blanks, then run and compare.

// 🎯 YOUR TURN — replace each ___ then run main()

// 1) Make Email a zero-overhead wrapper around a String
___ value class Email(val raw: String)   // 👉 put: @JvmInline

fun main() {
    val e = Email("ada@dev.io")

    // 2) Print the wrapped value using .raw
    println(e.___)        // 👉 put: raw

    // ✅ Expected output:
    // ada@dev.io
}

Mini-Challenge: Type-Safe Money

Wrap an Int of cents and add a dollars computed property.

// 🎯 MINI-CHALLENGE: type-safe money.
// Save as Money.kt and run main().
//
// 1. Make a value class Cents(val amount: Int).
// 2. Give it a computed property 'dollars' = amount / 100.
// 3. In main(), create Cents(250) and print its dollars and amount.
//
// ✅ Expected output:
//    2
//    250

@JvmInline
value class Cents(val amount: Int) {
    // add the 'dollars' computed property here
}

fun main() {
    val price = Cents(250)
    // print dollars, then amount
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Alias vs Value Class vs Data Class

Featuretypealiasvalue classdata class
New type?NoYesYes
Propertiesn/aexactly 1 val1 or more
Allocates object?NoUsually noYes
Best forreadabilitysafe wrappersrecords

🎉 Lesson Complete!

Practice quiz

Does a typealias create a brand-new type?

  • Yes, a distinct type
  • No, it is just another name for an existing type
  • Only inside a class
  • Only for generics

Answer: No, it is just another name for an existing type. A typealias is purely a readable rename; the alias and original are interchangeable.

What is the main purpose of a typealias?

  • Adding type safety
  • Allocating less memory
  • Improving readability of long or repeated types
  • Making code run faster

Answer: Improving readability of long or repeated types. Aliases shorten types like Map<String, Int> for readability, not safety.

Which annotation is required on a JVM value class?

  • @Inline
  • @Value
  • @Data
  • @JvmInline

Answer: @JvmInline. On the JVM you write @JvmInline above value class.

How many properties can a value class declare in its primary constructor?

  • Exactly one, and it must be a val
  • Any number
  • Exactly two
  • Zero

Answer: Exactly one, and it must be a val. A value class wraps exactly one immutable val property.

What is the runtime benefit of a value class in the common case?

  • It is always boxed
  • It runs on a separate thread
  • The compiler inlines it to the raw value, allocating no object
  • It caches results

Answer: The compiler inlines it to the raw value, allocating no object. Where possible the wrapper is inlined away, so no object is allocated.

What type-safety win does a value class give over a typealias?

  • It adds extra fields
  • It removes the need for a constructor
  • It makes the type nullable
  • A function taking UserId rejects a ProductId at compile time

Answer: A function taking UserId rejects a ProductId at compile time. Distinct value classes prevent mixing up ids even when both wrap an Int.

How is a value class different from a data class?

  • A value class holds one property and is usually inlined; a data class holds many and always allocates
  • They are identical
  • A data class holds exactly one property
  • A value class can have no methods

Answer: A value class holds one property and is usually inlined; a data class holds many and always allocates. Value class: one val, usually no allocation. Data class: many fields, real object.

The single property of a value class must be declared as which?

  • A var
  • A val
  • lateinit
  • An interface

Answer: A val. The wrapped property must be an immutable val.

Can a value class have methods and computed (get-only) properties?

  • No, never
  • Yes
  • Only on Android
  • Only if it is also a data class

Answer: Yes. Value classes may carry methods and get-only computed properties.

When is a value class boxed into a real object?

  • Always
  • Never
  • When used as a nullable type, in a generic, or where an interface/Any is expected
  • Only in main()

Answer: When used as a nullable type, in a generic, or where an interface/Any is expected. Certain uses force boxing, though the type safety remains.

Continue this course

Frequently asked questions

What is a typealias and does it create a new type?

A typealias gives an existing type a second, more descriptive name — for example 'typealias UserName = String'. It does NOT create a new type: UserName and String are completely interchangeable, so you can pass one where the other is expected and the compiler never complains. Type aliases are purely for readability, especially for long generic types like Map<String, List<Order>>. Because they offer no extra safety, they will not stop you from mixing up two aliases of the same underlying type.

What is a value class and why use @JvmInline?

A value class wraps exactly one property to create a genuinely distinct type for almost zero runtime cost. You declare it with the value keyword plus the @JvmInline annotation (required on the JVM). Where possible the compiler 'inlines' the wrapper away, so a UserId(42) is represented at runtime simply as the underlying Int 42 — no object is allocated. The win is type safety: a function taking UserId will reject a ProductId at compile time, even though both wrap an Int, eliminating an entire class of mix-up bugs.

How is a value class different from a data class?

A data class can hold many properties and always allocates a real object, generating equals, hashCode, copy, and componentN for you. A value class holds exactly ONE property and is usually inlined away to its underlying value, so there is no allocation in the common case. Use a data class when you are modelling a record with several fields (a Point with x and y). Use a value class when you want to give a single primitive or String a safe, distinct type — like UserId, Email, or Percentage — without paying for an object.

What are the restrictions on value classes?

A value class must have exactly one property declared in the primary constructor, and that property must be a val (immutable). It cannot have an init block in older Kotlin versions, cannot have backing fields beyond the single wrapped value, cannot extend another class (though it can implement interfaces), and cannot be used where identity matters because two equal values are indistinguishable. It can, however, have methods, computed properties (get-only), and secondary constructors. These limits are what allow the compiler to inline it safely.

Does the inlining always happen?

Not always. The compiler inlines a value class to its raw representation in the common path, but in certain situations it must 'box' it into a real object — for example when the value is used as a nullable type, stored in a generic collection, or passed where an interface or Any is expected. Even then the type safety remains; you simply lose the zero-allocation benefit in those spots. For the overwhelmingly common case of passing a UserId to a function and reading its value, no object is created.