AlgoMaster Logo

JavaScript Crash Course for DSA

Last Updated: March 31, 2026

This chapter is a focused crash course on the JavaScript 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

Here is something unique about JavaScript compared to Java, Python, C++, and C#: you do not need to import anything for DSA. Every data structure and utility function you need is globally available.

On LeetCode, your solution is a function. There is no class wrapping, no import statements, no #include directives. You write your function and submit. This is one of the reasons JavaScript is popular for coding interviews: zero boilerplate.

Here is what is available to you out of the box:

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
ObjectKey-value pairs (string keys)Simple frequency maps
Mathmax, min, floor, ceil, abs, sqrt, log2Utility calculations
JSONstringify, parseDeep copy, composite keys
Infinity / -InfinityGlobal sentinel valuesMin/max initialization

But 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 array with pointer, 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 workarounds for each of these missing structures later in this chapter. For now, just know that JavaScript gives you less out of the box than Java or Python, but what it does give you is clean, concise, and fast to write.

Variables, Types, and the Number Type Trap

JavaScript is dynamically typed. You do not declare types, you just assign values and the engine figures it out:

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 coming from any other language.

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 JavaScript numbers: there is no integer type. Every number is an IEEE 754 64-bit floating-point double. 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 JavaScript 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. But for problems involving large prefix sums or multiplication of large values, be aware. 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, JavaScript numbers safely hold sums up to 2^53. But you need Math.floor because division produces a float:

When a problem says "return an integer," you still return a number in JavaScript. But always make sure your arithmetic produces an integer result. Using Math.floor in binary search and division is a habit that prevents bugs.

The ternary operator works the same as in other languages:

One more thing about types: the typeof operator has some notorious quirks:

The typeof null === "object" is a bug from the first version of JavaScript in 1995 that was never fixed because it would break existing code. You will not encounter this in DSA problems, but it is worth knowing for interview trivia questions.

Operators and Control Flow

Most operators work as you would expect, but a few deserve special attention for DSA.

Strict Equality

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

There is one acceptable use of ==: checking for both null and undefined in a single comparison. val == null is true when val is either null or undefined, and false for everything else. Some codebases use this idiom, but val === null || val === undefined or val == null is a style choice. In DSA, just be consistent.

Modular Arithmetic

The % operator appears in hashing, cyclic array problems, and math-heavy questions. Like Java, JavaScript'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" to prevent large outputs. This is a standard pattern:

Bitwise Operators

Here is a critical JavaScript-specific gotcha: bitwise operators convert operands to 32-bit signed integers. This means:

This limits bitmask problems in JavaScript to 31 bits instead of the 32 you get in Java or C++. For most DSA problems, 31 bits (representing sets of up to 31 elements) is sufficient. But be aware of this if you are porting bitmask DP solutions from other languages.

The key bitwise operators:

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

Common bit manipulation patterns:

Control Flow

JavaScript offers several loop forms. Use each where it fits:

Critical warning: Never use for...in on arrays. It iterates over string keys (including inherited prototype properties), not values:

Short-Circuit Evaluation and Modern Operators

JavaScript has two operators that are extremely useful for DSA and largely unique to it:

Nullish coalescing (`??`): Returns the right side only if the left is null or undefined:

Optional chaining (`?.`): Short-circuits to undefined if any part is null or undefined:

The ?? operator is safer than || for default values. map.get(key) || 0 would incorrectly replace a stored value of 0 with 0. The ?? operator only replaces null and undefined, preserving legitimate falsy values like 0, "", and false.

Functions, Scope, and Closures

JavaScript offers two main ways to define functions, and both appear frequently in DSA code:

For DSA, use function declarations for your main solution functions and arrow functions for comparators, callbacks, and short helper functions. The choice is mostly style, but there is one practical difference: function declarations are hoisted, so you can call them before they appear in the code. Arrow functions are not.

Pass-by-Value for Primitives, Pass-by-Reference for Objects

JavaScript passes primitives (numbers, strings, booleans) by value and objects (arrays, maps, sets, plain objects) by reference. This is the same as Java and matters for the same reason:

This is critical for backtracking. When you add a path to your results, you must copy the array. Otherwise, all results point to the same array that gets modified during backtracking:

The [...path] spread creates a shallow copy of the array. This is the JavaScript equivalent of Java's new ArrayList<>(path).

Closures

Closures are uniquely powerful in JavaScript and relevant for memoization patterns:

You will not need closures for most standard DSA problems, but they come up in design problems (like LRU Cache) and can make memoization code cleaner.

Arrow functions and destructuring make JavaScript particularly elegant for backtracking and callback patterns. The spread operator [...path] is the idiomatic way to copy arrays in JS, and destructuring const [a, b] = pair makes working with tuples clean and readable.

