JavaScript Check Substring: Test If a String Contains
Master how to check if a string contains a substring in JavaScript using includes, indexOf, or RegExp. Learn compatibility, edge cases, and code examples.

In JavaScript, you can check whether a string contains a substring using includes, indexOf, RegExp, or test. The simplest, most readable approach is s.includes(sub). For older environments, use indexOf(sub) !== -1. For pattern matching, use /pattern/.test(str) or a RegExp. For case-insensitive checks, normalize case first.
Fundamentals of string containment in JavaScript
String containment is a foundational check in many apps. When you need to determine whether user input contains a required token, or verify that a URL includes a parameter, the technique matters for readability and performance. In modern JavaScript, the preferred method is includes, which reads like natural language. The older indexOf pattern remains essential for environments lacking ES2016 support. For complex patterns, RegExp offers powerful capabilities. According to JavaScripting, start with includes and fall back to alternatives as needed.
const s = "The quick brown fox jumps over the lazy dog";
console.log(s.includes("brown")); // trueconsole.log(s.indexOf("brown") !== -1); // trueconsole.log(/fox/.test(s)); // trueNotes: includes returns boolean; RegExp provides flexible matching. For case-insensitive checks, normalize case first.
Choosing the right approach: includes vs indexOf vs RegExp
Choosing the right approach depends on readability, browser support, and pattern complexity. For simple substring checks, includes is clear and concise. If you must support environments that do not implement ES2016, fall back to indexOf === -1. For patterns, RegExp with test is the most flexible tool. JavaScripting analysis shows that combining a direct string check with a RegExp pattern covers most real-world scenarios.
"hello".includes("ell"); // true
"hello".indexOf("ell") !== -1; // true
/ell/.test("hello"); // trueHandling edge cases: non-string inputs and empty substrings
If you pass non-string values, a naive includes call may fail. A robust helper should validate inputs and handle empty substrings gracefully (an empty substring is contained in any string). The following function demonstrates guard checks and a safe containment test:
function contains(main, sub) {
if (typeof main !== 'string' || typeof sub !== 'string') return false;
return main.includes(sub);
}console.log(contains("", "")); // true
console.log(contains(null, "a")); // falseBrowser compatibility and polyfills
Many developers defer to a polyfill when targeting older browsers. The ES2016 includes method is not present in all environments, so a polyfill ensures consistent behavior without rewriting logic:
if (!String.prototype.includes) {
String.prototype.includes = function(search, start) {
'use strict';
if (search === undefined) return false;
if (start === undefined) start = 0;
return this.indexOf(search, start) !== -1;
};
}This snippet lets you keep using includes in older runtimes.
Case-insensitive matching strategies
For case-insensitive containment checks, normalize both sides to a common case or use a RegExp with the i flag. The simplest approach is to compare lowercase forms:
function containsCI(main, sub) {
if (typeof main !== 'string' || typeof sub !== 'string') return false;
return main.toLowerCase().includes(sub.toLowerCase());
}Real-world example: validating user input and search features
In real apps, you often validate input fields or implement search features that rely on substring checks. A small utility function can be reused across modules:
export function matchesQuery(text, query) {
if (typeof text !== 'string' || typeof query !== 'string') return false;
return text.includes(query);
}This keeps logic clear and testable, and makes refactoring safer when requirements evolve.
Performance considerations and alternatives
Contains checks run in linear time relative to the length of the main string. For extremely long strings, avoid repeated substring checks in hot loops. If you need multiple patterns, compile a single RegExp and reuse it instead of multiple includes calls. In practice, prefer includes for readability unless profiling shows a bottleneck.
Steps
Estimated time: 15-25 minutes
- 1
Define task and choose method
Identify whether you need a simple containment check or a pattern search, then pick the appropriate method (includes, indexOf, or RegExp).
Tip: Start with the simplest approach to maximize readability. - 2
Write a small helper
Implement a helper function that encapsulates your containment logic and type checks.
Tip: Encapsulation makes testing and reuse easier. - 3
Add tests for edge cases
Cover empty strings, non-string inputs, and case-insensitive scenarios.
Tip: Test both true and false outcomes. - 4
Consider compatibility
If you must support older runtimes, include a polyfill or fall back to indexOf.
Tip: Profile in your target browsers. - 5
Optimize for patterns
When patterns are complex, use RegExp with test and flags, caching results when possible.
Tip: Cache compiled RegExp if used repeatedly. - 6
Document and share
Document behavior (case sensitivity, empty substring rules) and share a reusable utility.
Tip: Keep a single source of truth.
Prerequisites
Required
- Required
- Required
- Basic knowledge of strings and arrays in JavaScriptRequired
- Familiarity with the console or browser DevToolsRequired
Optional
- Optional
Keyboard Shortcuts
| Action | Shortcut |
|---|---|
| Copy codeIn editor or terminal | Ctrl+C |
| Paste codeIn editor or terminal | Ctrl+V |
| Find textIn code editor or DevTools | Ctrl+F |
| Replace in editorIn editor search/replace | Ctrl+H |
| Toggle commentsIn editor | Ctrl+/ |
Questions & Answers
Does String.prototype.includes work in all environments?
Includes is ES2016. Many modern environments support it, but older browsers may lack it. Use a polyfill or a safe fallback with indexOf for compatibility.
Includes is widely supported now, but you might need a polyfill for very old browsers.
How can I perform case-insensitive substring checks?
Convert both strings to the same case using toLowerCase or toUpperCase, then perform the containment check. RegExp with the i flag is another option.
Convert both to the same case or use a RegExp with i to ignore case.
When should I use RegExp over includes or indexOf?
Use RegExp when you need pattern matching, wildcards, or capture groups. For simple substrings, includes remains clearer and faster.
RegExp is best for patterns; includes is best for plain text.
What about null or undefined inputs?
Guard against non-string values by validating input types before applying containment checks. Returning false is a safe default in many utilities.
Always validate inputs to avoid runtime errors.
How to test substring checks efficiently?
Write unit tests for typical cases, edge cases, and performance scenarios. Reuse a small helper function across modules to keep tests maintainable.
Test with unit tests and keep tests reusable.
What to Remember
- Use includes for simple substring checks
- Fallback to indexOf for old environments
- Normalize case for case-insensitive checks
- RegExp offers flexible pattern matching
- Guard inputs to ensure string types