🎯 JavaScript Hashing β€” Top 25 Interview Questions


Q1 β€” Two Sum

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Complement lookup β€” store seen values in a Map.

Step 2 β€” Brute force (what NOT to do): Nested loops O(nΒ²).

Step 3 β€” Optimal insight: As you iterate, check if target - num is already in Map.

Step 4 β€” Algorithm:

1. For each num: if Map has (target - num) β†’ return [map.get(comp), i]

2. Else map.set(num, i)

Step 5 β€” Edge cases: Same index cannot be used twice (check before inserting).

function twoSum(nums, target) {
    const map = new Map();
    for (let i = 0; i < nums.length; i++) {
        const comp = target - nums[i];
        if (map.has(comp)) return [map.get(comp), i];
        map.set(nums[i], i);
    }
    return [];
}

Time: O(n) | Space: O(n)


Q2 β€” Group Anagrams

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Anagrams share the same sorted character key.

Step 2 β€” Brute force (what NOT to do): Compare each pair β€” O(nΒ² Β· k log k).

Step 3 β€” Optimal insight: Sort each string as key; group by key in a Map.

Step 4 β€” Algorithm:

1. For each string: sort chars β†’ key

2. Group strings by key

Step 5 β€” Edge cases: Single string; empty string.

function groupAnagrams(strs) {
    const map = new Map();
    for (const s of strs) {
        const key = s.split('').sort().join('');
        if (!map.has(key)) map.set(key, []);
        map.get(key).push(s);
    }
    return [...map.values()];
}

Time: O(n Β· k log k) | Space: O(n Β· k)


Q3 β€” Top K Frequent Elements

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Frequency map + bucket sort by frequency.

Step 2 β€” Brute force (what NOT to do): Sort all by frequency β€” O(n log n).

Step 3 β€” Optimal insight: Bucket sort with index = frequency; collect from high to low.

Step 4 β€” Algorithm:

1. Build frequency Map

2. Bucket by frequency (size n+1)

3. Scan from high bucket; collect k elements

Step 5 β€” Edge cases: All unique; k = n.

function topKFrequent(nums, k) {
    const freq = new Map();
    for (const n of nums) freq.set(n, (freq.get(n) ?? 0) + 1);
    const buckets = Array.from({ length: nums.length + 1 }, () => []);
    for (const [n, f] of freq) buckets[f].push(n);
    const result = [];
    for (let i = buckets.length - 1; i >= 0 && result.length < k; i--)
        for (const n of buckets[i]) { result.push(n); if (result.length === k) break; }
    return result;
}

Time: O(n) | Space: O(n)


Q4 β€” Valid Anagram

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Frequency balance β€” increment for s, decrement for t.

Step 2 β€” Brute force (what NOT to do): Sort both strings β€” O(n log n).

Step 3 β€” Optimal insight: Single Map; all values must reach zero.

Step 4 β€” Algorithm:

1. If lengths differ β†’ false

2. freq.set(c, +1) for s; freq.set(c, -1) for t

3. Any non-zero value β†’ false

Step 5 β€” Edge cases: Unicode characters.

function isAnagram(s, t) {
    if (s.length !== t.length) return false;
    const freq = new Map();
    for (const c of s) freq.set(c, (freq.get(c) ?? 0) + 1);
    for (const c of t) {
        if (!freq.has(c) || freq.get(c) === 0) return false;
        freq.set(c, freq.get(c) - 1);
    }
    return true;
}

Time: O(n) | Space: O(1) β€” at most 26 chars


Q5 β€” Contains Duplicate

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Set membership check as you iterate.

Step 2 β€” Brute force (what NOT to do): Nested loops O(nΒ²).

Step 3 β€” Optimal insight: If element already in Set β†’ duplicate found.

Step 4 β€” Algorithm:

1. For each num: if in Set β†’ return true; add to Set

2. Return false

Step 5 β€” Edge cases: Empty array β†’ false; single element β†’ false.

