AlgoMaster Logo

TypeScript Crash Course for DSA

Last Updated: March 31, 2026

This chapter is a focused crash course on the TypeScript features you will use repeatedly while learning DSA and preparing for coding interviews. Instead of covering the entire language, we will concentrate only on the parts that matter for solving problems efficiently in interviews.

No Imports Needed (Mostly)

TypeScript on LeetCode works like JavaScript with types bolted on. You do not need to import anything for the core data structures and utilities. Everything is globally available:

Built-inWhat It ProvidesDSA Use Case
ArrayDynamic array with rich methodsPrimary data structure for everything
MapHash map with any key typeFrequency counting, O(1) lookups
SetHash set with any value typeVisited tracking, deduplication
Mathmax, min, floor, ceil, abs, sqrt, log2Utility calculations
JSONstringify, parseDeep copy, composite keys
Infinity / -InfinityGlobal sentinel valuesMin/max initialization

But TypeScript (and JavaScript) also has notable gaps. These are data structures you will need in DSA that have no built-in equivalent:

Not Built-inJava / Python EquivalentWhat You Must Do
QueueQueue / dequeUse object with pointers, or linked list
DequeArrayDeque / dequeUse array (limited), or custom class
PriorityQueuePriorityQueue / heapqImplement a min-heap yourself
TreeMap / TreeSetTreeMap / SortedListSorted array + binary search (limited)

We will cover typed implementations for each of these missing structures later in this chapter. For now, know that TypeScript gives you less out of the box than Java or Python, but what it does give you is clean, type-safe, and fast to write.

Variables, Types, and the Number Type Trap

TypeScript is a statically typed superset of JavaScript. You declare types explicitly, or let the compiler infer them:

For variable declarations, follow one rule: use `const` by default, `let` when you need to reassign, never `var`. The var keyword has function scope (not block scope), which leads to subtle bugs in loops and conditionals. Both let and const are block-scoped, which is what you expect.

For DSA, you will use a small set of types:

TypeExampleDSA Use Case
number42, 3.14, InfinityEverything numeric (no separate int/float!)
string"hello"Immutable character sequences
booleantrue, falseVisited arrays, flags
nullnullIntentional absence (tree/linked list terminators)
undefinedundefinedUninitialized variables, missing properties
bigint42nValues exceeding 2^53 - 1 (rare in DSA)

Now here is the most important thing to understand about TypeScript numbers: there is no integer type. Every number is an IEEE 754 64-bit floating-point double, exactly like JavaScript. The number 42 and the number 42.0 are the same value, the same type, stored the same way.

This has three critical implications for DSA:

1. Division always produces a float. Unlike Java where 7 / 2 gives 3, in TypeScript 7 / 2 gives 3.5. You must explicitly truncate:

2. Integer precision is limited to 2^53 - 1. The Number.MAX_SAFE_INTEGER is 9,007,199,254,740,991. Beyond this, integer arithmetic silently loses precision:

For most DSA problems where constraints are up to 10^9, this is not an issue. If you truly need arbitrary precision, use BigInt:

3. Binary search midpoint does not overflow, but needs floor. Unlike Java where (left + right) can overflow, TypeScript numbers safely hold sums up to 2^53. But you need Math.floor because division produces a float:

The ternary operator works exactly as in other languages:

Type Annotations and Inference

TypeScript's type system is its biggest advantage over JavaScript for DSA. The compiler catches bugs before you run your code. Here is what you need to know.

When to annotate vs. let TypeScript infer:

Function signatures should always be annotated. This is where type safety pays off most:

Notice the ! after map.get(complement). This is the non-null assertion operator. We already checked map.has(complement), so we know the value exists, but TypeScript does not know that. The ! tells the compiler "trust me, this is not undefined." We will cover this in detail in the Null Handling section.

Array types have two equivalent syntaxes:

Use number[] for simple types and Array<T> when the element type is complex (like Array<[number, number]>).

Tuple types are one of TypeScript's best features for DSA. Unlike plain arrays, tuples have fixed length and typed positions:

Union types handle cases where a value can be one of several types:

Type narrowing is how TypeScript automatically refines a union type based on control flow:

The compiler tracks which branches have already handled null, so you get full type safety without extra casts.

Operators and Control Flow

Most operators work exactly like JavaScript, but a few deserve special attention for DSA.

Strict Equality

Always use `===` and `!==` in TypeScript. The == operator performs type coercion, which creates surprising results:

TypeScript's type system helps here. It will flag many invalid == comparisons as errors. But === is still the right habit.

Modular Arithmetic

