TypeScript for Coding Interviews: Types, Traps, and What to Skip

- TypeScript for coding interviews adds auto-complete and structural safety, but the type system is fully erased at runtime so
instanceoffails on interfaces - Array.sort() comparator must always be passed for numbers; TypeScript will not warn you when you omit it and you get lexicographic order
- Non-null assertion (
!) silences the compiler entirely and causes runtime crashes when the value is actually absent - String enums are required for graph coloring and cycle detection; numeric enums generate reverse mappings that double the key count
BigIntis the only option for exact 64-bit integers; it cannot mix withnumberin arithmetic and TypeScript catches the violation at compile time- Generic
MinHeap<T>with a comparator constructor handles min-heap, max-heap, and tuple heaps from one class - Annotate function signatures and container declarations; skip deeply nested generics where inference already gets it right
TypeScript is the working language of a huge slice of the engineering world. If you write it every day, reaching for it in the interview is the obvious call. But TypeScript has opinions, and under 45 minutes of pressure, those opinions come out fast. This guide covers the data structures you need, five traps that bite you when you're already stressed, and a cheat sheet for the morning of.
The JavaScript Foundation Still Applies
TypeScript compiles to JavaScript. Every JS quirk is still lurking: Array.sort() is lexicographic without a comparator, typeof null === "object", there is no native heap, and shift() is O(n). If you haven't read the JavaScript for coding interviews guide, start there. This article covers what the TypeScript layer adds, breaks, or changes.
What TypeScript Buys You (and What It Costs)
The type system buys you three concrete things:
- Auto-complete in supported editors (CoderPad, VS Code in browser). Less time remembering method names.
- Compiler errors that catch structural bugs early. Passing the wrong shape to a function shows up before you run the code.
- Generic data structures with type safety. One
MinHeap<T>works everywhere without casting.
What the type system costs you is time. Every annotation is time you didn't spend thinking about the algorithm. The right balance depends on your fluency. If annotations feel automatic, great. If you're hunting for the right generic syntax, the compiler is working against you.
Data Structures: The Typed Toolkit
Array
TypeScript arrays are generically typed: number[] or Array<number>. At runtime they're plain JS arrays, same performance characteristics.
const nums: number[] = [3, 1, 4, 1, 5]; nums.sort((a, b) => a - b); // always pass a comparator for numbers
The most common mistake: omitting the comparator and getting lexicographic sort. [10, 9, 2].sort() returns [10, 2, 9]. TypeScript will not warn you. The comparator is a runtime concern, not a type concern. The compiler watched you do it and said nothing.
Map and Set
Map<K, V> and Set<T> work identically to their JS counterparts:
const freq = new Map<string, number>(); freq.set("a", (freq.get("a") ?? 0) + 1); const seen = new Set<number>(); seen.add(42); seen.has(42); // true
Use ?? not || when values might be 0. freq.get("a") ?? 0 treats undefined as 0 without swallowing legitimate falsy values.
Defining Nodes: Interface vs Type Alias
Prefer interface for extendable shapes; prefer type for unions and mapped types.
interface TreeNode { val: number; left: TreeNode | null; right: TreeNode | null; } type ListNode = { val: number; next: ListNode | null; };
LeetCode provides these in the problem scaffold. You don't need to write them. Know how to read them quickly.
Generic Queue
TypeScript has no built-in Queue. Use a head pointer to avoid O(n) shift():
class Queue<T> { private items: T[] = []; private head = 0; enqueue(item: T): void { this.items.push(item); } dequeue(): T | undefined { return this.head < this.items.length ? this.items[this.head++] : undefined; } get size(): number { return this.items.length - this.head; } isEmpty(): boolean { return this.head >= this.items.length; } }
Generic MinHeap
No built-in priority queue either. One comparator-based class handles min-heap, max-heap, and tuple heaps:
class MinHeap<T> { private heap: T[] = []; constructor(private compare: (a: T, b: T) => number) {} push(val: T): void { this.heap.push(val); this.bubbleUp(this.heap.length - 1); } pop(): T | undefined { if (this.heap.length === 0) return undefined; const top = this.heap[0]; const last = this.heap.pop()!; if (this.heap.length > 0) { this.heap[0] = last; this.sinkDown(0); } return top; } peek(): T | undefined { return this.heap[0]; } get size(): number { return this.heap.length; } private bubbleUp(i: number): void { while (i > 0) { const parent = (i - 1) >> 1; if (this.compare(this.heap[i], this.heap[parent]) >= 0) break; [this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]]; i = parent; } } private sinkDown(i: number): void { const n = this.heap.length; while (true) { let smallest = i; const left = 2 * i + 1, right = 2 * i + 2; if (left < n && this.compare(this.heap[left], this.heap[smallest]) < 0) smallest = left; if (right < n && this.compare(this.heap[right], this.heap[smallest]) < 0) smallest = right; if (smallest === i) break; [this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]]; i = smallest; } } } // Usage const minHeap = new MinHeap<number>((a, b) => a - b); const maxHeap = new MinHeap<number>((a, b) => b - a); const pairHeap = new MinHeap<[number, number]>((a, b) => a[0] - b[0]);
Pass the comparator at construction time, not as a generic constraint. This is the same pattern as Java's PriorityQueue. One class, three heap variants.
Five Ways TypeScript Will Betray You
1. Type Erasure: The Compiler Leaves the Building
TypeScript's type system is completely erased when compiled to JavaScript. The types were never really there to begin with:
interface Point { x: number; y: number; } function isPoint(obj: unknown): obj is Point { // obj instanceof Point fails. Interfaces don't exist at runtime. return typeof obj === "object" && obj !== null && "x" in obj && "y" in obj; }
instanceof works for classes, not interfaces. Use property checks for runtime shape guards. The compiler will happily wave you through, then disappear at runtime.
2. The ! Operator: A Pinky Promise to the Compiler
The ! postfix tells the compiler "trust me, this is not null." The compiler believes you, unconditionally:
const count = freq.get(key)!; // fine if the key always exists const node = nodeMap.get(id)!.left; // runtime crash if key is absent
Use ! only when the algorithm guarantees the value is present. In tree traversal under pressure, this is where null crashes appear. When in doubt, add an explicit guard. You made a promise. The computer will collect.
3. as Assertions: Gaslighting Your Type Checker
const val = JSON.parse(input) as number; // compiles, but lies if input is "{}" const node = map.get(key) as TreeNode; // crashes at runtime if key is absent
as is useful when you know more than the compiler. It's harmful when you use it to make a type error disappear without understanding why it appeared. And in an interview, that distinction matters.

as any is the nuclear option. Every TypeScript developer has reached for it. Fewer admit it.
4. number Is Float64, Not an Integer
TypeScript has no integer type. Every number is an IEEE 754 double, safe for exact integers up to Number.MAX_SAFE_INTEGER (2^53 - 1). For problems explicitly requiring 64-bit integers, use BigInt:
const big = 9007199254740993n; // BigInt literal
BigInt cannot mix with number in arithmetic. TypeScript catches this at compile time. Keep them separate.
5. Numeric Enums Have a Reverse Mapping Trap
TypeScript's numeric enums generate reverse mappings at runtime. The object has twice as many keys as you expect:
enum State { UNVISITED, VISITING, VISITED } Object.keys(State).length; // 6, not 3 (surprise) // Use string enums to avoid this: enum State { UNVISITED = "UNVISITED", VISITING = "VISITING", VISITED = "VISITED" } Object.keys(State).length; // 3
For graph coloring or cycle detection, use string enums. Numeric enums bite you if you ever iterate over the enum object. The value count is wrong, the lookup behavior is confusing, and debugging it under a timer is a bad time.
When to Add Types and When to Skip Them
Add types where they prevent real bugs. Skip them where they only add noise.
In a 45-minute interview:
- Always type function signatures.
function twoSum(nums: number[], target: number): number[]takes three seconds and eliminates argument-order bugs. - Type your container declarations.
const graph = new Map<number, number[]>()is unambiguous. - Skip deeply nested generics when inference works.
const result = []is fine if you push typed values immediately. - Use
!for tree traversal only when the algorithm guarantees non-null.
TypeScript's inference is strong. Let it run. Annotate where it gets it wrong.
TypeScript vs JavaScript: Which One?
Use TypeScript if it's what you write daily. The edge cases where it slows you down are smaller than the cost of switching to a less fluent language.
The only scenario where JavaScript wins: you're fighting the compiler instead of solving the problem. If you spend 90 seconds on a type error that would not exist in JavaScript, the type safety is costing more than it delivers. That's the whole argument for switching. One metric.
For the full language comparison, see Best Language for Coding Interviews.
Quick-Reference Cheat Sheet
// --- Core containers --- const arr: number[] = []; const map = new Map<string, number>(); const set = new Set<number>(); // --- Sorted iteration --- arr.sort((a, b) => a - b); // ascending arr.sort((a, b) => b - a); // descending [...map.entries()].sort((a, b) => a[0].localeCompare(b[0])); // --- Map idioms --- map.set(k, (map.get(k) ?? 0) + 1); map.get(k) ?? fallback; // --- Sentinels --- let min = Infinity, max = -Infinity; // --- Math --- Math.floor(n / 2) Math.abs(n) Math.max(a, b) Math.min(a, b) // --- String --- const chars = s.split(""); chars.join(""); s.charCodeAt(i) - "a".charCodeAt(0); // char to 0-25 // --- Binary tricks --- n >> 1 // floor(n/2) n & 1 // odd check n & (n - 1) // clear lowest set bit // --- Type narrowing --- if (node === null) return; if (typeof val === "number") { } if ("left" in node) { } // --- Non-null assertion --- const val = map.get(key)!; // --- String enum --- enum Color { WHITE = "WHITE", GRAY = "GRAY", BLACK = "BLACK" } // --- Node types --- interface TreeNode { val: number; left: TreeNode | null; right: TreeNode | null; } interface ListNode { val: number; next: ListNode | null; }
Practicing Under Real Conditions
Reading patterns is one thing. Producing them out loud, under a timer, while explaining your reasoning to a silent interviewer is a different skill entirely. The syntax knowledge only matters if you can access it while narrating an algorithm.
SpaceComplexity runs voice-based mock interviews with rubric-graded feedback, so you can practice the full loop in TypeScript and get specific feedback on where your reasoning broke down.
For the data structures underlying these patterns, Hash Map Time Complexity and Heap Data Structure are worth a review before your interview.
Further Reading
- TypeScript Handbook, official documentation
- TypeScript Deep Dive: Number, float64 limits and pitfalls
- TypeScript FAQ (microsoft/TypeScript), covers type erasure and common misconceptions
- Tech Interview Handbook: Algorithm Study Cheatsheets, language-agnostic pattern reference
- ECMAScript Specification: Array.prototype.sort, why sort is not guaranteed stable in all engines (V8's Timsort has been stable since Node 11)