function containsDuplicate(nums) {
    const seen = new Set();
    for (const n of nums) {
        if (seen.has(n)) return true;
        seen.add(n);
    }
    return false;
}

Time: O(n) | Space: O(n)


Q6 β€” LRU Cache

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Map + implicit doubly linked list using Map insertion order.

Step 2 β€” Brute force (what NOT to do): Array with linear search β€” O(n) per operation.

Step 3 β€” Optimal insight: JavaScript Map preserves insertion order; delete + re-insert = move to end (most recent).

Step 4 β€” Algorithm:

1. get(key): if exists β†’ delete + re-insert (refresh), return val; else -1

2. put(key, val): if exists β†’ delete; set new; if over cap β†’ delete Map.keys().next()

Step 5 β€” Edge cases: Capacity 1; repeated puts.

class LRUCache {
    constructor(capacity) {
        this.cap = capacity;
        this.map = new Map(); // LRU at front, MRU at back
    }
    get(key) {
        if (!this.map.has(key)) return -1;
        const val = this.map.get(key);
        this.map.delete(key);
        this.map.set(key, val); // move to end (MRU)
        return val;
    }
    put(key, value) {
        if (this.map.has(key)) this.map.delete(key);
        this.map.set(key, value);
        if (this.map.size > this.cap) this.map.delete(this.map.keys().next().value);
    }
}

Time: O(1) get/put | Space: O(capacity)


Q7 β€” Subarray Sum Equals K

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Prefix sum; count(sum - k) tells us subarrays ending here that sum to k.

Step 2 β€” Brute force (what NOT to do): All subarrays O(nΒ²) or O(nΒ³).

Step 3 β€” Optimal insight: Map stores prefix sum β†’ count; look up sum-k each step.

Step 4 β€” Algorithm:

1. map.set(0, 1); sum=0, result=0

2. sum += n; result += map.get(sum-k) ?? 0; map.set(sum, ...)

Step 5 β€” Edge cases: Negative numbers; k=0.

function subarraySum(nums, k) {
    const map = new Map([[0, 1]]);
    let sum = 0, count = 0;
    for (const n of nums) {
        sum += n;
        count += map.get(sum - k) ?? 0;
        map.set(sum, (map.get(sum) ?? 0) + 1);
    }
    return count;
}

Time: O(n) | Space: O(n)


Q8 β€” Longest Consecutive Sequence

🧠 How to Approach This

Step 1 β€” Recognize the pattern: For each sequence start, count streak using Set.

Step 2 β€” Brute force (what NOT to do): Sort then count β€” O(n log n).

Step 3 β€” Optimal insight: Only start counting when no n-1 in Set.

Step 4 β€” Algorithm:

1. Build Set from nums

2. For each n: if n-1 not in Set β†’ count streak n, n+1, n+2...

3. Track max streak

Step 5 β€” Edge cases: Empty array; duplicates.

function longestConsecutive(nums) {
    const set = new Set(nums); let best = 0;
    for (const n of set) {
        if (!set.has(n - 1)) {
            let curr = n, streak = 1;
            while (set.has(curr + 1)) { curr++; streak++; }
            best = Math.max(best, streak);
        }
    }
    return best;
}

Time: O(n) | Space: O(n)


Q9 β€” 4Sum II

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Split into 2 pairs; count complementary sums.

Step 2 β€” Brute force (what NOT to do): 4 nested loops O(n⁴).

Step 3 β€” Optimal insight: Map all sums of first two arrays; scan sums of last two for -(sum).

Step 4 β€” Algorithm:

1. Map a+b β†’ count for all pairs in nums1, nums2

2. For all c,d: result += map.get(-(c+d)) ?? 0

Step 5 β€” Edge cases: All zeros.

function fourSumCount(nums1, nums2, nums3, nums4) {
    const map = new Map(); let count = 0;
    for (const a of nums1) for (const b of nums2) map.set(a+b, (map.get(a+b)??0)+1);
    for (const c of nums3) for (const d of nums4) count += map.get(-(c+d)) ?? 0;
    return count;
}

