Checking if one string contains another (substring search) remains one of the most common string operations in JavaScript development. Whether you’re validating user input, implementing search filters in React/Vue apps, parsing API responses, building autocomplete features, or processing logs in Node.js, this check appears everywhere.
As of February 2026, ECMAScript 2026 (ES2026) is the current or imminent standard (finalized mid-2026). The core String.prototype methods for substring detection — includes(), indexOf(), search(), and regex .test() — remain unchanged from ES2015/ES6 onward. No major new string search methods landed in ES2025 or ES2026; focus stays on performance, readability, Unicode correctness, and modern best practices in engines like V8 (Node 22+/Chrome 130+), SpiderMonkey (Firefox 135+), and JavaScriptCore (Safari 19+).
This guide covers every practical technique, performance realities in 2026, edge cases, security considerations, and production-ready recommendations.
Why Substring Checks Are Everywhere
- UI/UX: Live search, tag filtering, form validation
- backend: Log parsing, route matching, content moderation
- Data processing: Filtering arrays of strings/objects
- Seguridad: Detecting malicious patterns (XSS payloads, forbidden words)
- Performance-sensitive: Autocomplete (millions of checks/sec), real-time monitoring
Small inefficiencies multiply quickly in loops or large datasets.
1. Modern Standard: String.prototype.includes()
The go-to method since ES2015 — clean, intent-revealing, and heavily optimized.
Signature
js str.includes(searchString, position?)
searchString:substring to find (coerced to string; RegExp throws)position(optional): starting index (default 0; clamped ≥0)
Returns: boolean
Ejemplos
js
const phrase = "JavaScript in 2026 is powerful and fast";
console.log(phrase.includes("2026")); // true
console.log(phrase.includes("2025")); // false
console.log(phrase.includes("script")); // true (case-sensitive)
console.log(phrase.includes("")); // true ← empty always matches
console.log(phrase.includes("fast", 30)); // true
console.log(phrase.includes("fast", 40)); // falseWhy prefer includes() in 2026?
- Maximum readability: clearly expresses “does it contain?”
- Engine-optimized (V8/SpiderMonkey use fast Boyer-Moore or two-way algorithms)
- Universal support: 100% in modern browsers/Node since ~2017
Downsides
- Only boolean (no index)
- Always case-sensitive
2. Classic & Position-Aware: String.prototype.indexOf()
Un caballo de batalla anterior a ES6, que sigue siendo excelente cuando necesitas la ubicación.
Signature
js str.indexOf(cadenaDeBúsqueda, índiceInicial?)
Returns: primer índice ≥ desdeÍndice o -1
Comprobación de existencia
js if (frase.indexOf("2026") !== -1) { /* encontrado */ }
// Estilo preferido:
if (frase.indexOf("2026") >= 0) { /* encontrado */ }Buscar todas las apariciones
js let posiciones = []; let idx = -1; while ((idx = frase.indexOf("a", idx + 1)) !== -1) { posiciones.push(idx); } console.log(posiciones); // [1, 4, 11, ...]Realidad en 2026: includes() y indexOf() tienen una velocidad casi idéntica para las comprobaciones booleanas: elija includes() para mayor claridad, a menos que necesites el índice.
3. Comprobaciones que no distinguen entre mayúsculas y minúsculas (la necesidad más frecuente en el mundo real)
No existe ningún indicador nativo que ignore las mayúsculas y minúsculas para includes()/indexOf().
Mejor práctica 2026: Normalizar el caso
js
function containsIgnoreCase(text, term) {
if (text == null || term == null) return false;
return text.toLowerCase().includes(term.toLowerCase());
// O con reconocimiento de configuración regional (recomendado para producción):
// text.toLocaleLowerCase('es').includes(term.toLocaleLowerCase('es'));
} console.log(contieneIgnorarMayúsculas(frase, "POTENTE")); // true¿Por qué? toLowerCase() normalmente gana
- Más rápido que las expresiones regulares para búsquedas literales.
- Las cadenas temporales son baratas en el GC moderno.
- Evita la sobrecarga de compilación/escape de expresiones regulares.
Precaución local — Turco ("I".toLowerCase() → "ı") u otros idiomas pueden sorprender. Utilizar toLocaleLowerCase() con configuración regional explícita cuando la internacionalización es importante.
Alternativa a Regex (flexible pero más lenta)
función js contieneIgnoreCaseRegex(texto, término) { si (!término) devuelve verdadero;
// Escapar caracteres especiales si el término proviene del usuario.
const escapado = término.reemplazar(/[.*+?^${}()|[\]\\]/g, '\\$&'); devolver nuevo RegExp(escapado, 'i').probar(texto); }O una sola línea (solo término confiable):
js /2026/i.test(frase); // true
Cuándo elegir expresiones regulares
- Se necesitan límites de palabras (
\bterm\b) - Alternancia (
gato|perro) - Lookarounds u otros patrones
Consejo de rendimientoCompilar expresión regular una vez bucles externos/rutas calientes.
4. Otros métodos y cuándo utilizarlos
String.prototype.search(regexp)
Returns first match index or-1. Rarely used today —includes()o.test()are clearer.String.prototype.match()/matchAll()
For extracting matches, not simple existence.startsWith()/endsWith()(ES2015)
Specialized checks — faster for prefix/suffix.
js
phrase.startsWith("Java"); // true
phrase.endsWith("fast"); // true5. Performance in 2026 Engines
Approximate relative speeds (V8/Node 22+, large string ~10k chars, literal search):
| Method | Rel. Speed | Lo mejor para | Notes |
includes() literal | 1.0× | Simple yes/no | Top choice |
indexOf() | ~1.0–1.02× | Need index or multiple finds | Same family as includes |
toLowerCase() + includes() | 0.65–0.80× | Case-insensitive literal | Two temp strings but fast |
/literal/i.test() | 0.25–0.45× | Case-insens. or boundaries | Regex overhead |
new RegExp(escaped, 'i').test() | 0.20–0.40× | Dynamic/sanitized input | Escape cost + regex |
Key takeaway: Utilizar includes() for literals. Normalize case for ignore-case unless regex features are required. Regex is 2–5× slower but acceptable unless in tight loops.
6. Edge Cases & Gotchas (Critical in 2026)
includes("") → true(empty substring matches everywhere)null/undefinedhaystack/needle → TypeError- Surrogate pairs & emojis → handled correctly (UTF-16)
js "Hello 🌍 2026".includes("🌍"); // true - Negative
position →treated as 0 - Very long strings → engines use efficient algorithms (average O(n))
- User input → never build regex from raw user strings without escaping
7. Production Helpers & Best Practices
Null-safe utility (TypeScript-friendly):
ts
function contains(
haystack: string | null | undefined,
needle: string | null | undefined,
options: { ignoreCase?: boolean; from?: number } = {}
): boolean {
if (haystack == null || needle == null) return false;
const { ignoreCase = false, from = 0 } = options;
const h = ignoreCase ? haystack.toLowerCase() : haystack;
const n = ignoreCase ? needle.toLowerCase() : needle;
return h.includes(n, from);
}
// Usage
contains("JavaScript 2026", "script", { ignoreCase: true }); // true2026 Recommendations
- Default to
includes()para mayor claridad - Normalize case instead of regex for simple ignore-case
- Cache regex patterns
- Measure real workloads (avoid micro-benchmark obsession)
- Use libraries (lodash
_.includes, Fuse.js) only when you need fuzzy/advanced search
Conclusión
In 2026, checking whether a string contains a substring in JavaScript remains a deceptively simple operation powered by highly optimized APIs. String.prototype.includes() continues to be the preferred choice for most scenarios — offering clarity, performance, and expressive readability. For case-insensitive checks, normalizing with toLowerCase() (or locale-aware alternatives when necessary) provides a reliable and efficient approach. Regular expressions should be reserved for situations requiring pattern-level flexibility beyond literal matching.
Common patterns developers rely on:
- Literal boolean check →
str.includes(sub) - With position/index →
str.indexOf(sub) >= 0 - Ignore case →
str.toLowerCase().includes(sub.toLowerCase()) - Complex needs → Cached
RegExp.test()
Selecting the appropriate method and accounting for edge cases — such as empty values, null inputs, Unicode handling, and user-generated data — ensures code that is both resilient and scalable.
Carmatec helps organizations build high-performance JavaScript-driven applications where attention to such foundational details contributes directly to reliability, speed, and maintainability at scale.