The % operator appears in hashing, cyclic array problems, and math-heavy questions. Like JavaScript, TypeScript's % can return negative values for negative operands: -7 % 3 gives -1, not 2. To guarantee a positive result:

Many problems ask you to return the result "modulo 10^9 + 7":

Bitwise Operators

Here is a critical gotcha: bitwise operators convert operands to 32-bit signed integers. This means they work correctly for values up to about 2 billion, but silently produce wrong results for larger numbers:

The key bitwise operators:

OperatorSymbolDSA Use
AND&Masking, checking if bit is set
OR|Setting bits
XOR^Finding unique elements, toggling
NOT~Bit inversion
Left shift<<Multiply by powers of 2: 1 << n equals 2^n
Right shift>>Signed right shift (preserves sign)
Unsigned right shift>>>Unsigned right shift (fills with 0)

Warning: 1 << 31 gives -2147483648 (negative!) because it sets the sign bit of a 32-bit integer. For bitmask problems with up to 20-25 elements, this is not a concern. For problems using all 32 bits, be careful.

Control Flow

TypeScript offers three loop forms. Use each where it fits:

One limitation: the for-of loop does not give you the index. If you need both index and value, use for with an index or nums.forEach((val, idx) => ...).

Nullish Coalescing and Optional Chaining

These two operators are essential for safe access in TypeScript:

The ?? operator is different from ||. The || operator treats 0, "", and false as falsy and returns the right side. The ?? operator only triggers on null and undefined. For frequency counting where 0 is a valid count, always use ??.

Functions, Scope, and Generics

In DSA problems on LeetCode, your solution is typically a function or a class method. TypeScript supports both regular functions and arrow functions:

Pass-by-Value with Reference Types

TypeScript, like JavaScript, is always pass-by-value. But for objects (including arrays, Maps, Sets), the "value" is a reference. This means:

  • Primitive parameters are copies. Changing them inside a function does not affect the caller.
  • Object/array parameters pass a copy of the reference. You can modify the contents (like arr[i] = 5), but reassigning the reference itself does not affect the caller.

This is why backtracking works. You modify a shared array and undo the modification after recursion:

Notice the [...path] spread when saving results. If you wrote results.push(path) instead, every entry in results would be a reference to the same array, and they would all end up empty after backtracking unwinds.

Generics

Generics let you write type-safe, reusable code. They are one of TypeScript's best features for DSA data structures:

You will see generics heavily used in the Queue and MinHeap implementations later. For now, think of <T> as "this works for any type T."

Closures

Closures capture variables from their enclosing scope. This is useful in DSA for creating comparators and memoization:

Arrays

Arrays are the most fundamental data structure and the starting point for almost every DSA problem.

Array initialization matters. There is a gotcha with 2D arrays:

This is one of the most common bugs in matrix problems. fill() does not clone objects. It fills every slot with the same reference.

The `.length` property gives the array size. Unlike Java where arrays, strings, and lists all use different syntax, TypeScript is consistent:

2D Arrays

2D arrays appear in every matrix problem (BFS on grid, dynamic programming tables):

For DP problems, you often need a table with one extra row and column for the base case:

The 4-directional neighbor pattern is one of the most common patterns in matrix problems:

For 8-directional movement (including diagonals), add the four diagonal pairs: [-1,-1], [-1,1], [1,-1], [1,1].

Essential Array Methods

The spread operator ... is invaluable for copying and combining arrays:

`Array.from()` creates arrays from iterables or with initialization logic:

Strings

Strings in TypeScript are immutable, just like Java and Python. Every time you "modify" a string, you create a new one. This has a critical performance implication:

TypeScript does not have a StringBuilder class like Java. The standard approach is to collect parts in an array and join at the end.

Here are the string methods you will use most often:

MethodReturnsExampleDSA Use
charAt(i) or [i]strings[0]Access individual characters
lengthnumbers.lengthLoop bounds (property, no parentheses)
substring(start, end)strings.substring(0, 3)Extract portions (end exclusive)
slice(start, end)strings.slice(0, 3)Same as substring, supports negative indices
split(sep)string[]s.split(" ")Tokenize strings
indexOf(str)numbers.indexOf("ab")Find substrings (-1 if not found)
includes(str)booleans.includes("ab")Substring check
startsWith(prefix)booleans.startsWith("ab")Prefix check
endsWith(suffix)booleans.endsWith("ab")Suffix check
trim()strings.trim()Remove leading/trailing whitespace
toLowerCase()strings.toLowerCase()Case conversion
toUpperCase()strings.toUpperCase()Case conversion
repeat(n)string"ab".repeat(3)"ababab"

