배열을 돌았는데 결과가 undefined라면 forEach를 map처럼 사용했을 가능성이 크다. 둘 다 모든 요소를 순회하지만, 새 값을 만들려는지 반복 중 작업을 하려는지가 다르다.
map은 변환 결과를 담은 새 배열을 만든다
도식은 같은 입력을 사용해도 map은 새 배열을 만들고 forEach 자체의 반환값은 undefined라는 차이를 보여 준다. 실제 반환값을 나란히 출력해 보자.
const prices = [1000, 2500, 3000];
const labels = prices.map((price) => `${price.toLocaleString()}원`);
// ["1,000원", "2,500원", "3,000원"]map의 콜백은 각 요소의 새 값을 반환해야 한다. 중괄호를 썼다면 return을 빼먹지 않도록 주의한다.
const doubled = prices.map((price) => {
return price * 2;
});
console.assert(doubled.join(",") === "2000,5000,6000");map 안에서 중괄호를 열고 return을 빼면 길이는 같은 새 배열이 만들어져도 각 자리는 undefined가 된다. 화면에서 빈 항목이 보인다면 원본 배열과 변환 결과를 따로 찍어 비교한다.
const missing = prices.map((price) => {
price * 2;
});
console.assert(missing.every((value) => value === undefined));forEach는 반복 중 부수 효과에 쓴다
prices.forEach((price) => {
console.log(price);
});
const result = prices.forEach(() => {});
console.assert(result === undefined);로그 출력, 이미 있는 객체에 명령 보내기처럼 반환 배열이 필요 없는 작업에는 forEach가 의도를 잘 보여 준다. forEach의 반환값에 체이닝을 이어 붙이면 기대와 달리 undefined가 되어 오류가 난다.
원본 변경 여부도 함께 본다
두 메서드 모두 배열 요소 자체를 자동으로 바꾸지는 않는다. 그러나 콜백 안에서 객체 속성을 수정하면 원본 객체가 바뀐다. UI 상태나 함수 입력에서 이런 변경은 추적하기 어려운 버그가 되므로, 필요한 속성만 복사해 새 객체를 만들자.
const products = [{ id: 1, price: 1000 }];
const discounted = products.map((product) => ({
...product,
price: product.price * 0.9,
}));
console.assert(products[0].price === 1000);
console.assert(discounted[0].price === 900);성능을 이유로 둘을 고르기보다 원하는 결과가 새 배열인지 부수 효과인지 먼저 정한다. 상태 업데이트라면 새 배열뿐 아니라 그 안에서 변경한 객체도 새로 만드는지 테스트한다. 로그 출력에는 forEach가 자연스럽지만, 네트워크 요청을 각 항목에서 시작한다면 완료·실패를 기다리는 별도 흐름이 필요하다. forEach의 반환값이 그것을 대신하지 않는다.
반환값과 원본 배열을 함께 검사한다
const prices = [1000, 2500];
const doubled = prices.map((price) => price * 2);
const seen = prices.forEach(() => {});
console.assert(JSON.stringify(doubled) === '[2000,5000]');
console.assert(seen === undefined);
console.assert(JSON.stringify(prices) === '[1000,2500]');map 콜백에 중괄호를 붙이고 return을 빼면 새 배열의 요소가 undefined가 된다. 객체 배열이라면 새 배열을 만들었더라도 내부 객체를 직접 바꾸지 않았는지 추가로 확인한다.
변환 결과를 검증할 때는 길이뿐 아니라 각 값도 검사한다. 반면 반복 중 명령을 보내는 코드라면 결과 배열이 아니라 어느 항목에서 부수 효과가 일어났는지 확인해야 한다. map의 반환값을 사용하지 않는 코드는 선택한 메서드가 의도에 맞는지 다시 살펴볼 신호다.
핵심 요약
새 배열이 필요하면 map, 로그·명령처럼 반환값이 필요 없으면 forEach를 쓴다. map 콜백에서는 값을 반환해야 하며, 객체를 다룰 때는 원본을 몰래 바꾸지 않는지 확인하자. 이 구분만 지켜도 렌더링 결과가 undefined가 되는 오류를 많이 피할 수 있다.

