Definition
- put(key, newValue): if key exists, replaces value, returns old value; if key absent, inserts, returns null
- Key not replaced: the original key object stays in the map; only the value changes
- Equality check: HashMap uses
hash == node.hash && key.equals(node.key)to find existing entries - putIfAbsent(key, value): inserts only if absent; returns null if inserted, existing value if skipped
- merge(key, value, fn): if key exists, applies fn(oldValue, newValue); if absent, inserts value
HashMap is like a filing cabinet where each drawer is labeled with an account number. Filing a new document under SAV2001 when one already exists replaces the old document – you get the old document back (the return value). The drawer label stays the same. You cannot have two drawers with the same label.
Understanding the Concepts
What happens when you put a duplicate key?
When you call put(key, newValue) and the key already exists in the map, HashMap walks the bucket chain, finds the matching node (same hash and equals), replaces the value, and returns the old value.
package com.bank.demo;
import java.math.BigDecimal;
import java.util.*;
public class DuplicateKeyDemo {
public static void main(String[] args) {
Map<String, BigDecimal> balances = new HashMap<>();
// First put: key absent → insert, returns null
BigDecimal old1 = balances.put("SAV2001", new BigDecimal("50000"));
System.out.println("First put returned: " + old1); // null
// Second put: key exists → replace, returns old value
BigDecimal old2 = balances.put("SAV2001", new BigDecimal("75000"));
System.out.println("Second put returned: " + old2); // 50000
// Map now has only one entry
System.out.println("Map: " + balances); // {SAV2001=75000}
System.out.println("Size: " + balances.size()); // 1
}
}
First put returned: null
Second put returned: 50000
Map: {SAV2001=75000}
Size: 1
Is the key object replaced?
No. When you put a duplicate key, the value is replaced but the key object in the map stays the same. The new key object (used in the put call) is discarded.
package com.bank.demo;
import java.util.*;
public class KeyNotReplacedDemo {
public static void main(String[] args) {
String key1 = new String("SAV2001"); // object A
String key2 = new String("SAV2001"); // object B (different instance, same value)
Map<String, String> map = new HashMap<>();
map.put(key1, "first"); // key1 stored in map
map.put(key2, "second"); // key2 is NOT stored; key1 stays
// Verify: the key in the map is key1, not key2
for (String key : map.keySet()) {
System.out.println("Key is key1: " + (key == key1)); // true
System.out.println("Key is key2: " + (key == key2)); // false
}
System.out.println("Value: " + map.get("SAV2001")); // second
}
}
Key is key1: true
Key is key2: false
Value: second
How do putIfAbsent, merge, and compute work?
These methods provide fine-grained control over what happens when a key exists or is absent.
package com.bank.service;
import java.math.BigDecimal;
import java.util.*;
public class MapMethodsDemo {
public static void main(String[] args) {
Map<String, BigDecimal> balances = new HashMap<>();
// putIfAbsent: insert only if key absent
balances.putIfAbsent("SAV2001", new BigDecimal("50000"));
balances.putIfAbsent("SAV2001", new BigDecimal("99999")); // skipped!
System.out.println("After putIfAbsent: " + balances.get("SAV2001")); // 50000
// merge: combine old and new values
balances.merge("SAV2001", new BigDecimal("10000"), BigDecimal::add);
System.out.println("After merge +10000: " + balances.get("SAV2001")); // 60000
// merge on absent key: just inserts the value
balances.merge("CUR3001", new BigDecimal("75000"), BigDecimal::add);
System.out.println("After merge (absent): " + balances.get("CUR3001")); // 75000
// compute: update based on key and current value
balances.compute("SAV2001", (k, v) -> v.add(new BigDecimal("5000")));
System.out.println("After compute: " + balances.get("SAV2001")); // 65000
// getOrDefault: avoid null checks
BigDecimal unknown = balances.getOrDefault("UNKNOWN", BigDecimal.ZERO);
System.out.println("Unknown account: " + unknown); // 0
// replaceAll: update all values
balances.replaceAll((k, v) -> v.multiply(new BigDecimal("1.05"))); // 5% interest
System.out.println("After 5% interest: " + balances);
}
}
After putIfAbsent: 50000
After merge +10000: 60000
After merge (absent): 75000
After compute: 65000
Unknown account: 0
After 5% interest: {SAV2001=68250.00, CUR3001=78750.00}
Why do hashCode() and equals() need to be consistent?
HashMap uses hashCode() to find the bucket and equals() to find the exact key in the bucket. If two objects are equal (equals returns true) but have different hash codes, they will end up in different buckets. HashMap will treat them as different keys, allowing "duplicates" that violate the Map contract.
package com.bank.demo;
import java.util.*;
public class InconsistentHashDemo {
static class BrokenAccount {
String id;
BrokenAccount(String id) { this.id = id; }
@Override public boolean equals(Object o) {
return o instanceof BrokenAccount ba && ba.id.equals(this.id);
}
// WRONG: no hashCode override! Uses Object.hashCode (memory address)
}
public static void main(String[] args) {
Map<BrokenAccount, String> map = new HashMap<>();
map.put(new BrokenAccount("SAV2001"), "Rajesh");
map.put(new BrokenAccount("SAV2001"), "Priya"); // different hash → different bucket!
System.out.println("Size: " + map.size()); // 2 – should be 1!
// Two entries with "equal" keys – duplicate snuck in!
}
}
Size: 2
What are common mistakes?
Mistake 1: Not using the return value of put()
// The return value tells you whether the key existed
BigDecimal oldBalance = balances.put("SAV2001", new BigDecimal("75000"));
if (oldBalance != null) {
System.out.println("Updated from " + oldBalance + " to 75000");
} else {
System.out.println("New account created");
}
Mistake 2: Using put when you mean putIfAbsent
// WRONG: overwrites if key exists
map.put("SAV2001", createDefaultAccount());
// CORRECT: only inserts if absent
map.putIfAbsent("SAV2001", createDefaultAccount());
// BETTER: lazy creation (factory called only if absent)
map.computeIfAbsent("SAV2001", k -> createDefaultAccount());
What should a senior engineer know?
- putIfAbsent vs computeIfAbsent. putIfAbsent takes the value directly (eager – always created). computeIfAbsent takes a function (lazy – only called if key is absent). For expensive value creation, use computeIfAbsent.
- merge for counters. map.merge(key, 1, Integer::sum) is the idiomatic way to count occurrences. It handles both the absent case (inserts 1) and the present case (adds 1 to existing).
- compute returning null removes the entry. If the remapping function in compute() returns null, the entry is removed from the map.
What are common interview follow-up questions?
- "What does put() return?" – The old value if the key existed, null if it was absent.
- "Is the key replaced?" – No, only the value is replaced.
- "What is the difference between putIfAbsent and computeIfAbsent?" – Eager vs lazy value creation.
- "How does merge work?" – If key absent, inserts value. If present, applies function to old and new values.
Visual Overview
put("SAV2001", 75000) when "SAV2001" → 50000 exists:
1. hash("SAV2001") → bucket 7
2. Walk chain in bucket 7:
Node[hash=xxx, key="SAV2001", value=50000, next=null]
hash matches AND equals matches → FOUND!
3. Replace: value = 75000
4. Return old value: 50000
Map.Entry methods:
put(key, value) → replace value, return old
putIfAbsent(key, value) → insert only if absent
merge(key, val, fn) → fn(oldVal, val) if present
compute(key, fn) → fn(key, oldVal) always
computeIfAbsent(key, fn)→ fn(key) only if absent
Key Takeaway
HashMap does not allow duplicate keys. Putting a value with an existing key replaces the old value and returns it. The key object itself is not replaced – only the value changes. The size stays the same.
For more control, use putIfAbsent (insert only if absent), computeIfAbsent (lazy insert), merge (combine old and new values), and compute (arbitrary update). These methods are especially useful in ConcurrentHashMap where they execute atomically.
In the National Bank of India system, Rajesh uses merge("SAV2001", depositAmount, BigDecimal::add) to update account balances. If the account exists, the deposit is added to the current balance. If the account is new, the deposit amount becomes the initial balance. One line, no null checks, no conditional logic.
Remember one sentence: put replaces the value and returns the old one; use putIfAbsent when you do not want to overwrite, and merge when you want to combine.
---