Ova

What is the Difference Between HashMap and HashSet in Kotlin?

Published in Kotlin Collections 4 mins read

The fundamental difference between HashMap and HashSet in Kotlin lies in their purpose and how they store data: HashMap stores unique key-value pairs, whereas HashSet stores unique individual elements. Both are highly efficient collections backed by hash tables, providing fast average-case performance for adding, removing, and checking for the presence of elements.

Understanding the Core Distinction

At its heart, the difference stems from the broader concepts of a Map versus a Set in computer science.

  • HashMap (Kotlin's MutableMap): This data structure maps unique keys to specific values. Think of it like a dictionary where you look up a word (key) to find its definition (value). Each key must be unique, but multiple keys can point to the same value.
  • HashSet (Kotlin's MutableSet): This data structure stores a collection of unique individual elements. Its primary purpose is to maintain a group of distinct items and efficiently check if an item is already present. Duplicates are not allowed; if you try to add an element that already exists, the set remains unchanged.

Key Differences Summarized

Here’s a concise comparison of HashMap and HashSet in Kotlin:

Feature HashMap HashSet
Primary Purpose Stores and retrieves data based on unique key-value associations. Stores a collection of unique individual elements.
Data Storage Stores elements as key-value pairs (Map.Entry). Stores individual elements.
Uniqueness Guarantees unique keys. Values can be duplicates. Guarantees unique elements. No duplicates are allowed.
Access/Lookup Elements are accessed by their key (e.g., map[key]). Elements are typically accessed by iterating or checking for existence with contains().
Kotlin Interface Implements MutableMap<K, V> (or Map<K, V> for immutable). Implements MutableSet<E> (or Set<E> for immutable).
Internal Logic Uses the hashCode() of the key for placement and retrieval. Uses the hashCode() of the element for placement and retrieval.
Common Use Cases Dictionaries, configuration settings, caching, object lookups by ID. Removing duplicates, membership testing, performing set operations (union, intersection).

When to Use HashMap

HashMap is ideal when you need to store and retrieve data where each piece of information has a distinct identifier.

  • Data Association: Link a unique identifier (like a user ID, product SKU, or country code) to a specific piece of information (user object, product details, country name).
    val userScores = HashMap<String, Int>()
    userScores["Alice"] = 1500
    userScores["Bob"] = 1200
    println("Alice's score: ${userScores["Alice"]}") // Output: Alice's score: 1500
  • Lookup Tables: Create quick reference tables where values need to be fetched based on a key.
    val countryCapitals = HashMap<String, String>()
    countryCapitals["USA"] = "Washington D.C."
    countryCapitals["Germany"] = "Berlin"
    println("Capital of Germany: ${countryCapitals["Germany"]}")
  • Caching: Store pre-computed results, mapped by their input parameters, to avoid redundant computations.

When to Use HashSet

HashSet is the go-to choice when you need a collection of distinct items and primarily care about their presence or absence.

  • Storing Unique Items: Ensure that a collection contains only unique elements, automatically discarding duplicates.

    val shoppingList = HashSet<String>()
    shoppingList.add("Apples")
    shoppingList.add("Milk")
    shoppingList.add("Apples") // This "Apples" will be ignored as it's a duplicate
    println("Items in shopping list: $shoppingList") // Output: Items in shopping list: [Milk, Apples] (order may vary)
  • Membership Testing: Efficiently check if an element is already part of the collection. This is often an O(1) operation on average.

    val registeredEmails = HashSet<String>()
    registeredEmails.add("[email protected]")
    registeredEmails.add("[email protected]")
    
    if (registeredEmails.contains("[email protected]")) {
        println("User 1 is registered.")
    }
  • Removing Duplicates: Easily filter out duplicates from another list or array.

    val numbersWithDuplicates = listOf(1, 2, 2, 3, 1, 4)
    val uniqueNumbers = HashSet(numbersWithDuplicates)
    println("Unique numbers: $uniqueNumbers") // Output: Unique numbers: [1, 2, 3, 4]
  • Set Operations: Perform mathematical set operations like union, intersection, and difference.

    val setA = setOf(1, 2, 3)
    val setB = setOf(3, 4, 5)
    
    val union = setA.union(setB)        // [1, 2, 3, 4, 5]
    val intersection = setA.intersect(setB) // [3]

Practical Considerations

  • Performance: Both HashMap and HashSet offer excellent average-case performance for add, remove, and contains operations (typically O(1)). This efficiency comes from their underlying use of hashing. However, in worst-case scenarios (e.g., poor hash function leading to many collisions), performance can degrade to O(n).
  • Mutability: In Kotlin, HashMap is a concrete implementation of MutableMap, and HashSet implements MutableSet. If you need to modify the collection after creation (add, remove elements), always use their Mutable versions. The basic Map and Set interfaces provide immutable views.
  • Hashing Contract: For custom objects used as keys in a HashMap or as elements in a HashSet, it is crucial to correctly implement both the equals() and hashCode() methods. Failure to do so can lead to unexpected behavior, such as duplicates being added or elements not being found when they should be. Learn more about hashCode() and equals() in Kotlin.

By understanding whether you need to associate data with a unique identifier (HashMap) or simply store a collection of unique items (HashSet), you can choose the most appropriate and efficient data structure for your Kotlin application.