Conceptly
← 전체 목록
🔄

Map·Filter·Reduce

컬렉션 처리컬렉션을 변환하는 세 가지 기본 도구

map, filter, reduce는 배열(또는 컬렉션)을 다루는 세 가지 기본 도구입니다. map은 각 원소를 변환하고, filter는 조건에 맞는 원소만 남기고, reduce는 원소를 하나의 값으로 모읍니다. 세 함수 모두 원본을 바꾸지 않고 새로운 값을 만들어 반환한다는 공통점이 있으며, 고차 함수의 가장 대표적인 활용 사례입니다. 함수형 스타일로 데이터를 다루는 실무 코드의 대부분이 이 세 가지 조합으로 이뤄집니다.

아키텍처 다이어그램

📊 데이터 흐름 다이어그램

점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다

왜 필요한가요?

배열의 각 원소를 바꾸거나, 특정 조건만 고르거나, 합계를 구하는 일은 프로그래밍에서 가장 자주 반복되는 작업입니다. 전통적인 for 반복문으로 쓰면 매번 인덱스 변수를 선언하고, 결과 배열을 미리 만들고, 조건문을 넣고, push를 호출하는 구조가 비슷비슷하게 반복됩니다. 문제는 이 반복되는 뼈대 때문에 정작 중요한 '각 원소에 무엇을 할 것인가'가 코드 속에 묻힌다는 점입니다. 코드를 읽을 때도 '이 반복문이 변환하는 건지, 거르는 건지, 집계하는 건지'를 본문을 끝까지 봐야 알 수 있습니다. 작업의 의도를 코드 표면에 바로 드러내는 도구가 필요합니다.

왜 이런 방식이 등장했나요?

map, filter, reduce는 Lisp와 ML 같은 함수형 언어에서 이미 표준이었고, 2000년대 후반 JavaScript가 ES5에서 배열 메서드로 도입하면서 주류 언어 개발자에게도 일상 도구가 됐습니다. Python의 리스트 컴프리헨션, Ruby의 each/map/select, Java Stream API, Kotlin의 컬렉션 연산자도 결국 같은 발상의 변주입니다. for 루프가 '어떻게'를 기술했다면 이 세 도구는 '무엇을'을 기술합니다. 같은 결과를 얻더라도 '각 원소를 이렇게 바꾼다', '이 조건을 만족하는 것만 남긴다', '이렇게 누적한다'는 의도가 함수 이름에 담겨서, 코드를 읽는 사람이 본문 없이도 작업 유형을 바로 파악할 수 있습니다. 이 명료함이 함수형 스타일이 주류에 자리 잡은 핵심 이유 중 하나입니다.

내부적으로 어떻게 동작하나요?

세 함수는 전부 '함수를 인자로 받아 배열의 각 원소에 적용한다'는 틀을 공유하지만 결과를 만드는 방식이 다릅니다. map은 원소마다 변환 함수를 적용해 같은 길이의 새 배열을 만듭니다. 원본이 3개면 결과도 3개입니다. filter는 원소마다 조건 함수를 적용해 true가 나온 것만 모읍니다. 결과 길이는 원본보다 작거나 같습니다. reduce는 누적 변수와 각 원소를 받는 함수를 적용해 최종적으로 하나의 값을 만듭니다. 결과는 배열일 수도, 숫자일 수도, 객체일 수도 있습니다. 세 함수 모두 원본 배열을 건드리지 않고 새 결과를 반환하므로 불변성과도 잘 맞고, 결과가 다시 배열이라면 바로 다음 함수로 이어 붙이는 체이닝이 자연스럽게 가능합니다.

코드로 보면

세 도구를 이어 쓰는 파이프라인

const users = [
  { name: "준영", age: 30, active: true },
  { name: "민수", age: 25, active: false },
  { name: "지원", age: 28, active: true },
  { name: "서연", age: 35, active: true }
];

// 활성 사용자의 나이 평균 구하기
const avgAge = users
  .filter(u => u.active)           // 활성만 남김 → 3명
  .map(u => u.age)                 // 나이만 뽑음 → [30, 28, 35]
  .reduce((sum, age) => sum + age, 0) / 3;  // 합계 → 93, 평균 31

// for 루프로 쓰면 ↓
let total = 0;
let count = 0;
for (const u of users) {
  if (u.active) {
    total += u.age;
    count += 1;
  }
}
const avgAgeImperative = total / count;

