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.
// ——— 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.
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.
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; // 0Map.has()
Returns true when the key is present — even if the value is undefined.
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'); // undefinedMap.delete()
Removes one key. Returns true if something was removed.
const ages = new Map([
['ada', 37],
['lin', 29],
]);
ages.delete('lin'); // true
ages.delete('zoe'); // false
ages.has('lin'); // falseObject 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: 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.
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 itWhen an object is fine
JSON-shaped data, fixed string keys, or configs are still a good fit for objects.
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.
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.
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); // falseSet.size
How many unique values are stored.
const seen = new Set([1, 2, 2, 3]);
seen.size; // 3Frequency 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.
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.
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.
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.
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.
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.
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.
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.
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..3Chaining put / get
Each bucket holds a list of [key, value] pairs. Collisions share a chain.
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