Question 14 · Section 10

What Are the Requirements for a HashMap Key?

A HashMap key must follow several important rules:

Language versions: English Russian Ukrainian

🟢 Junior Level

A HashMap key must follow several important rules:

  1. Override equals() and hashCode() — both methods must work together
  2. Be immutable — after creation, the key must not change
  3. Return the same hashCode on repeated calls — if fields haven’t changed

Good keys:

String key = "userId";           // Ideal key
Integer id = 42;                 // Also great
record Key(String name) {}       // Java 14+ — automatically immutable

Bad keys:

StringBuilder key = new StringBuilder("userId"); // Mutable!
List<String> listKey = new ArrayList<>();         // Can be modified

🟡 Middle Level

Main Requirements

Requirement Description Consequence of Violation
equals/hashCode contract If equals() = true, then hashCode() is the same Lost elements in the map
Immutability Key fields must not change “Broken key”, memory leak
Consistency Repeated calls give the same result Unpredictable behavior
Quality distribution hashCode should distribute keys evenly Collisions, performance degradation

Why String is the Ideal Key

  1. Immutable by design
  2. Caches its hashCode
  3. Implements Comparable
  4. Good hashCode distribution

Anti-patterns

// Anti-pattern 1: hashCode depends on time
@Override public int hashCode() { return System.currentTimeMillis(); }

// Anti-pattern 2: hashCode depends on random numbers
@Override public int hashCode() { return new Random().nextInt(); }

// Anti-pattern 3: URL as key (makes network requests in equals)
Map<URL, Data> map = new HashMap<>(); // Very slow!

Comparable Support

If the key implements Comparable<K>, HashMap uses compareTo() instead of tieBreakOrder() for tree balancing on collisions (Java 8+). This is faster, because compareTo() works with the object’s content, while tieBreakOrder() requires additional computation via System.identityHashCode().

🔴 Senior Level

Formal Requirements

From the Map documentation:

The behavior of a map is not specified if the value of an object used as a key is changed in a manner that affects equals comparisons.

Consistency Contract

Methods equals() and hashCode() must be consistent:

  • Must not depend on external resources (network, file system)
  • Must not use random/temporal values
  • URL.equals() — a classic violation example: makes DNS requests, blocks on network failure

Internal: Comparable for Treeification

On bucket treeification:

// If key instanceof Comparable:
//   compareTo() is used for tree balancing
// If not:
//   tieBreakOrder() via System.identityHashCode() — slower

Implementing Comparable significantly speeds up tree balancing, as compareTo() works with the object’s content, while tieBreakOrder() requires additional computation.

Memory Implications

  • Each entry = Node object (~32-48 bytes) + key + value
  • String literals from String Pool — save memory, as identical literals reference the same object. This does NOT apply to new String(...).
  • Record keys — compact, but require Java 14+

Edge Cases

  1. null key: Allowed (one), always in table[0]. In large systems — anti-pattern
  2. Enum as key: Ideal option — EnumMap. It’s faster because it uses an array with ordinal() as index — O(1) without hashCode or collisions.
  3. Array as key: equals() compares references, not content. Use a wrapper

Security

Keys can be an attack vector:

  • Hash Flooding: crafting keys with the same hashCode
  • DoS via heavy equals() (comparing large data)
  • Side-channel attacks via timing analysis of equals()

Best Practices

  1. Record for complex keys (Java 14+)
  2. Final fields — the minimum you can do
  3. Defensive copy in constructor and getters
  4. Don’t use mutable collections as keys
  5. Minimize fields in equals/hashCode — only identifiers

🎯 Interview Cheat Sheet

Must know:

  • equals/hashCode contract: if equals = true, hashCode is the same
  • Immutability: key fields must not change after creation
  • Consistency: repeated hashCode calls = same result
  • Quality distribution: hashCode should distribute keys evenly
  • String — ideal key: immutable, caches hashCode, implements Comparable
  • Comparable support speeds up treeification (compareTo instead of tieBreakOrder)

Common follow-up questions:

  • Why is URL a bad key? — equals() makes DNS requests, violates consistency
  • Can an array be a key? — no, equals compares references; use a wrapper
  • Why is EnumMap faster than HashMap? — array with ordinal() as index, O(1) without hashCode
  • Can null be a key? — yes, one, but in large systems — anti-pattern

Red flags (DO NOT say):

  • “Mutable key is fine if you don’t change it” — fragile decision
  • “hashCode can depend on time” — no, violates consistency
  • “Any object works as a key” — no, must follow the contract

Related topics:

  • [[07. What is the equals() and hashCode() Contract]]
  • [[12. Can You Use a Mutable Object as a Key in HashMap]]
  • [[15. Why String Is Often Used as a Key in HashMap]]