What Are the Requirements for a HashMap Key?
A HashMap key must follow several important rules:
🟢 Junior Level
A HashMap key must follow several important rules:
- Override equals() and hashCode() — both methods must work together
- Be immutable — after creation, the key must not change
- 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
- Immutable by design
- Caches its hashCode
- Implements
Comparable - 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
- null key: Allowed (one), always in
table[0]. In large systems — anti-pattern - Enum as key: Ideal option — EnumMap. It’s faster because it uses an array with ordinal() as index — O(1) without hashCode or collisions.
- 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
- Record for complex keys (Java 14+)
- Final fields — the minimum you can do
- Defensive copy in constructor and getters
- Don’t use mutable collections as keys
- 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]]