-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
179: Big-O Notation with JavaScript Examples
I'll be honest: when I first encountered Big-O notation in university, it felt like a math class masquerading as a coding lesson. It seemed academic and disconnected from the actual work of building apps. But once I started working with datasets that weren't just 10 items long, Big-O became the only way to explain why my app was freezing or why a server was crashing under load.
At its core, Big-O isn't about timing a function with a stopwatch. It's about scaling. We're asking: "As the input (n) grows, how does the time or space required grow?"
The slow way to find a user
Let's build a simple system to find a user by their ID in a list. I'll start with the most intuitive approach: a simple loop. This is what most of us write first.
const users = [
{ id: 'a1', name: 'Alice' },
{ id: 'b2', name: 'Bob' },
{ id: 'c3', name: 'Charlie' },
// Imagine thousands more users here...
];
function findUser(id) {
for (let i = 0; i < users.length; i++) {
if (users[i].id === id) {
return users[i];
}
}
return null;
}
This is O(n), or linear time. If we have 10 users, we might look at 10 items. If we have a million users, we might look at a million items. The time it takes grows in a straight line relative to the size of the array. For a small app, this is totally fine. For a global platform, it's a disaster.
My detour into a costly "optimization"
Now, I remember doing this early in my career: I thought, "Wait, if I sort the list first, I can use a binary search to find the user much faster!" I figured that since binary search is O(log n), I was making the code more efficient.
function findUserOptimized(id) {
// I thought sorting first was a smart move
const sortedUsers = [...users].sort((a, b) => a.id.localeCompare(b.id));
// Binary search logic here...
// (Searching in O(log n) time)
}
Here was my mistake: I forgot that Array.prototype.sort() in JavaScript typically uses Timsort, which has a time complexity of O(n log n). By sorting the array every time I wanted to find a single user, I actually made the function slower than the simple loop. I traded an O(n) operation for an O(n log n) operation. Lesson learned: always look at the total cost of your "optimizations."
Trading memory for instant speed
If we're going to search this list frequently, the real pro move isn't to search the array at all. We change the data structure. By mapping the IDs to the user objects in a Map, we can achieve O(1), or constant time.
const userMap = new Map(users.map(user => [user.id, user]));
function findUserFast(id) {
return userMap.get(id) || null;
}
Now, it doesn't matter if we have 10 users or 10 million. Looking up a key in a Map takes roughly the same amount of time regardless of the size. We've achieved O(1) time complexity. The trade-off? We're using more memory to store that Map—this is what we call a space-time tradeoff. In 99% of modern web development, trading a bit of RAM for a massive speed boost is a deal I'll take every single time.
📋 Practical Task
Optimizing the Email Duplicate Detector
You have a function that checks if a list of emails contains any duplicates. Currently, it uses a nested loop, which makes it O(n²) (quadratic time). This is causing the browser to hang when processing lists of 10,000 emails.
Your task is to rewrite the hasDuplicates function to use a Set to track seen emails, bringing the time complexity down to O(n).
const emails = ['test@example.com', 'hello@world.com', 'test@example.com', 'dev@code.com'];
// CURRENT VERSION: O(n^2) - Too slow!
function hasDuplicates(list) {
for (let i = 0; i < list.length; i++) {
for (let j = i + 1; j < list.length; j++) {
if (list[i] === list[j]) {
return true;
}
}
}
return false;
}
// TODO: Rewrite this function using a Set to achieve O(n) complexity
function hasDuplicatesOptimized(list) {
// Your code here
}
console.log(hasDuplicatesOptimized(emails)); // Should return true
There are no comments for now.