To find the maximum value in an array of objects in TypeScript, decide first what you want back. For just the number, map the property and use Math.max(): Math.max(...employees.map(e => e.salary)). For the whole object that holds it, use reduce() and keep whichever item is larger. Both are one line, and both have an edge case that bites in production: an empty array. This guide covers both forms, ties, minimums, a typed generic helper, what happens on empty and very large arrays, and the separate question of the largest number TypeScript can hold.
Every example was compiled and run with TypeScript 5.2.2 and Node.js v22.22.2 in the Windows Command Prompt, with tsc --target es2022 --lib es2023,dom --strict file.ts and then node file.js. The output is the real output. Reference: Math.max() on MDN.
Find the maximum value in an array of objects
Math.max() takes numbers, so map the property first. reduce() compares whole objects and returns the winner, which is what you want when you need the name next to the number:
interface Employee {
name: string;
city: string;
salary: number;
started: string;
}
const employees: Employee[] = [
{ name: "Emma Johnson", city: "Austin", salary: 92_000, started: "2021-03-01" },
{ name: "Michael Brown", city: "Denver", salary: 118_500, started: "2019-07-15" },
{ name: "Olivia Davis", city: "Austin", salary: 118_500, started: "2023-01-09" },
{ name: "Noah Wilson", city: "Seattle", salary: 76_400, started: "2024-11-04" },
];
// just the number
const highestSalary = Math.max(...employees.map(e => e.salary));
console.log(highestSalary);
// the whole object that holds it
const topEarner = employees.reduce((best, current) => (current.salary > best.salary ? current : best));
console.log(topEarner.name, topEarner.city);
// the smallest, with the comparison the other way round
const lowest = employees.reduce((best, current) => (current.salary < best.salary ? current : best));
console.log(lowest.name, lowest.salary);
Output:
118500
Michael Brown Denver
Noah Wilson 76400
| You want | Use |
|---|---|
| The largest number | Math.max(...items.map(i => i.value)) |
| The object that holds it | items.reduce((b, i) => (i.value > b.value ? i : b)) |
| The smallest | the same with < |
| Safe on an empty array | items.length ? items.reduce(...) : undefined |
| All the tied winners | items.filter(i => i.value === max) |
| Any property, typed | a generic maxBy(items, key) |
| A very large array | reduce() or a loop, never Math.max(...) |
Empty arrays: the two failures
This is the part that breaks in production, because test data always has rows. Math.max() with nothing to compare returns -Infinity, which is a number and so passes silently through the rest of your code. reduce() without a starting value throws instead:
interface Employee {
name: string;
salary: number;
}
const none: Employee[] = [];
console.log(Math.max()); // no arguments at all
console.log(Math.max(...none.map(e => e.salary))); // an empty array is the same thing
try {
none.reduce((best, current) => (current.salary > best.salary ? current : best));
} catch (error) {
console.log((error as Error).name + ":", (error as Error).message);
}
// two safe versions
const topEarner = none.length > 0 ? none.reduce((b, c) => (c.salary > b.salary ? c : b)) : undefined;
console.log("top earner:", topEarner?.name ?? "no employees");
const highest = none.reduce<number | null>((best, e) => (best === null || e.salary > best ? e.salary : best), null);
console.log("highest:", highest);
Output:
-Infinity
-Infinity
TypeError: Reduce of empty array with no initial value
top earner: no employees
highest: null
-Infinity quietly, or a TypeError loudly. Neither is what you want on screen.Guard with a length check and return undefined, or give reduce() a starting value and a wider type, as the last two lines do. If the array might be null as well, checking whether an array is null or empty covers that side.
Ties, groups and the minimum
When two rows share the highest value, the comparison operator decides which one you keep: > keeps the first it met, >= keeps the last. If you want all of them, take the maximum first and filter. Restricting the search to a group is just a filter beforehand:
interface Employee {
name: string;
city: string;
salary: number;
started: string;
}
const employees: Employee[] = [
{ name: "Emma Johnson", city: "Austin", salary: 92_000, started: "2021-03-01" },
{ name: "Michael Brown", city: "Denver", salary: 118_500, started: "2019-07-15" },
{ name: "Olivia Davis", city: "Austin", salary: 118_500, started: "2023-01-09" },
{ name: "Noah Wilson", city: "Seattle", salary: 76_400, started: "2024-11-04" },
];
// > keeps the FIRST of equal values, >= keeps the last
const first = employees.reduce((best, e) => (e.salary > best.salary ? e : best));
const last = employees.reduce((best, e) => (e.salary >= best.salary ? e : best));
console.log(first.name, "|", last.name);
// everyone on the highest salary
const highest = Math.max(...employees.map(e => e.salary));
console.log(employees.filter(e => e.salary === highest).map(e => e.name));
// the maximum inside one group only
const austin = employees.filter(e => e.city === "Austin");
console.log(austin.reduce((best, e) => (e.salary > best.salary ? e : best)).name);
Output:
Michael Brown | Olivia Davis
[ 'Michael Brown', 'Olivia Davis' ]
Olivia Davis
reduce() returns.A typed helper for any property
K extends keyof T keeps this honest: only real property names are accepted, and returning T | undefined forces the caller to handle the empty case. The comparator version handles values that are not directly comparable, such as dates parsed from strings, which pairs with sorting an array of objects by property:
interface Employee {
name: string;
city: string;
salary: number;
started: string;
}
const employees: Employee[] = [
{ name: "Emma Johnson", city: "Austin", salary: 92_000, started: "2021-03-01" },
{ name: "Michael Brown", city: "Denver", salary: 118_500, started: "2019-07-15" },
{ name: "Olivia Davis", city: "Austin", salary: 118_500, started: "2023-01-09" },
{ name: "Noah Wilson", city: "Seattle", salary: 76_400, started: "2024-11-04" },
];
// one helper for any numeric property, and undefined when the array is empty
function maxBy<T extends object, K extends keyof T>(items: readonly T[], key: K): T | undefined {
return items.reduce<T | undefined>(
(best, item) => (best === undefined || item[key] > best[key] ? item : best),
undefined,
);
}
console.log(maxBy(employees, "salary")?.name);
console.log(maxBy(employees, "started")?.name); // ISO dates compare as text
const nobody: Employee[] = [];
console.log(maxBy(nobody, "salary")?.name ?? "empty array -> undefined");
// the same shape with a comparator, for values that are not simply bigger or smaller
function maxWith<T extends object>(items: readonly T[], score: (item: T) => number): T | undefined {
return items.reduce<T | undefined>(
(best, item) => (best === undefined || score(item) > score(best) ? item : best),
undefined,
);
}
console.log(maxWith(employees, e => new Date(e.started).getTime())?.name, "started most recently");
Output:
Michael Brown
Noah Wilson
empty array -> undefined
Noah Wilson started most recently
Large arrays: do not spread into Math.max()
Spreading turns every element into an argument, and engines cap how many a call can take. With a few hundred thousand values it throws instead of answering. reduce() and a plain loop have no limit, and sorting to grab the last element does far more work than the job needs, as it also reorders the whole array:
const salaries: number[] = Array.from({ length: 200_000 }, (_, i) => (i * 7919) % 500_000);
function time(label: string, fn: () => number): void {
fn(); // warm-up run
const start = performance.now();
const max = fn();
console.log(label.padEnd(26), (performance.now() - start).toFixed(1).padStart(8), "ms", "max", max);
}
time("reduce", () => salaries.reduce((best, n) => (n > best ? n : best), -Infinity));
time("for loop", () => {
let best = -Infinity;
for (let i = 0; i < salaries.length; i++) if (salaries[i] > best) best = salaries[i];
return best;
});
time("sort then take the last", () => [...salaries].sort((a, b) => a - b)[salaries.length - 1]);
try {
console.log(Math.max(...salaries)); // 200,000 arguments in one call
} catch (error) {
console.log((error as Error).name + ":", (error as Error).message);
}
reduce() and the loop are milliseconds; sorting is far slower; the spread throws.“Maximum number” in the other sense
If you came here looking for the biggest number TypeScript can hold, that is a different question with its own constants. Integers stay exact up to Number.MAX_SAFE_INTEGER; past it, two different values can compare as equal. Number.MAX_VALUE is the largest float, and anything beyond it is Infinity:
console.log(Number.MAX_SAFE_INTEGER); // the largest integer you can trust
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2); // past it, maths breaks
console.log(Number.MAX_VALUE); // the largest float
console.log(Number.MAX_VALUE * 2); // beyond that it is Infinity
console.log(Number.isSafeInteger(9_007_199_254_740_993));
const big = 9_007_199_254_740_993n; // BigInt keeps going
console.log(big + 1n, typeof big);
Output:
9007199254740991
true
1.7976931348623157e+308
Infinity
false
9007199254740994n bigint
MAX_SAFE_INTEGER, + 1 and + 2 give the same answer.For ids and money beyond that range, use BigInt or keep the value as a string. TypeScript types both, but they do not mix with number without an explicit conversion.
More TypeScript array guides worth reading:
- Arrays of objects in TypeScript
- Filter an array of objects
- Sort an array of objects by property
- Find an object in a TypeScript array
- Get unique values from an array of objects
Frequently asked questions
How do I find the maximum value in an array of objects in TypeScript?
Use reduce() to keep the larger object, or Math.max(...items.map(i => i.value)) when you only need the number.
How do I get the object with the highest value, not just the number?
items.reduce((best, item) => (item.value > best.value ? item : best)) returns the whole object.
What happens if the array is empty?
Math.max() returns -Infinity and reduce() without an initial value throws a TypeError. Check the length first.
How do I find the minimum instead?
Change the comparison: item.value < best.value, or use Math.min() on the mapped numbers.
What if two objects share the highest value?
> keeps the first and >= keeps the last. To keep them all, find the maximum and then filter on it.
Why does Math.max throw on a big array?
Spreading passes one argument per element and engines limit that. Use reduce() or a loop for large arrays.
What is the maximum number in TypeScript?
Number.MAX_SAFE_INTEGER (9,007,199,254,740,991) for exact integers and Number.MAX_VALUE for floats. Beyond those, use BigInt.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn