Skip to Content
Course content

206: Code Review Checklist for Idiomatic Kotlin

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you've just started working in a high-end professional kitchen. You're a great cook, and the food you produce tastes delicious, but the Head Chef keeps pulling you aside. He's not complaining about the flavor—he's complaining that you're using a blender to mince garlic when there's a chef's knife right there, or that you're leaving your prep station a mess until the very end of the shift. You're getting the job done, but you aren't "cooking the way this kitchen operates."

Writing idiomatic Kotlin is exactly like that. Most developers transition from Java or C#, so they tend to write "Java with Kotlin syntax." The code compiles, the tests pass, and the feature works, but it feels clunky to a seasoned Kotlin dev. When I'm reviewing your PRs, I'm not just looking for bugs; I'm looking for those "blenders" where a "knife" would be faster and cleaner.

Hunting for the Bang-Bang Operator

The first thing I look for is the !! operator. In a professional codebase, seeing a double-bang is like seeing a "Do Not Enter" sign that someone decided to ignore. It tells me the author was frustrated with the compiler and decided to gamble. I'll almost always ask you to replace it with a safe call ?., the Elvis operator ?:, or a requireNotNull() call if the value truly must be there for the app to function. If you're using !!, you're bypassing Kotlin's greatest strength: null safety.

Ditching the Manual Loops

If I see a for (i in 0 until list.size) loop, my instinct is to suggest a functional approach. Kotlin's standard library is massive, and we should be using it. Instead of manually managing indices or creating temporary mutable lists to hold filtered results, use the collection operators.

// Instead of this:
val activeUsers = mutableListOf<User>()
for (user in users) {
    if (user.isActive) {
        activeUsers.add(user)
    }
}

// Do this:
val activeUsers = users.filter { it.isActive }

It's more concise, and more importantly, it's declarative. You're telling me what you want (the active users), not how to build the list step-by-step.

Cleaning Up the Scope Noise

I often see developers repeating the same object name four times in a row. This is where scope functions—let, apply, run, also, and with—come into play. If you're configuring an object, apply is your best friend. If you're performing an operation on a nullable object only if it exists, let is the way to go.

// Clunky:
val config = DatabaseConfig()
config.url = "jdbc:mysql://localhost:3306"
config.timeout = 30
config.maxConnections = 10
repository.init(config)

// Idiomatic:
repository.init(DatabaseConfig().apply {
    url = "jdbc:mysql://localhost:3306"
    timeout = 30
    maxConnections = 10
})

The second version groups the configuration logic together, making it clear that those settings belong to that specific instance creation.

The War on Mutable State

Finally, I check your var count. In Kotlin, we strive for immutability. If a variable doesn't need to change after its first assignment, it should be a val. This isn't just about being "pure"—it's about thread safety and reducing cognitive load. If I see a var, I'm going to ask "Why?" If it's only used as a temporary accumulator in a loop, I'll suggest a fold or reduce. If it's a class property that changes frequently, I'll check if we can move that state into a StateFlow or a data class copy.




📋 Practical Task

Refactoring the Clunky OrderProcessor

You've been assigned to review a piece of code written by a developer who is still thinking in Java. The code works perfectly, but it's not idiomatic Kotlin. Refactor the OrderProcessor class below.

Your goals:

  • Remove the !! operator.
  • Replace the manual for loop with a collection function.
  • Use apply or let to reduce repetition.
  • Change var to val wherever possible.

data class Order(val id: String, val amount: Double, var status: String?)
data class OrderSummary(var total: Double, var count: Int)

class OrderProcessor {
    fun processOrders(orders: List<Order>?): OrderSummary {
        val summary = OrderSummary(0.0, 0)
        
        if (orders != null) {
            for (i in 0 until orders.size) {
                val order = orders[i]
                if (order.status == "COMPLETED") {
                    summary.total += order.amount
                    summary.count += 1
                }
            }
        }
        
        return summary
    }

    fun updateOrderAddress(order: Order?, newAddress: String) {
        // The developer knows order isn't null here, so they used !!
        val currentOrder = order!! 
        println("Updating order ${currentOrder.id} to $newAddress")
    }
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.