Back to HashMap Internals
Q6Question 6 of 10

What happens when you put a duplicate key in HashMap?

Key Concepts
  • put(existingKey, newValue) replaces the old value and returns the old value; size does not change
  • The key object itself is NOT replaced; only the value changes
  • Key equality is determined by hashCode() first (for bucket) and equals() second (for exact match)
  • putIfAbsent(key, value) inserts only if the key is not already present; returns null if inserted, existing value if skipped
  • merge(key, value, remappingFunction) combines old and new values atomically; ideal for counters and accumulators
  • getOrDefault(key, defaultValue) returns the default when the key is absent, avoiding null-check boilerplate

Definition

put() with an existing key replaces the old value and returns it; the key object is not replaced, the size stays the same, and putIfAbsent only inserts if the key is absent.
  • 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.

JAVA
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
    }
}
TEXT
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.

JAVA
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
    }
}
TEXT
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.

JAVA
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);
    }
}
TEXT
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.

JAVA
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!
    }
}
TEXT
Size: 2

What are common mistakes?

Mistake 1: Not using the return value of put()

JAVA
// 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

JAVA
// 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

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate