Free lesson · DSA Mastery - Interview Prep, End to End
TypeScript for DSA
TypeScript for DSA
TypeScript is JavaScript with a static type system layered on top. Everything from the JavaScript topic applies - the same data structures, the same idioms, the same workarounds for missing built-ins. What TypeScript adds is the ability to catch type errors at compile time, which makes interview code safer at the cost of some extra typing.
If you write TypeScript daily, you can use it for interviews. Most interviewers will accept it. If you do not write TypeScript daily, you are probably better off with plain JavaScript or Python - adding a type system on top of an interview is rarely worth the overhead.
Why TypeScript for Interviews
Interactive animation: The case for and against TypeScript in an interview.
The case for TypeScript:
- Type errors caught at compile time. Useful for catching bugs like calling
.pushon something that should be a number. - Better IDE support. Autocomplete and inline hints help during the interview if the environment supports it.
- Familiar to teams using TypeScript heavily. If you target frontend or fullstack roles at companies like Microsoft, Slack, or various startups, TypeScript is the lingua franca.
The case against:
- Verbose. Type annotations add lines of code. A 10-line JavaScript solution is 12-15 lines of TypeScript with full annotations.
- No runtime benefit. TypeScript types are erased at compilation. The runtime behavior is identical to JavaScript, so the data structure limitations from the JavaScript topic still apply.
- Some interview environments do not support it. CoderPad, HackerRank, and LeetCode all support TypeScript, but some custom platforms do not.
If your team uses TypeScript and the interview environment supports it, use it. Otherwise, plain JavaScript is usually the better interview choice.
Interactive animation: The same function in JavaScript and TypeScript - the types catch the missing-key bug before it runs.
Type Annotations Basics
Interactive animation: Type annotations line by line: parameters, returns, arrays and maps.
The most useful annotations for interview code:
// Primitives
let n: number = 5;
let s: string = "hello";
let b: boolean = true;
// Arrays
let arr: number[] = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3]; // equivalent
// Tuples
let pair: [number, string] = [1, "hello"];
// Maps and sets
let m: Map<string, number> = new Map();
let st: Set<number> = new Set();
// Union types
let id: number | string = 5;
// Nullable
let val: number | null = null;
For function signatures:
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 [];
}
The ! after seen.get(complement) is the non-null assertion operator. It tells TypeScript "I know this is not null, trust me." Used when you know a value is present but the type system cannot prove it.
Interactive animation: Typed in, typed out: a function signature the compiler checks.
Generic Functions and Classes
Interactive animation: A generic function: one implementation for any comparable type.
TypeScript's generics let you write functions and classes that work for any type.
function maxOf<T>(arr: T[], compare: (a: T, b: T) => number): T {
let result = arr[0];
for (const x of arr) {
if (compare(x, result) > 0) {
result = x;
}
}
return result;
}
const maxNum = maxOf([1, 5, 3], (a, b) => a - b);
const maxStr = maxOf(["a", "c", "b"], (a, b) => a.localeCompare(b));
For data structures, generics make custom containers type-safe:
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.bubbleDown(0);
}
return top;
}
peek(): T | undefined {
return this.heap[0];
}
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) {
[this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
i = parent;
} else break;
}
}
private bubbleDown(i: number): void {
const n = this.heap.length;
while (true) {
const left = 2 * i + 1, right = 2 * i + 2;
let smallest = i;
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 heap = new MinHeap<number>((a, b) => a - b);
heap.push(5);
heap.push(2);
heap.push(8);
heap.pop(); // 2
This is the most useful single piece of TypeScript for interviews. JavaScript lacks a built-in priority queue, and a generic, reusable implementation is exactly the kind of code TypeScript is good at expressing.
Interactive animation: A generic MinHeap<T> - a heap for any type, with its comparator.
Interactive animation: T is number, so only numbers go in.
Working with the DOM-Free Standard Library
Interactive animation: The same structures as JavaScript, now typed.
For DSA problems, you use the same Map, Set, and Array types as in plain JavaScript. TypeScript just adds the type parameters.
const counts: Map<string, number> = new Map();
counts.set("a", 1);
const value: number | undefined = counts.get("a");
if (value !== undefined) {
// use value as number
}
The | undefined in counts.get's return type forces you to handle the case where the key is not present. This is one of TypeScript's most useful features - it surfaces the possibility of missing keys, which JavaScript silently lets you ignore.
For sets:
const seen: Set<number> = new Set();
seen.add(5);
const arr: number[] = [...seen];
For arrays:
const arr: number[] = [3, 1, 4, 1, 5];
arr.sort((a, b) => a - b);
const grid: number[][] = Array.from({ length: 3 }, () => Array(4).fill(0));
Type Aliases and Interfaces
For complex types, define aliases to keep code readable.
type Point = [number, number];
type Edge = [number, number, number]; // from, to, weight
type Graph = Map<number, number[]>;
function shortestPath(graph: Graph, start: number, end: number): number {
// ...
return 0;
}
For object-shaped types, interfaces are more idiomatic:
interface TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
}
interface ListNode {
val: number;
next: ListNode | null;
}
These match the typical class definitions but document the shape explicitly.
Tree Node and List Node Classes
Interactive animation: Null is part of the type: node classes and null narrowing.
Standard interview classes in TypeScript:
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val: number = 0, left: TreeNode | null = null, right: TreeNode | null = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
class ListNode {
val: number;
next: ListNode | null;
constructor(val: number = 0, next: ListNode | null = null) {
this.val = val;
this.next = next;
}
}
The | null in the field types means these can be null - and TypeScript will force you to check before accessing methods or fields on them. This catches a lot of null-reference bugs that JavaScript misses.
Common Idioms Specific to TypeScript
A few patterns that TypeScript makes cleaner than plain JavaScript.
Null Checks with Type Narrowing
function process(node: TreeNode | null): number {
if (node === null) return 0;
// After this line, TypeScript knows node is TreeNode, not null
return node.val + process(node.left) + process(node.right);
}
The if (node === null) return 0; is type narrowing - TypeScript figures out that after the check, node cannot be null. This avoids the need for the ! non-null assertion.
Discriminated Unions
Interactive animation: Discriminated unions: one tag, and every case handled.
For problems with multiple message types or state variants:
type Operation =
| { kind: "push"; value: number }
| { kind: "pop" }
| { kind: "peek" };
function execute(op: Operation, stack: number[]): number | null {
switch (op.kind) {
case "push":
stack.push(op.value);
return null;
case "pop":
return stack.pop() ?? null;
case "peek":
return stack[stack.length - 1] ?? null;
}
}
TypeScript's exhaustiveness checking will catch missing cases. Useful for state-machine-style problems.
Readonly Arrays and Records
Interactive animation: readonly inputs: the compiler stops accidental mutation.
To express that a function does not modify its input:
function findMax(arr: readonly number[]): number {
let max = arr[0];
for (const x of arr) {
if (x > max) max = x;
}
return max;
}
readonly number[] prevents arr.push, arr.pop, and mutation. The function can still read freely. This is documentation as much as enforcement.
Common Mistakes to Avoid
Interactive animation: Three habits to keep, and four traps to avoid.
Many of these overlap with JavaScript mistakes, plus a few TypeScript-specific ones.
Overusing any. any disables type checking. Use it as a last resort. If you find yourself reaching for any, the right move is usually to refine the type.
Trusting as too much. value as SomeType tells the compiler to treat the value as a different type without checking. Easy to get wrong. Prefer type guards (if (x instanceof Y) or if (typeof x === "string")) when possible.
Forgetting to handle undefined from Map.get. This is the most common TypeScript trip-up. map.get(key) returns T | undefined. The compiler will force you to handle the undefined case - either with a check, with the non-null assertion (!), or with the nullish coalescing operator (??).
Slow array.shift(). Same as JavaScript. Use the index trick for queues.
Sort without comparator. Same as JavaScript. Always pass (a, b) => a - b for numeric sorts.
Number precision. Same as JavaScript. Use BigInt for integers beyond 2^53.
Boilerplate to Remember
A minimal TypeScript interview template:
class Solution {
solve(nums: number[]): number {
const counts: Map<number, number> = new Map();
for (const x of nums) {
counts.set(x, (counts.get(x) ?? 0) + 1);
}
return 0;
}
}
The ?? (nullish coalescing) operator returns the right side if the left is null or undefined. It is the cleanest way to provide a default for Map.get.
When to Choose TypeScript Over JavaScript
Interactive animation: Two questions decide it: the platform, and how complex the problem is.
For interviews, the decision usually comes down to two questions:
1. Does the interview environment support TypeScript? Most major platforms do (LeetCode, HackerRank, CoderPad), but not all. Check before committing.
2. Will the type system help more than it slows you down? If you are fluent in TypeScript, the type system catches bugs. If you are not fluent, the annotations slow you down without providing much benefit.
For most interview prep, JavaScript is the simpler choice. Use TypeScript when:
- You write TypeScript daily and the syntax is automatic.
- The role explicitly requires TypeScript.
- You are solving a complex problem where the type system meaningfully catches errors.
Otherwise, JavaScript or Python are usually faster paths to the solution.
The rest of this course uses Java for code examples. TypeScript ports follow the same patterns as JavaScript, just with explicit type annotations. If TypeScript is your interview language, the algorithmic content transfers directly - translate the Java examples using the JavaScript and TypeScript patterns covered in this and the previous topic.