Time: O(nΒ²) | Space: O(nΒ²)


Q10 β€” Intersection of Two Arrays

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Set for first array; check second array elements.

Step 2 β€” Brute force (what NOT to do): Nested loops β€” O(nΒ·m).

Step 3 β€” Optimal insight: Set from nums1; iterate nums2, collect matches in result Set.

Step 4 β€” Algorithm:

1. set1 = new Set(nums1)

2. For each in nums2: if in set1 β†’ add to result Set

Step 5 β€” Edge cases: No intersection; duplicates.

function intersection(nums1, nums2) {
    const set1 = new Set(nums1), result = new Set();
    for (const n of nums2) if (set1.has(n)) result.add(n);
    return [...result];
}

Time: O(n + m) | Space: O(n)


Q11 β€” Happy Number

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Cycle detection using Set.

Step 2 β€” Brute force (what NOT to do): Run indefinitely without detection.

Step 3 β€” Optimal insight: Track seen numbers; if 1 β†’ happy; if repeat β†’ not happy.

Step 4 β€” Algorithm:

1. While n != 1 and n not in seen: compute digit-square sum; add to seen

2. Return n === 1

Step 5 β€” Edge cases: n=1 immediately true; n=7 is happy.

function isHappy(n) {
    const seen = new Set();
    while (n !== 1) {
        if (seen.has(n)) return false;
        seen.add(n);
        let sum = 0;
        while (n > 0) { const d = n % 10; sum += d * d; n = Math.floor(n / 10); }
        n = sum;
    }
    return true;
}

Time: O(log n) | Space: O(log n)


Q12 β€” Isomorphic Strings

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Bijection β€” dual Maps ensuring one-to-one mapping.

Step 2 β€” Brute force (what NOT to do): Compare all possible mappings.

Step 3 — Optimal insight: Two Maps (s→t and t→s); conflict in either = not isomorphic.

Step 4 β€” Algorithm:

1. For each pair (cs, ct): check both Maps for conflicts

Step 5 β€” Edge cases: Repeated same chars; single char.

function isIsomorphic(s, t) {
    const st = new Map(), ts = new Map();
    for (let i = 0; i < s.length; i++) {
        const cs = s[i], ct = t[i];
        if (st.has(cs) && st.get(cs) !== ct) return false;
        if (ts.has(ct) && ts.get(ct) !== cs) return false;
        st.set(cs, ct); ts.set(ct, cs);
    }
    return true;
}

Time: O(n) | Space: O(1)


Q13 β€” Word Pattern

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Bijection at word level β€” same as isomorphic strings.

Step 2 β€” Brute force (what NOT to do): Generate all pattern mappings.

Step 3 — Optimal insight: Dual Maps: char→word and word→char.

Step 4 β€” Algorithm:

1. Split s; if count differs β†’ false

2. Dual Map check per position

Step 5 β€” Edge cases: Pattern length β‰  word count.

function wordPattern(pattern, s) {
    const words = s.split(' ');
    if (pattern.length !== words.length) return false;
    const cw = new Map(), wc = new Map();
    for (let i = 0; i < pattern.length; i++) {
        const c = pattern[i], w = words[i];
        if (cw.has(c) && cw.get(c) !== w) return false;
        if (wc.has(w) && wc.get(w) !== c) return false;
        cw.set(c, w); wc.set(w, c);
    }
    return true;
}

Time: O(n) | Space: O(n)


Q14 β€” First Missing Positive

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Cyclic sort β€” use array indices as hash.

Step 2 β€” Brute force (what NOT to do): Sort + scan β€” O(n log n).

Step 3 β€” Optimal insight: Place each num in [1..n] at index num-1; first mismatch = answer.

Step 4 β€” Algorithm:

1. Cyclic sort: while nums[i] in range and not at correct pos β†’ swap

2. First index where nums[i] !== i+1 β†’ return i+1

