-
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)
227: Implementing Array and String Algorithms in JavaScript
A few years ago, I was reviewing a PR for a teammate who was building a search feature for a massive inventory of product SKUs. He had written a function to find the first non-repeating character in a string—basically a way to identify a specific unique identifier in a batch of codes. His solution was simple: he looped through the string, and for every character, he used indexOf() and lastIndexOf() to see if they matched. On his machine, with a test string of ten characters, it was instantaneous. But when we pushed it to staging with a dataset of 100,000 characters, the browser tab completely locked up. He had accidentally created an $O(n^2)$ time complexity nightmare. He didn't need a faster computer; he needed a better algorithm.
Optimizing Search with Frequency Maps
The most common mistake I see when developers tackle string or array algorithms is relying too heavily on nested loops. When you find yourself putting a search method (like includes, indexOf, or find) inside another loop, a red flag should go up in your head. In the case of the "first unique character" problem, the trick is to trade a little bit of memory for a lot of speed.
Instead of scanning the string over and over, we can use a Map (or a plain JavaScript object) to create a frequency count. We traverse the string once to count every occurrence, and then traverse it a second time to find the first character that has a count of one. This turns a quadratic time operation into a linear one.
function findFirstUniqueChar(text) {
const charCount = new Map();
// First pass: Build the frequency map
for (const char of text) {
charCount.set(char, (charCount.get(char) || 0) + 1);
}
// Second pass: Find the first char with a count of 1
for (const char of text) {
if (charCount.get(char) === 1) {
return char;
}
}
return null; // No unique character found
}
console.log(findFirstUniqueChar("stress")); // "t"
The Power of Two-Pointer Logic
Another pattern that will save you from writing inefficient code is the "two-pointer" technique. I use this constantly when dealing with sorted arrays. Instead of using nested loops to find a pair of elements that meet a certain condition, you place one pointer at the start and one at the end, then move them toward each other based on the result.
Take the "Two Sum" problem on a sorted array. If you're looking for two numbers that add up to a specific target, you don't need to check every possible combination. If the sum of your current pointers is too high, you move the right pointer inward to decrease the sum. If it's too low, you move the left pointer inward to increase it. It's a clean, elegant way to avoid the $O(n^2)$ trap.
function findPairWithSum(sortedArr, target) {
let left = 0;
let right = sortedArr.length - 1;
while (left < right) {
const currentSum = sortedArr[left] + sortedArr[right];
if (currentSum === target) {
return [sortedArr[left], sortedArr[right]];
} else if (currentSum < target) {
left++; // Need a larger sum
} else {
right--; // Need a smaller sum
}
}
return null;
}
const numbers = [10, 20, 35, 50, 75, 110];
console.log(findPairWithSum(numbers, 90)); // [20, 70] - wait, 20+70 isn't here, [15, 75]... actually [15, 75] isn't here either. Let's use [20, 70] if 70 was there, or [15, 75]. With the array above: [15, 75] is not possible, but [10, 110] is 120. Let's try target 65: [15, 50]... wait, if the array is [10, 20, 35, 50, 75, 110], and target is 85, it returns [10, 75].
I'll be honest: in a small project, the difference between $O(n)$ and $O(n^2)$ might not be noticeable. But as a professional, your goal isn't just to make it "work"—it's to make it scale. When you start thinking in terms of pointers and maps rather than just "loops within loops," you stop writing code that crashes in production.
📋 Practical Task
Implementing a Longest Substring Without Repeating Characters Finder
Your task is to write a function called findLongestUniqueSubstring(str). This function should take a string and return the length of the longest substring that contains no repeating characters.
To do this efficiently, avoid nested loops that restart the search from every index. Instead, try implementing a "sliding window" approach: use two pointers to maintain a window of unique characters, and move the left pointer forward whenever you encounter a character that is already inside your current window.
Requirements:
- The function must handle empty strings (return 0).
- It should be case-sensitive (e.g., "A" and "a" are different characters).
- Aim for a linear time complexity $O(n)$.
findLongestUniqueSubstring("abcabcbb")should return3("abc").findLongestUniqueSubstring("bbbbb")should return1("b").findLongestUniqueSubstring("pwwkew")should return3("wke").
There are no comments for now.