← Back to list

The Secret Behind Kotlin’s hashCode: Why 31 is the Magic Number

Have you ever wondered why that mysterious number 31 keeps showing up in hashCode implementations? Let’s uncover the fascinating story…

Sk Niyaj Ali · 2025-08-08 20:43 · 6 claps · 6.4 min read
#kotlin #kotlin-beginners #java #android #hashcode
Open on Medium ↗
Wiki topics: 📱 · Mobile Development

The Secret Behind Kotlin’s hashCode: Why 31 is the Magic Number

Have you ever wondered why that mysterious number 31 keeps showing up in hashCode implementations? Let’s uncover the fascinating story behind this choice and why it makes your Kotlin apps lightning fast.

Imagine you’re organizing a massive library with millions of books. How would you arrange them so that finding any specific book takes just seconds, not hours? This is exactly the challenge that computer programs face when storing and retrieving data, and the solution involves a clever mathematical trick that revolves around the number 31.

The Library Analogy: Understanding Hash Codes

Think of a hash code as a super-smart librarian who can instantly tell you which section of the library contains your book. When you ask for “The Kotlin Programming Guide,” this librarian doesn’t search through every single book. Instead, they perform a quick mental calculation based on the book’s title and immediately point you to section 247.

In programming terms, a hash code is a number that represents your object. This number helps data structures like HashMap quickly locate where your object should be stored or found. The better this number spreads objects across different “sections” (called buckets), the faster your program runs.

Let’s see this in action with a simple Kotlin example:

data class Book(
    val title: String,
    val author: String,
    val year: Int
) {
    // This is what Kotlin generates automatically for data classes
    override fun hashCode(): Int {
        var result = title.hashCode()
        result = result * 31 + author.hashCode()
        result = result * 31 + year
        return result
    }
}

Notice that mysterious number 31 appearing twice? There’s a beautiful reason why computer scientists chose this specific number, and it’s not random at all.

The Great Number Hunt: Why Not 2, 10, or 100?

To understand why 31 is special, let’s imagine what would happen if we used different numbers. Suppose we used the number 2 as our multiplier:

// A terrible hashCode implementation - DON'T DO THIS!
override fun hashCode(): Int {
    var result = title.hashCode()
    result = result * 2 + author.hashCode()  // Using 2 instead of 31
    result = result * 2 + year
    return result
}

What’s wrong with using 2? Well, multiplying by 2 is the same as shifting all the bits in a number one position to the left. This creates predictable patterns. Imagine if our library sections were numbered only with even numbers: 2, 4, 6, 8, 10. We’d be wasting half our library space because odd-numbered sections would never be used!

The same problem occurs with any even number. They create patterns that waste “storage space” in our hash table, leading to what we call clustering. Clustering is like having a party where everyone crowds into one corner of the room while the rest of the space sits empty.

Enter the Prime Numbers: The Heroes of Hash Functions

Prime numbers are special because they don’t play nicely with other numbers, and that’s exactly what we want! A prime number only divides evenly by 1 and itself, which means it doesn’t create the regular patterns that even numbers do.

But why 31 specifically? Computer scientists tested many prime numbers and found that 31 hits a sweet spot. It’s large enough to create good distribution but small enough to be computationally efficient. Here’s the really clever part: multiplying by 31 can be optimized by smart compilers into a much faster operation.

Think of it this way: 31 = 32–1, and 32 is 2⁵. So multiplying by 31 becomes:

// What the compiler might optimize internally:
// result * 31  becomes  (result << 5) - result
// This means: shift left by 5 positions, then subtract the original

This bit manipulation is incredibly fast on modern processors, making 31 both mathematically excellent and practically efficient.

Building Complex Objects: The Snowball Effect

When you have objects with multiple properties, creating a good hash code becomes like building a snowball. Each property adds to the growing hash value, but in a way that preserves the contribution of all previous properties.

Let’s create a more complex example with a User class:

data class User(
    val username: String,
    val email: String,
    val age: Int,
    val isActive: Boolean,
    val salary: Double?
) {
    override fun hashCode(): Int {
        var result = 17  // Start with a prime number seed

        // Each property gets folded in with our magic multiplier
        result = result * 31 + username.hashCode()
        result = result * 31 + email.hashCode()
        result = result * 31 + age
        result = result * 31 + if (isActive) 1 else 0
        result = result * 31 + (salary?.hashCode() ?: 0)

        return result
    }
}

Notice how we start with 17 (another prime) as our seed? This prevents objects where the first property is zero or null from starting with a hash of zero, which could create unwanted patterns.

The mathematical beauty here is that each property gets “weighted” differently based on its position. The username gets multiplied by 31⁴, email by 31³, age by 31², and so on. This means that even if two users have the same values in different orders, they’ll produce different hash codes.

The Real-World Impact: From Theory to Practice