Step 5 β€” Edge cases: [1,2,3] β†’ 4; all negatives β†’ 1.

function firstMissingPositive(nums) {
    const n = nums.length;
    for (let i = 0; i < n; i++) {
        while (nums[i] >= 1 && nums[i] <= n && nums[nums[i]-1] !== nums[i])
            [nums[i], nums[nums[i]-1]] = [nums[nums[i]-1], nums[i]];
    }
    for (let i = 0; i < n; i++) if (nums[i] !== i + 1) return i + 1;
    return n + 1;
}

Time: O(n) | Space: O(1)


Q15 β€” Minimum Window Substring

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Sliding window with frequency Maps; expand right, shrink left.

Step 2 β€” Brute force (what NOT to do): Try all substrings β€” O(nΒ² Β· m).

Step 3 β€” Optimal insight: formed counter tracks satisfied character requirements.

Step 4 β€” Algorithm:

1. Build need Map from t; expand r; when all formed β†’ shrink l; track min window

Step 5 β€” Edge cases: t longer than s β†’ ""; t not in s β†’ "".

function minWindow(s, t) {
    if (t.length > s.length) return '';
    const need = new Map(), have = new Map();
    for (const c of t) need.set(c, (need.get(c) ?? 0) + 1);
    let formed = 0, required = need.size;
    let l = 0, minLen = Infinity, res = '';
    for (let r = 0; r < s.length; r++) {
        const c = s[r]; have.set(c, (have.get(c) ?? 0) + 1);
        if (need.has(c) && have.get(c) === need.get(c)) formed++;
        while (formed === required) {
            if (r - l + 1 < minLen) { minLen = r - l + 1; res = s.slice(l, r + 1); }
            const lc = s[l]; have.set(lc, have.get(lc) - 1);
            if (need.has(lc) && have.get(lc) < need.get(lc)) formed--;
            l++;
        }
    }
    return res;
}

Time: O(n + m) | Space: O(n + m)


Q16 β€” Find All Anagrams in a String

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Fixed-size sliding window with frequency tracking.

Step 2 β€” Brute force (what NOT to do): Sort every window β€” O(n Β· k log k).

Step 3 β€” Optimal insight: Slide window; maintain matched counter for O(1) comparison.

Step 4 β€” Algorithm:

1. Init freq maps for first window; compute matched

2. Slide: update right + left; check matched

Step 5 β€” Edge cases: p longer than s β†’ [].

function findAnagrams(s, p) {
    if (s.length < p.length) return [];
    const need = {}, have = {}, result = [];
    for (const c of p) need[c] = (need[c] ?? 0) + 1;
    for (let i = 0; i < p.length; i++) have[s[i]] = (have[s[i]] ?? 0) + 1;
    let matched = 0;
    for (const c in need) if ((have[c] ?? 0) === need[c]) matched++;
    if (matched === Object.keys(need).length) result.push(0);
    for (let i = p.length; i < s.length; i++) {
        const r = s[i]; have[r] = (have[r] ?? 0) + 1;
        if (need[r] !== undefined) { if (have[r] === need[r]) matched++; else if (have[r] === need[r]+1) matched--; }
        const l = s[i - p.length]; have[l]--;
        if (need[l] !== undefined) { if (have[l] === need[l]) matched++; else if (have[l] === need[l]-1) matched--; }
        if (matched === Object.keys(need).length) result.push(i - p.length + 1);
    }
    return result;
}

Time: O(n) | Space: O(1)


Q17 β€” Ransom Note

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Frequency Map β€” magazine must have enough of each char.

Step 2 β€” Brute force (what NOT to do): For each char in note, scan magazine β€” O(nΒ·m).

Step 3 β€” Optimal insight: Count magazine chars; verify note requirements are met.

Step 4 β€” Algorithm:

1. Build freq Map from magazine

2. For each char in ransomNote: if freq < 1 β†’ false; decrement

Step 5 β€” Edge cases: Empty ransomNote β†’ true.

