Source notes
- MDN Array.prototype.sortDefault string conversion, comparator behavior, in-place mutation, and the absence of a required universal sort complexity.
- MDN Number.isSafeIntegerThe safe-integer boundary for exact Number arithmetic.
- MDN MapMap key equality, insertion order, and key/value operations.
- TypeScript handbook: Everyday TypesTypeScript number, unions, narrowing, arrays, and the compile-time nature of annotations.
Input and output conventions#
- Function interview
- Implement the requested function against the interviewer-provided signature. Return the exact value, avoid reading stdin or printing unless asked, and state whether the function mutates an input array, object, Map, or Set.
- Online assessment
- Read the platform contract before writing an adapter. Parse tokens or lines explicitly, convert numeric text with Number only after validation, call a pure solve function, and print exactly the requested format without labels or debug output.
Runtime and input/output contract#
These examples target Node.js 22.13.0 and TypeScript 5.9. Confirm the employer runtime and module format instead of assuming the newest local syntax or a browser environment is available.
- In a function interview, implement the supplied signature and return the requested result; do not add stdin parsing or logging unless asked.
- In an online assessment, keep parsing and formatting in a thin adapter around a pure solve function so tests can exercise the algorithm directly.
- Number(text) rejects trailing junk that parseInt may partially accept, but it still accepts values such as an empty string as zero; validate the input grammar required by the prompt.
- TypeScript annotations are erased. Values from stdin, JSON, network calls, and unknown harnesses still need runtime checks when the contract does not guarantee their shape.
Arrays, stacks, queues, and mutation#
Array is the main indexed sequence and a natural stack. End push/pop operations are the intended stack operations; a BFS queue should use a monotonically increasing head index instead of repeatedly removing index zero.
- sort, reverse, splice, push, pop, shift, and unshift mutate the array. toSorted and slice return new arrays, but confirm runtime support before using newer APIs.
- Array assignment copies a reference. Use [...values] or values.slice() for a shallow outer copy, and state whether nested objects remain shared.
- A head-index queue retains processed slots until the array is released. For one bounded traversal this is usually clear and efficient; for a long-lived queue, use an explicit deque implementation with compaction.
- Strings are immutable, so converting to an array is useful only when the copying cost and character model are acceptable.
Map, Set, and equality#
Use Map for keyed values and Set for membership. Their keys use SameValueZero semantics: NaN matches NaN, -0 and 0 are treated alike, and objects match only by reference identity.
- Use map.has(key) when undefined can be a stored value; map.get(key) === undefined cannot distinguish missing from present-with-undefined.
- Plain objects coerce keys to property keys and inherit object-specific behavior. Prefer Map when keys are not intentionally strings or symbols.
- Set does not deduplicate separate arrays or objects with equal contents. Encode a stable primitive key such as row + ',' + column when value equality is required.
- Map and Set iterate in insertion order. Do not confuse that guarantee with sorted order.
- Use === and !== by default. The coercions performed by == can hide a representation mistake in an interview solution.
Numeric sorting, comparison, and ties#
Array.prototype.sort converts values to strings when no comparator is supplied, so numeric work needs an explicit comparator. The method mutates the receiver and the language does not promise one universal implementation-specific time or space bound.
- Ascending numbers: (a, b) => a - b. Descending numbers: (a, b) => b - a.
- For multiple fields, compare the first field, then explicitly return the second comparison on a tie.
- A comparator must be pure, reflexive, anti-symmetric, and transitive. Returning only true/false does not satisfy the negative/zero/positive contract.
- Do not use subtraction for BigInt comparison because a comparator must return a Number; use a < b ? -1 : a > b ? 1 : 0.
- sort mutates; use [...values].sort(compare) when the input contract forbids mutation.
Heap and priority queue#
ECMAScript does not provide a standard PriorityQueue. Use a compact, tested binary-heap helper with an explicit comparator, or use a platform-provided class only when the assessment documents it.
- For top k largest values, keep a min-heap capped at k; the root is the smallest retained candidate.
- Store a stable tie field when equal priorities need deterministic ordering.
- Do not sort the heap's backing array or iterate it as though every element were globally ordered. Only the root is guaranteed to be the highest-priority element under the comparator.
- A comparator makes one helper work for numbers or structured entries, but all compared fields must obey one consistent order.
Number, safe integers, and BigInt#
JavaScript Number is IEEE-754 double precision, and TypeScript number is the same runtime value - not a separate integer type. Integer arithmetic is exact only within Number.MIN_SAFE_INTEGER through Number.MAX_SAFE_INTEGER.
- Check Number.isSafeInteger when exact counts, identifiers, products, or sums may approach the safe boundary.
- Use BigInt only when the contract needs larger exact integers and the platform permits it. Write literals with n and keep every operand in that expression BigInt.
- Number and BigInt cannot be mixed in arithmetic. Convert deliberately only when the target range is known to be safe.
- Math.floor and Math.trunc differ for negative fractions. Choose the rule that matches the prompt.
- Bitwise operators coerce Number values to 32-bit integers, so they are unsafe shortcuts for general 53-bit integer arithmetic.
Strings and the UTF-16 boundary#
String length and bracket indexing operate on UTF-16 code units, not user-perceived characters. State an ASCII or lowercase-English assumption when the prompt permits it; otherwise choose a representation that matches the requested text semantics.
- Array.from(text) and for...of iterate Unicode code points, which keeps a surrogate pair together, but they do not combine multiple code points into one grapheme cluster.
- text.slice(start, end) uses code-unit offsets and creates a new string.
- Build many fragments in an array and join once instead of relying on repeated concatenation when the accumulated copying cost matters.
- localeCompare is locale-sensitive behavior, not a drop-in comparator for a prompt that requires code-unit or ASCII ordering.
- Normalize only when the problem contract requests a normalization form; visually similar strings are not automatically equal.
TypeScript nullability and discriminated state#
Use strict null checks and narrow unknown input before operating on it. A discriminated union can make algorithm states explicit and prevent fields from being read in the wrong phase.
- Prefer unknown to any at an untrusted boundary, then narrow with typeof, Array.isArray, property checks, or a dedicated parser.
- Use value === undefined or map.has(key) deliberately; truthiness would also reject valid 0, false, and empty-string values.
- Model mutually exclusive states with a literal tag such as { kind: 'unseen' } | { kind: 'distance', value: number }.
- The non-null assertion operator tells the typechecker to trust you; it performs no runtime check and should follow an invariant you can explain.
- Avoid wrapper types Number, String, and Boolean. Use the primitive types number, string, and boolean.
Operation-specific complexity#
State costs for the operation you actually use and name implementation caveats. Do not transfer a bound from a similarly named method in another language.
| Operation | Complexity | Interview note |
|---|---|---|
array[index] | O(1) | Indexed access to an existing dense sequence position. |
array.push / array.pop | O(1) amortized | End operations on the ordinary dynamic-array representation. |
array.shift / array.unshift | O(n) | Remaining indexed elements are repositioned; use a head index for BFS. |
array.slice(a, b) | O(k) | Creates a shallow copy of k selected elements. |
Map/Set get, set, has, delete | Sublinear on average | The specification requires access times sublinear on average but does not mandate one hash-table implementation. |
array.sort(compare) | Runtime-dependent | Do not promise a universal implementation-specific time or space complexity; state the tested runtime when it matters. |
binary heap peek | O(1) | Only the root priority is guaranteed. |
binary heap push / pop | O(log n) | For the explicit helper used by this guide. |
string.length / string[index] | O(1) | Counts or returns UTF-16 code units, not grapheme clusters. |
Array.from(text) | O(n) | Allocates code-point entries; n is the number of traversed UTF-16 code units. |
Common Interview Templates#
These are structural recall aids, not answers to proprietary problems. Open only the pattern you need.
Frequency MapCounts, complements, grouping, or visited frequency must be retrieved by a stable key.
Use when: Counts, complements, grouping, or visited frequency must be retrieved by a stable key.
Typical complexity: O(n) expected time and O(u) space for n items and u distinct keys.
function frequencies(values) {
const counts = new Map();
for (const value of values) {
counts.set(value, (counts.get(value) ?? 0) + 1);
}
return counts;
}Queue BFSUnit-cost edges, levels, nearest targets, or simultaneous sources require frontier order.
Use when: Unit-cost edges, levels, nearest targets, or simultaneous sources require frontier order.
Typical complexity: O(V + E) time and O(V) space when each node is marked on enqueue.
function bfs(graph, starts) {
const queue = [...starts];
const seen = new Set(starts);
const order = [];
for (let head = 0; head < queue.length; head += 1) {
const node = queue[head];
order.push(node);
for (const neighbor of graph.get(node) ?? []) {
if (seen.has(neighbor)) continue;
seen.add(neighbor);
queue.push(neighbor);
}
}
return order;
}Iterative DFSDepth-first traversal is required and an explicit stack is clearer or safer than recursion.
Use when: Depth-first traversal is required and an explicit stack is clearer or safer than recursion.
Typical complexity: O(V + E) time and O(V) auxiliary space.
function dfs(graph, start) {
const stack = [start];
const seen = new Set();
const order = [];
while (stack.length > 0) {
const node = stack.pop();
if (seen.has(node)) continue;
seen.add(node);
order.push(node);
const neighbors = graph.get(node) ?? [];
for (let index = neighbors.length - 1; index >= 0; index -= 1) {
if (!seen.has(neighbors[index])) stack.push(neighbors[index]);
}
}
return order;
}Lower BoundA sorted sequence needs the first index whose value is at least a target.
Use when: A sorted sequence needs the first index whose value is at least a target.
Typical complexity: O(log n) time and O(1) space.
function lowerBound(values, target) {
let left = 0;
let right = values.length;
while (left < right) {
const middle = left + Math.floor((right - left) / 2);
if (values[middle] < target) left = middle + 1;
else right = middle;
}
return left;
}Size-K Min-HeapRetain the k largest candidates without sorting the complete input.
Use when: Retain the k largest candidates without sorting the complete input.
Typical complexity: O(n log k) time and O(k) space.
class MinHeap {
constructor() {
this.values = [];
}
get size() {
return this.values.length;
}
peek() {
return this.values[0];
}
push(value) {
const values = this.values;
values.push(value);
let index = values.length - 1;
while (index > 0) {
const parent = Math.floor((index - 1) / 2);
if (values[parent] <= values[index]) break;
[values[parent], values[index]] = [values[index], values[parent]];
index = parent;
}
}
pop() {
const values = this.values;
if (values.length === 0) return undefined;
const root = values[0];
const last = values.pop();
if (values.length > 0) {
values[0] = last;
let index = 0;
while (true) {
const left = index * 2 + 1;
const right = left + 1;
let smallest = index;
if (left < values.length && values[left] < values[smallest]) smallest = left;
if (right < values.length && values[right] < values[smallest]) smallest = right;
if (smallest === index) break;
[values[index], values[smallest]] = [values[smallest], values[index]];
index = smallest;
}
}
return root;
}
}
function largestK(values, k) {
if (!Number.isInteger(k) || k < 0) throw new RangeError("k must be a nonnegative integer");
const heap = new MinHeap();
for (const value of values) {
if (heap.size < k) heap.push(value);
else if (k > 0 && value > heap.peek()) {
heap.pop();
heap.push(value);
}
}
return [...heap.values].sort((a, b) => b - a);
}Union-FindIncremental undirected connectivity and component merging are central.
Use when: Incremental undirected connectivity and component merging are central.
Typical complexity: O((n + q) alpha(n)) time for n items and q operations, with O(n) space.
class UnionFind {
constructor(size) {
this.parent = Array.from({ length: size }, (_, index) => index);
this.componentSize = Array(size).fill(1);
}
find(node) {
let root = node;
while (root !== this.parent[root]) root = this.parent[root];
while (node !== root) {
const next = this.parent[node];
this.parent[node] = root;
node = next;
}
return root;
}
union(left, right) {
let leftRoot = this.find(left);
let rightRoot = this.find(right);
if (leftRoot === rightRoot) return false;
if (this.componentSize[leftRoot] < this.componentSize[rightRoot]) {
[leftRoot, rightRoot] = [rightRoot, leftRoot];
}
this.parent[rightRoot] = leftRoot;
this.componentSize[leftRoot] += this.componentSize[rightRoot];
return true;
}
}BacktrackingA bounded choice tree must enumerate valid configurations while restoring shared state.
Use when: A bounded choice tree must enumerate valid configurations while restoring shared state.
Typical complexity: Problem-dependent and commonly exponential; output copying contributes to the bound.
function subsets(values) {
const result = [];
const path = [];
function search(index) {
if (index === values.length) {
result.push([...path]);
return;
}
search(index + 1);
path.push(values[index]);
search(index + 1);
path.pop();
}
search(0);
return result;
}One-Dimensional DPA linear state depends on a small, already-computed prefix of states.
Use when: A linear state depends on a small, already-computed prefix of states.
Typical complexity: O(n) time and O(1) auxiliary space for this two-state recurrence.
function maxNonAdjacentSum(values) {
let bestBeforePrevious = 0;
let bestThroughPrevious = 0;
for (const value of values) {
const bestHere = Math.max(bestThroughPrevious, bestBeforePrevious + value);
bestBeforePrevious = bestThroughPrevious;
bestThroughPrevious = bestHere;
}
return bestThroughPrevious;
}Common Interview Mistakes#
Default numeric sort
Without a comparator, values are ordered by their string forms rather than numeric magnitude.
Invented PriorityQueue
ECMAScript does not supply a standard PriorityQueue; a package or platform class cannot be assumed.
Repeated front removal
Array.shift reindexes remaining entries and can destroy a BFS traversal's intended linear bound.
Reference equality mistaken for value equality
Separate arrays and objects are distinct Map and Set keys even when their contents look equal.
Type annotation treated as validation
TypeScript types disappear at runtime and cannot validate untrusted input.
Unsafe integer arithmetic
Number cannot represent every integer beyond the safe range, and bitwise operators narrow values to 32 bits.
UTF-16 assumption left unstated
String length and indexing count code units, which can split supplementary code points.
Hidden mutation
sort, reverse, splice, and other array methods can change caller-visible input when the contract forbids it.
Broken and corrected examples#
Each pair names the failing case that the language fixture must catch.
Default sort is not numeric order
Failure: Without a comparator, sort compares UTF-16 string forms. The code also mutates values, which must match the input contract.
Regression case: Assert that sorting [2, 10, 1, -3, 2] with the numeric comparator produces [-3, 1, 2, 2, 10].
const values = [2, 10, 1];
values.sort();
// [1, 10, 2]const values = [2, 10, 1];
values.sort((left, right) => left - right);
// [1, 2, 10]Array.shift turns a BFS queue into repeated front removal
Failure: shift reindexes remaining elements, so repeated dequeues can add quadratic movement to an otherwise linear traversal.
Regression case: Run both forms on the same branching graph and assert identical visit order; instrument the fixed form to show each queue slot is read once.
while (queue.length > 0) {
const node = queue.shift();
visit(node);
}for (let head = 0; head < queue.length; head += 1) {
const node = queue[head];
visit(node);
}Equal-looking object keys are not the same Map key
Failure: Map compares objects by reference identity, so two separately created coordinate objects do not match.
Regression case: Store coordinates (1, 2), (11, 2), and (1, 12); assert that the delimiter-based key retrieves each independently and a missing coordinate stays absent.
const distance = new Map();
distance.set({ row: 1, column: 2 }, 4);
const answer = distance.get({ row: 1, column: 2 });
// undefinedconst key = (row, column) => row + "," + column;
const distance = new Map();
distance.set(key(1, 2), 4);
const answer = distance.get(key(1, 2));
// 4Number loses exact integer distinctions beyond the safe range
Failure: Number cannot represent every integer beyond the safe boundary, while BigInt preserves exact integer values but cannot be mixed with Number arithmetic.
Regression case: Assert the Number values compare equal, the BigInt values compare unequal, and adding 1n to the smaller BigInt produces the larger.
const left = Number.MAX_SAFE_INTEGER + 1;
const right = Number.MAX_SAFE_INTEGER + 2;
console.log(left === right); // trueconst left = 9007199254740992n;
const right = 9007199254740993n;
console.log(left === right); // falseA TypeScript cast does not validate runtime input
Failure: The as number assertion is erased during compilation, so the string remains a string and addition concatenates.
Regression case: Typecheck the fixed function, assert readSize(4) returns 4, and assert that '4', -1, 1.5, and a value beyond Number.MAX_SAFE_INTEGER throw.
function readSize(value: unknown): number {
return value as number;
}
readSize("4") + 1; // "41" at runtimefunction readSize(value: unknown): number {
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
throw new TypeError("size must be a nonnegative safe integer");
}
return value;
}
readSize(4) + 1; // 5String indexing can split a Unicode code point
Failure: Bracket indexing addresses UTF-16 code units. A supplementary code point occupies two code units and can be split.
Regression case: Assert 'A💡B'.length is 4, Array.from('A💡B').length is 3, and the fixed middle value is '💡'.
const text = "A💡B";
const middle = text[1];
// One surrogate code unit, not "💡"const text = "A💡B";
const middle = Array.from(text)[1];
// "💡"Language transfer exercises#
Use these to check runtime judgment before another algorithm repetition. Open the answer check only after committing to a result.
- predict
Predict numeric and lexical order
Predict the results of [2, 10, 1].sort() and [...values].sort((a, b) => a - b), and identify which expression mutates the original array.
Answer check
Default sort produces [1, 10, 2] by string comparison. The numeric comparator produces [1, 2, 10]. sort mutates its receiver; spreading before sort protects the original outer array.
- trace
Trace a head-index BFS
Trace queue, head, and seen for edges A→B, A→C, B→C starting at A. Explain when C becomes visited.
Answer check
Start queue [A], head 0, seen {A}. Processing A appends B and C and marks both immediately. Processing B does not append C again because C was marked on enqueue. The order is A, B, C.
- repair
Repair an unsafe parser
Repair a TypeScript function that returns value as number for unknown online-assessment input. Reject non-number, non-integer, negative, and unsafe values.
Answer check
Narrow with typeof value === 'number', Number.isSafeInteger(value), and value >= 0 before returning. The cast alone must be removed because it has no runtime effect.
- choose
Choose Number, BigInt, or a different contract
A path-counting task may exceed 2^53 - 1 and requires the exact decimal integer. Choose the representation and name one interoperability caveat.
Answer check
Use BigInt throughout the exact count when the platform accepts it, never mixing Number operands. Convert to a decimal string for output if required; JSON.stringify does not directly serialize BigInt without an explicit representation.
- transfer
Adapt the size-k heap
Adapt the min-heap template to retain the k events with largest score, breaking equal scores by earlier sequence number. State the root meaning and comparator order.
Answer check
The root must be the worst retained event: lower score first, and for equal score the later sequence first. Replace the root only when a candidate is better under score descending then sequence ascending, and return the retained values in the requested deterministic order.