HashMap / HashTable Explained Simply — With Code Examples in JavaScript, Python & Java

A HashMap is one of the most important data structures in programming — it gives you O(1) average lookup, insert, and delete. Understanding how hash maps work makes you dramatically better at writing efficient code and solving algorithm problems. This guide covers the internals, all three major language implementations, and the essential interview patterns.

O(1)

average get/set/delete

O(n)

worst case (hash collisions)

Key→Value

maps any key to any value

~75%

of LeetCode problems easier with a map

1

The Core Idea

The storage locker analogy

Think of a HashMap like a storage locker facility. The "hash function" converts your key (like a name) into a locker number. Instead of checking every locker to find yours, you go directly to locker 42. That's O(1) access. The hash function computes the location from the key in constant time.

javascriptHow Hashing Works (Simplified)
// Simplified hash function concept
function hash(key, tableSize) {
  let h = 0;
  for (const char of key) {
    h = (h * 31 + char.charCodeAt(0)) % tableSize;
  }
  return h; // this is the array index
}

hash("name", 100)   // → 57 (example)
hash("email", 100)  // → 82 (example)

// The HashMap stores entries at computed indices:
// array[57] = { key: "name", value: "Alice" }
// array[82] = { key: "email", value: "alice@example.com" }

// Lookup: get("name") → hash("name") = 57 → array[57].value = "Alice"
// No searching — direct jump to the right slot in O(1)

// Collision: if hash("age") also returns 57, we have a COLLISION
// Resolved by chaining: array[57] = linked list of all entries that hash to 57
2

Using Hash Maps in JavaScript, Python, Java

javascriptJavaScript — Map (modern) and Object
// Modern Map: any key type, preserves insertion order, has .size
const map = new Map();
map.set('name', 'Alice');
map.set('age', 30);
map.set(42, 'the answer');  // non-string key!
map.set({ id: 1 }, 'obj'); // object as key!

map.get('name');             // → 'Alice'
map.has('age');              // → true
map.delete('age');
map.size;                    // → 3

// Iterate in insertion order:
for (const [key, value] of map) {
  console.log(key, value);
}

// Convert to array:
[...map.keys()];    // ['name', 42, {id:1}]
[...map.values()];  // ['Alice', 'the answer', 'obj']
[...map.entries()]; // [['name','Alice'], ...]

// Plain Object (legacy style — keys are strings/symbols only):
const obj = { name: 'Alice', age: 30 };
obj['name'];              // → 'Alice'
obj.name;                 // → 'Alice' (dot notation)
Object.keys(obj);         // → ['name', 'age']
Object.entries(obj);      // → [['name','Alice'], ['age',30]]
pythonPython — dict and Counter
# Python dict is a hash map — first-class citizen
user = {'name': 'Alice', 'age': 30}

# CRUD operations
user['email'] = 'alice@example.com'  # insert/update
user['name']                          # → 'Alice'
'age' in user                         # → True (O(1) check)
del user['age']                       # delete

# Safe get with default — avoids KeyError
user.get('phone', 'N/A')  # → 'N/A' (key not found)
user.get('name', 'Unknown')  # → 'Alice'

# setdefault — initialize if missing
user.setdefault('scores', []).append(95)  # {'scores': [95]}

# Iterate
for key, value in user.items():
    print(f"{key}: {value}")

# Counter — frequency map (extremely useful)
from collections import Counter
words = ['apple', 'banana', 'apple', 'cherry', 'apple']
freq = Counter(words)  # Counter({'apple': 3, 'banana': 1, 'cherry': 1})
freq.most_common(2)   # [('apple', 3), ('banana', 1)]

# defaultdict — auto-initializes missing keys
from collections import defaultdict
groups = defaultdict(list)
for name, dept in [('Alice','Eng'), ('Bob','Eng'), ('Carol','Sales')]:
    groups[dept].append(name)
# {'Eng': ['Alice','Bob'], 'Sales': ['Carol']}
javaJava — HashMap and related classes
import java.util.*;

Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);

scores.get("Alice");                    // → 95
scores.getOrDefault("Carol", 0);       // → 0 (safe default, no NPE)
scores.containsKey("Bob");             // → true
scores.putIfAbsent("Dave", 100);       // only adds if key absent
scores.computeIfAbsent("Eve", k -> k.length()); // compute value if absent
scores.remove("Bob");

// Iterate
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// Java 8+ forEach:
scores.forEach((key, val) -> System.out.println(key + "=" + val));

// LinkedHashMap: preserves insertion order (HashMap doesn't guarantee order)
Map<String, Integer> ordered = new LinkedHashMap<>();

// TreeMap: sorted by key (O(log n) operations)
Map<String, Integer> sorted = new TreeMap<>();

// ConcurrentHashMap: thread-safe (prefer over Hashtable)
Map<String, Integer> concurrent = new ConcurrentHashMap<>();
3

The Frequency Counter Pattern (Most Important)

Using a HashMap as a frequency counter transforms O(n²) problems into O(n). This pattern appears in roughly 40% of array/string algorithm interview problems.

O(n²) nested loop

