[JavaScript] reduce – 무조건 이해되는 자바스크립트

주요 포인트 한눈에 보기

JavaScript의 reduce() 함수는 배열을 하나의 값으로 축약하는 기능을 넘어서, 초심자에게는 복잡하게 느껴질 수 있는 데이터 변환을 단계별로 안전하고 예측 가능하게 처리해주는 핵심 도구입니다. 이 글에서는 완전 기초 개념부터 “왜 이렇게 동작하는지”, “어떤 상황에서 reduce를 써야 하는지”, “map/filter와 어떻게 다른지” 등을 초심자도 이해할 수 있도록 쉬운 비유와 상세 예시를 통해 설명합니다.

reduce란 무엇인가?

reduce는 “배열 전체를 순회하여 하나의 결과를 만들어내는 함수”입니다. 초심자는 “하나의 값으로 만든다”는 표현 때문에 어렵게 느끼지만, 실제로는 아래와 같이 생각하면 훨씬 쉽습니다.

비유: 쇼핑할 때 장바구니에 물건을 하나씩 넣으며 총합을 계산하는 것과 같습니다. 물건(배열 요소)을 하나씩 꺼내어 현재 총합(accumulator)과 합쳐가는 과정이 바로 reduce입니다.

reduce 함수의 누적 구조를 시각화한 이미지
reduce 함수의 활용 패턴을 나타낸 인포그래픽

reduce 기본 구조와 동작 원리

reduce 구조 전체를 한 눈에 보기

array.reduce((accumulator, currentValue, currentIndex, array) => {
    // 누적 방식 정의
    return 새로운_누적값;
}, 초기값);

위 코드의 각 파라미터가 무엇을 의미하는지 초심자도 이해할 수 있도록 매우 상세하게 설명하면 다음과 같습니다.

파라미터 설명 예시 값
accumulator 지금까지 누적된 값입니다. 이전 콜백의 return 값이 그대로 전달됩니다. 총합 계산 시: 현재까지의 합계(예: 5 → 12 → 20)
currentValue 배열에서 현재 처리 중인 요소입니다. [1,2,3] 처리 시: 1 → 2 → 3
currentIndex 현재 요소의 인덱스 번호입니다. 0부터 시작합니다. 첫 번째 요소: 0 / 두 번째: 1 / 세 번째: 2
array reduce를 호출한 원본 배열 전체입니다. [1, 2, 3]
initialValue (초기값) accumulator의 시작값으로, 반드시 명시하는 것이 권장됩니다. 합계를 0부터 시작 → 초기값: 0

정리: forEach는 단순 반복, map은 변환, filter는 조건 추출, reduce는 새로운 값 또는 구조를 만드는 데 사용됩니다. 특히 reduce는 나머지 세 메서드로 처리하지 못하는 복잡한 누적 로직을 구현할 때 강력합니다.

reduce는 어렵게 생겼지만 사실 함수형 프로그래밍에서 매우 단순한 규칙만 따라갑니다.

reduce를 쉽게 이해하는 예시 1: 쇼핑할 때 장바구니 합계

const cart = [1000, 3000, 2500];
const total = cart.reduce((acc, price) => acc + price, 0);
console.log(total); // 6500

초심자 기준으로 reduce를 이해하려면 “acc는 지금까지의 누적 결과”라고 기억하면 됩니다.

왜 초기값이 중요한가? (많이 발생하는 실수)

초기값이 없으면 배열의 첫 번째 요소가 자동으로 누적값이 되며, 빈 배열에서 reduce를 호출할 경우 오류가 발생할 수 있습니다. 특히 객체 배열을 누적할 때 초기 구조를 지정하지 않으면 타입 구조가 불안정해집니다.

// 올바른 방식: 초기값을 명시
const items = [{price: 1000}, {price: 2000}];
const total = items.reduce((acc, item) => acc + item.price, 0);
console.log(total); // 3000

자주 발생하는 실수: 초기값 생략 → 예외 발생 확률 증가

reduce의 누적 구조 설명 이미지
reduce를 통한 비동기 흐름 제어 예시

실무와 초심자를 위한 대표 활용 패턴

reduce는 map + filter의 동작을 모두 구현할 수 있기 때문에 실무에서는 데이터 변환, 집계, 그룹화 등에 널리 활용됩니다. 다음은 초심자들이 활용하기 좋은 사례입니다.

패턴 설명 예시
합/평균 숫자 배열 집계 [10, 20, 30]
객체 속성 합산 특정 속성 합계 [{price:10},{price:20}]
배열 평탄화 중첩 배열을 단일 배열로 [[1,2],[3,4]] → [1,2,3,4]
데이터 그룹화 특정 조건으로 묶기 도시별 사용자 그룹화