filter → map → reduce 체이닝은 각 단계의 의도가 함수 이름에 그대로 드러납니다. for 루프 버전은 같은 일을 하지만 '활성만 뽑고, 나이만 꺼내고, 합계 낸다'는 의도가 본문 안에 녹아 있어 한눈에 읽히지 않습니다.

같은 패턴의 배열 메서드들

const users = [
  { name: "준영", age: 30, active: true },
  { name: "민수", age: 25, active: false },
  { name: "지원", age: 28, active: true },
  { name: "서연", age: 35, active: true }
];

// find — 조건에 맞는 첫 원소 하나
const found = users.find(u => u.name === "지원");
// { name: "지원", age: 28, active: true }

// some — 하나라도 만족하면 true
const hasInactive = users.some(u => !u.active);  // true

// every — 전부 만족해야 true
const allActive = users.every(u => u.active);    // false

// flatMap — 변환 + 평탄화
const tags = [
  { name: "준영", skills: ["React", "Node"] },
  { name: "지원", skills: ["Vue", "Python"] }
];
const allSkills = tags.flatMap(u => u.skills);
// ["React", "Node", "Vue", "Python"]

find, some, every, flatMap은 모두 콜백을 받아 배열의 각 원소에 적용하는 같은 틀을 따릅니다. 이름만 보면 '하나 찾기', '하나라도?', '전부?', '펼치며 변환'이라는 의도가 바로 읽힙니다.

경계와 구분

map, filter, reduce와 for 루프는 같은 일을 할 수 있지만 관점이 다릅니다. for 루프는 '어떻게 순회하고 어떻게 결과를 모을지'를 단계별로 기술하는 명령형 스타일이고, 배열 메서드는 '무엇을 변환, 필터, 집계할지'를 선언적으로 표현합니다. 성능만 보면 단순한 for 루프가 살짝 빠를 수 있지만, 대부분의 UI나 API 응답 처리 규모에서는 체감되지 않고 가독성 이점이 훨씬 큽니다. 반대로 수백만 개 원소를 다루는 연산 집약적 루프거나 early return으로 중간에 빠져나가야 한다면 for 루프가 더 적합합니다. reduce와 map/filter도 구분이 필요합니다. reduce는 결과를 배열로 돌려줄 수도 있어서 map과 filter를 reduce로 구현할 수 있습니다. 하지만 reduce로 전부 쓰면 '무엇을 하는지' 이름만 보고 알기 어렵습니다. 각 원소를 변환만 한다면 map, 거른다면 filter, 누적해 다른 형태를 만든다면 reduce로 의도를 이름에 담는 편이 읽기 좋습니다. JavaScript 배열에는 이 세 가지 외에도 같은 패턴을 따르는 메서드가 더 있습니다. find는 조건에 맞는 첫 원소 하나를 반환하고, some은 하나라도 조건을 만족하면 true, every는 전부 만족해야 true를 돌려줍니다. flatMap은 map과 flat을 합쳐 변환 결과가 배열일 때 한 단계 펼쳐 줍니다. 모두 콜백을 받고, 원본을 건드리지 않으며, 이름이 곧 의도입니다. map·filter·reduce의 사고방식을 익히면 나머지는 자연스럽게 따라옵니다.

언제 쓰나요?

map, filter, reduce는 UI 리스트 렌더링, API 응답 가공, 데이터 테이블 처리, 통계 집계, 폼 검증 결과 모으기 같은 일상적인 작업 거의 전부에 쓰입니다. React에서 배열을 JSX로 변환하는 `users.map(u => <User ... />)` 패턴, 검색 결과 필터링, 장바구니 합계 계산이 모두 이 셋의 조합입니다. 실무에서는 나머지 배열 메서드도 자주 만납니다. 목록에서 특정 ID의 항목을 꺼낼 때 find, 폼 필드 중 하나라도 비었는지 확인할 때 some, 모든 약관에 동의했는지 검사할 때 every, 각 주문의 상품 목록을 하나의 배열로 합칠 때 flatMap을 씁니다. 이 메서드들도 map·filter·reduce와 동일하게 콜백을 받고 원본을 유지하므로, 세 가지 기본 도구에 익숙해지면 상황에 맞는 메서드를 고르는 일이 자연스러워집니다. 반복문을 쓰기 전에 '변환인가, 선택인가, 집계인가, 탐색인가, 검증인가'를 먼저 묻고 그에 맞는 함수를 고르면 이름만으로 의도가 드러납니다. 파이프라인이 4단계 이상으로 길어지고 중간 계산이 반복된다면 각 단계를 이름 있는 작은 함수로 빼서 조합하는 편이 읽기 쉬워집니다.

목록 표시조건 필터링집계파이프라인