TypeScript Interview Idioms: The Patterns That Actually Save Time

May 29, 20268 min read
dsaalgorithmsinterview-prepleetcode
TypeScript Interview Idioms: The Patterns That Actually Save Time
TL;DR
  • Array.from with a factory function is the only safe 2D grid initialization; fill(new Array()) aliases every row to the same object
  • ?? beats || for defaults when 0 or false is a valid value; use ??= to collapse memoization checks to one line
  • ??= builds adjacency lists in two lines: graph[u] ??= []; graph[u].push(v) replaces three-line if-check boilerplate
  • Optional chaining (?.) eliminates multi-line null guards in tree and linked list traversal
  • .sort() on numbers silently sorts lexicographically without a comparator; the TypeScript compiler won't warn you, so always pass (a, b) => a - b

You picked TypeScript for your coding interview. Smart move. Type errors surface before you run the code. Autocomplete fills in method names. The compiler catches you reaching into something that might be undefined. Real advantages.

But TypeScript has a second layer most engineers never fully use under pressure. These idioms compress boilerplate to one line and prevent whole categories of runtime bugs. Experienced TypeScript engineers reach for them automatically. Everyone else re-derives them from first principles while the interviewer watches.


The 2D Grid Trap Costs You Ten Minutes and Your Dignity

Two-dimensional arrays trip up TypeScript engineers more than almost anything else. Here's the version that looks correct and isn't:

const grid = new Array(m).fill(new Array(n).fill(0));

fill() copies a reference, not a new object. Every row in that grid points to the same underlying array. Mutate grid[0][0] and you've also mutated grid[1][0], grid[2][0], and every other row. Then you spend ten minutes staring at impossible state, wondering if you've lost your mind.

The only safe 2D initialization uses Array.from with a factory function:

const grid = Array.from({ length: m }, () => new Array(n).fill(0));

The factory function runs fresh for each row. Completely independent arrays.

For 1D arrays, new Array(n).fill(0) is fine. Primitives copy by value. The aliasing trap only bites reference types.

// Safe: primitives const freq = new Array(26).fill(0); const dp = new Array(n + 1).fill(Infinity); // Broken: all rows are the same array const visited = new Array(m).fill(new Array(n).fill(false)); // Correct: const visited = Array.from({ length: m }, () => new Array(n).fill(false));

Burn the Array.from pattern into muscle memory. You'll use it every time a grid problem appears.


?? Is Not a Preference, It's a Correctness Decision

The frequency counter is the most common data structure in interview problems. Here's what people write when they're moving fast:

count.set(ch, (count.get(ch) || 0) + 1);

It works for frequency counters because 0 means "hasn't appeared yet," and treating 0 as falsy is fine there. But the habit is dangerous. || treats any falsy value, including 0 and false, as missing. ?? only fires on null or undefined.

Get comfortable with ?? as your default:

const count = new Map<string, number>(); for (const ch of s) { count.set(ch, (count.get(ch) ?? 0) + 1); }

The distinction matters in DP tables. If you're memoizing results where 0 is a valid answer:

// Wrong: treats 0 as a cache miss if (!memo[n]) memo[n] = compute(n); // Correct: only misses on null/undefined memo[n] ??= compute(n);

??= is the logical nullish assignment operator, added in TypeScript 4.0. It assigns only if the left side is null or undefined. Using ||= here introduces a bug that will haunt exactly one test case at exactly the wrong moment.


??= Collapses Adjacency List Setup to Two Lines

Building graphs is mechanical work. Without ??=, you write three lines per edge:

if (!graph[u]) graph[u] = []; graph[u].push(v);

With ??=:

const graph: Record<number, number[]> = {}; for (const [u, v] of edges) { graph[u] ??= []; graph[v] ??= []; graph[u].push(v); graph[v].push(u); }

Record<number, number[]> plus ??= is the fastest path to a working adjacency list in an interview. The type annotation tells you and your interviewer exactly what the structure holds. No Map<any, any> squinting required.


Optional Chaining Eliminates the Null Guard

Tree and linked list problems involve constant pointer chasing through nodes that might not exist. The pre-optional-chaining version:

const val = node !== null && node.next !== null ? node.next.val : undefined;

Optional chaining short-circuits on null or undefined and returns undefined rather than throwing:

const val = node?.next?.val; const leftVal = root?.left?.val;

Same semantics, one line. In a tree problem where you're accessing left and right children, this compresses three to five lines per access into one.

Pair it with ?? when you want a fallback instead of undefined:

const leftDepth = node?.left?.depth ?? 0;

Destructuring: Three Patterns Worth Memorizing

The swap:

[arr[i], arr[j]] = [arr[j], arr[i]];