❌ Bad
// O(n²) — nested loop to find duplicates
function hasDuplicate(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] === arr[j]) return true;
    }
  }
  return false;
}
// 1000 elements → up to 500,000 comparisons

O(n) with Set/Map

✅ Good
// O(n) — hash set for O(1) lookup
function hasDuplicate(arr) {
  const seen = new Set();
  for (const n of arr) {
    if (seen.has(n)) return true; // duplicate found
    seen.add(n);
  }
  return false;
}
// 1000 elements → exactly 1000 operations
javascriptEssential HashMap Patterns for Interviews
// Pattern 1: Character frequency (anagram check)
function isAnagram(s, t) {
  if (s.length !== t.length) return false;
  const freq = {};
  for (const c of s) freq[c] = (freq[c] || 0) + 1;
  for (const c of t) {
    if (!freq[c]) return false;
    freq[c]--;
  }
  return true;
}

// Pattern 2: Two Sum (classic)
function twoSum(nums, target) {
  const seen = new Map(); // value → index
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (seen.has(complement)) {
      return [seen.get(complement), i];
    }
    seen.set(nums[i], i);
  }
  return [];
}
twoSum([2,7,11,15], 9); // → [0, 1]

// Pattern 3: Group by property
function groupAnagrams(words) {
  const groups = new Map();
  for (const word of words) {
    const key = [...word].sort().join(''); // sorted chars = anagram signature
    if (!groups.has(key)) groups.set(key, []);
    groups.get(key).push(word);
  }
  return [...groups.values()];
}
groupAnagrams(['eat','tea','tan','ate','nat','bat']);
// → [['eat','tea','ate'],['tan','nat'],['bat']]

// Pattern 4: Sliding window with frequency map
function lengthOfLongestSubstringKDistinct(s, k) {
  const freq = new Map();
  let left = 0, maxLen = 0;
  for (let right = 0; right < s.length; right++) {
    freq.set(s[right], (freq.get(s[right]) || 0) + 1);
    while (freq.size > k) {
      const c = s[left++];
      freq.set(c, freq.get(c) - 1);
      if (freq.get(c) === 0) freq.delete(c);
    }
    maxLen = Math.max(maxLen, right - left + 1);
  }
  return maxLen;
}
4

Hash Collisions — How They're Handled

Why HashMap is O(1) average but O(n) worst case

In the worst case, all keys hash to the same index — every lookup scans the entire collision chain. Good hash functions and a proper load factor (resize when ~75% full) keep collisions rare. Java's HashMap resizes by doubling when load factor exceeds 0.75. Python's dict uses ~66% load factor. Real performance: typically 1-2 comparisons per lookup even under moderate load.

Separate Chaining

Each bucket holds a linked list (or in Java 8+, a balanced tree when the chain grows beyond 8). Collision just adds to the chain. Simple, handles high load well. Used by Java HashMap, Python dict.

Open Addressing (Probing)

On collision, probe for the next open slot (linear, quadratic, or double hashing). Better cache performance (data stays in the array). Used by Python's older dict, C++'s unordered_map.

Load Factor and Resizing

When the hash map is too full (high load factor), collisions increase. Solution: resize — allocate a larger array and rehash all entries. Java defaults to 0.75 load factor before resizing.

Hash Function Quality

A bad hash function that maps many keys to the same bucket degrades performance to O(n). Good hash functions distribute keys uniformly. Java String.hashCode() uses polynomial rolling hash with prime base 31.

5

HashMap vs Array vs Set vs TreeMap

ItemData StructureBest For
Array / ListO(1) indexed access, O(n) searchOrdered data, index-based access, iteration
HashMap / MapO(1) key lookup, insert, deleteKey-value pairs, frequency counting, memoization
Set (HashSet)O(1) membership test, O(1) insertUnique values, deduplication, fast contains check
TreeMap / SortedDictO(log n) all operationsRange queries, ordered keys, min/max needed
LinkedHashMapO(1) operations + insertion orderLRU cache, ordered iteration by insertion time
1

Choose HashMap when you need O(1) lookup by key

Any time you need to check "have I seen this before?" or "what is the value for this key?", reach for a HashMap/dict. It's the right tool for ~40% of algorithm problems.

2

Use the frequency counter pattern

Count occurrences: freq[x] = (freq[x] || 0) + 1. This pattern converts dozens of O(n²) problems into O(n). Anagram check, two sum, group anagrams, sliding window all use it.

3

Understand the key requirements

HashMap keys must be hashable (immutable in Python: strings, numbers, tuples — not lists). In JavaScript's Map, any value is a valid key. In Java, objects must implement hashCode() and equals().

4

Watch for integer overflow in hash functions

In Java, Integer.MAX_VALUE + 1 overflows to negative. Use Long or use Math.abs(). In JavaScript, all numbers are floats — use BigInt for very large keys. In Python, integers are arbitrary precision.

5

Know the ordered alternatives

TreeMap/SortedDict when you need range queries or min/max. LinkedHashMap when insertion order matters. Python's dict (3.7+) preserves insertion order by default.

Frequently Asked Questions