function canConstruct(ransomNote, magazine) {
    const freq = new Map();
    for (const c of magazine) freq.set(c, (freq.get(c) ?? 0) + 1);
    for (const c of ransomNote) {
        if ((freq.get(c) ?? 0) < 1) return false;
        freq.set(c, freq.get(c) - 1);
    }
    return true;
}

Time: O(n + m) | Space: O(1)


Q18 β€” Jewels and Stones

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Set for jewel types; count stones that are jewels.

Step 2 β€” Brute force (what NOT to do): For each stone scan jewels string β€” O(nΒ·m).

Step 3 β€” Optimal insight: Set from jewels for O(1) lookup.

Step 4 β€” Algorithm:

1. jewelSet = new Set(jewels)

2. Count stones in jewelSet

Step 5 β€” Edge cases: Empty stones β†’ 0.

function numJewelsInStones(jewels, stones) {
    const set = new Set(jewels);
    return [...stones].filter(s => set.has(s)).length;
}

Time: O(j + s) | Space: O(j)


Q19 β€” Longest Palindrome from Characters

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Pairs contribute 2; at most one odd-count char as center.

Step 2 β€” Brute force (what NOT to do): Try all permutations.

Step 3 β€” Optimal insight: Sum ⌊freq/2βŒ‹*2 + 1 if any odd freq exists.

Step 4 β€” Algorithm:

1. Count freq of each char

2. sum += Math.floor(f/2)*2; track hasOdd

3. Return sum + (hasOdd ? 1 : 0)

Step 5 β€” Edge cases: Single char β†’ 1.

function longestPalindrome(s) {
    const freq = new Map();
    for (const c of s) freq.set(c, (freq.get(c) ?? 0) + 1);
    let len = 0, hasOdd = false;
    for (const f of freq.values()) {
        len += Math.floor(f / 2) * 2;
        if (f % 2 !== 0) hasOdd = true;
    }
    return len + (hasOdd ? 1 : 0);
}

Time: O(n) | Space: O(1)


Q20 β€” Pairs with Given Difference

🧠 How to Approach This

Step 1 β€” Recognize the pattern: For each n, check if n+k exists in Map.

Step 2 β€” Brute force (what NOT to do): Nested loops β€” O(nΒ²).

Step 3 β€” Optimal insight: Frequency Map; for each n: if n+k exists β†’ count pair.

Step 4 β€” Algorithm:

1. Build freq Map

2. For each key n: if k=0 and freq>1 β†’ count; if k>0 and map.has(n+k) β†’ count

Step 5 β€” Edge cases: k=0; negative k.

function findPairs(nums, k) {
    if (k < 0) return 0;
    const freq = new Map(); let count = 0;
    for (const n of nums) freq.set(n, (freq.get(n) ?? 0) + 1);
    for (const [n, f] of freq) {
        if (k === 0) { if (f > 1) count++; }
        else if (freq.has(n + k)) count++;
    }
    return count;
}

Time: O(n) | Space: O(n)


Q21 β€” Minimum Index Sum of Two Lists

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Map first list index β†’ element; scan second for matches.

Step 2 β€” Brute force (what NOT to do): Nested loops β€” O(nΒ·m).

Step 3 β€” Optimal insight: Map list1 element β†’ index; iterate list2; track min sum.

Step 4 β€” Algorithm:

1. Map list1: element β†’ index

2. For each (j, item) in list2: if in map, compute i+j; update min; collect ties

Step 5 β€” Edge cases: Multiple pairs with same min sum.

function findRestaurant(list1, list2) {
    const map = new Map(list1.map((v, i) => [v, i]));
    let minSum = Infinity, result = [];
    for (let j = 0; j < list2.length; j++) {
        if (map.has(list2[j])) {
            const sum = map.get(list2[j]) + j;
            if (sum < minSum) { minSum = sum; result = [list2[j]]; }
            else if (sum === minSum) result.push(list2[j]);
        }
    }
    return result;
}

