퀴즈 서비스를 운영하던 어느 날, 항의 메일이 도착했습니다. “저 방금 시험 봤는데 왜 ‘점수 없음’이라고 나오죠?” 확인해보니 그분의 점수는 0점. 분명히 데이터베이스에는 0이 저장돼 있었고, API도 0을 잘 돌려주고 있었습니다. 범인은 화면 코드의 딱 한 줄이었어요.
const score = userScore || "점수 없음";||는 왼쪽 값이 falsy면 오른쪽을 반환합니다. 그런데 자바스크립트에서 0은 falsy입니다. false, "", null, undefined, NaN도 마찬가지예요. 의도한 값인 0이 기본값으로 교체되는 버그, 생각보다 자주 발생합니다.

사진: Unsplash의 Erik Mclean
||의 동작 원리: falsy면 오른쪽#
||는 왼쪽이 falsy일 때 오른쪽 값을 반환합니다.
console.log(null || "기본값"); // "기본값"
console.log(undefined || "기본값"); // "기본값"
console.log(0 || "기본값"); // "기본값" ← 의도하지 않은 동작
console.log("" || "기본값"); // "기본값" ← 의도하지 않은 동작
console.log(false || "기본값"); // "기본값" ← 의도하지 않은 동작
자바스크립트의 falsy 값 목록:
| 값 | 타입 |
|---|---|
false | Boolean |
0, -0, 0n | Number, BigInt |
"", '', `` | String (빈 문자열) |
null | Null |
undefined | Undefined |
NaN | Number |
이 중 0, "", false는 의미 있는 값일 수 있습니다. 이게 ||의 함정이에요.
null과 undefined는 그렇다 쳐도, 숫자 0이 “없음"이라니. 자바스크립트가 처음 만들어질 때 이 결정을 내린 분이 지금은 어떤 심정일지 궁금합니다.
??의 동작 원리: null/undefined일 때만#
??(nullish coalescing)은 왼쪽 값이 null 또는 undefined일 때만 오른쪽을 반환합니다.
console.log(null ?? "기본값"); // "기본값" ✅
console.log(undefined ?? "기본값"); // "기본값" ✅
console.log(0 ?? "기본값"); // 0 ✅ 그대로 통과
console.log("" ?? "기본값"); // "" ✅ 그대로 통과
console.log(false ?? "기본값"); // false ✅ 그대로 통과
null과 undefined만 “값이 없는 상태"로 취급하고, 나머지는 모두 유효한 값으로 봅니다. 이 둘(null과 undefined)을 묶어 부르는 이름이 nullish예요 — 연산자 이름이 여기서 왔습니다.

참고로, 둘을 섞으면 문법 에러입니다#
||와 ??를 괄호 없이 한 식에 쓰면 어떻게 될까요? 실행조차 안 됩니다.
const value = a || b ?? c; // SyntaxError!
const value = (a || b) ?? c; // 괄호를 붙이면 OK
버그가 아니라 일부러 이렇게 설계됐어요. 두 연산자의 우선순위를 사람마다 다르게 기억할 게 뻔하니, 명세가 “싸우지 말고 괄호로 의도를 적어라"라고 심판을 본 겁니다. 언어가 강제하는 코드 리뷰인 셈이죠.
실무에서 자주 만나는 패턴#
숫자 설정값#
// 사용자가 타임아웃을 0으로 설정할 수 있는 경우
function createTimer(timeout) {
// ❌ 0을 설정하면 기본값 3000으로 대체됨
const delay = timeout || 3000;
// ✅ 0도 유효한 값으로 처리
const delay = timeout ?? 3000;
}빈 문자열 처리#
// 사용자가 설명을 의도적으로 비워둘 수 있는 경우
const description = userInput || "설명이 없습니다"; // ❌ 빈 문자열도 기본값으로
const description = userInput ?? "설명이 없습니다"; // ✅ null/undefined만 기본값으로
API 응답의 기본값#
const response = await fetchUserSettings();
// ❌ fontSize가 0이면 14로 덮어씌워짐
const fontSize = response.fontSize || 14;
// ✅ 명시적으로 null/undefined인 경우만 기본값 적용
const fontSize = response.fontSize ?? 14;Optional Chaining과 함께 쓰기#
??은 ?.(optional chaining)과 함께 쓸 때 진가를 발휘합니다.
const user = {
profile: {
age: 0, // 0세 사용자 (드물지만 유효한 값)
},
};
// ❌ age가 0이면 "나이 정보 없음"으로 표시됨
const age = user?.profile?.age || "나이 정보 없음";
// ✅ null/undefined인 경우만 기본값 표시
const age = user?.profile?.age ?? "나이 정보 없음";user?.profile?.age가 undefined(프로퍼티 없음)일 때만 “나이 정보 없음"을 보여주고, 0이 오면 그대로 0을 씁니다.
??= 할당 연산자#
??=는 값이 null 또는 undefined일 때만 할당합니다. 설정 초기화에 유용해요.
const config = {
theme: "dark",
language: null,
};
// null/undefined인 경우만 기본값 설정
config.language ??= "ko"; // null이었으니 "ko" 할당
config.theme ??= "light"; // "dark"가 있으니 그대로
console.log(config);
// { theme: "dark", language: "ko" }
기존에는 이걸 이렇게 썼어야 했죠.
config.language = config.language ?? "ko";??=로 훨씬 깔끔해집니다.
언제 ||를 써야 할까?#
??가 항상 좋은 건 아닙니다. 0, "", false도 기본값으로 교체해야 하는 경우가 있어요.
// 입력값이 없거나 비어있으면 기본 검색어 사용
const query = searchInput || "전체"; // "" → "전체"로 교체 의도적
// 권한이 없는 사용자에게 기본 역할 부여
const role = userRole || "guest"; // false/undefined/null 모두 "guest"
기준은 이렇게 생각하면 됩니다:
0,"",false도 “값이 없다"고 봐야 하면 →||null/undefined만 “값이 없다"고 봐야 하면 →??

헷갈리면 ??가 안전한 기본기입니다 — 실수해도 “기본값이 안 나오는” 쪽이지, 사용자의 0점이 증발하는 쪽은 아니니까요.
정리#
||: falsy 값(0,"",false,null,undefined등) 이면 기본값 반환??:null또는undefined일 때만 기본값 반환??=: 변수가null/undefined일 때만 할당
코드에서 기본값을 설정할 때, “0이나 빈 문자열도 유효한 값인가?“를 먼저 생각해보세요. 그 답이 ||와 ?? 중 어떤 걸 쓸지 알려줍니다.
그 항의 메일의 답장은 한 글자 수정이었습니다 — ||를 ??로. 0점을 받은 그분께는 죄송하지만, 덕분에 이 글이 나왔네요.
