What Are Kotlin Data Classes Used For?

Ever spent hours writing boilerplate code just to store and pass data around? Kotlin data classes solve this exact problem. Created by JetBrains as part of the Kotlin programming language, these specialized class types automatically generate common functions like equals()
, hashCode()
, and toString()
with a single keyword: data
.
data class User(val name: String, val email: String)
That’s it. No more manual implementation of property accessors, equality checks, or string representations.
As a modern Java alternative, Kotlin data classes are perfect for:
- Creating domain models and value objects
- Mapping API responses in backend development
- Building immutable data structures for thread-safe operations
- Simplifying database entity representations
This article explores everything from basic property declarations to advanced patterns with sealed classes. You’ll learn when to use data classes, how they integrate with frameworks like Spring Boot and Android development, and best practices for clean code with the Kotlin type system.
What Are Kotlin Data Classes?
Kotlin data classes are special classes designed to hold data. Declared with the data
keyword, they automatically generate useful methods like equals()
, hashCode()
, toString()
, and copy()
. This reduces boilerplate code and makes them ideal for representing simple data models, such as user profiles or API responses.
Core Features of Data Classes

Kotlin data classes reduce boilerplate code while handling common programming tasks. They’re a fundamental part of Kotlin’s type system, offering a concise way to create classes that hold data.
Built-in Functions
The JetBrains team designed these classes with practicality in mind. Each data class automatically generates several useful methods:
toString() implementation
Data classes come with a readable string representation out of the box. The toString()
method lists the class name and all properties with their values.
data class User(val name: String, val age: Int)
val user = User("Alice", 29)
println(user) // Output: User(name=Alice, age=29)
This automatic implementation saves time and ensures consistent string formatting. No more manual debugging output!
equals() and hashCode()
One of the most valuable features is structural equality. Kotlin compiler generates equals()
and hashCode()
methods that compare all properties declared in the primary constructor.
val user1 = User("Bob", 32)
val user2 = User("Bob", 32)
val user3 = User("Carol", 28)
println(user1 == user2) // true
println(user1 == user3) // false
This property makes data classes perfect for domain modeling and value objects. They work seamlessly with collections that rely on these functions, like HashSet
and HashMap
.
copy() function
Immutability is a core principle in modern software engineering. The generated copy()
function creates an instance copy with some properties changed.
val alice = User("Alice", 29)
val olderAlice = alice.copy(age = 30)
This enables a functional programming pattern where objects remain unchanged while creating modified versions as needed. Thread-safe data sharing becomes much easier.
Component functions for destructuring
Data classes generate component functions that allow destructuring declarations. This feature works with Kotlin standard library operations.
val (name, age) = User("Dave", 35)
println("$name is $age years old") // Dave is 35 years old
With collections of data classes, this creates powerful transformation patterns:
val users = listOf(User("Alice", 29), User("Bob", 32))
val names = users.map { (name, _) -> name }
Requirements and Limitations
Primary constructor requirements
Data classes must have a primary constructor with at least one parameter. All parameters need val or var modifiers to become properties.
data class InvalidExample() // Error: Data class must have at least one primary constructor parameter
data class AnotherInvalid(name: String) // Error: Primary constructor parameter needs val/var
Restrictions on inheritance
In the Kotlin programming language, data classes face certain inheritance constraints. They can’t be abstract, open, sealed, or inner. This design choice promotes their use as simple data containers rather than complex hierarchical structures.
abstract data class Invalid() // Error: Data class cannot be abstract
Data classes can implement interfaces, offering flexibility while maintaining their core purpose.
Restrictions on modifiers
The JVM bytecode generation for data classes works differently than regular classes. Several modifiers aren’t compatible:
abstract
: Data classes must be concreteopen
: They can’t be extendedsealed
: Can’t define a restricted hierarchyinner
: Can’t be tied to an outer class instance
These restrictions guide developers toward proper use cases for data classes in software architecture.
Practical Applications

