TypeScript Find Element in Array: find, findIndex, findLast

Find elements in a TypeScript array with find(), findIndex(), findLast() and findLastIndex(), handle the T | undefined result, and pick the right search method.

TypeScript vs JavaScript

The find() method returns the first element of an array for which a test function returns true, or undefined when no element passes. Its partner findIndex() returns the position of that element instead, or -1. ES2023 added findLast() and findLastIndex(), which do the same search from the end of the array.

We use these four methods to find an element in an array by a condition, for example the person whose name is “Raj” or the first order that is still unpaid.

The following example calls every search method once on an array of six scores, and the comments show what each one returns.

const scores = [90, 44, 76, 12, 0, 34];

// 1. First element that matches
const firstPass = scores.find((s) => s > 35);   // firstPass = 90, typed number | undefined

// 2. Index of the first match
const firstPassAt = scores.findIndex((s) => s > 35);   // firstPassAt = 0

// 3. Last match and its index (ES2023)
const lastPass = scores.findLast((s) => s > 35);   // lastPass = 76
const lastPassAt = scores.findLastIndex((s) => s > 35);   // lastPassAt = 2

// 4. No match
const perfect = scores.find((s) => s === 100);   // perfect = undefined
const perfectAt = scores.findIndex((s) => s === 100);   // perfectAt = -1

// 5. Search by value instead of a condition
const zeroAt = scores.indexOf(0);             // zeroAt = 4
const hasZero = scores.includes(0);           // hasZero = true
const anyFail = scores.some((s) => s < 35);   // anyFail = true

Notice that a search without a match returns either undefined or -1, and never throws an error.

The four methods are plain JavaScript methods, so everything here also applies to JavaScript. TypeScript adds one important detail. Because the search can fail, the return type of find() on a Person[] is Person | undefined, and in strict mode the compiler does not let us read a property from the result until we handle the undefined case.

Next, we cover the four methods, the ways to handle undefined, narrowing union types with find(), replacing a found element, and the value-based methods indexOf(), includes() and some(). A table at the end helps us pick the right method.

1. find(): The First Element That Matches

The method find() takes a callback and calls it with each element, its index and the array, in order from index 0. As soon as the callback returns a truthy value, find() stops and returns that element. The most common use is finding an object by a property value. For example, a login handler finds the user whose email matches the one in the submitted form.

interface Person { name: string; age: number; }

const people: Person[] = [
  { name: "Lokesh", age: 37 },
  { name: "Raj", age: 35 },
  { name: "John", age: 40 },
];

// 1. By property value
const raj = people.find((p) => p.name === "Raj");   // raj = { name: "Raj", age: 35 }

// 2. First of several matches
const over36 = people.find((p) => p.age > 36);   // over36 = Lokesh (John also matches)

// 3. Destructuring in the parameter
const john = people.find(({ name }) => name === "John");   // john = { name: "John", age: 40 }

The returned object is the same object that is stored in the array, not a copy. So when we change raj.age, the element inside people changes as well.

Because find() stops at the first match, it does less work than filter() on large arrays. A counter in the callback shows that the callback ran only twice.

let calls = 0;
const two = [1, 2, 3, 4].find((n) => {
  calls++;
  return n === 2;
});
// two = 2, calls = 2: elements 3 and 4 are never checked

When we need every match, not only the first one, filter() is the right method.

2. Handling the undefined Result

The TypeScript library declares find() as returning T | undefined. With strict mode on, which includes the strictNullChecks option, reading a property from the result without a check is a compile error.

const people = [{ name: "Lokesh", age: 37 }];

const raj = people.find((p) => p.name === "Raj");
const age: number = raj.age;
src/err.ts(4,21): error TS18048: 'raj' is possibly 'undefined'.

The error is useful, because the people array in the snippet has no “Raj”, so the code would throw a TypeError at runtime. There are three good ways to deal with a missing element, depending on what the program should do next. The snippet reuses the people array from section 1.

