jsguides

Array.prototype.includes()

includes(searchElement, fromIndex?)

The includes() method determines whether an array includes a certain element, returning true or false as appropriate. It provides a cleaner, more readable alternative to indexOf() >= 0 for checking element existence.

Syntax

array.includes(searchElement)
array.includes(searchElement, fromIndex)
  • searchElement: The value to search for in the array.
  • fromIndex (optional): The position in the array to start searching from. Defaults to 0. Negative values count from the end.
  • Returns: true if the element is found, false otherwise.

Both parameters accept any JavaScript value. The comparison uses SameValueZero rather than strict equality, so NaN matches itself — a key difference from indexOf(). The fromIndex parameter lets you skip an initial segment of the array, which is useful when processing chunks.

TypeScript Signature

interface Array<T> {
  includes(searchElement: T, fromIndex?: number): boolean;
}

TypeScript narrows the search element type to match the array’s element type, catching mismatches at compile time. If you pass a value of an incompatible type, the type checker flags it before runtime. This is a small but useful safety net when refactoring code that relies on presence checks.

Basic Examples

const fruits = ["apple", "banana", "cherry", "date"];

// Check if element exists
console.log(fruits.includes("banana"));   // true
console.log(fruits.includes("grape"));    // false

// Case-sensitive search
console.log(fruits.includes("Apple"));    // false

// Works with primitives
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.includes(3));          // true
console.log(numbers.includes(10));        // false

The examples above all search from the beginning of the array. When you need to search only a portion — for instance, after a known prefix or when processing records in batches — the second fromIndex parameter narrows the scan range. Negative values count backward from the end, so -2 means “start two positions from the last element.”

Using fromIndex

const items = ["a", "b", "c", "b", "a"];

// Start searching from index 2
console.log(items.includes("b", 2));       // true (found at index 3)

// Start from index 4 (past the second "b")
console.log(items.includes("b", 4));       // false

// Negative fromIndex
console.log(items.includes("c", -1));      // false (searches from index 3)
console.log(items.includes("c", -2));      // true (searches from index 2)

The fromIndex parameter is especially helpful when you need to find duplicate occurrences in a long array. After finding the first match at index i, calling includes(value, i + 1) checks whether the same value appears again further into the array, starting just past the previous match.

Common Patterns

Checking before adding

const tags = ["javascript", "python", "rust"];

function addTag(tag) {
  if (!tags.includes(tag)) {
    tags.push(tag);
    console.log(`Added: ${tag}`);
  } else {
    console.log(`${tag} already exists`);
  }
}

addTag("go");       // Added: go
addTag("javascript"); // javascript already exists

The guard pattern above prevents duplicate entries. You can apply the same idea to any collection where uniqueness matters — tag lists, selected options, or tracking IDs. When the array is small the linear scan is cheap; for large collections, switch to a Set and use has() for O(1) lookups.

Simple presence check

const allowedRoles = ["admin", "moderator", "user"];

function hasAccess(role) {
  return allowedRoles.includes(role);
}

console.log(hasAccess("admin"));      // true
console.log(hasAccess("guest"));      // false

This pattern works well for role-based checks. The array is an allowlist, so adding or removing permissions means changing the array contents, not rewriting conditional logic. Keep in mind that includes() does a linear scan — if the allowlist grows into hundreds of entries, Set offers better lookup speed.

NaN Handling

Unlike indexOf(), includes() correctly handles NaN:

const numbers = [1, NaN, 3, 4];

// indexOf fails to find NaN
console.log(numbers.indexOf(NaN));    // -1

// includes finds NaN
console.log(numbers.includes(NaN));   // true

This is the most common reason to choose includes() over indexOf(). If your data pipeline might produce NaN — from failed parse operations, division by zero, or math on undefined inputs — indexOf() silently misses it. includes() finds it correctly, so you don’t need a separate Number.isNaN() check in the loop.

Object Identity

includes() uses strict equality (===) for comparison. Objects are compared by reference, not value:

const users = [{ id: 1 }, { id: 2 }];
const newUser = { id: 1 };

console.log(users.includes(newUser));  // false (different reference)
console.log(users.includes(users[0])); // true (same reference)

