JavaScript map, filter and reduce Together: Build an Order Pipeline Step by Step

Follow one complete order example from raw API data to per-customer totals. See the array after map and filter, then trace every reduce accumulator.

KnowledgeGate Team

Exam prep & CS education

Updated 22 Sep 20266 min read

You may understand map, filter, and reduce separately, then lose track of data when they are chained. An API order array becomes an immutable, React-ready summary, with the exact value visible after every stage. This pipeline also fits naturally into the broader Coding & Skills learning path.

JavaScript map, filter and reduce each have one job

map transforms each input element into one output element. filter keeps or drops each element. reduce folds many elements into one accumulator.

Our data changes shape in sequence: 4 raw orders, 4 enriched orders, 2 eligible orders, and finally 1 summary object.

These methods do not mutate the source array, but callbacks can mutate its objects. Here, map returns a fresh object with spread syntax, filter only reads, and reduce returns a fresh accumulator.

Use map to calculate a field for every row, filter to remove rows, and reduce when the result is not another row-for-row array.

Order data and the exact result the pipeline must produce

The input remains unchanged:

js
const orders = [
  {
    id: "A101",
    customer: "Meera",
    status: "PAID",
    items: [
      { name: "Keyboard", unitPrice: 1200, quantity: 1 },
      { name: "Mouse", unitPrice: 500, quantity: 2 }
    ],
    coupon: 200
  },
  {
    id: "A102",
    customer: "Kabir",
    status: "CANCELLED",
    items: [
      { name: "Monitor", unitPrice: 8000, quantity: 1 }
    ],
    coupon: 0
  },
  {
    id: "A103",
    customer: "Meera",
    status: "PAID",
    items: [
      { name: "USB Cable", unitPrice: 300, quantity: 3 }
    ],
    coupon: 100
  },
  {
    id: "A104",
    customer: "Riya",
    status: "PAID",
    items: [
      { name: "Laptop Stand", unitPrice: 1500, quantity: 2 }
    ],
    coupon: 0
  }
];

The business rules are precise:

  1. Calculate subtotal and net for every order.

  2. Keep only PAID orders whose net is at least 1000.

  3. Group the survivors by customer and calculate grandTotal.

The target result serves as a reference for the pipeline. Only A101 and A104 should survive. grandTotal should be 5000, while byCustomer should contain Meera with { orderCount: 1, total: 2000 } and Riya with { orderCount: 1, total: 3000 }.

map enriches every order with subtotal and net

The inner reduce totals line items. The outer map preserves each order field and adds two calculated fields.

js
const withTotals = orders.map((order) => {
  const subtotal = order.items.reduce(
    (sum, item) => sum + item.unitPrice * item.quantity,
    0
  );

  return {
    ...order,
    subtotal,
    net: subtotal - order.coupon
  };
});

Work through all four calculations:

  • A101: 1200 * 1 + 500 * 2 = 2200, then 2200 - 200 = 2000.

  • A102: 8000 * 1 = 8000, then 8000 - 0 = 8000.

  • A103: 300 * 3 = 900, then 900 - 100 = 800.

  • A104: 1500 * 2 = 3000, then 3000 - 0 = 3000.

The snapshot is A101: PAID, net 2000, A102: CANCELLED, net 8000, A103: PAID, net 800, and A104: PAID, net 3000. The original objects still have no subtotal or net because each mapped result is new.

filter removes rows without changing the survivors

Now apply both eligibility conditions in one predicate:

js
const eligible = withTotals.filter(
  (order) => order.status === "PAID" && order.net >= 1000
);

A101 passes both checks. A102 fails on status despite its net of 8000. A103 passes status but fails because 800 >= 1000 is false. A104 passes both.

The output contains A101 at net 2000 and A104 at net 3000 in their original relative order. filter returns the existing enriched objects, not clones, so later code should avoid modifying them.

Pipeline diagram: four raw orders gain net values, then filter drops A102 and A103, leaving A101 and A104.

reduce groups customers and calculates the final total