Data Transport Objects (DTOs)
API response mapping
Data classes excel at API response mapping in backend development. Their concise syntax and JSON serialization compatibility make them ideal:
data class ProductResponse(
val id: String,
val name: String,
val price: Double,
val available: Boolean
)
Spring Boot and Kotlin work together perfectly here. The framework leverages these simple structures for efficient request/response handling.
Database entity representations
Many ORM solutions support Kotlin data classes. Room database in Android development uses them extensively:
@Entity(tableName = "users")
data class UserEntity(
@PrimaryKey val id: String,
val username: String,
val email: String,
val createdAt: Long
)
Their property declarations map cleanly to database columns. The equals/hashCode implementation helps with caching and identity management.
Cross-layer data transfer
In modern MVVM architecture, data classes facilitate information flow between layers:
// Domain model
data class User(val id: String, val name: String)
// Presentation model
data class UserViewModel(val displayName: String, val isVerified: Boolean)
// Mapping function
fun User.toViewModel() = UserViewModel(
displayName = name.capitalize(),
isVerified = id.isNotEmpty()
)
This approach maintains clear boundaries while enabling type-safe transformations.
Immutable Data Structures
Thread-safe data sharing
Data classes support immutability through val properties and copy(). This makes concurrent programming safer:
data class SharedConfig(val timeout: Int, val retryCount: Int)
// Can be shared between threads without synchronization
val config = SharedConfig(5000, 3)
Kotlin coroutines benefit greatly from this design. They exchange immutable data without worry about race conditions.
Functional programming patterns
With their copy function and destructuring capability, data classes fit naturally into functional programming:
data class Order(val items: List<String>, val total: Double)
fun applyDiscount(order: Order, discount: Double): Order {
return order.copy(total = order.total * (1 - discount))
}
val processedOrders = orders.map { applyDiscount(it, 0.1) }
This creates a clean, predictable code flow without side effects.
Working with collections
Kotlin’s standard library shines when combined with data classes:
data class Employee(val name: String, val department: String, val salary: Double)
val employees = listOf(
Employee("Alice", "Engineering", 85000.0),
Employee("Bob", "Marketing", 70000.0),
Employee("Carol", "Engineering", 90000.0)
)
val byDepartment = employees.groupBy { it.department }
val averageSalary = employees.map { it.salary }.average()
val highestPaid = employees.maxByOrNull { it.salary }
These operations become intuitive and expressive with well-defined data structures.
Configuration and Settings
Application settings storage
Data classes excel at storing application configuration:
data class AppSettings(
val darkMode: Boolean = false,
val fontSize: Int = 14,
val autoSave: Boolean = true
)
Default parameter values provide sensible starting points. Named parameters make usage clear and explicit.
User preferences
When managing user settings, data classes help maintain consistency:
data class UserPreferences(
val notificationsEnabled: Boolean,
val theme: String,
val language: String
)
Their toString() method simplifies debugging, while equals() ensures proper comparison during updates.
Feature flags and toggles
Modern applications use feature flags for gradual rollouts:
data class FeatureFlags(
val newUIEnabled: Boolean = false,
val betaFeaturesUnlocked: Boolean = false,
val debugMode: Boolean = false
)
Data classes make this pattern clean and maintainable. The copy function helps create variations for different user segments.
In clean code practices, Kotlin data classes truly shine. They express intent clearly while eliminating much of the tedium found in other object-oriented languages. From database interactions to UI state management, they’ve become an essential tool in the modern Kotlin developer’s toolkit.
Advanced Usage Patterns
Data classes in Kotlin provide remarkable flexibility. They’re more than simple data containers.
Composition with Data Classes
Nesting data classes
Nesting creates powerful domain structures. It’s perfect for complex hierarchical data:
data class Address(
val street: String,
val city: String,
val postalCode: String
)
data class Customer(
val id: String,
val name: String,
val email: String,
val address: Address
)
This approach aligns with object-oriented programming principles while maintaining JVM bytecode efficiency. Each class handles specific concerns. Debugging becomes easier when IntelliJ IDEA shows the nested structure.
Combining with sealed classes
Sealed classes create restricted hierarchies. When combined with data classes, they excel at representing diverse states:
sealed class Result {
data class Success(val data: List<String>) : Result()
data class Error(val message: String, val code: Int) : Result()
object Loading : Result()
}
This pattern works exceptionally well with Kotlin coroutines for asynchronous operations. The Kotlin compiler enforces exhaustive handling through when expressions. Android development particularly benefits from this approach.
Using with interfaces
Data classes can implement interfaces, creating a blend of behavior and data:
interface Printable {
fun print(): String
}
data class Report(val title: String, val content: String) : Printable {
override fun print(): String = "REPORT: $title\n$content"
}
This technique adds functionality while preserving the benefits of generated methods. The flexibility complements software engineering principles perfectly.
Customizing Generated Functions
Overriding toString()
Sometimes the default implementation doesn’t fit your needs:
data class LogEntry(
val timestamp: Long,
val level: String,
val message: String,
val stackTrace: String?
) {
override fun toString(): String =
"[$level] ${Date(timestamp)}: $message${stackTrace?.let {"\n$it"} ?: ""}"
}
Custom formatting enhances logging and debugging. The Kotlin reflection API still provides access to default implementations when needed.
Custom equals() implementations
While rare, you might need custom equality logic:
data class TimeRange(val start: Long, val end: Long) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is TimeRange) return false
// Custom logic: Two ranges are equal if they overlap
return !(end < other.start || start > other.end)
}
override fun hashCode(): Int {
// Must be overridden alongside equals()
return 31 * start.hashCode() + end.hashCode()
}
}
Remember: always override hashCode() when customizing equals(). It’s essential for consistent behavior with hash-based collections.
Modifying copy() behavior
The copy function creates exact duplicates by default. You can enhance it by using extension functions:
data class User(val id: String, val username: String, val lastLogin: Long = 0)
fun User.copyWithUpdatedLogin(): User = this.copy(lastLogin = System.currentTimeMillis())
This maintains immutability while adding domain-specific operations. It’s especially useful for working with data transfer objects in Spring Boot applications.
Working with Collections of Data Classes
Filtering and transformation
The Kotlin standard library shines with data class collections:
val transactions = listOf(
Transaction("T001", 250.0, "completed"),
Transaction("T002", 75.5, "pending"),
Transaction("T003", 125.0, "completed")
)
val completedTransactions = transactions.filter { it.status == "completed" }
val transactionIds = transactions.map { it.id }
These operations are concise and expressive. The property declarations make them type-safe and refactoring-friendly.
Grouping and aggregation
Data classes support sophisticated analysis patterns:
val orders = listOf(
Order("O1", "Customer1", listOf("Product1", "Product2"), 150.0),
Order("O2", "Customer2", listOf("Product3"), 75.0),
Order("O3", "Customer1", listOf("Product4"), 50.0)
)
val ordersByCustomer = orders.groupBy { it.customerId }
val totalsByCustomer = ordersByCustomer.mapValues { (_, orders) ->
orders.sumOf { it.total }
}
These functional programming patterns create clean, maintainable code. They’re particularly valuable in backend development scenarios.
Sorting and comparison
Data classes work naturally with sorting operations:
data class Task(val id: String, val priority: Int, val title: String)
val tasks = listOf(
Task("T1", 2, "Fix bug"),
Task("T2", 1, "Implement feature"),
Task("T3", 3, "Write documentation")
)
val sortedByPriority = tasks.sortedBy { it.priority }
val sortedByMultiple = tasks.sortedWith(
compareBy({ it.priority }, { it.title })
)
Complex sorting criteria become straightforward. This works seamlessly with Kotlin’s type system for compile-time safety.
Data Classes in Modern Kotlin Development
Integration with Kotlin Features
Working with nullable properties
Null safety is a cornerstone of Kotlin. Data classes handle it elegantly:
data class UserProfile(
val id: String,
val displayName: String,
val bio: String? = null,
val avatarUrl: String? = null
)
Optional properties get null defaults. The Kotlin compiler enforces null checks through the type system.
Using with extension functions
Extension functions add capabilities without modifying the original class:
data class Point(val x: Int, val y: Int)
fun Point.distanceTo(other: Point): Double =
Math.sqrt(((x - other.x) * (x - other.x) + (y - other.y) * (y - other.y)).toDouble())
val p1 = Point(0, 0)
val p2 = Point(3, 4)
println(p1.distanceTo(p2)) // 5.0
This approach keeps data classes focused while adding domain-specific operations. It’s widely used in Kotlin multiplatform projects.
Default and named parameters
Default values create flexible constructors:
data class SearchConfig(
val query: String,
val caseSensitive: Boolean = false,
val limit: Int = 100,
val sortField: String = "relevance",
val includeDisabled: Boolean = false
)
// Use with named parameters for clarity
val config = SearchConfig(
query = "kotlin",
limit = 50,
includeDisabled = true
)
Named parameters make complex object creation readable. The Kotlin programming guide recommends this approach for configuration objects.
Frameworks and Libraries Support
Spring Boot and data classes
Spring Boot embraces data classes for various purposes:
@RestController
class UserController(private val userService: UserService) {
@GetMapping("/users/{id}")
fun getUser(@PathVariable id: String): UserDTO =
userService.findById(id)
}
data class UserDTO(val id: String, val name: String, val email: String)
The framework automatically handles serialization/deserialization. This integration makes Spring Boot with Kotlin particularly productive for server-side development.
Ktor and serialization
Ktor uses data classes for request/response handling:
data class LoginRequest(val username: String, val password: String)
data class LoginResponse(val token: String, val expiresAt: Long)
install(ContentNegotiation) {
json()
}
post("/login") {
val request = call.receive<LoginRequest>()
// Process login...
call.respond(LoginResponse("token123", System.currentTimeMillis() + 3600000))
}
The Kotlin serialization library transparently converts between JSON and data classes. This creates a type-safe API development experience.
Room database and other ORM solutions
Android developers use Room with data classes for database operations:
@Entity(tableName = "tasks")
data class TaskEntity(
@PrimaryKey val id: String,
val title: String,
val description: String,
val dueDate: Long,
val completed: Boolean
)
@Dao
interface TaskDao {
@Query("SELECT * FROM tasks")
fun getAllTasks(): List<TaskEntity>
@Insert
fun insertTask(task: TaskEntity)
}
Room generates the necessary SQL code. Data classes handle the entity representation elegantly.
Retrofit and network requests
Retrofit leverages data classes for API communication:
interface GithubApi {
@GET("users/{user}/repos")
suspend fun listRepos(@Path("user") user: String): List<Repository>
}
data class Repository(
val id: Int,
val name: String,
val description: String?,
val stargazers_count: Int
)
The conversion between JSON and objects happens automatically. Developer productivity increases dramatically with this pattern.
Testing Benefits
Easy test data creation
Data classes simplify test fixture creation:
@Test
fun `should calculate correct discount`() {
// Given
val product = Product("P1", "Test Product", 100.0)
val order = Order("O1", listOf(product), 100.0)
// When
val result = calculateDiscount(order)
// Then
assertEquals(10.0, result)
}
The primary constructor makes object creation concise. Default and named parameters reduce setup code even further.
Predictable equality checking
Structural equality makes assertions clear and reliable:
@Test
fun `should transform entity to dto`() {
// Given
val entity = UserEntity("1", "test@example.com", "Test User", true)
// When
val dto = entity.toDto()
// Then
assertEquals(UserDto("1", "Test User"), dto)
}
Assertions become straightforward. The equals() implementation handles complex nested structures properly.
Simplified assertions
Modern testing libraries work beautifully with data classes:
@Test
fun `should return correct user details`() {
// Given
whenever(userRepository.findById("1")).thenReturn(User("1", "Test", "test@example.com"))
// When
val result = userService.getUserDetails("1")
// Then
assertThat(result).isEqualTo(UserDetails("1", "Test", "test@example.com"))
}
The output from test failures is readable and helpful. This accelerates the debugging process considerably.
Data classes have become indispensable in modern Kotlin development. From Stack Overflow examples to GitHub repositories, they appear in virtually every project. Their elegance and utility exemplify why the Kotlin programming language continues to gain popularity across Android development, backend systems, and multiplatform applications. The deep integration with the Kotlin standard library, functional programming patterns, and major frameworks makes them a fundamental part of the Kotlin ecosystem.
Best Practices
Effective use of data classes requires understanding their strengths and limitations. Let’s explore when and how to use them properly.
When to Use Data Classes
Appropriate use cases
Data classes shine in specific scenarios. They excel as data containers with minimal behavior:
// Perfect use case: Data Transfer Object
data class UserDto(val id: String, val name: String, val email: String)
// Good use case: Value object with domain validation
data class EmailAddress(val value: String) {
init {
require(value.matches(EMAIL_REGEX)) { "Invalid email format" }
}
companion object {
private val EMAIL_REGEX = Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")
}
}
They’re ideal for JSON serialization with frameworks like Spring Boot or Ktor. Domain modeling becomes clearer when you represent entities with data classes that match your problem space.
When regular classes are better
Not every class should be a data class. Consider regular classes when:
- Complex behavior dominates over data storage
- Identity matters more than structural equality
- Mutable state needs careful management
- Class hierarchies require extension
JetBrains’ own code bases follow this principle. Their Kotlin Style Guide recommends using regular classes for service implementations and components with complex lifecycles.
// Better as a regular class due to behavior focus
class UserAuthenticator(private val userRepository: UserRepository) {
fun authenticate(credentials: Credentials): AuthResult {
// Complex authentication logic here
}
}
Decision criteria
Ask yourself these questions:
- Is this primarily a data holder?
- Do I need structural equality?
- Will I benefit from the generated functions?
- Is immutability appropriate here?
If “yes” to most questions, a data class fits well. The JVM bytecode generation will be optimized accordingly.
Design Considerations
Property selection
Choose properties thoughtfully. Include only what belongs in the primary constructor:
// Good: Essential properties in constructor
data class Product(
val id: String,
val name: String,
val price: Double
) {
// Derived property, doesn't affect equality
val isDiscounted: Boolean
get() = price < 100.0
// Cache field, doesn't define identity
var cachedImageUrl: String? = null
}
Remember: only primary constructor properties affect equals(), hashCode(), and toString(). This creates cleaner APIs and more intuitive equality behavior.
Balancing size and purpose
Keep data classes focused. Large property lists suggest a need for splitting:
// Too many properties - unfocused
data class UserWithEverything(
val id: String,
val name: String,
val email: String,
val address: String,
val phone: String,
val registrationDate: Long,
val lastLoginDate: Long,
val preferences: Map<String, Any>,
val paymentMethods: List<PaymentMethod>,
val orderHistory: List<Order>
)
// Better: Split by purpose
data class UserProfile(val id: String, val name: String, val email: String)
data class UserContact(val userId: String, val address: String, val phone: String)
data class UserActivity(val userId: String, val registrationDate: Long, val lastLoginDate: Long)
This aligns with clean code practices and SOLID principles. Each data class has a single responsibility.
Naming conventions
Follow consistent naming patterns:
- Use nouns for entity-like classes (User, Product, Order)
- Use descriptive suffixes for specific roles (UserDto, ProductResponse)
- Avoid redundant “Data” suffixes (prefer Order over OrderData)
The Kotlin programming guide recommends PascalCase for class names with concise, meaningful terms. This improves code readability across your codebase.
Performance Implications
Memory usage
Data classes are lightweight but not free. Consider memory impact:
// Memory overhead when creating millions of instances
data class Point(val x: Double, val y: Double, val z: Double)
// More efficient for large collections
inline class OptimizedPoint(val packedValue: Long) {
val x: Double get() = unpackX(packedValue)
val y: Double get() = unpackY(packedValue)
val z: Double get() = unpackZ(packedValue)
companion object {
fun create(x: Double, y: Double, z: Double): OptimizedPoint =
OptimizedPoint(packValues(x, y, z))
}
}
For performance-critical applications, monitor memory allocation. Android development particularly benefits from careful data structure design.
Instantiation costs
Creating data class instances isn’t free. The JVM must allocate memory and initialize fields:
// Can be expensive when created frequently
data class TemporaryCalculation(
val input: Double,
val result: Double,
val intermediateSteps: List<Double>
)
// Use primitive values or object pools for high-frequency code paths
fun calculateEfficiently(input: Double): Double {
// Avoid creating temporary objects in hot loops
var result = input
for (i in 1..1000) {
result = transform(result)
}
return result
}
In mobile app development, excessive object creation can trigger garbage collection, causing frame drops. Profiling with Android Studio helps identify these issues.
Comparison with alternatives
Consider these alternatives for specific use cases:
- Value classes: Zero-overhead wrappers (available in Kotlin 1.5+)
@JvmInline value class UserId(val value: String)
- Type aliases: Simple type renaming without runtime overhead
typealias UserId = String
- Record classes: Java 16+ alternative (if interoperability matters)
// Java code public record User(String id, String name) {}
- Data objects: Kotlin 1.7+ feature for singleton data holders
data object ApplicationConfig { const val VERSION = "1.0.0" const val API_URL = "https://api.example.com" }
Each approach has unique benefits. The right choice depends on your specific requirements and platform constraints.
Smart use of data classes enhances code quality. They represent one of JetBrains’ most popular Kotlin features, with widespread adoption across GitHub repositories and Stack Overflow examples. Their integration with the Kotlin standard library and various frameworks makes them indispensable for modern software engineering.
Through thoughtful application of these best practices, you’ll leverage data classes effectively in your Kotlin projects, whether you’re building Android applications, Spring Boot services, or multiplatform solutions.
FAQ on Kotlin Data Classes
Can data classes inherit from other classes?
No. Data classes cannot be abstract, open, sealed, or inner. They can, however, implement interfaces and extend other classes. This restriction in the Kotlin type system ensures data classes remain focused on their primary purpose of holding data rather than building complex inheritance hierarchies.
How does the copy() function work in Kotlin data classes?
The copy()
function creates a new instance with the same values, allowing selective property changes:
val user = User("John", "john@example.com")
val updatedUser = user.copy(email = "new@example.com")
This supports immutable object patterns in functional programming while maintaining all original values except those explicitly changed.
What are component functions in data classes used for?
Component functions enable destructuring declarations in Kotlin data classes:
val user = User("John", "john@example.com")
val (name, email) = user // Calls component1() and component2()
This makes it convenient to extract multiple properties simultaneously, especially useful when working with collections in the Kotlin standard library.
Can I customize the generated functions in a data class?
Yes. You can override any auto-generated function:
data class CustomUser(val name: String, val id: Int) {
override fun toString(): String = "User $name with ID $id"
}
The Kotlin compiler respects your custom implementations while generating the other functions normally. This flexibility helps with special formatting or business logic requirements.
How do data classes perform with collections in Kotlin?
Exceptionally well. The generated equals()
and hashCode()
methods make data classes perfect for collections:
val users = hashSetOf(User("Alice", 1), User("Bob", 2))
users.contains(User("Alice", 1)) // Returns true
This makes filtering, mapping, and sorting operations straightforward and predictable, enhancing mobile app development and backend systems.
Are data classes supported by popular frameworks?
Absolutely. Spring Boot, Android’s Room database, and Ktor all have excellent data class integration. JSON serialization libraries work seamlessly with them. Their concise syntax and predictable behavior make them perfect for database entities, API requests/responses, and view models in MVVM architecture patterns.
What are the best practices for selecting properties in data classes?
Include only essential properties in the primary constructor that define the object’s identity. Derived properties can be declared in the class body. Keep classes focused on a single purpose—split large data classes into smaller ones. Follow software engineering principles of cohesion and single responsibility.
How do data classes compare to Java records?
Java records (introduced in Java 16) were inspired by Kotlin data classes but have differences. Kotlin’s version offers more flexibility with var properties, custom accessors, and the copy()
function. Data classes work on older Java versions and integrate with the entire Kotlin ecosystem, including extension functions and null safety.
When should I avoid using data classes?
Avoid data classes for objects with complex behavior, mutable state that needs careful management, or where identity-based equality is required. Classes focused on actions rather than data storage are better as regular classes. Entity classes that represent database records sometimes benefit from regular classes for proper lifecycle management in ORM solutions.
Conclusion
Understanding what are Kotlin data classes transforms how you structure code in JVM-based projects. These powerful constructs eliminate boilerplate while providing essential functionality through compiler-generated methods. They represent one of the most beloved features in the Kotlin programming language.
Data classes deliver exceptional value through:
- Reduced code volume with automatic method generation
- Clean domain modeling for complex business logic
- Thread-safe immutability via the copy function
- Simplified testing with predictable equality checks
The integration with Android development tools, Spring Framework, and Kotlin multiplatform makes data classes indispensable for modern software engineering. From database entity representations to API response mapping, they’ve become fundamental building blocks in application architecture.
As JetBrains continues enhancing Kotlin, data classes remain a cornerstone feature that exemplifies the language’s focus on developer productivity and code conciseness. Whether you’re building mobile applications, server-side systems, or full-stack solutions, mastering data classes will significantly improve your development experience.
- Kotlin Regex: A Guide to Regular Expressions - April 22, 2025
- What Is the Kotlin Enum Class? Explained Clearly - April 22, 2025
- How To Work With Maps In Kotlin - April 21, 2025