Conceptly
← 전체 목록
🔄

Map·Filter·Reduce

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

map, filter, reduce는 컬렉션을 다룰 때 제일 자주 손에 잡히는 세 가지 기본 도구입니다. map은 각 원소를 다른 값으로 바꾸고, filter는 조건에 맞는 것만 남기고, reduce는 여러 원소를 하나의 값으로 모읍니다. 셋 다 원본을 건드리지 않고 새 결과를 만든다는 공통점이 있고, 고차 함수가 실무에서 어떻게 쓰이는지 가장 선명하게 보여 주는 사례이기도 합니다.

아키텍처 다이어그램

📊 데이터 흐름 다이어그램

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

왜 필요한가요?

배열을 변환하고, 골라내고, 집계하는 일은 정말 자주 나옵니다. 그런데 for 반복문으로 풀면 매번 인덱스를 만들고, 결과 배열을 준비하고, 조건을 검사하고, push 하거나 누적값을 갱신하는 비슷한 뼈대가 따라붙습니다. 그러다 보면 진짜 중요한 '각 원소를 어떻게 다룰 건가'가 반복문 소음 속에 묻혀 버립니다. 코드를 읽는 쪽도 이 루프가 변환인지, 필터링인지, 집계인지 끝까지 내려가 봐야 알게 됩니다. 작업 의도를 함수 이름만으로 먼저 드러내고 싶을 때 map, filter, reduce가 필요합니다.

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

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 루프는 '어떻게 돌고 어디에 담을지'를 직접 적는 방식이고, map·filter·reduce는 '변환인가, 선택인가, 집계인가'를 이름으로 먼저 드러냅니다. 컬렉션 처리 의도를 빠르게 읽히게 하고 싶다면 map·filter·reduce가 맞고, 중간에 바로 멈춰야 하거나 루프 안에서 복잡한 상태를 함께 관리해야 한다면 for 루프가 더 솔직합니다. 세 함수끼리의 경계도 분명합니다. 각 원소를 같은 개수의 다른 값으로 바꾼다면 map, 일부만 남긴다면 filter, 여러 원소를 하나의 값이나 다른 구조로 접는다면 reduce가 맞습니다. reduce로 나머지를 흉내 낼 수는 있지만, 그렇게 쓰기 시작하면 코드가 '무엇을 하는가'보다 '어떻게 누적하는가' 쪽으로 기울어 읽기 어려워집니다.

트레이드오프

Gain 변환, 선택, 집계라는 작업 의도가 함수 이름에 바로 드러나서, 컬렉션 처리 흐름을 작은 단계로 읽고 조합하기 쉬워집니다. Cost 각 단계를 체이닝하면 중간 배열이 생기고 콜백이 늘어나므로, 데이터가 매우 크거나 핫 루프에서는 메모리와 실행 비용이 눈에 띌 수 있습니다. 특히 단순 변환까지 reduce 하나로 몰아쓰면 오히려 읽기 어려워집니다. Decision Scale 일반적인 UI, API 응답 가공, 데이터 변환처럼 가독성과 조합성이 중요한 곳에서는 map·filter·reduce가 기본 선택입니다. 반대로 중간 탈출이 중요하거나 대용량 컬렉션을 한 번에 최대한 싸게 처리해야 하는 경로에서는 for 루프나 지연 평가 도구가 더 적합할 수 있습니다.

언제 쓰나요?

map, filter, reduce는 UI 리스트 렌더링, API 응답 가공, 테이블 데이터 정리, 통계 집계, 폼 검증 결과 모으기처럼 거의 모든 일상 작업에 스며 있습니다. React에서 users.map(...)으로 JSX를 만들고, 검색 결과를 걸러내고, 장바구니 합계를 구하는 코드가 전부 이 셋의 조합입니다. 실무에서는 find, some, every, flatMap 같은 다른 배열 메서드도 함께 자주 씁니다. 그래도 기본 감각은 같습니다. '변환인가, 선택인가, 집계인가'를 먼저 묻고 거기에 맞는 함수를 고르면 코드가 훨씬 빨리 읽힙니다.

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