Let’s imagine you’re building a social media app with millions of users. Your User objects are stored in a HashMap for quick lookups during authentication. Here’s what happens behind the scenes:

class UserDatabase {
    private val users = HashMap<String, User>()

    fun findUser(username: String): User? {
        // The HashMap uses the String's hashCode to quickly locate the user
        // This should be nearly instant, not a slow search through millions of users
        return users[username]
    }

    fun addUser(user: User) {
        // Again, the hashCode determines where to store this user
        users[user.username] = user
    }
}

With a good hashCode implementation using 31, finding any user among millions takes roughly the same time whether you have 100 users or 100 million users. This is the magic of O(1) constant time lookup.

But with a poor hashCode (like using multiplier 2), your app might slow to a crawl as users get crowded into the same few “buckets,” creating long chains that need to be searched linearly.

The Hash Table Dance: What Happens After hashCode

Your carefully crafted hash code is just the first step in a fascinating dance. Hash tables like HashMap perform additional magic to ensure even better distribution:

// Simplified version of what HashMap does internally
fun distributeHash(originalHash: Int): Int {
    // XOR the high bits with the low bits for better distribution
    return originalHash xor (originalHash ushr 16)
}

This additional mixing ensures that the higher-order bits of your hash code influence the final bucket selection, not just the lower bits. It’s like having a second librarian double-check the first librarian’s work to ensure books are distributed as evenly as possible.

Modern Kotlin: The Easy Way

While understanding the internals is fascinating, modern Kotlin makes this easy for you. Data classes automatically generate excellent hashCode implementations:

// Kotlin automatically generates a perfect hashCode using the 31 pattern
data class Product(
    val name: String,
    val price: Double,
    val category: String
)

// Or you can use the built-in Objects utility for custom classes
class CustomProduct(val name: String, val price: Double) {
    override fun hashCode(): Int {
        return Objects.hash(name, price)  // Uses the same 31-based pattern internally
    }
}

A Performance Story: The Difference 31 Makes

Let me share a real-world example that shows why this matters. Imagine you’re processing customer orders for an e-commerce site:

data class Order(
    val customerId: String,
    val productId: String,
    val quantity: Int,
    val timestamp: Long
)

fun processOrders(orders: List<Order>) {
    val orderMap = HashMap<Order, OrderStatus>()

    // With good hashCode (using 31): Lightning fast insertions and lookups
    orders.forEach { order ->
        orderMap[order] = processOrder(order)  // Nearly instant, even with millions of orders
    }

    // Later lookups are also super fast
    val specificOrder = Order("user123", "product456", 2, System.currentTimeMillis())
    val status = orderMap[specificOrder]  // Found in microseconds, not milliseconds
}

If Order had a poorly designed hashCode, this same operation might take seconds or even minutes instead of milliseconds. The difference between a snappy user experience and a frustrating one often comes down to these fundamental data structure decisions.

The Deeper Philosophy: Why Small Details Matter

The story of 31 in hashCode implementations teaches us something profound about software engineering. It shows how mathematical elegance, performance optimization, and practical engineering can come together in a single number.

This isn’t just academic curiosity. Every time you use a Map, Set, or any hash-based collection in Kotlin, you’re benefiting from decades of research into optimal hash functions. The computer scientists who discovered that 31 was the sweet spot weren’t just solving a theoretical problem — they were making every app you’ll ever write a little bit faster.

Wrapping Up: The Magic of 31

The next time you see that number 31 in a hashCode implementation, you’ll know you’re looking at a small piece of computer science history. It represents the perfect balance between mathematical theory and practical performance, chosen through careful testing and proven by billions of successful hash table operations.

Whether you’re building the next great social media platform, a high-performance trading system, or a simple todo app, understanding these fundamentals will make you a better programmer. And now you know why 31 isn’t just any random number — it’s a carefully chosen key that unlocks lightning-fast data access in your Kotlin applications.

Remember, in the world of software engineering, the smallest details often make the biggest difference. The humble number 31 is proof that sometimes, the most elegant solutions hide in plain sight.

Want to dive deeper into Kotlin performance optimization? The next time you write a custom class, take a moment to think about its hashCode implementation. Your future self (and your users) will thank you for those extra microseconds of speed.


메타데이터
post_id
c2bb8c8e98f1
slug
the-secret-behind-kotlins-hashcode-why-31-is-the-magic-number-c2bb8c8e98f1
url
https://medium.com/@skniyajali/the-secret-behind-kotlins-hashcode-why-31-is-the-magic-number-c2bb8c8e98f1
canonical_url
https://medium.com/@skniyajali/the-secret-behind-kotlins-hashcode-why-31-is-the-magic-number-c2bb8c8e98f1
author_url
https://medium.com/@skniyajali
status
ok
fetched_at
2026-06-28 04:42:08