Template literals let you embed expressions in strings, which is useful for creating composite Map keys:

Character Utilities

TypeScript does not have a separate char type. Characters are single-character strings. To work with character codes, use charCodeAt and String.fromCharCode:

The frequency array pattern using character codes:

This is faster and more memory-efficient than Map<string, number> when you know the character set is limited to lowercase (or uppercase, or digits).

Collections: Map, Set, and Record

This is the most important section of this chapter. Choosing the right collection is often the difference between an O(n) and an O(n^2) solution. TypeScript inherits JavaScript's collections but adds type safety through generics.

Map: The HashMap Equivalent

ES6 Map with TypeScript generics gives you a type-safe hash map with O(1) average-case operations:

A critical type safety point: map.get(key) returns V | undefined in TypeScript. This forces you to handle the missing-key case, unlike Java where you might silently unbox null to int and crash:

Set: The HashSet Equivalent

Important: Sets use reference equality for objects and arrays. new Set([[1,2], [1,2]]) has size 2, not 1, because the two [1,2] arrays are different objects. For composite keys, serialize to strings:

Record Type

TypeScript's Record type creates typed plain objects, useful as lightweight maps with string keys:

Use Map<K, V> as your default. Use Record only when you need a quick string-keyed frequency map and want concise code.

Queue Implementation (Typed)

TypeScript has no built-in queue with O(1) dequeue. Here is a typed implementation using an object as a sparse array with head/tail pointers:

BFS using this queue:

For level-order traversal, capture the queue size at the start of each level:

PriorityQueue / MinHeap (Typed)

TypeScript has no built-in priority queue. For problems like top-K elements, Dijkstra's algorithm, or merge-K-sorted-lists, you need a min-heap. Here is a generic typed implementation:

Usage:

No TreeMap / TreeSet

TypeScript has no sorted map or sorted set. This is a genuine disadvantage for problems that need:

  • Finding the floor/ceiling of a key (nearest smaller/larger)
  • Range queries on keys
  • Maintaining elements in sorted order with O(log n) insertions

Workarounds:

  1. Sorted array + binary search: Maintain a sorted array and use binary search for lookups. Insertion is O(n) due to shifting, but works for small datasets.
  2. Sort when needed: If you only need sorted access at the end, collect elements and sort once.
  3. Accept the limitation: In a TypeScript interview, mention the missing data structure and discuss the trade-off.

Collections Quick Reference

CollectionTypeKey MethodsTime ComplexityDSA Use
Array<T>Dynamic arraypush, pop, shift, splice, slice, sortpush/pop O(1), shift O(n), access O(1)Stacks, results, DP tables
Map<K,V>Hash mapset, get, has, delete, sizeO(1) averageFrequency counting, two-sum
Set<T>Hash setadd, has, delete, sizeO(1) averageVisited tracking, dedup
Record<K,V>Typed objectbracket access, in, Object.keysO(1) averageSimple frequency maps
Queue<T> (custom)FIFO queueenqueue, dequeue, peek, sizeO(1) allBFS
MinHeap<T> (custom)Priority queuepush, pop, peek, sizeO(log n) push/popTop-K, Dijkstra

Sorting and Comparators

Sorting is a prerequisite for many algorithms: binary search, two pointers on sorted arrays, merge intervals, and greedy approaches.

The biggest JavaScript/TypeScript sorting gotcha: Array.sort() converts elements to strings and sorts lexicographically by default:

Note that Array.sort() modifies the array in place and returns the same array. If you need to preserve the original, spread first:

Important: TypeScript's sort() is not guaranteed to be stable in all engines, but in practice V8 (Node.js, Chrome) and SpiderMonkey (Firefox) both use TimSort, which is stable. On LeetCode, you can rely on stability.

Functional Patterns for DSA

TypeScript inherits JavaScript's functional array methods. These make DSA code concise, but use them with awareness of their performance characteristics:

Performance note: Each of these methods creates a new array (except forEach, some, every, and find). Chaining .filter().map() creates two intermediate arrays. In hot inner loops processing millions of elements, a plain for loop is faster. For most DSA problems, the difference is negligible.

Null, Undefined, and Strict Null Checks

TypeScript distinguishes between null and undefined, and its type system helps you handle both safely.

The difference:

  • undefined means "not assigned." It is the default value for uninitialized variables, missing object properties, and Map.get() for missing keys.
  • null means "intentionally absent." You use it explicitly to represent "no node" in trees and linked lists.