실제 예시: 그룹화 (초심자가 reduce를 어려워하는 대표 사례)

const users = [
  {name: "A", city: "Seoul"},
  {name: "B", city: "Busan"},
  {name: "C", city: "Seoul"}
];

const grouped = users.reduce((acc, user) => {
  if (!acc[user.city]) acc[user.city] = [];
  acc[user.city].push(user);
  return acc;
}, {});

console.log(grouped);
// {
//   Seoul: [{...}, {...}],
//   Busan: [{...}]
// }

이 패턴은 실무에서 매우 자주 쓰이며, 도시별로 데이터를 묶어야 할 때 가장 많이 활용되는 형태입니다.

reduce가 users 배열을 순회하면서 도시 이름을 기준으로 객체를 구성해 나가는 과정입니다. 각 사용자는 해당 도시 이름을 key로 하는 배열에 차례대로 추가됩니다.

1) 최초 상태
초기값이 {} 이므로 아직 아무 도시도 없는 상태에서 시작합니다.

2) 첫 번째 사용자 A (Seoul)
acc["Seoul"]이 없으므로 빈 배열 생성
– A를 push하여 Seoul: [{name:"A", city:"Seoul"}] 형태가 됩니다.

3) 두 번째 사용자 B (Busan)
– Busan 키가 없으므로 새로운 배열 생성
– B를 push하여 Busan: [{name:"B", city:"Busan"}]이 됩니다.

4) 세 번째 사용자 C (Seoul)
– Seoul 배열이 이미 있으므로 그대로 push하여 Seoul: [{A}, {C}] 형태가 됩니다.

최종 결과
모든 순회가 끝나면 도시별로 사용자가 묶인 객체가 완성됩니다.

{
  Seoul: [ {name: "A", city: "Seoul"}, {name: "C", city: "Seoul"} ],
  Busan: [ {name: "B", city: "Busan"} ]
}

이처럼 reduce는 단순한 누적뿐 아니라 배열을 “새로운 구조”로 재조립할 때 매우 강력하게 사용됩니다.

고급 활용: 조건 분기 & 비동기 흐름 제어

reduce는 단순 계산을 넘어, 복잡한 조건문을 활용한 데이터 흐름 제어나 비동기 작업 순차 실행에도 활용됩니다.

조건부 누적 처리

const nums = [1,2,3,4,5];
const evens = nums.reduce((acc, num) => {
  if (num % 2 === 0) acc.push(num);
  return acc;
}, []);
console.log(evens); // [2,4]

이 예시는 filter와 비슷해 보이지만, reduce를 사용하면 조건을 만족하는 값만 누적 배열에 직접 담을 수 있습니다. 조건이 더 복잡하거나 여러 누적값을 동시에 다뤄야 할 때 filter보다 유연하게 확장할 수 있다는 점이 큰 장점입니다.

비동기 순차 실행 (초심자 필수 이해)

const tasks = [task1, task2, task3];

tasks.reduce((prevPromise, task) => {
  return prevPromise.then(result => task().then(r => [...result, r]));
}, Promise.resolve([]))
.then(console.log);

이 패턴은 여러 개의 비동기 작업을 순서대로 실행해야 할 때 유용합니다. reduce는 이전 작업의 결과를 다음 작업으로 자연스럽게 넘겨줄 수 있기 때문에, async/await을 사용하지 않은 환경에서도 직관적인 흐름 제어가 가능합니다. 특히 API 호출을 순차적으로 진행하거나, 순서가 중요한 계산 작업에 자주 활용됩니다.

reduce를 학습한 개발자의 성장 이미지

map / filter / forEach / reduce 비교 테이블

아래 비교 표는 초심자가 네 가지 배열 메서드를 정확히 구분할 수 있도록 구성한 정리표입니다. 어떤 상황에서 어떤 메서드를 써야 하는지 직관적으로 이해할 수 있습니다.

메서드 주요 목적 반환값 원본 배열 변경 여부 대표 사용 예
forEach 단순 반복 수행 undefined 변경하지 않음 (직접 변경 시 변함) 로그 출력, DOM 조작 등 부수 효과 작업
map 요소 변환 새로운 배열 변경하지 않음 값 변환, 새로운 배열 생성
filter 조건에 맞는 요소만 추출 새로운 배열 변경하지 않음 검색 또는 특정 조건 데이터만 모으기
reduce 하나의 값 또는 구조로 축약 숫자, 배열, 객체 등 무엇이든 가능 변경하지 않음 합계, 그룹화, 중첩 구조 변환 등 고급 로직

