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.

JavaScripting
JavaScripting Team
·5 min read
Substring Check Guide - JavaScripting
Quick AnswerDefinition

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.

JavaScript
const s = "The quick brown fox jumps over the lazy dog"; console.log(s.includes("brown")); // true
JavaScript
console.log(s.indexOf("brown") !== -1); // true
JavaScript
console.log(/fox/.test(s)); // true

Notes: 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.

JavaScript
"hello".includes("ell"); // true "hello".indexOf("ell") !== -1; // true /ell/.test("hello"); // true

Handling 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:

JavaScript
function contains(main, sub) { if (typeof main !== 'string' || typeof sub !== 'string') return false; return main.includes(sub); }
JavaScript
console.log(contains("", "")); // true console.log(contains(null, "a")); // false

Browser 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:

JavaScript
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:

JavaScript
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:

JavaScript
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. 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. 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. 3

    Add tests for edge cases

    Cover empty strings, non-string inputs, and case-insensitive scenarios.

    Tip: Test both true and false outcomes.
  4. 4

    Consider compatibility

    If you must support older runtimes, include a polyfill or fall back to indexOf.

    Tip: Profile in your target browsers.
  5. 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. 6

    Document and share

    Document behavior (case sensitivity, empty substring rules) and share a reusable utility.

    Tip: Keep a single source of truth.
Pro Tip: Use includes for readability; switch to indexOf only for older environments.
Warning: Avoid calling toLowerCase on null or undefined values to prevent runtime errors.
Note: RegExp is powerful for complex patterns but may be slower on long strings.

Prerequisites

Required

Keyboard Shortcuts

ActionShortcut
Copy codeIn editor or terminalCtrl+C
Paste codeIn editor or terminalCtrl+V
Find textIn code editor or DevToolsCtrl+F
Replace in editorIn editor search/replaceCtrl+H
Toggle commentsIn editorCtrl+/

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

Related Articles