An explicit initial accumulator makes an empty eligible array safely produce { grandTotal: 0, byCustomer: {} }.

js
const summary = eligible.reduce(
  (acc, order) => {
    const previous = acc.byCustomer[order.customer] ?? {
      orderCount: 0,
      total: 0
    };

    return {
      grandTotal: acc.grandTotal + order.net,
      byCustomer: {
        ...acc.byCustomer,
        [order.customer]: {
          orderCount: previous.orderCount + 1,
          total: previous.total + order.net
        }
      }
    };
  },
  { grandTotal: 0, byCustomer: {} }
);

Start with grandTotal: 0 and an empty byCustomer object. After A101, grandTotal is 2000 and Meera is { orderCount: 1, total: 2000 }. After A104, grandTotal is 5000, Riya is added as { orderCount: 1, total: 3000 }, and Meera remains unchanged.

js
{
  grandTotal: 5000,
  byCustomer: {
    Meera: { orderCount: 1, total: 2000 },
    Riya: { orderCount: 1, total: 3000 }
  }
}

The final arithmetic is 2000 + 3000 = 5000. The computed property name [order.customer] selects the correct customer key. For a customer's first order, previous supplies zeroes before the count and total are increased.

Accumulator trace table: grandTotal moves 0, 2000, 5000 as A101 then A104 are folded into byCustomer.

Chain map, filter and reduce without losing the intermediate logic

The function runs against the original input:

js
function buildOrderSummary(orders) {
  return orders
    .map((order) => {
      const subtotal = order.items.reduce(
        (sum, item) => sum + item.unitPrice * item.quantity,
        0
      );

      return {
        ...order,
        subtotal,
        net: subtotal - order.coupon
      };
    })
    .filter(
      (order) => order.status === "PAID" && order.net >= 1000
    )
    .reduce(
      (acc, order) => {
        const previous = acc.byCustomer[order.customer] ?? {
          orderCount: 0,
          total: 0
        };

        return {
          grandTotal: acc.grandTotal + order.net,
          byCustomer: {
            ...acc.byCustomer,
            [order.customer]: {
              orderCount: previous.orderCount + 1,
              total: previous.total + order.net
            }
          }
        };
      },
      { grandTotal: 0, byCustomer: {} }
    );
}

const summary = buildOrderSummary(orders);

Each dot receives a known type and value: raw orders, four enriched orders with net values 2000, 8000, 800, and 3000, two eligible orders A101 and A104, then the final object with grandTotal: 5000. Line breaks make the chain easier to debug. If a stage is wrong, split the chain back into steps and log withTotals, eligible, and summary.

The net threshold cannot run before map creates net. A status-only filter could first skip subtotal work for cancelled A102, but mapping first keeps all four values visible. The Web Development guide places this transformation in a wider front-end context.

JavaScript array pipeline traps and how coding tasks test them

Four mistakes are especially common:

  • Braces without return in a map callback produce four undefined values.

  • Writing order.net = subtotal - order.coupon mutates the API objects. Return a new object instead.

  • Omitting the initial value from the final reducer makes the first eligible order the accumulator, so the expected summary shape breaks.

  • Filtering on order.net before it exists removes every row because undefined >= 1000 is false.

In React, call this pure function after the API response. Use useMemo(() => buildOrderSummary(orders), [orders]) only when calculation cost or render frequency justifies it. Memoisation is not automatic.

Assessments may ask you to predict stage outputs, choose a method for a shape change, repair an empty-array reducer, or implement group-by. Python Output-Based Questions offers optional cross-language practice with lists and mutability.

The short version and the next useful step

map calculates totals for all four orders. filter keeps A101 and A104. reduce groups those survivors and confirms 2000 + 3000 = 5000 as the grand total.

Try adding a second paid Meera order whose items and coupon produce a net of 1200. Your prediction should be Meera { orderCount: 2, total: 3200 }, Riya { orderCount: 1, total: 3000 }, and grandTotal: 6200.

When you want to place this JavaScript transformation inside React and a full-stack project, the MERN Stack course is a relevant next step.