Arrays

Arrays are the backbone of DSA in JavaScript. They are dynamic (no fixed size), zero-indexed, and come with a rich set of built-in methods. You will use arrays in virtually every problem.

Creation and Initialization

The .length property (no parentheses, just like Java arrays) gives the current size:

2D Array Initialization

This is one of the most common JavaScript bugs in interviews:

The 2D array initialization gotcha is one of the most common JavaScript DSA mistakes. fill(new Array(...)) evaluates new Array(...) once and fills every slot with a reference to that single array. Use Array.from() with a mapping function to create independent row arrays. This is the kind of bug that silently produces wrong answers and wastes precious interview time debugging.

Essential Array Methods

MethodMutates?ReturnsTimeDSA Use
push(val)Yesnew lengthO(1)Append to end (stack push)
pop()Yesremoved elementO(1)Remove from end (stack pop)
shift()Yesremoved elementO(n)Remove from front (queue dequeue)
unshift(val)Yesnew lengthO(n)Add to front
splice(i, count, ...items)Yesremoved elementsO(n)Remove/insert at index
slice(start, end)Nonew arrayO(k)Copy a portion
concat(arr2)Nonew arrayO(n+m)Merge arrays
reverse()Yessame arrayO(n)Reverse in place
includes(val)NobooleanO(n)Linear search
indexOf(val)Noindex or -1O(n)Find first occurrence
fill(val, start, end)Yessame arrayO(n)Fill range with value
flat(depth)Nonew arrayO(n)Flatten nested arrays

Pay attention to the Mutates? column. Methods like sort(), reverse(), splice(), fill() modify the array in place. Methods like slice(), concat(), map(), filter() return new arrays.

Stack Operations with Arrays

Arrays are your stack in JavaScript. No need for a separate class:

Queue Operations: The O(n) Trap

Here is where JavaScript's limitations show. The naive approach uses shift() as dequeue:

For BFS on a graph with 100,000 nodes, using shift() makes the algorithm O(n^2) instead of O(n). We will cover a proper queue implementation in the Collections section.

That said, for problems with small inputs (n < 10,000), shift() is pragmatically acceptable on LeetCode. V8's internal optimizations make the constant factor small. Use your judgment based on the problem constraints.

The 4-Directional Neighbor Pattern

Grid problems (number of islands, shortest path in maze) need to visit adjacent cells. This pattern appears in almost every grid BFS/DFS:

Destructuring Swap

One of JavaScript's nicest features for DSA. No temporary variable needed:

Spread Operator for Copies

Array.from() Utility

Array.from() is one of the most versatile tools in JavaScript for DSA:

Strings

Strings in JavaScript are immutable, just like in Java. Every time you modify a string, JavaScript creates a new object. This has the same performance implication:

Unlike Java, JavaScript has no StringBuilder. The Array.push() + .join("") pattern is the standard alternative. Array join concatenates all elements into a single string with the separator between them.

No Char Type

JavaScript has no character type. A "character" is just a string of length 1:

This means you cannot compare characters with < and > in the numeric sense directly. Character comparisons work lexicographically as string comparisons, which is actually what you want for most DSA problems.

String Methods

MethodReturnsExampleDSA Use
s[i] or s.charAt(i)strings[0]Access character
s.lengthnumbers.lengthLoop bounds (property, no parens)
s.slice(start, end)strings.slice(0, 3)Substring (start inclusive, end exclusive)
s.split(sep)arrays.split(" ")Tokenize
s.indexOf(str)numbers.indexOf("ab")Find substring (-1 if not found)
s.includes(str)booleans.includes("ab")Substring check
s.startsWith(pre)booleans.startsWith("ab")Prefix check
s.endsWith(suf)booleans.endsWith("ab")Suffix check
s.repeat(n)string"ab".repeat(3)Repeat: "ababab"
s.trim()strings.trim()Remove surrounding whitespace
s.toLowerCase()stringCase conversion
s.toUpperCase()stringCase conversion
s.replace(a, b)stringReplace first occurrence
s.replaceAll(a, b)stringReplace all occurrences

Character Operations

Since JavaScript has no Character utility class like Java, you use charCodeAt() and String.fromCharCode():

Frequency Array Pattern

This is faster and more memory-efficient than Map when the character set is known and small (lowercase letters, digits, etc.).

Template Literals for Composite Keys

Template literals (backtick strings) are one of JavaScript's most useful features for DSA:

The template literal ` ${row},${col} ` pattern is extremely common in JavaScript for creating composite keys in Sets and Maps. In grid BFS problems, you will use this to track visited cells. It is clean, readable, and idiomatic JavaScript.

