JavaScript
Completed
-
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)
152: Lookahead and Lookbehind in JS Regex
I see this a lot when developers start parsing strings for data: they write a regex that finds exactly what they're looking for, but the regex "grabs" too much. They end up with a match that includes the anchor or label they used to find the value, and then they have to write extra code to trim or slice the result.
The "NaN" Currency Trap
const priceList = "Total: $120, Tax: $10, Shipping: $5";
const matches = priceList.match(/\$\d+/g);
// I want an array of numbers to sum them up
const totals = matches.map(price => parseInt(price));
console.log(totals); // [NaN, NaN, NaN]
If you've run into this, you know the frustration. The regex /\$\d+/g is doing exactly what we told it to: find a dollar sign followed by digits. The problem is that parseInt doesn't know how to handle that $ character. It sees the symbol at the start, gives up immediately, and returns NaN.
Usually, the "quick fix" is to use a capture group or call .slice(1) on every match. But that's messy. We're adding extra steps to clean up a match that should have been clean to begin with. We want the digits, but only if they are preceded by a dollar sign. We don't actually want the dollar sign in our result.
Using Positive Lookbehind for Clean Extraction
This is where Lookbehind comes in. A lookbehind tells the regex engine: "Check if this pattern exists behind the current position, but don't actually include it in the match."
The syntax is (?<=pattern). Let's rewrite that price extractor:
const priceList = "Total: $120, Tax: $10, Shipping: $5";
// This says: Match digits, but only if they follow a '$'
const matches = priceList.match(/(?<=\$)\d+/g);
const totals = matches.map(price => parseInt(price));
console.log(totals); // [120, 10, 5]
Notice the difference? The $ is still there in the string—the regex engine uses it as a landmark—but it isn't "consumed." The match only contains the digits. This makes the rest of your data pipeline much smoother because you aren't fighting with your own regex results.
Filtering Context with Positive Lookahead
Lookahead is the mirror image of lookbehind. It checks if a pattern follows the current position without including that pattern in the match. The syntax is (?=pattern).
I used this recently when parsing a configuration file where I needed to find IDs, but only if they were specifically marked as (active). If I used a standard match, I'd get the (active) string attached to every ID.
const config = "user_123(active), user_456(inactive), user_789(active)";
// Match digits only if they are followed by '(active)'
const activeUsers = config.match(/\d+(?=\(active\))/g);
console.log(activeUsers); // ["123", "789"]
If you're wondering why you wouldn't just use a capture group, remember that .match() with the /g flag ignores capture groups and returns the full match. Lookaheads allow you to filter your matches based on the surrounding context without polluting your result array.
Handling the "Not" Case (Negative Lookarounds)
Sometimes you need the opposite: you want to match something only if a certain pattern isn't there. This is called a Negative Lookaround.
- Negative Lookbehind:
(?<!pattern)— Match if the pattern does NOT precede. - Negative Lookahead:
(?!pattern)— Match if the pattern does NOT follow.
Imagine you want to find all numbers in a string, but you want to ignore any number that is specifically a price (preceded by $). You'd use a negative lookbehind:
const text = "The item costs $50, but the quantity is 2";
// Match digits, provided they are NOT preceded by '$'
const nonPrices = text.match(/(?
I'll leave you with a word of caution: lookarounds can be expensive for the regex engine if you're running them against massive blocks of text. They aren't "slow" in the traditional sense, but they add complexity to how the engine scans the string. Use them when they make your code more readable, but keep an eye on performance if you're processing megabytes of logs.
📋 Practical Task
Exercise: Parsing a System Log for Non-Error Codes
You are building a log analyzer. You have a string of system events. Each event has a code (a 3-digit number). Some codes are followed by the tag [CRITICAL], and some are followed by [INFO].
Your task is to extract all the 3-digit codes that are NOT critical. Use a Negative Lookahead to ensure that any number followed by [CRITICAL] is ignored, while numbers followed by [INFO] (or nothing at all) are captured.
const logData = "101[INFO], 404[CRITICAL], 200[INFO], 500[CRITICAL], 302[INFO], 999";
// Your code here:
const regex = // ... write your regex here
const safeCodes = logData.match(regex);
console.log(safeCodes);
// Expected Output: ["101", "200", "302", "999"]
There are no comments for now.