// 1. Check before use; the type narrows to Person
const raj = people.find((p) => p.name === "Raj");
if (raj) {
  console.log(raj.age);                       // 35
}

// 2. Optional chaining and a default
const age = people.find((p) => p.name === "Brian")?.age ?? 0;   // age = 0

// 3. Fail fast when the element must exist
const lokesh = people.find((p) => p.name === "Lokesh");
if (!lokesh) throw new Error("Lokesh not found");
const lokeshAge = lokesh.age;                 // lokeshAge = 37

Each option fits a different situation.

  • Option 1 fits when a missing element is a normal case.
  • Option 2 uses the optional chaining operator ?., which returns undefined instead of throwing when the left side is undefined, and ??, which then supplies a default.
  • Option 3 fits when a missing element means a bug or bad data, because after the throw, TypeScript knows that lokesh is a Person.

The non-null assertion people.find(…)! also silences the error, but it only removes the check, so a missing element still crashes later with a less helpful message.

A truthy check such as if (raj) is fine for objects. For an array of numbers or strings, the found value itself can be 0 or an empty string, which are falsy, so we compare with !== undefined instead.

3. Narrowing a Union Type With find()

When an array holds several kinds of objects, find() can also narrow the type of the result. Starting with TypeScript 5.5, a callback such as (s) => s.kind === “circle” gets an inferred type predicate, and find() returns only the matching member of the union. For example, a drawing app keeps circles and squares in one Shape[] array and finds the first circle to show its radius.

type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };

const shapes: Shape[] = [
  { kind: "square", side: 2 },
  { kind: "circle", radius: 1 },
];

const circle = shapes.find((s) => s.kind === "circle");   // { kind: "circle"; radius: number } | undefined
const radius = circle?.radius;                // radius = 1

Without the narrowing, circle would be a Shape, and reading radius would need another kind check. The undefined part stays because the array might contain no circle, and findLast() narrows in the same way. The type Shape is a discriminated union type, where the kind field tells the members apart.

4. findIndex(): Finding the Position

The method findIndex() runs the same search as find() but returns the index of the match, or -1 when nothing matches. We use it when we need to change the array at that position, either by replacing the found element or by calling splice() to remove it or insert something next to it. For example, when a user changes their age in a profile form, we find the index of that person and replace the object at that index.

interface Person { name: string; age: number; }

const people: Person[] = [
  { name: "Lokesh", age: 37 },
  { name: "Raj", age: 35 },
];

// 1. Replace in place
const i = people.findIndex((p) => p.name === "Raj");   // i = 1
if (i !== -1) {
  people[i] = { ...people[i], age: 36 };      // Raj is now 36
}

// 2. Replace in a copy (ES2023)
const j = people.findIndex((p) => p.name === "Lokesh");   // j = 0
const updated = j === -1 ? people : people.with(j, { name: "Lokesh", age: 38 });   // Lokesh is 38 in the copy

The -1 check is not optional. Using -1 as an index with splice() or with() points to the last element, so a missing match would change the wrong element without any error. The same bug appears when we remove an item at an index that is -1. The with() method in step 2 is one of the ES2023 copying methods that return a new array.

5. findLast() and findLastIndex(): Searching From the End

The methods findLast() and findLastIndex() walk the array from the last element to the first and return the first match they meet, which is the last match in array order. Both methods are useful for data that is appended over time, such as log entries or chat messages, where the newest entry is at the end.

interface LogEntry { level: string; text: string; }

const logs: LogEntry[] = [
  { level: "error", text: "disk full" },
  { level: "info", text: "retrying" },
  { level: "error", text: "timeout" },
  { level: "info", text: "done" },
];

// 1. Most recent error
const lastError = logs.findLast((e) => e.level === "error");   // lastError.text = "timeout"

// 2. Its position
const lastErrorAt = logs.findLastIndex((e) => e.level === "error");   // lastErrorAt = 2

// 3. Before ES2023: reverse a copy, then find
const oldWay = [...logs].reverse().find((e) => e.level === "error");   // oldWay.text = "timeout"

