In our last two lessons, we established a framework for analyzing algorithmic efficiency through the lenses of time and space complexity. You learned how to assess an algorithm's scalability and decide if it's a viable approach based on input constraints. Now, we will connect this high-level analysis to the ground-level reality of the code you write every day.
This lesson focuses on the practical performance characteristics of TypeScript's most common built-in data structures. We will evaluate the specific time costs of operations on Array, Map, and Set. We'll also address the nuances of JavaScript's numeric types—Number and BigInt—which have important implications for correctness in algorithmic problems. Mastering this will enable you to make informed decisions when implementing an algorithm, choosing the data structure that provides the best performance for the task at hand. This is often the most critical step in translating a correct idea into a solution that passes the time limit.
The Cost of Array Operations
As a front-end developer, you work with arrays constantly. However, in the context of algorithms, not all array operations are created equal. Their performance depends on the underlying way arrays are typically implemented in memory: as a contiguous, ordered block. This structure makes some operations cheap and others surprisingly expensive.
Let's dive into a detailed breakdown of these costs. The following reading provides two excellent tables summarizing the time complexity of JavaScript's built-in methods.
Algorithms & Big O - 33 JavaScript Concepts
This resource from "33 JavaScript Concepts" provides a concise, well-organized summary of the Big O costs for essential data structure operations.
Please read two parts of this document. First, focus on the table under the heading Array Methods. Pay close attention to the "Why" column, as it explains the reasoning behind each complexity class. Then, scroll down to the Key Takeaways section and review the numbered list, which summarizes the most important rules for performance.
The key distinction to internalize is between operations at the ends of the array and those at the beginning or in the middle.
-
O(1)Operations:- Access (
arr[i]): Accessing an element by its index is a direct memory calculation, making it instantaneous. push()andpop(): Adding or removing from the end of the array is also constant time on average, as it doesn't require re-indexing other elements.
- Access (
-
O(n)Operations:shift()andunshift(): Adding or removing from the beginning is expensive. To addunshift(), every existing element must be shifted one position to the right. To removeshift(), every remaining element must be shifted one position to the left. The cost is proportional to the number of elements.
splice(): Similarly, inserting or deleting from the middle requires shifting all subsequent elements.
- Search (
includes(),indexOf(),find()): Without any other information, finding an element requires a linear scan, checking each item one by one.
This O(1) vs. O(n) difference is a frequent source of performance bottlenecks. A loop that repeatedly calls shift() on a large array can turn a seemingly fast algorithm into a slow one.
The Hashing Advantage: Map and Set
When you need fast lookups, insertions, and deletions, hash-based structures are the answer. In TypeScript/JavaScript, these are Map and Set. They work by using a hash function to convert a key into an index in an underlying array, allowing for near-instantaneous operations.
The following video from the Builder.io team gives an excellent, practical overview of why Map is often a better choice than a plain Object for dynamic collections, a distinction that is highly relevant in algorithmic problem-solving.
Use Maps more and Objects less
This video clearly explains the ergonomic and performance advantages of using Map and Set over plain Objects and Arrays for common algorithmic tasks.
Please watch the following segments: The core argument for using Maps when keys are added/deleted frequently. The problems with Objects, like prototype pollution and awkward iteration. The advantages of Maps, such as guaranteed order and the ability to use any object as a key. A brief mention of Sets and their performance benefits over arrays for uniqueness checks. Finally, the summary from When to use what, which provides clear guidelines.
Let's formalize the performance characteristics:
| Data Structure | Operation | Average Time Complexity | Why? |
|---|---|---|---|
Map | set, get, has, delete | O(1) | Direct computation via hashing. |
Set | add, has, delete | O(1) | Direct computation via hashing. |
Object | obj[key], delete obj[key] | O(1) | Also uses hashing, but with caveats. |
Array | includes, indexOf | O(n) | Linear scan required. |
The most important pattern to recognize is using a Set for fast membership testing. If you find yourself writing array.includes() inside a loop, you have likely found a major bottleneck. By first converting the array to a Set, you can change the lookups inside the loop from O(n) to O(1), often improving the overall algorithm from O(n^2) to O(n).
This cheat sheet provides a comprehensive visual summary of the time and space complexities for all the common data structures and sorting algorithms you'll encounter. It's an invaluable reference.

Numeric Precision and BigInt
A final, but critical, piece of the puzzle involves how TypeScript/JavaScript handles numbers. This is an area where your extensive front-end experience gives you an advantage, but it's worth reviewing in the specific context of algorithms.
Let's consult a clear guide on this topic.
JavaScript Crash Course for DSA
The "Data Types" section of this AlgoMaster guide offers a focused look at the numeric types in JavaScript and their implications for algorithms.
Please read the section on number and bigint. Focus on the three critical implications of all numbers being floating-point doubles. This covers integer division, precision limits, and how to handle them.
Here are the key takeaways for interview problems:
- Integer Division: JavaScript division
/always produces a float (e.g.,7 / 2is3.5). In algorithms that require integer indices, like binary search, you must explicitly useMath.floor()to truncate the result (e.g.,Math.floor(7 / 2)is3). - Precision Limits: All
numbertypes are stored as 64-bit floats, which can only safely represent integers up toNumber.MAX_SAFE_INTEGER(which is 2^53 - 1, or about 9 quadrillion). For most problems, this is fine. But if you're dealing with problems that might involve very large numbers (e.g., factorials, complex combinatorial counts, or large financial calculations), standard arithmetic can fail silently. BigInt: When you anticipate numbers exceedingNumber.MAX_SAFE_INTEGER, you must useBigInt. You create them by appendingnto an integer literal (e.g.,100n). Arithmetic operations betweenBigInts work as expected and maintain full precision, but you cannot mixBigIntandNumbertypes in the same operation without explicit conversion.
Practical Analysis: A LeetCode Example
Let's apply these concepts to a concrete piece of code. The image below shows a TypeScript solution to the "Plus One" problem, where a number represented as an array of digits (e.g., [1, 2, 3]) is incremented by one.

Let's analyze the time complexity of this plusOne function:
- The Loop: The code iterates through the
digitsarray from right to left. In the worst case (e.g., an input of[9, 9, 9]), it will visit every element once. This part isO(n). - Inside the Loop: The operations inside the loop are arithmetic and array indexing (
digits[i]), which are allO(1). - The
unshiftCall: The linedigits.unshift(1)is the most interesting. This operation is only called in the specific case where adding one causes a carry that propagates all the way to the beginning of the array (e.g.,[9, 9, 9]becomes[1, 0, 0, 0]). As we learned,unshiftis anO(n)operation because it requires shifting all existing elements.
Overall Complexity: The total time complexity is the sum of the loop and the potential unshift. Since both are O(n), the final time complexity is O(n). Although the unshift only happens in a specific scenario, we analyze based on the worst-case behavior.
Space Complexity: The algorithm modifies the input array in-place. It uses a few variables (rest, number, i, temp), which occupy constant space. Therefore, the auxiliary space complexity is O(1).
Conclusion
In this lesson, you've moved from abstract Big O notation to the concrete performance costs of the tools you use every day. Understanding these details is what separates a developer who can write working code from one who can write efficient code.
Key Takeaways:
- Array operations have varied costs:
push/popand index access areO(1), butshift/unshift/spliceandincludesareO(n). - Use
MapandSetfor speed: Their hash-basedO(1)average time for lookups, insertions, and deletions makes them superior for frequency counting and membership testing. - Respect numeric limits: Always use
Math.floor()for integer division in algorithms. Be mindful ofNumber.MAX_SAFE_INTEGERand switch toBigIntwhen dealing with potentially huge numbers. - Analyze library calls: The performance of your algorithm depends not just on your loops, but on the complexity of the built-in functions you call within them.
You now have the tools to identify the cost of each line of your code. In our next and final lesson of this module, we will learn how to use this knowledge to systematically improve an algorithm. You'll learn to start with a simple brute-force solution, identify its primary performance bottleneck, and then use a more appropriate data structure or technique to create an optimized solution.
Can't find a good explanation? Sign up and we'll make it for you
Sign up