..

map + filter vs flatMap

map + filter 대신 flatMap을 선택한 이유

실무에서 Promise.allSettled 결과를 다루다 보면 실패한 케이스만 추려내야 하는 순간이 꽤 자주 나온다.

예를 들면 이런 상황이다.

  • 여러 API를 병렬 호출한다.
  • 일부는 성공하고 일부는 실패한다.
  • 실패한 대상만 모아서 후처리하거나 로그를 남겨야 한다.

이때 자연스럽게 이런 코드를 작성하게 된다.


처음에 썼던 코드 — map + filter

const failedTargets = results
  .map((result, index) => {
    return result.status === 'rejected'
      ? {
          error: result.reason,
          target: targetList[index],
        }
      : undefined;
  })
  .filter(Boolean);

굉장히 흔한 패턴이다.

  1. map으로 변환하고
  2. 필요 없는 값은 undefined로 만든 뒤
  3. filter(Boolean)으로 제거한다.

처음엔 별 생각 없이 썼다. JS에서 워낙 자주 보던 패턴이니까.


그런데 TypeScript에서 뭔가 찜찜하다

이 코드가 TS strict 모드에 들어가면 미묘한 문제가 생긴다.

filter(Boolean)은 런타임 동작일 뿐이다. 타입 시스템은 이걸 완전하게 이해하지 못한다.

결과 타입은 여전히 이렇게 나온다:

({ error: any; target: string } | undefined)[]

즉, undefined가 남아있다고 판단한다.

그래서 결국 이렇게 타입 가드를 추가하게 된다.

.filter(
  (v): v is { error: any; target: string } => v !== undefined,
);

여기서부터 조금 이상해진다.

  • 애초에 undefined를 만들지 않으면 되지 않나?
  • 왜 일부러 만들고 다시 제거하고 있지?

코드가 살짝 “절차적으로” 느껴지기 시작한다.


그래서 flatMap으로 바꿨다

const failedTargets = results.flatMap((result, index) =>
  result.status === 'rejected'
    ? [{ error: result.reason, target: targetList[index] }]
    : [],
);

이 코드를 보고 처음 든 생각은 단순했다.

“실패한 것만 결과 배열에 넣는다.”

딱 이 문장이 그대로 코드가 된다.


무엇이 달라졌을까?

1️⃣ 순회가 한 번이다

  • map + filter → 2번 순회
  • flatMap → 1번 순회

물론 이 차이는 대부분의 경우 체감 불가다. 여기서 병목이 생길 일은 거의 없다.

하지만 불필요한 중간 상태가 사라진다.


2️⃣ undefined가 존재하지 않는다

이게 가장 중요했다.

map + filter는 이런 사고 흐름이다:

일단 다 변환 → 필요 없는 건 undefined → 나중에 제거

flatMap은 이렇게 생각한다:

필요한 것만 결과로 만든다

중간 쓰레기 값이 없다.


3️⃣ TypeScript 타입이 깔끔해진다

flatMap의 결과 타입은 정확하게:

{ error: any; target: string }[]

추가 타입 가드가 필요 없다. strict 모드에서도 아무 경고가 없다.

리팩터링할 때도 더 안전하다.


성능은 솔직히 중요하지 않다

이 코드는 대부분 이런 맥락에서 사용된다.

  • API 병렬 호출
  • DB 작업
  • 네트워크 I/O

실제 비용은 Promise 내부 작업에 있기에 배열 한 번 더 도는 건 거의 의미 없다. 그래서 선택 기준은 성능이 아니다.


결국 기준은 이것이다

✔ 의도가 코드에 얼마나 잘 드러나는가

✔ 타입 시스템과 잘 맞는가

flatMap은 불필요한 상태를 만들지 않아 타입 시스템과 자연스럽게 어울린다.


언제 어떤 걸 써야 할까?

상황 추천
단순 변환 map
필터링만 필요 filter
조건에 맞는 것만 결과로 생성 flatMap
TS strict 실무 코드 flatMap 쪽이 더 안정적

정리

JavaScript라면 둘 다 괜찮다. 성능 차이도 사실상 무시 가능하다.

하지만 TypeScript 실무 코드라면

“필요한 것만 결과로 만든다”

이 사고 모델을 그대로 표현하는 flatMap이 더 깔끔하고, 더 안전하고, 더 읽기 좋다.