정리

reduce는 초심자에게 어렵게 느껴지지만, 누적(accumulate) 개념과 초기값의 중요성을 이해하면 map·filter의 한계를 극복하고 더욱 강력한 데이터 처리 로직을 구성할 수 있습니다. 실무에서 데이터 그룹화, 변환, 비동기 흐름 제어 등 다양한 문제를 해결하는 데 reduce는 매우 중요한 역할을 합니다.

FAQ

Q. reduce가 너무 복잡해 보이는데 쉽게 이해하는 방법이 있나요?
accumulator를 “지금까지의 결과”, currentValue를 “지금 처리 중인 값”이라고 생각하면 매우 간단합니다. 쇼핑할 때 장바구니 합계를 떠올리면 바로 이해됩니다.

Q. map과 filter 대신 reduce만 써도 되나요?
기술적으로는 가능하지만 가독성을 위해 map/filter를 우선 사용하는 것이 좋습니다. 여러 변환을 한 번에 처리해야 할 때만 reduce를 선택하는 것이 실무적입니다.

Q. 빈 배열에서 reduce를 호출하면 오류가 나는 이유는?
초기값이 없으면 reduce는 첫 요소를 누적값으로 사용합니다. 그러나 배열이 비어 있으면 첫 요소가 없어 오류가 발생합니다. 따라서 실무에서는 항상 초기값을 명시합니다.

Q. reduce가 너무 복잡해 보이는데 쉽게 이해하는 방법이 있나요?
accumulator를 “지금까지의 결과”, currentValue를 “지금 처리 중인 값”이라고 생각하면 매우 간단합니다. 쇼핑할 때 장바구니 합계를 떠올리면 바로 이해됩니다.

Q. map과 filter 대신 reduce만 써도 되나요?
기술적으로는 가능하지만 가독성을 위해 map/filter를 우선 사용하는 것이 좋습니다. 여러 변환을 한 번에 처리해야 할 때만 reduce를 선택하는 것이 실무적입니다.

Q. 빈 배열에서 reduce를 호출하면 오류가 나는 이유는?
초기값이 없으면 reduce는 첫 요소를 누적값으로 사용합니다. 그러나 배열이 비어 있으면 첫 요소가 없어 오류가 발생합니다. 따라서 실무에서는 항상 초기값을 명시합니다.

면접 질문으로 자주 등장하는 reduce 관련 Q&A

Q. reduce와 다른 배열 메서드(map, filter)의 가장 큰 차이점은 무엇인가요?
map과 filter는 “항상 새로운 배열을 반환”하지만, reduce는 숫자·문자열·배열·객체 등 어떤 형태든 만들어낼 수 있습니다. 즉, reduce는 구조 변환 및 누적 로직에 최적화된 메서드입니다.

Q. reduce 콜백에서 accumulator를 불변하게 유지해야 하는 이유는?
불변성을 유지하면 예측 가능성이 높아지고 디버깅이 쉬워집니다. 특히 React 등 상태 기반 UI 환경에서는 불변성을 유지하지 않으면 렌더링이 정상적으로 트리거되지 않는 문제가 발생할 수 있습니다.

Q. reduce에서 초기값(initialValue)을 설정하는 것이 중요한 이유는 무엇인가요?
초기값이 존재하면 비어 있는 배열에서도 오류가 발생하지 않으며, 누적값의 타입을 명확하게 유지할 수 있습니다. 이는 타입 안정성과 예측 가능한 로직 구현에 매우 중요합니다.

Q. reduce를 사용할 때 흔히 발생하는 실수는 무엇인가요?
누적값의 타입을 일관되게 유지하지 않아 예상치 못한 결과가 발생하는 경우가 많습니다. 또한 초기값을 생략할 때 빈 배열 처리에서 오류가 발생하기 쉬운 점도 대표적인 실수입니다.

Q. reduce를 실무에서 가장 많이 사용하는 예는 무엇인가요?
데이터 그룹화, 합계 계산, 통계 처리, 중첩 배열 평탄화, API 응답 재구조화 등입니다. 특히 백엔드에서 넘어온 데이터를 프론트엔드에서 화면에 적합한 형태로 변환할 때 자주 사용됩니다.

Q. async/await과 reduce를 함께 사용할 때 주의해야 하는 점은?
reduce는 순차 실행 구조를 만들 수 있지만, 콜백을 async로 만들면 누적된 Promise 객체가 의도대로 동작하지 않을 수 있습니다. 이 경우 명시적으로 Promise 체이닝을 구성하거나 for-await-of을 고려해야 합니다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기