Time: O(n + m) | Space: O(n)


Q22 β€” Custom Sort String

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Frequency Map of s; output in order string first, then remaining.

Step 2 β€” Brute force (what NOT to do): Custom comparator sort β€” O(n log n).

Step 3 β€” Optimal insight: Count freq; output ordered chars first, then leftover.

Step 4 β€” Algorithm:

1. Build freq Map of s

2. For each char in order: append freq[c] times; delete from map

3. Append remaining chars

Step 5 β€” Edge cases: Chars in s not in order.

function customSortString(order, s) {
    const freq = new Map();
    for (const c of s) freq.set(c, (freq.get(c) ?? 0) + 1);
    let result = '';
    for (const c of order) {
        if (freq.has(c)) { result += c.repeat(freq.get(c)); freq.delete(c); }
    }
    for (const [c, f] of freq) result += c.repeat(f);
    return result;
}

Time: O(n) | Space: O(n)


Q23 β€” Count Pairs Divisible by K

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Remainder Map β€” (a+b)%k=0 when complements match.

Step 2 β€” Brute force (what NOT to do): Nested loops β€” O(nΒ²).

Step 3 β€” Optimal insight: For each n, need = (k - n%k) % k; check remMap.

Step 4 β€” Algorithm:

1. For each n: need = (k - n%k) % k; count += remMap.get(need) ?? 0

2. Then add n%k to remMap

Step 5 β€” Edge cases: n%k=0 β†’ need=0; k=1 β†’ all pairs.

function countPairs(nums, k) {
    const remMap = new Map(); let count = 0;
    for (const n of nums) {
        const rem  = n % k;
        const need = (k - rem) % k;
        count += remMap.get(need) ?? 0;
        remMap.set(rem, (remMap.get(rem) ?? 0) + 1);
    }
    return count;
}

Time: O(n) | Space: O(k)


Q24 β€” Number of Boomerangs

🧠 How to Approach This

Step 1 β€” Recognize the pattern: For each anchor, count others at same distance; ordered pairs = f*(f-1).

Step 2 β€” Brute force (what NOT to do): Triple nested loop β€” O(nΒ³).

Step 3 β€” Optimal insight: For each point as anchor: build distMap; count += f*(f-1).

Step 4 β€” Algorithm:

1. For each point as anchor: build distance β†’ count Map

2. For each freq f: count += f*(f-1) (ordered pairs)

Step 5 β€” Edge cases: n < 3 β†’ 0.

function numberOfBoomerangs(points) {
    let count = 0;
    for (const p of points) {
        const distMap = new Map();
        for (const q of points) {
            const d = (p[0]-q[0])**2 + (p[1]-q[1])**2;
            distMap.set(d, (distMap.get(d) ?? 0) + 1);
        }
        for (const f of distMap.values()) count += f * (f - 1);
    }
    return count;
}

Time: O(nΒ²) | Space: O(n)


Q25 β€” Longest Subarray with Sum Divisible by K

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Prefix sum mod k; same remainder at two indices = divisible subarray.

Step 2 β€” Brute force (what NOT to do): All subarrays β€” O(nΒ²).

Step 3 β€” Optimal insight: Map first occurrence of each remainder; max length = i - firstOccurrence[rem].

Step 4 β€” Algorithm:

1. remMap.set(0, -1); running sum %= k (normalize negative)

2. If rem seen β†’ max(best, i - remMap.get(rem)); else remMap.set(rem, i)

Step 5 β€” Edge cases: Negative numbers; no valid subarray β†’ 0.

function subarraysDivByK(nums, k) {
    const remMap = new Map([[0, -1]]);
    let sum = 0, best = 0;
    for (let i = 0; i < nums.length; i++) {
        sum += nums[i];
        const rem = ((sum % k) + k) % k;
        if (remMap.has(rem)) best = Math.max(best, i - remMap.get(rem));
        else remMap.set(rem, i);
    }
    return best;
}

Time: O(n) | Space: O(k)