Creates a temporary array under the hood, so it's not zero-cost. Fast enough for interview purposes, and a lot cleaner than a temp variable your interviewer will watch you mistype.

Defaults on extraction:

const { x = 0, y = 0, z = 0 } = point;

The default only applies when the value is undefined, not null. Same ?? behavior as the operators above.

Typed tuple returns:

function bounds(arr: number[]): [number, number] { return [Math.min(...arr), Math.max(...arr)]; } const [lo, hi] = bounds(nums);

Tuple return types are one place where TypeScript annotations earn their keep. The caller knows they're unpacking exactly two numbers, in order, and the type checker enforces it.


.sort() Will Betray You Silently

For scanning, filtering, and transforming, TypeScript's array methods are first-class tools:

const doubled = nums.filter(x => x > 0).map(x => x * 2); const first = intervals.find(([start]) => start > threshold); const allValid = nums.every(x => x >= 0 && x < n); const total = nums.reduce((acc, x) => acc + x, 0);

TypeScript will not warn you when you forget the comparator for .sort(). The compiler accepts arr.sort() on a number[] with no complaint, and [10, 2, 1].sort() returns [1, 10, 2] because the default comparison is lexicographic. It's been this way since JavaScript was invented. The compiler has always been fine with it. You just won't notice until your solution passes all the easy test cases and fails mysteriously on [10, 2, 1].

Always pass an explicit comparator:

arr.sort((a, b) => a - b); // ascending arr.sort((a, b) => b - a); // descending

An AI coding agent confidently writes shell commands that look like proper error handling but where the failure message is unreachable code

Code that compiles without complaint and does exactly the wrong thing. TypeScript's .sort() has this same energy.


Annotate Signatures, Infer Everything Else

Annotate function signatures and complex data structure declarations. Let TypeScript infer everything else.

function twoSum(nums: number[], target: number): number[] { const seen = new Map<number, number>(); for (let i = 0; i < nums.length; i++) { const complement = target - nums[i]; if (seen.has(complement)) return [seen.get(complement)!, i]; seen.set(nums[i], i); } return []; }

Inside the loop, TypeScript infers complement as number. No annotation needed. The ! non-null assertion on .get() is safe here because you just checked .has(); TypeScript doesn't follow that logic automatically, so you assert manually. It's not lying to the compiler. It's telling the compiler something true that it can't figure out on its own.

Where annotations earn their keep:

  • Graph types: Map<number, number[]> not Map<any, any>
  • DP tables: number[][], number[]
  • Priority queue items: [number, number] for [cost, node] tuples
  • Return types on recursive functions TypeScript struggles to infer

Where they cost more than they give: local loop variables, intermediate values, return types from simple expressions.


What TypeScript Still Won't Save You From

Four traps the compiler accepts without complaint:

Sort comparator missing. arr.sort() on number[] compiles clean and produces wrong results silently. This section of the post has now mentioned this twice, because it costs candidates that often.

Integer overflow is invisible. TypeScript numbers are float64. Beyond 2^53, arithmetic silently loses precision. Use BigInt if you need exact large integers. The compiler will not remind you.

Object spread is shallow. { ...obj } copies top-level properties by value but nested objects remain shared references. Same aliasing problem as the 2D array, different syntax, same painful debugging session.

NaN === NaN is false. Use Number.isNaN(x) for the check. TypeScript won't flag the comparison. JavaScript won't throw. Your test case for the missing value will just silently fail.


TypeScript Interview Idioms: Quick Reference

IdiomPatternWhen to use
2D grid initArray.from({length: m}, () => new Array(n).fill(0))Any grid or DP table
Frequency countercount.set(k, (count.get(k) ?? 0) + 1)Character or element counts
Adjacency listgraph[u] ??= []; graph[u].push(v)Graph construction
Swap[arr[i], arr[j]] = [arr[j], arr[i]]In-place reordering
Optional chainnode?.next?.valTree or linked list traversal
Memoization missmemo[n] ??= compute(n)Top-down DP
Numeric sortarr.sort((a, b) => a - b)Always, without exception
Non-null assertmap.get(k)!After explicit .has() check
Typed adjacencyconst g: Record<number, number[]> = {}Graph problems
Tuple returnfunction f(): [number, number]Pair or bounded returns

These patterns are easy to read and harder to produce under pressure. SpaceComplexity runs voice-based mock interviews where you write and explain your code simultaneously, which is exactly when you find out whether idioms like ??= and Array.from are actually in your fingers or just in your notes.

For a broader look at the TypeScript collection interface and how it compares to plain JavaScript, see TypeScript for Coding Interviews. If you're still deciding whether to switch from JavaScript, JavaScript vs TypeScript for Coding Interviews covers the honest tradeoffs. And if you're choosing your interview language from scratch, Best Language for Coding Interviews walks through what actually matters.


Further Reading