
한 달간 JavaScript로 코딩테스트 문제를 풀면서 새로 배우거나 헷갈렸던 개념들을 정리했다.
JavaScript는 조건식에 숫자를 그대로 사용할 수 있다. 0은 falsy, 나머지 숫자는 truthy이기 때문이다.
num % 2 // 1 또는 0 → 0이면 false, 나머지면 true
Java처럼 강타입 언어는 조건식에 반드시 boolean이 와야 하므로 이와 다르다.
if (num % 2) // 컴파일 에러
if (num % 2 == 0) // 정상 동작
.join(""): 배열 요소를 합쳐 문자열로 반환.split(""): 문자열을 잘라 배열로 반환.reverse(): 배열 순서를 뒤집음 (원본 변경).slice(start, end): 문자열/배열 일부를 잘라 반환.repeat(n): 문자열을 n번 반복한 새 문자열 반환.replace(대상, 내용): 문자열에서 특정 문자/패턴을 치환replace()는 두 가지 방식으로 사용한다// 1. 문자열: 처음 만나는 것 하나만 변경
"apple apple".replace("apple", "grape"); // "grape apple"
// 2. 정규표현식 + g 플래그: 전체 변경
"apple apple".replace(/apple/g, "grape"); // "grape grape"
Array(n) // 길이 n인 빈 슬롯 배열 (요소 없음)
Array(n).fill(0) // 길이 n, 모두 0으로 채움
Array.from({ length: n }, (_, i) => i + 1) // 길이 n, 인덱스 기반 초기화
Array(n)은 빈 슬롯이라 map이 동작하지 않는다. fill()로 채우거나 Array.from()을 사용해야 한다.
배열.map((value, index, array) => ...)
reduce는 예외로, (누적값, 현재값, 인덱스, 배열) 순이다.
const arr = ['a', 'b', 'c'];
for (let x of arr) console.log(x); // 'a', 'b', 'c' → 값(Value)
for (let x in arr) console.log(x); // '0', '1', '2' → 인덱스(Index)
for...of: 값을 꺼낼 때. 배열, 문자열 모두 사용 가능.for...in: 키/인덱스를 꺼낼 때. 주로 일반 객체 {}의 속성 순회에 사용.배열에 for...in을 쓰면 예상치 못한 동작이 생길 수 있으므로, 배열에는 for...of를 사용하는 것이 안전하다.
셋 다 배열을 순회하지만, 목적이 다르다.
forEach: 반환값 없음 (undefined). 단순 반복 실행할 때 사용.map: 새 배열 반환. 각 요소를 변환한 새 배열이 필요할 때 사용.reduce: 단일 값 반환. 합산, 객체 생성 등 하나의 값으로 줄일 때 사용.// forEach
arr.forEach((num) => console.log(num));
// map
arr.map((num) => num * 2); // [2, 4, 6, ...]
// reduce
arr.reduce((acc, cur) => acc + cur, 0); // 합산
reduce의 초기값은 항상 명시하는 것이 좋다. 생략하면 빈 배열에서 에러가 발생한다.
arr.sort((a, b) => a - b); // 오름차순
arr.sort((a, b) => b - a); // 내림차순
콜백 반환값이 음수이면 a가 앞으로, 양수이면 b가 앞으로 정렬된다.
n개 요소를 절반씩 나누면 log₂n번 분할되고, 각 단계에서 최대 n번 비교하므로 n × log n이 된다.
JavaScript V8 엔진(Node.js, Chrome)은 TimSort를 사용한다. 삽입 정렬과 병합 정렬을 결합한 방식으로, 최선/평균/최악 모두 O(n log n)을 보장한다.
삽입 정렬 → 작은 구간에서 빠름
병합 정렬 → 큰 구간에서 O(n log n) 보장
TimSort → 둘을 상황에 따라 혼합
// 문자열 정렬
arr.sort((a, b) => a.localeCompare(b)); // 오름차순
// 객체 배열 정렬
arr.sort((a, b) => a.age - b.age);
// 숫자를 이어붙인 수가 최대가 되도록 정렬
arr.sort((a, b) => (String(b) + String(a)) - (String(a) + String(b)));
arr.indexOf("Kim"); // 값이 일치하는 첫 번째 인덱스 반환
arr.findIndex(x => x === "Kim"); // 조건을 만족하는 첫 번째 인덱스 반환
단순한 값 비교라면 indexOf, 조건이 복잡하다면 findIndex를 쓴다.
arr.filter(n => n % 2 === 0); // 조건이 true인 요소만 새 배열로 반환
arr.every(n => n > 0); // 모두 true면 true, 하나라도 false면 즉시 false 반환
arr.includes(3); // 특정 값 포함 여부 (true/false)
every는 조건을 하나라도 만족하지 않으면 즉시 순회를 멈추기 때문에 효율적이다.
arr.splice(index, deleteCount); // 원본 배열 직접 수정
arr.filter(n => n !== target); // 원본 유지, 새 배열 반환
요소를 제거할 때 splice는 해당 인덱스 하나만 처리하지만, filter는 전체를 순회한다. 단, filter는 원본을 건드리지 않아 안전하다.
중복을 허용하지 않는 자료구조다.
const set = new Set([1, 2, 2, 3]); // Set {1, 2, 3}
set.has(2); // true
배열의 includes가 O(n)인 반면, Set.has()는 평균 O(1)이다. 포함 여부를 자주 확인해야 할 때는 배열 대신 Set을 쓰는 것이 유리하다.
Math.sqrt()는 부동소수점 연산을 사용하기 때문에 큰 수에서 미세한 오차가 생길 수 있다.
// 위험: 부동소수점 오차 가능성
Math.sqrt(n) === Math.floor(Math.sqrt(n))
// 안전: 반올림 후 역산으로 검증
const sqrt = Math.round(Math.sqrt(n));
sqrt * sqrt === n
정수 여부를 판별할 때는 Number.isInteger()를 사용할 수도 있다.
Number.isInteger(Math.sqrt(16)); // true
Number.isInteger(Math.sqrt(15)); // false
문자열을 배열로 바꿀 때 두 가지 방법이 있다.
"hello".split(""); // ["h", "e", "l", "l", "o"]
[..."hello"]; // ["h", "e", "l", "l", "o"]
차이가 생기는 경우는 이모지처럼 유니코드 보충 문자를 포함할 때다.
"😀".split(""); // ["\uD83D", "\uDE00"] → 이모지가 깨짐
[..."😀"]; // ["😀"] → 정상
split("")은 UTF-16 코드 유닛 단위로 자르기 때문에 이모지를 깨뜨린다. 스프레드는 유니코드 코드 포인트 단위로 처리하므로 이모지를 온전히 유지한다.
Math.min(...arr);
스프레드로 배열을 인자에 펼치는 방식은 인자 개수 제한이 있다. 배열 크기가 약 6만~12만 개를 넘으면 RangeError: Maximum call stack size exceeded 에러가 발생한다.
대용량 데이터에서는 직접 순회하는 방식이 안전하다.
let min = Infinity;
for (const num of numbers) {
if (num < min) min = num;
}
약수의 개수가 홀수인 수는 완전제곱수뿐이다.
일반적인 수는 약수가 쌍으로 존재하지만, 완전제곱수는 제곱근이 혼자 존재하므로 약수 개수가 홀수가 된다.
12의 약수 → (1, 12), (2, 6), (3, 4) → 6개 (짝수)
9의 약수 → (1, 9), (3) → 3개 (홀수) ← 완전제곱수
코딩테스트에서 약수 개수의 홀짝을 판별해야 할 때, 전체를 세는 대신 완전제곱수 여부만 확인하면 된다.
Number.isInteger(Math.sqrt(num)); // true면 약수 개수가 홀수
알고리즘의 효율성을 나타내는 표기법이다.
O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ)
빠름 ◀────────────────────────────────────────▶ 느림
O(1): 입력 크기와 무관하게 항상 일정한 시간O(n): 데이터 양에 비례해 시간이 늘어남O(n log n): 대부분의 정렬 알고리즘등차수열 합 공식처럼 반복문 없이 수식 하나로 풀 수 있다면 O(n)을 O(1)로 줄일 수 있다.
// O(n)
let sum = 0;
for (let i = 1; i <= n; i++) sum += i;
// O(1) - 가우스 공식
const sum = (n * (n + 1)) / 2;
JavaScript에서 원시값(number, string, boolean, null, undefined, symbol, bigint)은 힙(heap)이 아닌 스택(stack)에 저장된다. 스코프가 끝나면 그냥 스택에서 pop되며, 가비지 컬렉터(GC)가 개입하지 않는다.
GC는 힙에 할당된 객체, 배열, 함수 같은 참조 타입에만 해당한다.