JavaScript sort() 숫자 정렬 오류 해결법 (10이 2보다 앞에 올 때)

JavaScript에서 숫자 배열에 sort()만 호출하면 숫자 크기가 아니라 문자열 기준으로 순서를 정합니다. 유한한 Number 값만 있는 배열을 숫자 오름차순으로 정렬하려면 sort((a, b) => a - b)를 씁니다. 원본을 남겨야 한다면 복사한 배열에 적용하세요.

같은 입력을 두 기준으로 정렬하면

AI가 만든 정렬 코드의 결과가 낯설다면, 비교 함수를 빼고 넣은 결과부터 나란히 보세요. 아래는 실제 주문이나 고객 자료가 아닌 짧은 합성 배열입니다.

const numbers = [10, 2, 1, 20];
const byDefault = [...numbers].sort();
const ascending = [...numbers].sort((a, b) => a - b);

console.log(JSON.stringify(byDefault)); // [1,10,2,20]
console.log(JSON.stringify(ascending)); // [1,2,10,20]
console.log(typeof byDefault[1]); // number

기본 정렬은 [1,10,2,20], 숫자 비교 함수를 넣은 정렬은 [1,2,10,20]입니다. 비교할 때 문자열 기준을 쓴다고 배열 안의 숫자 자체가 문자열로 바뀌는 것은 아닙니다. 첫 결과에서 10의 타입을 읽으면 여전히 number입니다.

10이 먼저라는 글자 아래 남색 10 카드가 코랄색 2 카드보다 앞에 놓인 종이 조형
숫자 크기와 기본 정렬의 문자열 기준이 다르다는 뜻을 표현한 설명용 생성 일러스트입니다. 실제 실행 화면은 아닙니다.

10이 먼저 놓이는 이유

기본 sort()는 이 예제의 숫자를 문자열로 바꿔 비교합니다. 문자열 "10"과 "2"의 첫 글자는 각각 "1"과 "2"입니다. UTF-16 코드 단위 순서에서 "1"이 앞서므로 "10"도 "2"보다 앞에 놓입니다. 여기서 UTF-16은 JavaScript가 문자열을 비교할 때 쓰는 코드 단위 기준입니다.

숫자 크기와 문자열 순서는 다른 기준입니다. [1,2,3]처럼 한 자리 양의 정수만 넣어 확인하면 차이가 드러나지 않을 수 있습니다. 예제처럼 10과 2를 함께 넣으면 두 기준을 구별하기 쉽습니다.

MDN의 기본 정렬 설명에서 해당 규칙을 확인할 수 있습니다.

윗줄은 1·10·2·20, 아랫줄은 1·2·10·20 순서로 놓인 숫자 카드
위는 기본 비교 기준, 아래는 숫자 비교 기준을 설명하는 생성 그림입니다. 정확한 입력과 실행 출력은 본문 코드를 따릅니다.

비교 함수는 참·거짓이 아니라 부호를 돌려줍니다

sort()의 비교 함수가 음수를 반환하면 a가 b보다 앞에, 양수를 반환하면 뒤에 놓입니다. 0이면 두 값을 정렬 기준상 같다고 취급합니다. 숫자 오름차순에서는 a - b가 이 부호를 만들어 줍니다.

console.log(10 - 2); // 8
console.log(2 - 10); // -8
console.log(2 - 2);  // 0

const values = [10, 2, 1, 20];
console.log(JSON.stringify([...values].sort((a, b) => b - a)));
// [20,10,2,1]

10 - 2는 양수이므로 10이 2보다 뒤에 갑니다. 2 - 10은 음수이므로 2가 앞에 갑니다. 내림차순은 b - a처럼 뺄 순서를 반대로 둡니다. (a, b) => a > b처럼 참·거짓만 반환하는 식으로 바꾸지 마세요. 비교 함수가 요구하는 음수·0·양수의 역할과 다릅니다.

MDN의 비교 함수 반환값 설명에서 해당 규칙을 확인할 수 있습니다.

결과 변수에 담아도 원본은 바뀝니다

숫자순 정렬과 원본 보존은 따로 결정해야 합니다. sort()는 원래 배열을 정렬하고, 바로 그 배열을 반환합니다. 결과를 새 변수에 받는 것만으로 복사본이 생기지는 않습니다.

const original = [10, 2, 1, 20];
const returned = original.sort((a, b) => a - b);
console.log(JSON.stringify(original)); // [1,2,10,20]
console.log(returned === original);    // true

const kept = [10, 2, 1, 20];
const sortedCopy = [...kept].sort((a, b) => a - b);
console.log(JSON.stringify(kept));       // [10,2,1,20]
console.log(JSON.stringify(sortedCopy)); // [1,2,10,20]
console.log(sortedCopy === kept);       // false

첫 비교가 true인 이유는 returned와 original이 같은 배열을 가리키기 때문입니다. 원본을 남겨야 할 때는 뒤 예제처럼 [...kept]로 새 배열을 만든 뒤 그 배열을 정렬합니다. 이 글은 숫자 원시값만 다루며, 객체가 들어 있는 배열의 깊은 복사를 설명하는 예제가 아닙니다.

MDN의 sort 반환값 설명에서 해당 규칙을 확인할 수 있습니다.

원본 트레이에는 10·2·1·20, 사본 트레이에는 1·2·10·20이 놓인 종이 조형
원본을 남기고 별도 배열을 정렬하는 개념을 보여주는 설명용 생성 일러스트입니다. 객체의 깊은 복사를 뜻하지 않습니다.

문자열 메서드와도 구분해야 합니다. replaceAll()은 새 문자열을 반환하지만, 이 글의 sort()는 원래 배열을 바꿉니다. 메서드 이름이나 결과 변수의 유무만 보고 원본 보존 여부를 판단하지 마세요.

replaceAll의 반환값과 원문을 구분한 예제와 원본 변경 여부를 비교해 볼 수 있습니다.

AI에게 다시 요청할 때는 기대 출력도 적으세요

“정렬이 이상해”보다 입력과 기대 출력을 함께 주면 고칠 기준이 분명해집니다. 다음은 설명용 요청문입니다.

[10, 2, 1, 20]을 숫자 오름차순 [1, 2, 10, 20]으로 정렬해 줘. 원래 배열은 그대로 두고 새 배열을 반환해 줘. 기본 sort()만 쓴 결과와 비교 함수로 정렬한 결과도 각각 출력해 줘.

코드를 받은 뒤에는 두 자릿수 값이 섞인 순서와 원본 배열을 함께 확인하세요. 이 예제의 범위는 유한한 Number 값으로 채운 배열입니다. 문자열·객체·BigInt, NaN·Infinity, 빈 슬롯이 섞인 입력에는 이 설명을 그대로 확대하지 않습니다.

2026년 10월 31일 Windows x64의 Aside 브라우저에서 본문 코드 3블록을 그대로 실행해 출력을 확인했습니다. User-Agent가 보고한 Chrome 버전은 154.0.0.0입니다. 음수·소수·중복 값, 빈 배열과 단일 값도 별도 합성 입력으로 확인했습니다. 이 버전 문자열은 브라우저의 보고값이며 실제 사용자 서비스나 다른 브라우저 전체를 검증한 결과는 아닙니다.

출처 및 확인일