In DSA, you encounter null/undefined primarily in three situations: tree/linked list problems, map lookups, and optional function parameters.

Rule 1: Use union types to express nullability.

Rule 2: Use nullish coalescing (`??`) for safe defaults.

Rule 3: Use the non-null assertion (`!`) sparingly and only when you are certain.

Rule 4: Use optional chaining (`?.`) for safe property access.

Iterating and Modifying Collections Safely

Unlike Java's ConcurrentModificationException, TypeScript does not throw errors when you modify a collection during iteration. But that does not mean it is safe. The behavior is simply undefined and can produce bugs that are hard to track down.

Safe alternatives:

For Maps and Sets, deleting during for-of iteration is actually safe per the ES6 specification. But adding new elements during iteration may or may not be visited, so avoid it:

In practice, most DSA problems do not require removing during iteration. You are far more likely to build a new collection with the desired elements.

Recursion and the Call Stack

Recursion is the backbone of tree traversal, graph DFS, backtracking, and divide-and-conquer. Understanding how TypeScript handles recursion is essential.

Every function call goes on the call stack, which has limited space. If your recursion goes too deep, you get a RangeError: Maximum call stack size exceeded:

When to worry about stack depth:

ScenarioTypical DepthRisk
Balanced binary tree (n nodes)O(log n)Safe for n up to 10^6
Linked list / skewed tree (n nodes)O(n)Dangerous if n > 5,000-10,000
Backtracking (k choices, depth d)O(d)Usually safe (d is small)
DFS on graph (n nodes)O(n)Dangerous for large n

The fix: Convert deep recursion to an iterative approach using an explicit stack:

Note that TypeScript (and JavaScript) does not optimize tail recursion in practice. While the ES6 spec includes tail call optimization, only Safari implements it. Node.js and V8 do not. If your recursion depth is proportional to input size and the input can be large, convert to iteration.

Graph Representation Patterns

Graphs appear in a large portion of DSA problems. TypeScript does not have a built-in graph class, so you need to build representations yourself. There are two common approaches:

Approach 1: Adjacency list with `Array<number[]>` -- Use when nodes are numbered 0 to n-1.

Approach 2: Adjacency list with `Map<number, number[]>` -- Use when node IDs are not contiguous or are very large.

Weighted graphs use tuples to store neighbor and weight:

ApproachProsConsUse When
Array<number[]>Fast index access, no hashing overheadWastes space if node IDs are sparseNodes are 0 to n-1
Map<number, number[]>Handles any node IDs, no wasted spaceSlightly slower due to hashingNode IDs are large, sparse, or non-numeric

Tuple and Pair Alternatives

Unlike Python, TypeScript does not have a built-in tuple at runtime. But TypeScript's type system lets you type arrays as tuples, which gives you fixed-length, typed positions:

Option 1: TypeScript tuple types (preferred for typed code)

Option 2: Plain arrays (when you want flexibility)

Option 3: Interface (when clarity matters)

In practice, typed tuples [number, number] are the standard approach in TypeScript DSA. They combine type safety with conciseness.

Deduplication Patterns

Many DSA problems require avoiding duplicate results (e.g., 3Sum, 4Sum, permutations with duplicates). TypeScript offers two approaches:

Approach 1: Sort and skip duplicates (preferred for sorted array problems)

This is the standard approach for problems like 3Sum and 4Sum. It runs in O(1) extra space (beyond the sort) and produces results in sorted order.

Approach 2: Use a Set to collect unique results

Since Sets compare objects by reference, you must serialize (e.g., JSON.stringify) to detect duplicate arrays. This is slower than the sort-and-skip approach but simpler to implement.

Common DSA Idioms in TypeScript

These are the small patterns and utility calls you will reach for repeatedly.

Swapping elements (TypeScript's destructuring makes this clean):

Sentinel values for tracking min/max:

Infinity and -Infinity are globally available. They compare correctly with any number: Infinity > 1e18 is true.

Math utilities:

Ceiling division without floating point:

Type conversions:

`parseInt` always needs a radix. Without the second argument, parseInt("08") might be interpreted as octal in some environments. Always pass 10:

Deep copy vs shallow copy:

OOP Basics for Design Problems

DSA problems often define custom node classes. TypeScript classes give you type-safe definitions with clean syntax:

TypeScript also supports a shorthand constructor syntax that reduces boilerplate:

Interfaces vs Classes: TypeScript also lets you define types with interfaces. For DSA, classes are more common because LeetCode provides them, but interfaces are useful for typing function parameters:

Building linked lists concisely with constructor chaining: