Understanding Different Types of Classes in Kotlin with Real-World Examples

Aditya DasAditya Das
5 min read

Kotlin provides various types of classes, each designed for specific use cases. In this blog, we will break them down using real-world analogies, code examples, and key differences.

1️⃣ Normal Class (class)

A normal class is used to create objects that can store data and have methods. When we compare two instances, they are compared by reference, meaning two different objects are never equal even if they hold the same values.

Real-World Example

Imagine a Car Factory 🚗🚗🚗 that produces cars. Each car has a different identity (even if they have the same model and color).

Implementation

class Car(val model: String, val color: String)

fun main() {
    val car1 = Car("Tesla", "Red")
    val car2 = Car("Tesla", "Red")

    println(car1 == car2)  // false (compared by reference)
}

Two objects with the same values are still different instances.


2️⃣ Data Class (data class)

A data class is optimized for storing data. Unlike normal classes, instances are compared by content, not reference. It automatically provides equals(), hashCode(), and toString() functions.

Real-World Example

Think of User Profiles in an app. Two users with the same name and email should be considered equal, even if they are different objects.

Implementation

data class User(val name: String, val email: String)

fun main() {
    val user1 = User("Aditya", "aditya@example.com")
    val user2 = User("Aditya", "aditya@example.com")

    println(user1 == user2)  // true (compared by content)
}

data class instances are compared by their actual data, not memory reference.


3️⃣ Singleton (object)

A singleton is a class that only allows one instance throughout the application.

Real-World Example

Think of a Logger System 📜 in an application. There should only be one logging system handling all logs.

Implementation

object Logger {
    fun log(message: String) {
        println("LOG: $message")
    }
}

fun main() {
    Logger.log("App started")
    Logger.log("User logged in")
}

You cannot create multiple instances of Logger (Logger() is not allowed).

We cannot do this:

val logger1 = Logger()  // ❌ ERROR! Cannot create a new instance
val logger2 = Logger()  // ❌ ERROR!

What "Creating Multiple Instances" Means

Let’s compare with a real-world example:

ConceptReal-World Example
Multiple instances (normal class)A car factory making many cars (each car is different) 🚗🚗🚗
Single instance (object/data object)A traffic signal system in a city 🚦 (only one system for all)

4️⃣ Data Object (data object)

A data object is a singleton but specifically for immutable data storage.

Real-World Example

Think of App Configurations ⚙️ that store settings like API keys.

Implementation

data object AppConfig {
    val apiKey: String = "ABC123XYZ"
}

fun main() {
    println(AppConfig.apiKey)  // ABC123XYZ
}

Useful for storing constant, read-only data in a singleton format.

Key Differences (Simple Table)

FeatureSingleton (object)Data Object (data object)
PurposeShared utilities, global logicSingle-instance immutable data
Can store data?✅ Yes, but usually used for functions✅ Yes, main purpose is to store data
Can modify values?✅ Yes, if variables are var❌ No, only val (immutable)
Use caseLogger, DatabaseHelperApp settings, default configurations
ExampleLogger.log("App started")AppConfig.theme

Final Analogy

ConceptExample in Real Life
Singleton (object)TV remote 🕹️ (one instance, shared by everyone)
Data Object (data object)Wi-Fi settings 📶 (one copy, read-only)

5️⃣ Enum Class (enum class)

An enum class is used when you need a fixed set of values that won’t change.

Real-World Example

Think of Traffic Lights 🚦 - the colors Red, Yellow, Green are predefined and never change.

Implementation

enum class TrafficLight {
    RED, YELLOW, GREEN
}

fun main() {
    val currentLight = TrafficLight.RED
    when (currentLight) {
        TrafficLight.RED -> println("Stop 🚦")
        TrafficLight.YELLOW -> println("Get Ready ⚠️")
        TrafficLight.GREEN -> println("Go! 🏎️")
    }
}

Enum values are fixed at compile time and cannot be changed dynamically.


6️⃣ Sealed Class (sealed class)

A sealed class is similar to an enum, but each type can hold different data.

Real-World Example

Think of Bank Transactions 💰:

  • Success → Stores a transaction ID.

  • Failure → Stores an error message.

  • Pending → No extra data.

Implementation

sealed class Transaction {
    data class Success(val transactionId: String) : Transaction()
    data class Failure(val errorMessage: String) : Transaction()
    data object Pending : Transaction()
}

fun handleTransaction(transaction: Transaction) {
    when (transaction) {
        is Transaction.Success -> println("Transaction Successful! ID: ${transaction.transactionId}")
        is Transaction.Failure -> println("Transaction Failed! Error: ${transaction.errorMessage}")
        is Transaction.Pending -> println("Transaction Pending... Please wait.")
    }
}

fun main() {
    handleTransaction(Transaction.Success("TXN12345"))
    handleTransaction(Transaction.Failure("Insufficient Balance"))
    handleTransaction(Transaction.Pending)
}

Allows different types with different data, unlike enums.


7️⃣ Key Differences (Comparison Table)

FeatureEnum Class (enum)Sealed Class (sealed class)
Use caseFixed set of constantsLimited set of types that store different data
Data storageEach value is just a nameEach type can hold different properties
Can add new values dynamically?❌ No✅ Yes
Use when statement?✅ Yes, but must cover all values✅ Yes, but no need for else case
ExampleTraffic lights 🚦Bank Transactions 💰

Conclusion

  • Use Normal Class (class) for creating multiple instances.

  • Use Data Class (data class) for objects that store data.

  • Use Singleton (object) when only one instance is needed.

  • Use Data Object (data object) for immutable global data.

  • Use Enum (enum class) for a fixed set of constants.

  • Use Sealed Class (sealed class) when you need different types with different data.

Each class type serves a specific purpose, and understanding their differences will help you write cleaner and more efficient Kotlin code. 🚀

Useful References


0
Subscribe to my newsletter

Read articles from Aditya Das directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aditya Das
Aditya Das

B.Tech student in Information Technology with a passion for Android app development. Specializing in Kotlin, Java, and modern development practices. Exploring the latest trends in mobile and web technologies, and committed to sharing knowledge and insights through blogging. Follow along for tutorials, tips, and industry updates!