The old approach in step 3 copies the whole array before searching, and getting the original index from it needs extra arithmetic. The method findLastIndex() returns the original index without any arithmetic. Both new methods need lib ES2023 or later in tsconfig.json, and with an older lib, the compiler reports error TS2550.

src/err.ts(2,21): error TS2550: Property 'findLast' does not exist on type 'number[]'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2023' or later.

At runtime, both methods need Node.js 18 or later, or any current browser.

6. Searching by Value: indexOf(), includes() and some()

When we look for a known value rather than a condition, the callback is unnecessary. The methods indexOf() and lastIndexOf() return the first and last position of the value, and includes() answers whether it exists. Both kinds of methods compare with strict equality (===), so for objects they match only the same reference. To match objects by a property, we go back to find() or some().

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

// 1. First and last position of a value
const first = fruits.indexOf("apple");        // first = 0
const last = fruits.lastIndexOf("apple");     // last = 2
const missing = fruits.indexOf("kiwi");       // missing = -1

// 2. Exists or not
const hasBanana = fruits.includes("banana");  // hasBanana = true

// 3. NaN: includes() finds it, indexOf() does not
const nanAt = [NaN].indexOf(NaN);             // nanAt = -1
const hasNaN = [NaN].includes(NaN);           // hasNaN = true

For a yes/no question, includes() reads better than indexOf(x) !== -1. For example, a signup form calls includes() to check a new user name against a list of reserved names. The method some(callback) is the condition-based version of includes(), and it returns true as soon as one element passes the test.

One TypeScript-specific error appears when the array has literal types, for example an array created with as const. Its includes() method accepts only those literal values, so checking an arbitrary string fails.

const sizes = ["S", "M", "L"] as const;
const input: string = "M";
const ok = sizes.includes(input);
src/err.ts(3,27): error TS2345: Argument of type 'string' is not assignable to parameter of type '"L" | "M" | "S"'.

The usual fix is a small type guard that widens the array for the check and narrows the input when the check passes.

const sizes = ["S", "M", "L"] as const;
type Size = (typeof sizes)[number];           // "S" | "M" | "L"

const isSize = (value: string): value is Size => (sizes as readonly string[]).includes(value);

const input: string = "M";
if (isSize(input)) {
  const size: Size = input;                   // size = "M"
}

7. Choosing the Right Search Method

The right method follows from two questions, namely whether we search by a condition or by a value, and whether we need the element itself or only something smaller, such as its position or a yes/no answer.

MethodSearches byReturns on matchReturns on no match
find()ConditionFirst matching elementundefined
findLast()ConditionLast matching elementundefined
findIndex()ConditionIndex of the first match-1
findLastIndex()ConditionIndex of the last match-1
filter()ConditionArray of all matchesEmpty array
some()Conditiontruefalse
indexOf() / lastIndexOf()Value (===)Index of the first / last match-1
includes()Value (SameValueZero)truefalse

All of these methods check the elements one by one, so the time grows with the length of the array. When a program looks up elements by the same key many times, for example users by name, building a Map once and calling get() is much faster than calling find() in a loop.

8. Running the find() Examples

Each snippet is in the javascript-array-find folder of the TypeScript-Examples repository, one file per section, and src/index.ts prints every commented value. The project uses TypeScript 7.0.2 with lib ES2024 (needed for findLast() and with()) and runs on Node.js 22 or newer.

npm install
npm start

9. Conclusion

To find an element in a TypeScript array, we use find() for the first match and findLast() for the last, and findIndex() or findLastIndex() when we need the position. The method find() returns T | undefined, so we handle the missing case before reading a property, either with a check and a default from ?? or with a throw when the element must exist. We always check findIndex() results for -1 before using them as an index. For known values, includes() and indexOf() need no callback, and for repeated lookups by key, a Map is the better structure.

10. References

The MDN pages describe each method’s runtime behavior, and the TypeScript documentation covers strictNullChecks and inferred type predicates.

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.