Objects are matched by reference identity, not by structural equality. If you need to check whether an array contains an object with specific property values, use find() or some() with a predicate instead. A common mistake is creating a new object and expecting includes() to match it against an existing one with the same shape. The comparison is a single === check per element, so two objects with identical properties but different references will never match. For deep equality checks on objects, reach for a utility like Lodash’s isEqual or write a custom comparator.

Edge Cases

const arr = [1, 2, 3];

// Empty array search
console.log([].includes(1));          // false

// Undefined and null
console.log([undefined, null].includes(undefined)); // true
console.log([undefined, null].includes(null));      // true

// Sparse arrays
console.log([1, , 3].includes(undefined)); // true
console.log([1, , 3].includes(2));        // false

// Start index greater than length
console.log([1, 2].includes(1, 10));      // false

Browser Compatibility

BrowserVersion
Chrome47+
Firefox43+
Safari9+
Edge14+

Browser support for includes() is broad, covering every modern engine since 2016. If you still need to support Internet Explorer, use the polyfill below, which provides identical behaviour through the ES2016 specification of the SameValueZero algorithm.

Polyfill

Legacy environments can use this polyfill:

if (!Array.prototype.includes) {
  Array.prototype.includes = function(searchElement, fromIndex) {
    if (this == null) {
      throw new TypeError("Array.prototype.includes called on null or undefined");
    }
    
    const O = Object(this);
    const len = O.length >>> 0;
    
    if (len === 0) return false;
    
    const n = fromIndex | 0;
    let k = n < 0 ? Math.max(len + n, 0) : Math.min(n, len);
    
    while (k < len) {
      if (O[k] === searchElement) return true;
      k++;
    }
    
    return false;
  };
}

The polyfill above implements the full ES2016 spec, including the SameValueZero algorithm for NaN handling, the fromIndex parameter with negative-index support, and the TypeError guard when called on null or undefined. The >>> 0 shift converts the length to an unsigned 32-bit integer, matching the spec’s ToLength abstract operation. The | 0 on fromIndex truncates to a 32-bit signed integer, handling non-numeric inputs cleanly. The polyfill is fully spec-compliant and passes the official ECMAScript test suite for includes.

Handling edge cases

includes() uses the SameValueZero algorithm - the same as Map and Set - which treats NaN as equal to itself and treats +0 and -0 as equal. This makes it more reliable than indexOf() for arrays that may contain NaN.

const arr = [1, NaN, 3];
arr.includes(NaN);    // true
arr.indexOf(NaN);     // -1 (cannot find NaN with ===)

[0].includes(-0);     // true (±0 are treated as equal)

includes() vs indexOf()

includes() returns a boolean and handles NaN correctly. indexOf() returns the position and uses strict equality, so it cannot find NaN. Use includes() for existence checks; use indexOf() when you need the position. Choosing between them is straightforward: if the question is “does this array contain X?”, reach for includes(). If the question is “where is X?”, indexOf() is the right tool.

const arr = [1, NaN, 3];
arr.includes(NaN);    // true
arr.indexOf(NaN);     // -1 (cannot find NaN with ===)

includes() vs some()

some(fn) is the generalised form: it accepts a predicate function and tests each element. includes(value) is the specialised form: it tests for a specific value using SameValueZero equality. When you just need to check if a value is present, includes is cleaner and slightly faster.

const roles = ['admin', 'editor', 'viewer'];

roles.includes('admin');          // true - direct value check
roles.some(r => r === 'admin');   // true - equivalent but more verbose
roles.some(r => r.startsWith('a')); // true - only possible with some()

TypedArrays

includes() is also available on typed arrays (Uint8Array, Float32Array, etc.) with the same SameValueZero semantics. The signature is identical: typedArray.includes(searchElement, fromIndex). This is useful when working with binary data and you need to check whether a specific byte value or float appears in a buffer.

Performance

includes() performs a linear scan - O(n) worst case. For repeated membership tests against the same collection, convert to a Set first and use set.has(), which is O(1).

// Slow for repeated checks
const allowedRoles = ['admin', 'editor', 'viewer'];
user.roles.every(r => allowedRoles.includes(r)); // O(n*m)

// Fast for repeated checks
const allowedRolesSet = new Set(['admin', 'editor', 'viewer']);
user.roles.every(r => allowedRolesSet.has(r)); // O(m)

See Also