Một hàm tính tổng chạy ổn với hai số nhưng lỗi khi bộ lọc trả về mảng rỗng. Khi chỉ còn một phần tử, log trong callback lại không xuất hiện. Trước khi sửa bằng cách thêm 0, hãy xác định reduce đang lấy accumulator đầu tiên từ đâu.
Bài đối chiếu MDN ngày 06/11/2026 và chạy các ví dụ bằng Node.js 24.18.1. Các mảng trong demo là mảng đặc, không có getter, Proxy hoặc thao tác sửa cấu trúc trong callback. Không có benchmark hay kết luận về mọi dạng dữ liệu có thể gọi reduce.
Hai cách bắt đầu phép gộp
Nếu có initialValue, nó là accumulator đầu tiên và callback xử lý từ phần tử đầu. Nếu không có, reduce lấy phần tử đầu làm accumulator rồi bắt đầu callback từ phần tử tiếp theo.
Với mảng rỗng và không có initialValue, không có gì để dùng làm accumulator nên reduce ném TypeError. Với đúng một phần tử và không có initialValue, nó có thể trả ngay phần tử đó mà không gọi callback. Đây là khác biệt cần giải thích được trước khi đoán số lần log.
1. Ghi lại chỉ số callback trên mảng số
Lưu đoạn sau vào file .cjs rồi chạy Node. Assertions xác nhận cả giá trị cuối và các chỉ số thật sự đã được duyệt:
const assert = require('node:assert/strict');
const withoutInitial = [];
const withInitial = [];
const firstSum = [2, 3].reduce((acc, value, index) => {
withoutInitial.push({acc, value, index});
return acc + value;
});
const secondSum = [2, 3].reduce((acc, value, index) => {
withInitial.push({acc, value, index});
return acc + value;
}, 0);
assert.equal(firstSum, 5);
assert.equal(secondSum, 5);
assert.deepEqual(withoutInitial, [{acc: 2, value: 3, index: 1}]);
assert.deepEqual(withInitial, [
{acc: 0, value: 2, index: 0},
{acc: 2, value: 3, index: 1},
]);
assert.throws(() => [].reduce((acc, value) => acc + value), TypeError);
assert.equal([].reduce((acc, value) => acc + value, 0), 0);
let calls = 0;
const single = [7].reduce((acc, value) => {
calls += 1;
return acc + value;
});
assert.equal(single, 7);
assert.equal(calls, 0);
console.log('initial value and callback indices verified');
Hai phép gộp cùng trả 5 nhưng có đường chạy callback khác nhau. Vì thế, chỉ kiểm tra tổng cuối sẽ không giúp bạn phát hiện assumption rằng callback luôn nhận mọi phần tử.
Không đặt một bước validation bắt buộc cho từng phần tử vào callback rồi bỏ initialValue. Trường hợp một phần tử có thể đi qua mà callback không chạy. Nếu cần kiểm tra toàn bộ đầu vào, thực hiện việc đó rõ ràng trước hoặc bảo đảm cấu trúc phép gộp phù hợp.
2. InitialValue còn định nghĩa kiểu accumulator
Ví dụ độc lập sau cộng trường amount của các dòng. Accumulator được khai báo là số bằng cách bắt đầu từ 0:
const assert = require('node:assert/strict');
function totalAmount(rows) {
if (!Array.isArray(rows) || !rows.every(row =>
row !== null && typeof row === 'object' &&
Number.isFinite(row.amount)
)) {
throw new TypeError('Expected rows with finite numeric amounts');
}
return rows.reduce((sum, row) => sum + row.amount, 0);
}
assert.equal(totalAmount([]), 0);
assert.equal(totalAmount([{amount: 7}]), 7);
assert.equal(totalAmount([{amount: 7}, {amount: 3}]), 10);
assert.throws(() => totalAmount([{amount: '7'}]), TypeError);
assert.throws(() => totalAmount([{amount: NaN}]), TypeError);
console.log('numeric accumulator verified');
Nếu bỏ 0, phần tử đầu là object sẽ được dùng làm accumulator. Nhưng callback đang kỳ vọng sum là số. Lỗi không chỉ nằm ở ca mảng rỗng: kiểu của giá trị bắt đầu cũng không còn đúng với phép cộng mà bạn viết.
Validation mẫu dành cho mảng đặc và object dữ liệu đơn giản; không coi đây là schema validator cho object tùy ý. Nếu dữ liệu có quy tắc số nguyên, không âm hoặc đơn vị tiền tệ, bổ sung hợp đồng tương ứng thay vì mặc định Number.isFinite đã đủ.
Cách trả lời và kiểm tra khi phỏng vấn
Nêu accumulator ban đầu, chỉ số callback đầu tiên và giá trị trả về trong ba ca: rỗng, một phần tử, hai phần tử. Sau đó chỉ ra initialValue phù hợp với kết quả cần tạo: số, chuỗi, mảng hay một cấu trúc khác.
Đừng thêm 0 theo thói quen cho mọi reduce. Nếu đang xây danh sách, 0 không phải accumulator phù hợp. Nếu dùng object hoặc mảng rồi sửa nó trong callback, nhớ trả accumulator cho lần lặp tiếp theo và xác định nó có đang được chia sẻ ra ngoài hay không.
Một vòng lặp rõ ràng cũng là lựa chọn hợp lệ nếu phép gộp khó đọc. Điều cần bảo vệ là hợp đồng dữ liệu và các ca biên, không phải việc mọi bài toán xử lý mảng đều phải được viết bằng reduce.

