Published on

16 JavaScript Patterns for Technical Interviews

Authors
  • avatar
    Name
    Ronald Luo, MSc
    Twitter

16 JavaScript Patterns for Technical Interviews

16 patterns to have on autopilot before your next technical interview.


#1: Check If Key Exists in Object

Scenario: Building a frequency counter, checking if you've visited a node in a graph. You need to know if a key is already in your object/map before accessing it.

Naive:

if (obj[key] !== undefined) {
    // breaks if the value is legitimately undefined, 0, or null
}

Better:

// plain object
if ('key' in obj)            // checks prototype chain too
if (obj.hasOwnProperty(key)) // own properties only

// Map (preferred in interviews)
if (map.has(key))

Rule: Default to Map with .has(): unambiguous, no prototype weirdness, keys can be any type.

Practice: Two Sum, Contains Duplicate, Longest Consecutive Sequence


#2: Infinity / -Infinity

Scenario: Tracking a running min or max (min cost path, max profit, initializing Dijkstra's distance table). You need a starting value that any real number will beat.

Naive:

let min = 9999999;
let max = -9999999;
// fragile: what if input exceeds your magic number?

Better:

let min = Infinity;
let max = -Infinity;

Gotcha: Math.min() with no arguments returns Infinity. Math.max() with no arguments returns -Infinity. That's the identity element for each operation: not a bug.

Practice: Best Time to Buy and Sell Stock, Maximum Subarray, Network Delay Time


#3: Integer Division (Floor)

Scenario: Binary search midpoint, dividing elements into buckets, any index math. The result must be a whole number, not a float.

Naive:

let mid = (left + right) / 2;
// mid could be 3.5, arr[3.5] is undefined

Better:

let mid = Math.floor((left + right) / 2);

// bitwise shortcut (positive numbers only)
let mid = (left + right) >> 1;

Note: Python has // for floor division. JS has no equivalent operator: you always need Math.floor or the bitwise trick.

Practice: Binary Search, Search in Rotated Sorted Array, Median of Two Sorted Arrays


#4: Swap Two Variables

Scenario: Reversing an array in place, partitioning in quicksort, two-pointer problems like moving zeroes or Dutch national flag.

Naive:

let temp = a;
a = b;
b = temp;

Better:

[a, b] = [b, a];

// works for array elements too
[arr[i], arr[j]] = [arr[j], arr[i]];

Practice: Reverse String, Sort Colors, Kth Largest Element in an Array


#5: Default Dict / Counter

Scenario: Counting character frequencies for anagram check, counting word occurrences, building an adjacency list. Key might not exist yet.

Naive:

let counts = {};
for (let ch of str) {
    if (counts[ch] === undefined) {
        counts[ch] = 0;
    }
    counts[ch]++;
}

Better:

let map = new Map();
for (let ch of str) {
    map.set(ch, (map.get(ch) || 0) + 1);
}

Gotcha: || 0 fails if the legitimate value could be 0. In counting contexts this never matters, but for other use cases use ?? 0 (nullish coalescing): only triggers on undefined or null.

Practice: Valid Anagram, Group Anagrams, Course Schedule


#6: Initialize 2D Array

Scenario: DP tables (longest common subsequence, edit distance, unique paths), BFS visited matrix, any 2D grid initialized to a default value.

Naive (THE TRAP):

let grid = Array(3).fill(Array(3).fill(0));

grid[0][1] = 5;
// [[0,5,0], [0,5,0], [0,5,0]]: every row changed!
// every row is the same reference

Better:

let dp = Array.from({length: m}, () => Array(n).fill(0));

The arrow function runs fresh for each row: each one is a unique array. No shared references.

This is the #1 silent bug killer in DP problems. If you take one thing from this entire list, it's this.

Practice: Unique Paths, Edit Distance, Longest Common Subsequence


#7: Check If Array Is Empty

Scenario: Popping from a stack, processing a BFS queue, guarding against operating on an empty array.

Naive:

if (arr.length === 0) { /* empty */ }
if (arr.length > 0) { /* not empty */ }

Better:

if (!arr.length) { /* empty */ }
if (arr.length) { /* not empty */ }

// BFS loop
while (queue.length) {
    let node = queue.shift();
}

arr.length is 0 when empty, which is falsy. That's it.

Practice: Valid Parentheses, Binary Tree Level Order Traversal, Evaluate Reverse Polish Notation


#8: Get Last Element

Scenario: Using an array as a stack, checking the top before push/pop, comparing against the most recent result (like in merge intervals).

Naive:

let last = arr[arr.length - 1];

Better:

let last = arr.at(-1);

Negative indexing, just like Python. -1 is last, -2 is second to last.

Gotcha: .at() is read-only for assignment. You can't do arr.at(-1) = 5. For writing to a primitive position, you still need arr[arr.length - 1] = 5. But you CAN mutate through it: arr.at(-1)[1] = value works because it returns the reference.

Practice: Merge Intervals, Min Stack, Daily Temperatures


#9: String to Char Array

Scenario: Reversing a string, rearranging characters, any in-place transformation. JS strings are immutable, so you can't do str[i] = 'x'.

Naive:

let reversed = '';
for (let i = str.length - 1; i >= 0; i--) {
    reversed += str[i];
}
// string concatenation in a loop is O(n²) worst case

Better:

// string to array
let chars = str.split('');

// manipulate
chars[0] = 'X';
[chars[1], chars[2]] = [chars[2], chars[1]];

// array back to string
let result = chars.join('');

// classic reverse one-liner
let reversed = str.split('').reverse().join('');

Gotcha: split('') breaks on emojis and multi-byte unicode. Use [...str] instead if unicode could matter. Mentioning this unprompted scores points.

Practice: Reverse String, Reorganize String, Reverse Words in a String


#10: Character Code Math

Scenario: Mapping characters to array indices (frequency bucket for lowercase letters, anagram check with a fixed-size array, group anagrams with an O(M) key instead of O(M log M) sort).

Naive:

// using a Map when the problem says "lowercase English letters only"
let map = new Map();
for (let ch of str) {
    map.set(ch, (map.get(ch) || 0) + 1);
}

Better:

const START = 'a'.charCodeAt(0);          // don't memorize 97
const SIZE = 'z'.charCodeAt(0) - START + 1; // don't memorize 26

let counts = Array(SIZE).fill(0);
for (let ch of str) {
    counts[ch.charCodeAt(0) - START]++;
}

// use as a hash key for group anagrams
let key = counts.join(',');

How it works: ASCII assigns every character a sequential number. a=97 through z=122. Subtracting START shifts them to 0-25. Each letter maps to an array index. Same letters always produce the same frequency fingerprint.

Adapting to different ranges:

  • Uppercase (A-Z): START = 'A'.charCodeAt(0), SIZE = 'Z'.charCodeAt(0) - START + 1
  • Digits (0-9): START = '0'.charCodeAt(0), SIZE = '9'.charCodeAt(0) - START + 1
  • Mixed ranges: Just use a Map: ASCII ranges aren't contiguous across groups.

Key insight: The pattern is always START = first char's code, SIZE = last - first + 1. Derive everything from the characters. Zero memorization.

Practice: Group Anagrams, Valid Anagram, First Unique Character in a String


#11: Set Operations

Scenario: Detecting duplicates, checking if a complement exists, tracking visited nodes in BFS/DFS, finding intersection of two arrays.

Naive:

// O(n) lookup each time: total O(n²)
let seen = [];
for (let num of arr) {
    if (seen.includes(num)) return true;
    seen.push(num);
}

Better:

// O(1) lookup: total O(n)
let seen = new Set();
for (let num of arr) {
    if (seen.has(num)) return true;
    seen.add(num);
}

// one-liner dedupe
let unique = [...new Set(arr)];

// intersection
let setA = new Set(arrA);
let intersection = arrB.filter(x => setA.has(x));

Mental rule:

  • "Have I seen this before?" → Set
  • "Have I seen this and what was the value?" → Map
  • Plain object {} → avoid in interviews. Keys coerce to strings, so obj[1] and obj['1'] are the same key. Map doesn't have that problem.

Practice: Contains Duplicate, Two Sum, Clone Graph


#12: Min/Max of Array

Scenario: Finding the smallest/largest value (min cost, max profit, range of values, validating bounds).

Naive:

let min = arr[0];
for (let i = 1; i < arr.length; i++) {
    if (arr[i] < min) min = arr[i];
}

Better:

let min = Math.min(...arr);
let max = Math.max(...arr);

Gotcha: Spread puts every element on the call stack. 100k+ elements will stack overflow. For huge arrays:

let min = arr.reduce((a, b) => Math.min(a, b), Infinity);

In an interview, mention the limitation if the interviewer says "millions of elements." Otherwise spread is fine.

Practice: Maximum Subarray, Maximum Product Subarray, Maximum Product of Three Numbers


#13: Clone an Array

Scenario: Sorting without modifying the original, snapshotting a path in backtracking, building the next DP row from the current one.

Naive:

let copy = [];
for (let i = 0; i < arr.length; i++) {
    copy.push(arr[i]);
}

Better:

let copy = [...arr];
// or
let copy = arr.slice();

The trap: shallow clone only. For 2D arrays:

let grid = [[1,2],[3,4]];
let copy = [...grid];
copy[0][0] = 999;
console.log(grid[0][0]); // 999: original mutated!

// deep clone for 2D
let copy = grid.map(row => [...row]);

Backtracking killer:

// WRONG: pushes the reference, path keeps mutating
result.push(path);

// RIGHT: snapshots the current state
result.push([...path]);

If your permutations answer has every entry identical, this is why.

Practice: Permutations, Subsets, Sort an Array


#14: Priority Queue / MinHeap

Scenario: Merge K sorted lists, Kth largest element, Dijkstra's shortest path, task scheduling. Repeatedly need the smallest/largest from a dynamic collection.

Naive:

arr.push(newVal);
arr.sort((a, b) => a - b); // O(n log n) every insertion
let min = arr.shift();

Better (memorize this):

class MinHeap {
    constructor() { this.data = []; }
    size() { return this.data.length; }
    push(val) {
        this.data.push(val);
        this._bubbleUp(this.data.length - 1);
    }
    pop() {
        let top = this.data[0];
        let last = this.data.pop();
        if (this.data.length) {
            this.data[0] = last;
            this._sinkDown(0);
        }
        return top;
    }
    _bubbleUp(i) {
        while (i > 0) {
            let parent = (i - 1) >> 1;
            if (this.data[i] >= this.data[parent]) break;
            [this.data[i], this.data[parent]] = [this.data[parent], this.data[i]];
            i = parent;
        }
    }
    _sinkDown(i) {
        while (true) {
            let smallest = i;
            let left = 2 * i + 1;
            let right = 2 * i + 2;
            if (left < this.data.length && this.data[left] < this.data[smallest]) smallest = left;
            if (right < this.data.length && this.data[right] < this.data[smallest]) smallest = right;
            if (smallest === i) break;
            [this.data[i], this.data[smallest]] = [this.data[smallest], this.data[i]];
            i = smallest;
        }
    }
}

This is the one item you must memorize and drill. Python has heapq built in. JS has nothing. A heap problem in JS means 5-7 minutes of infrastructure before you start solving. Practice until you can write it in under 3 minutes.

Practice: Merge k Sorted Lists, Kth Largest Element in an Array, Network Delay Time


#15: Iterate a Map in Insertion Order

Scenario: Returning grouped anagrams, finding the first non-repeating character, any problem where the order you inserted matters.

Naive (plain object):

let obj = {};
obj['b'] = 2;
obj['a'] = 1;

for (let key in obj) {
    console.log(key, obj[key]);
}
// integer-like keys get sorted numerically first
// 'for...in' traverses the prototype chain

Better:

let map = new Map();
map.set('b', 2);
map.set('a', 1);

for (let [key, value] of map) {
    console.log(key, value);
}
// always b, a: insertion order guaranteed

Rule: Map always iterates in insertion order. No surprises, no prototype chain, no numeric key reordering. Another reason to default to Map over {}.

Practice: Group Anagrams, First Unique Character in a String, LRU Cache


#16: True Modulo for Negative Numbers

Scenario: Circular array problems like rotating an array, wrapping around a circular buffer, Caesar cipher with left shift, navigating circular indices. Any time subtraction could make an index go negative.

Naive:

let index = (i - k) % n;

// i=1, k=3, n=5
// (1 - 3) % 5 = -2 % 5 = -2  ← wrong, wanted 3
// arr[-2] is undefined

JS % is a remainder, not a true modulo. It keeps the sign of the left operand. Python's % returns the correct positive result natively.

Better:

let index = ((i - k) % n + n) % n;

// ((-2) % 5 + 5) % 5
// (-2 + 5) % 5
// 3 % 5
// 3  ← correct

The + n shifts any negative remainder into positive territory. The outer % n handles cases where the result was already positive so + n doesn't overshoot.

Every circular array/rotation problem is a landmine without this.

Practice: Rotate Array, Maximum Sum Circular Subarray, Next Greater Element II