Fix Common Errors

Fix "Reduce of Empty Array" Error

Fix JavaScript's reduce of empty array with no initial value error. Learn the causes, see solutions, and discover defensive patterns for production code.

When you call .reduce() on an empty array without providing an initial value, JavaScript throws a TypeError: "Reduce of empty array with no initial value". It's one of those errors that looks cryptic until you understand the method's contract. This guide shows you why it happens, how to fix it, and how to trace it back to the source in production.

The error occurs because Array.reduce() has a specific behavior: if you don't supply an initial value, it uses the first element of the array as the accumulator and starts iterating from the second element. When the array is empty, there is no first element, so the method has nowhere to start—and it fails. Understanding this design choice is the key to preventing the bug.

Understanding Array.reduce() and the Initial Value

reduce() collapses an array into a single value by applying a callback function repeatedly. The callback receives four arguments: the accumulator, the current element, the index, and the array itself.

const sum = [1, 2, 3].reduce((acc, val) => acc + val);
// Works: acc starts as 1, then processes 2, then 3
// Result: 6

When you omit the initial value, JavaScript assumes the first array element is the starting accumulator. If the array is empty, there is no first element, and the method throws:

const sum = [].reduce((acc, val) => acc + val);
// TypeError: Reduce of empty array with no initial value

The fix is straightforward: provide an initial value as the second argument. The initial value becomes the starting accumulator, and .reduce() processes every element in the array—even if it's empty.

const sum = [].reduce((acc, val) => acc + val, 0);
// Works: acc starts as 0, iterates zero times
// Result: 0

The initial value can be any type—a number, string, object, or array. Choose whatever makes sense for your aggregation. For a sum, use 0; for concatenating strings, use ""; for building an object, use {}.

Common Scenarios Where This Happens

This error is common in data aggregation pipelines. You transform an array with .map() or .filter(), then immediately call .reduce() without checking if the result is empty. Similar patterns cause errors like cannot read properties of undefined when working with optional data.

const users = [
  { name: "Alice", role: "admin" },
  { name: "Bob", role: "user" },
];

// Filter for editors, then count
const editorCount = users
  .filter((u) => u.role === "editor")
  .reduce((acc, u) => acc + 1, 0);
// If no editors exist, filter returns [], and reduce would fail without the initial value

Another common scenario: aggregating API responses that may be paginated or conditionally empty.

async function getTotalRevenue(orders) {
  const validOrders = orders.filter((o) => o.status === "completed");
  // What if no orders are completed?
  const revenue = validOrders.reduce((sum, order) => sum + order.amount);
  // TypeError if validOrders is empty and no initial value
  return revenue;
}

If you're building objects or nested structures with .reduce(), the risk is even higher:

const grouped = [].reduce((acc, item) => {
  const key = item.type;
  acc[key] = (acc[key] || 0) + item.count;
  return acc;
}); // Error: no initial value

How to Fix It: Always Provide an Initial Value

The safest approach is to always supply an initial value, even if you're confident the array isn't empty. It costs nothing and eliminates an entire class of bugs.

// Bad: relies on array not being empty
const sum = numbers.reduce((a, b) => a + b);

// Good: always has a starting point
const sum = numbers.reduce((a, b) => a + b, 0);

For object aggregation, start with an empty object:

const usersByRole = users.reduce((acc, user) => {
  if (!acc[user.role]) {
    acc[user.role] = [];
  }
  acc[user.role].push(user);
  return acc;
}, {}); // Initial value is an empty object

For array aggregation, start with an empty array:

const flattened = nestedArrays.reduce(
  (acc, arr) => acc.concat(arr),
  [] // Initial value is an empty array
);

Use TypeScript or a linter like ESLint with the array-callback-return rule to catch missing initial values before they reach production. A type-aware linter can infer whether your initial value makes sense for your callback function. Combined with good error tracking best practices, you'll catch these issues early.

Handling Truly Optional Aggregations

Sometimes you genuinely want to handle empty arrays differently—perhaps returning null or a sentinel value instead of a default like 0. In those cases, check the array length explicitly:

function aggregateMetrics(events) {
  if (events.length === 0) {
    return null; // No events to aggregate
  }
  return events.reduce((acc, event) => ({
    count: acc.count + 1,
    totalMs: acc.totalMs + event.duration,
  }));
}

Alternatively, use .reduceRight() or chain with .at(0) if you have control over the input:

const firstValue = values.at(0) ?? defaultValue;
const rest = values.slice(1);
const result = rest.reduce((acc, val) => acc + val, firstValue);

Defensive Patterns for Data Pipelines

In production, data pipelines often have unpredictable sizes. Use these patterns to be resilient:

Pattern 1: Safe wrapper

function safeReduce(arr, fn, initial = 0) {
  return arr.reduce(fn, initial);
}

const sum = safeReduce(filteredData, (a, b) => a + b);
// No error, even if filteredData is empty

Pattern 2: Null coalescing with reduce

const result = data.reduce(
  (acc, item) => processItem(acc, item),
  null
);
const finalResult = result ?? defaultValue;

Pattern 3: Object/array defaults

const grouped = items.reduce(
  (acc, item) => {
    acc[item.key] = (acc[item.key] ?? 0) + item.value;
    return acc;
  },
  {}
);

Tracing Empty Array Reductions in Production

When this error bubbles up in production, you need to answer: where did the empty array come from? Was it a bad API response, a filtering step that removed everything, or missing data?

A good error tracking system captures the full context. When the error is logged, you get:

  • The stack trace pointing to the exact .reduce() call
  • Breadcrumbs showing the preceding steps (API calls, filters, data transformations)
  • Local variables and state at the time of failure
  • Source maps to pinpoint the line in your original TypeScript or ES6 code

With this context, you can see whether the empty array was expected or a bug in upstream logic. You'll also notice patterns—if 5% of users hit empty arrays at step X, the issue is likely a data condition you didn't anticipate, not a code bug.

If you're seeing "reduce of empty array" errors in production but your code always provides an initial value, double-check that the error is coming from your own code and not a third-party library or polyfill.

Best Practices

  • Always provide an initial value to .reduce(), even if you think the array will never be empty.
  • Type your accumulators in TypeScript to catch mismatches early.
  • Test with empty arrays as part of your test suite. A simple test like expect(aggregate([])).toBe(0) prevents this category of error entirely.
  • Use error tracking to catch unexpected empty arrays in production and understand their causes.
  • Prefer .at() and modern array methods when they simplify your logic, but always validate array bounds.

Understanding this error teaches you something deeper about JavaScript's functional paradigms and defensive coding. Take it as a signal to audit other array operations—.map(), .filter(), .find()—and ensure they handle edge cases gracefully. The principles here apply across languages; for instance, understanding how collection iteration works defensively in C# or other platforms helps you write more robust code everywhere.

Start tracking errors in minutes

Seeing array errors in production but struggling to trace their source? LightTrace captures full stack traces, breadcrumbs, and local state so you can debug these issues in seconds, not hours. Start free today.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.