Hash Tables

Key-to-value lookup in average O(1) via hashing into buckets.

Hash Tables Operations & Functions

Hash table functions

In TypeScript you usually reach for Map and Set. Below are the methods and patterns you will use most on interview problems — plus a tiny DIY hash so you can see what the built-ins hide.

Prefer Overview for how hashing and buckets work, and Problems for practice once problems are linked.

Map: set, get, has, delete

Map is the everyday key → value hash table in TypeScript. Any key type works — numbers, strings, objects.

new Map()

Starts an empty map, or builds one from [key, value] pairs.

map-ctor.tsTypeScript
// ——— empty ———
const ages = new Map<string, number>();

// ——— from pairs ———
const scores = new Map<string, number>([
  ['ada', 98],
  ['lin', 91],
]);

Map.set()

Stores a value under a key. Overwrites if the key already exists.

map-set.tsTypeScript
const ages = new Map<string, number>();

// ——— insert / update ———
ages.set('ada', 36);
ages.set('lin', 29);
ages.set('ada', 37); // overwrite

// ——— chainable ———
ages.set('kai', 41).set('sam', 33);

Map.get()

Reads the value for a key, or undefined if it is missing.

map-get.tsTypeScript
const ages = new Map([['ada', 37]]);

const ada = ages.get('ada'); // 37
const missing = ages.get('zoe'); // undefined

// ——— default with ?? ———
const n = ages.get('zoe') ?? 0; // 0

Map.has()

Returns true when the key is present — even if the value is undefined.

map-has.tsTypeScript
const ages = new Map<string, number | undefined>([['ada', 37]]);

ages.has('ada'); // true
ages.has('zoe'); // false

// ——— has vs get ———
ages.set('ghost', undefined);
ages.has('ghost'); // true
ages.get('ghost'); // undefined

Map.delete()

Removes one key. Returns true if something was removed.

map-delete.tsTypeScript
const ages = new Map([
  ['ada', 37],
  ['lin', 29],
]);

ages.delete('lin'); // true
ages.delete('zoe'); // false
ages.has('lin'); // false

Object as a map — caveats

Plain objects can act like string-key maps, but they carry prototype keys and only string/symbol keys. Prefer Map for general lookups.

Object vs Map

Objects coerce keys to strings. Map keeps the real key type.

object-vs-map.tsTypeScript
// ——— object: number key becomes string ———
const obj: Record<string, string> = {};
obj[1] = 'one';
obj['1']; // 'one'

// ——— Map: number and string stay distinct ———
const map = new Map<number | string, string>();
map.set(1, 'one');
map.set('1', 'string one');
map.get(1); // 'one'
map.get('1'); // 'string one'

Prototype traps

Inherited names like toString can surprise you. Map never inherits keys.

object-prototype.tsTypeScript
const bag: Record<string, number> = Object.create(null);
// or start from {}
const risky: Record<string, number> = {};

risky['toString']; // function — inherited, not your data

// ——— safer membership on objects ———
Object.hasOwn(risky, 'toString'); // false

// ——— Map sidesteps this entirely ———
const safe = new Map<string, number>();
safe.has('toString'); // false until you set it

When an object is fine

JSON-shaped data, fixed string keys, or configs are still a good fit for objects.

object-ok.tsTypeScript
type Config = { host: string; port: number };

const config: Config = { host: 'localhost', port: 3000 };

// ——— known keys, no dynamic lookup storms ———
const port = config.port;

Set — unique values

A Set is a hash table of keys with no separate value. Perfect for “have I seen this?” checks.

new Set()

Starts empty, or dedupes values from an iterable.

set-ctor.tsTypeScript
const seen = new Set<number>();

// ——— dedupe an array ———
const unique = [...new Set([1, 1, 2, 3])]; // [1, 2, 3]

Set.add() / has() / delete()

Add a value, ask if it exists, or remove it.

set-ops.tsTypeScript
const seen = new Set<number>();

seen.add(7);
seen.add(7); // still one entry
seen.has(7); // true
seen.delete(7); // true
seen.has(7); // false

Set.size

How many unique values are stored.

set-size.tsTypeScript
const seen = new Set([1, 2, 2, 3]);
seen.size; // 3

Frequency counting

Count how often each value appears. One pass builds the map; later lookups are O(1) average.

Count with Map

Increment a counter for every element you see.

freq-map.tsTypeScript
function frequencies(nums: number[]): Map<number, number> {
  const freq = new Map<number, number>();
  for (const v of nums) {
    freq.set(v, (freq.get(v) ?? 0) + 1);
  }
  return freq;
}

frequencies([1, 2, 1, 3, 1]); // Map { 1 → 3, 2 → 1, 3 → 1 }

Most frequent

After counting, scan the map for the biggest count.

freq-mode.tsTypeScript
function mode(nums: number[]): number | undefined {
  const freq = new Map<number, number>();
  let best: number | undefined;
  let bestCount = 0;

  for (const v of nums) {
    const next = (freq.get(v) ?? 0) + 1;
    freq.set(v, next);
    if (next > bestCount) {
      best = v;
      bestCount = next;
    }
  }
  return best;
}

Two-sum hash pattern

Store what you have seen. For each number, ask whether the complement already lives in the map.

Two sum (indexes)

Return the two indexes that add up to target — one pass with a Map.

two-sum.tsTypeScript
function twoSum(nums: number[], target: number): [number, number] | null {
  const seen = new Map<number, number>(); // value → index

  for (let i = 0; i < nums.length; i++) {
    const need = target - nums[i]!;
    const j = seen.get(need);
    if (j !== undefined) return [j, i];
    seen.set(nums[i]!, i);
  }
  return null;
}

twoSum([2, 7, 11, 15], 9); // [0, 1]

Pair exists (Set)

When you only care whether a pair exists, a Set is enough.

two-sum-set.tsTypeScript
function hasPair(nums: number[], target: number): boolean {
  const seen = new Set<number>();
  for (const x of nums) {
    if (seen.has(target - x)) return true;
    seen.add(x);
  }
  return false;
}

Grouping anagrams

Anagrams share a sorted signature. Hash that signature to a list of words.

Group by sorted key

Sort each word’s letters, use the sorted string as the Map key.

group-anagrams.tsTypeScript
function groupAnagrams(words: string[]): string[][] {
  const groups = new Map<string, string[]>();

  for (const word of words) {
    const key = [...word].sort().join('');
    const bucket = groups.get(key);
    if (bucket) bucket.push(word);
    else groups.set(key, [word]);
  }

  return [...groups.values()];
}

groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat']);
// [['eat','tea','ate'], ['tan','nat'], ['bat']]

Iterate keys & entries

Walk every key, value, or pair. Order is insertion order for Map and Set.

Map.keys() / values() / entries()

Loop the pieces you need, or spread them into arrays.

map-iterate.tsTypeScript
const ages = new Map([
  ['ada', 37],
  ['lin', 29],
]);

for (const key of ages.keys()) {
  // 'ada', 'lin'
}

for (const [key, value] of ages.entries()) {
  // 'ada' 37, 'lin' 29
}

const keys = [...ages.keys()]; // ['ada', 'lin']

Map.forEach()

Visit every entry with a callback. Prefer for...of when you need break.

map-foreach.tsTypeScript
const ages = new Map([
  ['ada', 37],
  ['lin', 29],
]);

ages.forEach((value, key) => {
  // key, value
});

DIY hash → bucket

A tiny chaining table so you can see hash, modulo, and collisions without the built-in Map.

hash + bucket index

Turn a string into a number, then fold it into the bucket array length.

diy-hash.tsTypeScript
function hashString(key: string): number {
  let h = 0;
  for (let i = 0; i < key.length; i++) {
    h = (h * 31 + key.charCodeAt(i)) | 0;
  }
  return Math.abs(h);
}

function bucketIndex(key: string, bucketCount: number): number {
  return hashString(key) % bucketCount;
}

bucketIndex('ada', 4); // some slot 0..3

Chaining put / get

Each bucket holds a list of [key, value] pairs. Collisions share a chain.

diy-chaining.tsTypeScript
type Entry = { key: string; value: number };

class TinyHashMap {
  private buckets: Entry[][];

  constructor(size = 4) {
    this.buckets = Array.from({ length: size }, () => []);
  }

  private index(key: string): number {
    let h = 0;
    for (let i = 0; i < key.length; i++) h = (h * 31 + key.charCodeAt(i)) | 0;
    return Math.abs(h) % this.buckets.length;
  }

  set(key: string, value: number): void {
    const chain = this.buckets[this.index(key)]!;
    const hit = chain.find((e) => e.key === key);
    if (hit) hit.value = value;
    else chain.push({ key, value });
  }

  get(key: string): number | undefined {
    return this.buckets[this.index(key)]!.find((e) => e.key === key)?.value;
  }
}

const m = new TinyHashMap();
m.set('ada', 37);
m.set('lin', 29);
m.get('ada'); // 37