search()
ES3+Executes a regular-expression search and returns the index of the first match, or -1 when no match exists. It is the pattern-powered counterpart of indexOf(): the same answer shape, but the needle is a regex rather than a literal string. The global flag and lastIndex state of the regex are ignored, so it always searches from the start.
Syntax
string.search(regexp)Parameters
regexp RegExp A regular expression object
Return Value
The index of the first match, or -1 if not found
Examples
const str = 'Hello World';
console.log(str.search(/World/));
console.log(str.search(/xyz/)); 📌 When to Use
Use search() when you need the position of the first thing shaped like a pattern: the first digit in a product code, the first non-alphanumeric character in a username, the first whitespace in a command string, or where a case-insensitive keyword begins inside user text. It occupies a precise niche among its neighbors: indexOf() finds literal text but cannot express 'any digit'; test() answers whether a pattern matches but not where; match() and exec() return the matched text and groups but cost more and return structures you must unpack. When the question is exactly 'at what index does the pattern first match', search() is the direct tool, and its -1 not-found convention drops into code shaped around indexOf() without change. Two practical notes temper its use. First, it ignores both the global flag and lastIndex, always scanning from position zero and offering no fromIndex parameter, so scan-as-you-go parsing is better served by exec() on a sticky or global regex, which tracks position. Second, case-insensitive containment checks are often better expressed with search(/term/i) !== -1 than by lowercasing both sides, though building the regex from user input requires escaping metacharacters first. If you find yourself immediately slicing at the returned index to inspect the matched text, consider match() instead, which hands you the text and index together.
⚠️ Common Mistakes
Assuming a string argument is searched literally: search() converts non-regex arguments with new RegExp(), so 'price: 3.14'.search('3.14') matches '3x14' too, and user-supplied search text containing parentheses or brackets can throw a SyntaxError. Escape dynamic input or use indexOf() for literal needles.
Testing the result for truthiness: like indexOf(), a match at position 0 returns 0, which is falsy, and no match returns -1, which is truthy. The only correct checks are explicit comparisons such as !== -1 or === 0.
Expecting the global flag or lastIndex to matter: search() ignores both and always scans from the beginning, so it cannot resume where a previous search left off, and reusing a global regex with it neither advances nor respects lastIndex. Iterative scanning belongs to exec() or matchAll().
Using search() when the matched text is needed: it returns only an index, so following it with manual slicing to recover the match re-does work that match() would hand over directly, and the slice length is unknowable for variable-length patterns without re-matching.
Reaching for search() where a cheaper tool answers: for pure existence, test() communicates the intent and avoids implying the index matters; for literal substrings, indexOf() and includes() skip regex compilation entirely.
Forgetting that positions are UTF-16 code-unit indices: with emoji or other astral characters earlier in the string, the returned index will not equal the count of user-perceived characters, which matters when the index feeds cursor positioning or display logic.
✅ Best Practices
Match the tool to the question: test() for does-it-match, search() for where-does-it-match, match() or matchAll() for what-matched; each choice documents the code's actual need.
Compare explicitly against -1 or 0, and give the result a meaningful name like firstDigitAt, so the index semantics are impossible to misread as a boolean.
Escape regex metacharacters whenever the pattern is assembled from user input, or the search will misbehave on innocent punctuation and can throw on unbalanced brackets.
Hoist regex construction out of loops: new RegExp(keyword, 'i') built per item recompiles the pattern each time, and the compiled object is trivially reusable.
Anchor patterns when checking format prefixes, as in search(/^[A-Z]{2,3}\d/) === 0, though startsWith() or test() usually express fixed-prefix checks more directly.
For repeated position-tracked scanning through one large string, switch to exec() with a global or sticky regex, which continues from lastIndex instead of rescanning from the start.
⚡ Performance Notes
search() runs the regex engine from the start of the string and stops at the first match, allocating no match objects, which makes it lighter than match() for position-only questions and essentially the regex twin of indexOf(). Its cost is the pattern's cost: simple character classes scan linearly, while nested quantifiers and broad alternation can backtrack badly on adversarial input, so patterns applied to user text should be kept specific. Because it cannot resume from an offset, calling search() repeatedly to walk through a long document rescans the prefix on every call, degrading quadratically; exec() with a sticky or global regex tracks lastIndex and stays linear for that job. For literal substrings, indexOf() avoids compilation and the regex engine entirely and should win by default. Reusing one compiled RegExp across many search() calls avoids repeated compilation, the main avoidable overhead in loops.
🌍 Real World Example
Pattern-Based Position Finding
Five position-finding patterns. findKeywordPosition() locates a keyword case-insensitively by building a RegExp with the i flag, returning where it begins rather than merely whether it exists. findFirstDigit() and findFirstSpecialChar() answer 'where does the first character of this class appear', questions indexOf() cannot express, useful for splitting codes like 'ABC-123' or validating username characters. startsWithPattern() and hasValidPrefix() anchor the pattern and compare the result to 0, turning search() into a regex-powered startsWith() for format checks like two or three letters followed by digits. Together they map the method's niche: index answers to pattern-shaped questions.
// Case-insensitive search for keyword position
function findKeywordPosition(text, keyword) {
const pattern = new RegExp(keyword, 'i');
return text.search(pattern);
}
console.log(findKeywordPosition('Welcome to JavaScript!', 'javascript')); // 11
// Find first digit position in string
function findFirstDigit(str) {
return str.search(/\d/);
}
console.log(findFirstDigit('Order ABC-123')); // 10
console.log(findFirstDigit('No digits here')); // -1
// Check if string starts with specific pattern (similar to startsWith but with regex)
function startsWithPattern(str, pattern) {
const regex = new RegExp('^' + pattern);
return str.search(regex) === 0;
}
console.log(startsWithPattern('Hello World', 'Hello')); // true
console.log(startsWithPattern('Hello World', 'World')); // false
// Find position of first special character
function findFirstSpecialChar(str) {
return str.search(/[!@#$%^&*(),.?":{}|<>]/);
}
console.log(findFirstSpecialChar('Hello, World!')); // 5 (the comma)
// Validate string format by checking pattern position
function hasValidPrefix(code) {
// Code must start with 2-3 letters followed by digits
return code.search(/^[A-Z]{2,3}\d/) === 0;
}
console.log(hasValidPrefix('AB123')); // true
console.log(hasValidPrefix('ABC456')); // true
console.log(hasValidPrefix('123ABC')); // false