πŸ”· 1. HashMap

  • Unordered: Does not maintain any order of keys.
  • Allows null keys and values (one null key allowed).
  • Not thread-safe.
  • Time complexity: O(1) for get() and put() (on average).
  • βœ… Best for general-purpose use when order is not important.
Map<String, Integer> map = new HashMap<>();
						

βœ… Use When:

  • You need fast access(O(1) time complexity for basic operations)
  • Order of elements doesn't matter.
  • You are working in a single-threaded environment.

πŸ”§ Example Use Case:

  • Caching results of expensive calculations
  • Storing user profiles by user ID.

πŸ”· 2. LinkedHashMap

  • Maintains insertion orderof keys.
  • Allows null keys and values.
  • Not thread-safe.
  • Not thread-safe.
  • Slightly slower thanHashMap due to ordering overhead.
  • βœ… Best when you need predictable iteration order.
Map<String, Integer> map = new  LinkedHashMap<>();
						

βœ… Use When:

  • You want to maintain insertion order.
  • Need fast access.withpredictable iteration.

πŸ”§ Example Use Case:

  • Implementing LRU cache (can remove the oldest entry using .removeEldestEntry())
  • Keeping track of items accessed in the order they were added.

πŸ”· 3. TreeMap

  • Sorted map based on keys (natural order or custom Comparator)
  • Does not allow null keys (throws NullPointerException).
  • Not thread-safe.
  • βœ…Best when you need sorted keys.
Map<String, Integer> map = new  TreeMap<>();
						

βœ…Use When:

  • You need to keep keys sorted (natural order or with a custom comparator)
  • You're doing range queries(like getting all keys between two values).

Example Use Case:

  • Storing items in a leaderboard or ranking system.
  • Keeping a schedule or calendar sorted by date

πŸ”· 4. Hashtable

  • Legacy class, synchronized (thread-safe).
  • Does not allow null keys or values.
  • Slower than HashMapdue to synchronization.
  • βœ…Best when thread safety is needed and you can't usee ConcurrentHashMap.
Map<String, Integer> map = new  Hashtable<>();
						

βœ…Use When:

  • You need a synchronized map for thread safety (older applications)
  • You're maintaining legacy code.

Example Use Case:

  • Shared configuration settings in legacy multi-threaded applications.

πŸ”· 5. ConcurrentHashMap

  • Thread-safe without locking the whole map.
  • Does not allow null keys or values.
  • High performance in concurrent environments.
  • βœ…Best for multi-threaded applications.
Map<String,Integer> map = new ConcurrentHashMap<>();
						

βœ…Use When:

  • You're in amulti-threaded environment.
  • You need athread-safe map without locking the entire structure.
  • High concurrency is required.

πŸ”§ Example Use Case:

  • Caching data in aweb application with many threads.
  • Storing active user sessions.

βœ… Java Map Comparison Table

Feature / Type HashMap LinkedHashMap TreeMap Hashtable ConcurrentHashMap
Ordering No order Insertion order Sorted (by key) No order No order
Null Keys/Values 1 null key, multiple null value Same as HashMap ❌ No null keys, allows null values ❌ No nulls at al ❌ No nulls at all
Thread-Safe ❌ No ❌ No ❌ No βœ… Yes (synchronize d βœ… Yes (high concurrency)
Performance Fastest(O(1)) Slightly slower than HashMap Slower(O(log n)) Slower due to sync Very fast, scalable
Use Case General-purpose Predictable iteration Sorted Data Legacy multi-threaded High-performance concurrency
Custom Sorting ❌ No ❌ No βœ… Yes (Comparator) ❌ No ❌ No
Best For Caching, key-value storage LRU cache, ordered data Sorted leaderboard, ranges Legacy apps Web apps, shared caches

βš™ Internal Working of HashMap β€” Step by Step

πŸ’‘ Basic Concept:

  • Each key is hashed into an integer hash code.
  • The hash code determines the index(bucket) in an internal array where the key-value pair will be stored.
  • If two keys land in the same bucket (hash collision), Java handles it using LinkedList or TreeNode (from Java 8+).

Let's Take an Example:

import java.util.*;
public class HashMapExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 100);
        map.put("banana", 200);
        map.put("orange", 300);
        System.out.println("apple: " + map.get("apple"));
    }
}
            
        

πŸ”§ Behind the Scenes:

Step 1: Hashing the Keys
Let’s say the internal array has a default capacity of 16.
int hash = "apple".hashCode(); // Let's say this gives 93029210
int index = hash % 16;         // 93029210 % 16 = 2
        
So, the entry ("apple", 100) is stored in bucket 2.
Step 2: Putting More Entries
map.put("banana", 200);
β€’ "banana".hashCode() might return, say, 2059142
β€’ 2059142 % 16 = 6 β†’ store in bucket 6

map.put("orange", 300);
β€’ "orange".hashCode() β†’ say, 7539312
β€’ 7539312 % 16 = 0 β†’ store in bucket 0
            
        
So the internal array (table) now looks like:
Index | Entries
--------------------------
0     | ("orange", 300)
2     | ("apple", 100)
6     | ("banana", 200)
Others| null
            
        
Step 3: Getting a Value
map.get("apple");
β€’ "apple".hashCode() = 93029210
β€’ 93029210 % 16 = 2 β†’ Go to index 2
β€’ Search for the key using .equals() β†’ Found β†’ Return 100
            
        

🎯 1. Collision Scenario in HashMap

⚠ What is a Collision?

A collision happens when two different keys generate the same bucket index. This can happen even if the keys are different, just because their hash codes resolve to the same array index.

πŸ§ͺ Example:
Map<String, Integer> map = new HashMap<>();
map.put("FB", 10); // Let's say hashCode() = 2236
map.put("Ea", 20); // Also has hashCode() = 2236
            
        
Both "FB" and "Ea" happen to have the same hashCode() (yes, this is a known trick example).
Assuming capacity is 16:
index = hashCode % 16 = 2236 % 16 = 12
        

How It Retrieves the Right Value:

  • When you call map.get("FB"):
    • HashMap goes to index 12
    • Compares each key using .equals() until it finds "FB"

2. Duplicate Key Scenario in HashMap

⚠ What Happens If You Add the Same Key Again?

If you add a key that already exists in the map, HashMap updates the value β€” it does not create a new entry.

Example:
					
Map<String, Integer> map = new HashMap<>();
map.put("apple", 100);
map.put("apple", 200); // Same key, different value

        
  • "apple"'s hashCode and bucket index are the same both times.
  • The second put() overwrites the existing value.
πŸ“ Final State of the Map:
{ "apple": 200 }
						
Only one key "apple" exists, and its value is now updated to 200.

πŸ”„ Visual Comparison

Scenario Keys HashCode Bucket Index Result
Collision "FB", "Ea" Same (e.g., 2236) Same Stored together in one bucket
Duplicate Key "apple" twice Same Same Old value replaced with new