Collections: Map, Set, and Object

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. JavaScript's collection landscape is simpler than Java's, but it has its own quirks.

Map: The HashMap Equivalent

ES6 Map is JavaScript's hash map. It offers O(1) average-case get, set, has, and delete:

Map vs Object

Plain objects ({}) can also serve as hash maps, but with limitations:

FeatureMapObject
Key typesAny (numbers, objects, arrays)Strings and Symbols only
Size.size propertyObject.keys(obj).length
Iteration orderInsertion order guaranteedMostly insertion order
PerformanceOptimized for frequent adds/deletesOptimized for static structures
Default keysNoneHas prototype properties
DSA recommendationDefault choiceFine for simple string-key frequency maps

Use Map as your default. Use objects only when you are counting frequencies of characters or short strings and want the most concise code.

Set: The HashSet Equivalent

Unlike Java's Set.add() which returns a boolean indicating whether the element was new, JavaScript's Set.add() returns the Set itself. To check if an element was already present, call has() before add():

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: visited.add(${row},${col}).

Queue Implementation

JavaScript has no built-in queue with O(1) dequeue. Here is a lightweight 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: Must Implement Yourself

JavaScript 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 minimal implementation:

Usage:

In an interview, mention upfront that JavaScript lacks a built-in PriorityQueue. Most interviewers will either let you use a simplified version or accept that you know the heap interface and can implement it if needed.

No TreeMap / TreeSet

JavaScript 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 JavaScript interview, mention the missing data structure and discuss the trade-off with the interviewer.

Collections Quick Reference

CollectionTypeKey MethodsTime ComplexityDSA Use
ArrayDynamic arraypush, pop, shift, splice, slice, sortpush/pop O(1), shift O(n), access O(1)Stacks, results, DP tables
MapHash mapset, get, has, delete, sizeO(1) averageFrequency counting, two-sum
SetHash setadd, has, delete, sizeO(1) averageVisited tracking, dedup
ObjectString-key mapbracket access, in, Object.keysO(1) averageSimple frequency maps
Queue (custom)FIFO queueenqueue, dequeue, peek, sizeO(1) allBFS
MinHeap (custom)Priority queuepush, pop, peek, sizeO(log n) push/popTop-K, Dijkstra

Sorting and Comparators

Sorting is fundamental to DSA. And JavaScript has the single biggest sorting gotcha of any mainstream language.

The Default Sort Trap

Array.sort() converts elements to strings and sorts lexicographically by default. The string "10" comes before "9" because "1" < "9" in Unicode code points. Always pass a comparator for numeric sorting:

Comparator Pattern

The comparator function takes two elements and returns:

  • Negative: a comes first
  • Zero: order does not matter
  • Positive: b comes first

Sort Mutates the Original Array

Unlike methods like map and filter which return new arrays, sort() modifies the array in place and returns the same array reference. If you need to preserve the original:

Stability

Array.sort() is guaranteed stable in all modern JavaScript engines since ES2019. This means elements with equal sort keys maintain their original relative order. The algorithm used is TimSort (same as Java's Collections.sort() and Python's sorted()).

Overflow Warning

The (a, b) => a - b comparator can theoretically overflow if a and b are very large numbers of opposite sign. In practice, this is extremely rare in DSA because JavaScript numbers are doubles with huge range. But if you need to be safe:

Functional Patterns for DSA

JavaScript's functional array methods can make DSA code more concise. Use them when they improve readability, but do not force them when a simple loop is clearer.

map, filter, reduce

Frequency Counting Patterns

Adjacency List Building

Destructuring in Iterations

Array.from() for Generation

Null and Undefined Handling

JavaScript has two "nothing" values, and understanding the difference matters for DSA.

  • `null`: Intentional absence of a value. Used as tree/linked list terminators. You set this explicitly.
  • `undefined`: Variable declared but not assigned, missing object/map property, or function with no return value. The engine sets this.

In DSA, null appears when a tree node has no left or right child, or when a linked list ends. undefined appears when you call map.get(key) for a missing key or access an array index out of bounds.

Three Rules for Null Safety

Rule 1: Guard clauses for edge cases.

Note: !null and !undefined are both true, so if (!root) catches both.

Rule 2: Use `??` for defaults, not `||`.

If the map stores 0 as a legitimate value, || 0 would incorrectly treat it as missing. The ?? operator only triggers for null and undefined.

Rule 3: Optional chaining for safe navigation.

Iterating and Modifying Collections Safely

Unlike Java, JavaScript does not throw a ConcurrentModificationException when you modify a collection during iteration. But modifying arrays during iteration still causes subtle bugs.

Arrays: Use Backward Loop for Safe Removal

Map and Set: Safe Deletion During Iteration

