In the world of algorithmic challenges, LeetCode stands out as a premier platform for honing coding skills and preparing for technical interviews.
One of its classic problems, Two Sum, tasks participants with finding two numbers in an array that add up to a specific target.
While the problem might seem straightforward at first glance, there are multiple ways to approach it, each with its own trade-offs in terms of time and space complexity.
In this article, we’ll delve into two distinct solutions for the Two Sum problem: a naive implementation and an optimized one.

Our goal is to not only provide working solutions but also to shed light on the underlying principles of algorithm design, emphasizing the importance of understanding Big O notation and its implications for real-world applications.
Understanding the Two Sum Problem
Imagine you’re at a store, and you have a certain amount of money to spend. You see a lot of items with different prices. Your goal is to pick exactly two items such that their combined price matches the money you have.

The Two Sum problem is quite similar.
Given a list of numbers (like item prices) and a target number (like your money), you need to find which two numbers from the list add up to that target. The catch is, you can’t use the same number twice.
So, if you have $10 and you see two items priced at $5 each, you can pick both. But if there’s only one item priced at $5, you can’t pick it twice to make up the $10.
In coding terms, you’re given an array of integers and a target integer. Your task is to return the positions (or indices) of the two numbers in the array that add up to the target. Simple, right?
But as with shopping, the challenge is to do this efficiently, without having to try every possible combination.
Naive Implementation (Brute Force)
The most straightforward way to solve this problem is to use two nested loops to check every possible pair of numbers in the array.

This approach has a time complexity of O(n2) because for each element in the array, we are checking it against every other element.
TypeScript Implementation:
function twoSum(nums: number[], target: number): number[] {
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
return [i, j];
}
}
}
throw new Error("No solution found!");
}

Optimized Implementation (Using a Hash Map)
To optimize the solution, we can use a hash map (or an object in TypeScript/JavaScript) to store the numbers we’ve seen so far and their indices.
This way, for each number, we can check if its complement (i.e., target - number
) exists in the hash map in constant time O(1). This approach has a time complexity of O(n) because we are iterating through the array only once.
TypeScript Implementation:
function twoSum(nums: number[], target: number): number[] {
const numIndices: { [key: number]: number } = {};
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (complement in numIndices) {
return [numIndices[complement], i];
}
numIndices[nums[i]] = i;
}
throw new Error("No solution found!");
}

Think of the “Hash Map” pocket as a super-efficient organizer.

As you go through each number on the board, instead of manually checking every other number (which would be time-consuming), you quickly peek into this organizer.
If the complement of your number is already stored inside, you’ve found your pair!
Explanation in terms of Big O Notation
Naive Implementation
The time complexity is O(n2) because we are using two nested loops to check every possible pair of numbers.
The space complexity is O(1) because we are not using any additional data structures that grow with the input size.
Optimized Implementation
The time complexity is O(n) because we are iterating through the array only once.
The space complexity is O(n) because, in the worst case, we might have to store every number in the hash map.
Takeaways
Understanding time and space complexity is crucial when designing algorithms, especially for large datasets.

The naive implementation might work for small arrays, but it will be inefficient for larger ones.
It’s important to state that at best it’ll be inefficient and at worst your program will just crash.
The optimized solution, while using a bit more memory, is much faster and scales better with larger inputs.
Incremental Improvement
In code, everything is a tradeoff, so when determining your approach you can use leetcode to benchmark your changes. Here’s a code snippet that gets both memory and execution into the green.
function twoSum(nums: number[], target: number): number[] {
const numIndices: { [key: number]: number } = {};
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (numIndices.hasOwnProperty(complement)) {
return [numIndices[complement], i];
}
numIndices[nums[i]] = i;
}
throw new Error("No solution found!");
}
My goal was to get both runtime and memory into the green which isn’t always that easy.
Can you think of something that’ll make it even better?

Additional Resources: Dive Deeper into Algorithms and TypeScript
If you’re keen on expanding your knowledge and diving deeper into the world of algorithms and TypeScript, here are some highly recommended books available on Amazon.
NOTE: By using these affiliate links you can support continued efforts on adding content to this site:
- “Introduction to Algorithms” by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein
- A comprehensive guide that covers a wide range of algorithms in depth, yet makes their design and analysis accessible to all levels of readers.
- “You Don’t Know JS: Scope & Closures” by Kyle Simpson
- Part of the “You Don’t Know JS” series, this book dives deep into the core mechanisms of the JavaScript language, which is the foundation of TypeScript.
- “TypeScript Handbook” by Dan Wellman
- Build, scale and maintain Modern Web Applications with Typescript
- “Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People” by Aditya Bhargava
- A fully illustrated, friendly guide that teaches you how to apply common algorithms to the practical problems you face every day as a programmer.
- “TypeScript Quickly” by Yakov Fain and Anton Moiseev
- This book introduces you to TypeScript with lots of hands-on examples. You’ll start with the basics and then move on to more advanced topics as you explore TypeScript’s features.
- “The Algorithm Design Manual” by Steven S. Skiena
- A comprehensive manual that provides straightforward access to combinatorial algorithms technology, stressing design over analysis.
Remember, while books are a great resource, practice is key. Implementing what you learn, solving coding challenges, and building projects will solidify your understanding and enhance your skills.
Happy coding!