Unlike Java, JavaScript's Map and Set allow you to delete the current element during for...of iteration. This is part of the ES6 specification:

Best Practice

In DSA, you rarely need to modify collections during iteration. Most problems build new collections from existing ones. When you do need to remove elements, prefer building a new array with filter() or using the backward loop pattern.

Recursion and the Call Stack

Recursion is fundamental to DSA: tree traversals, DFS, backtracking, and divide-and-conquer all rely on it. JavaScript handles recursion the same as other languages, with one important limitation.

Stack Depth Limit

V8 (the engine behind Chrome and Node.js) has a call stack limit of approximately 10,000 to 15,000 frames, depending on the size of each frame. Unlike Python, there is no way to increase this limit at runtime. Unlike Java, which has a relatively generous stack size, JavaScript's limit is fixed.

ScenarioTypical DepthRisk
Balanced binary treeO(log n)Safe for n up to 10^6
Linked list / skewed treeO(n)Dangerous if n > 10,000
BacktrackingO(d)Usually safe (d is decision depth)
DFS on graphO(n)Dangerous for large n

Converting Recursive DFS to Iterative

When the input is large and recursion depth could exceed 10,000, convert to an iterative approach with an explicit stack:

No Tail Call Optimization in Practice

JavaScript's ES6 specification includes tail call optimization (TCO), but only Safari implements it. Chrome and Node.js do not. This means even "tail-recursive" functions will still add a frame per call. Always assume recursion adds to the stack.

Graph Representation Patterns

Graphs appear in a huge number of DSA problems: shortest path, connected components, topological sort, and more. The two main representations map directly to when each is appropriate.

Approach 1: Array of Arrays (Contiguous Node IDs 0 to n-1)

When nodes are numbered 0 through n-1, use an array:

Approach 2: Map (Sparse or Non-Contiguous Node IDs)

When node IDs are not contiguous (like string labels or large integers):

Weighted Graphs

Store edge weights alongside destination nodes using small arrays:

Comparison

FeatureArray of ArraysMap
Node IDsMust be 0 to n-1Any type (int, string)
MemoryCompact, no hash overheadHash table overhead
Access speedO(1) direct indexO(1) average (hash lookup)
Use whenNodes are numbered, denseNodes are sparse, string labels
Most problemsThis oneGraph with city names, etc.

Pair and Tuple Alternatives

JavaScript has no Pair or Tuple type, but the language provides clean alternatives through arrays and destructuring.

Option 1: Arrays (Most Common)

Option 2: Objects (When Clarity Matters)

Option 3: String Keys for Composite Map/Set Keys

Since arrays use reference equality ([1,2] !== [1,2]), you cannot use arrays directly as Map keys or Set values for deduplication. Serialize to strings:

Destructuring makes arrays the clear winner for tuples in JavaScript. const [a, b] = pair is cleaner and more concise than any alternative.

Deduplication Patterns

Two main approaches for avoiding duplicates in results.

Approach 1: Sort and Skip (Preferred for Sorted Input)

This pattern appears in problems like 3Sum, Combination Sum II, and Subsets II. The sort brings duplicates adjacent, and the skip condition ensures each unique value is processed only once at each position.

Approach 2: Set with Serialization

Since JavaScript Sets compare objects and arrays by reference, you must serialize to strings for deduplication:

This is useful when you cannot sort the input or when duplicate detection is not straightforward. But JSON.stringify and JSON.parse are slower than the sort-and-skip approach, so prefer Approach 1 when possible.

Common DSA Idioms in JavaScript

Element Swapping

Sentinel Values

Math Utilities

Warning about `Math.max()` and `Math.min()` with arrays:

Integer Ceiling Division Without Floating Point

Type Conversions

Deep Copy Patterns

Remember: [...arr] and slice() are shallow copies. For 2D arrays, you must map over rows and spread each one individually, or the inner arrays will still be shared references.

OOP Basics for Design Problems

JavaScript uses class syntax (ES6) for object-oriented code. In DSA, you need classes primarily for node definitions that LeetCode provides, and occasionally for design problems like LRU Cache.

Standard Node Classes

Notice that JavaScript constructors use default parameters (val = 0, next = null) instead of constructor overloading. This is cleaner than Java's multiple constructor pattern. A single constructor handles all cases.

LeetCode provides ListNode and TreeNode definitions for you. You do not need to write them yourself, but understanding the structure is essential.

Building Data Structures Concisely

No Access Modifiers

JavaScript has no private or public keywords in the traditional sense. Class fields declared with # prefix are truly private (ES2022), but this is never used in DSA. All properties are public and accessed directly:

This is the same convention as Java DSA code, where fields are package-private for conciseness.