퀴즈 서비스를 운영하던 어느 날, 항의 메일이 도착했습니다. “저 방금 시험 봤는데 왜 ‘점수 없음’이라고 나오죠?” 확인해보니 그분의 점수는 0점. 분명히 데이터베이스에는 0이 저장돼 있었고, API도 0을 잘 돌려주고 있었습니다. 범인은 화면 코드의 딱 한 줄이었어요.

javascript
const score = userScore || "점수 없음";

||는 왼쪽 값이 falsy면 오른쪽을 반환합니다. 그런데 자바스크립트에서 0은 falsy입니다. false, "", null, undefined, NaN도 마찬가지예요. 의도한 값인 0이 기본값으로 교체되는 버그, 생각보다 자주 발생합니다.

아케이드 게임기 점수판에 YOUR SCORE 0이 표시된 모습 - 0점은 엄연히 존재하는 점수인데, 자바스크립트의 || 연산자는 0을 falsy로 보고 기본값으로 바꿔버립니다
아케이드 게임기 점수판에 YOUR SCORE 0이 표시된 모습 - 0점은 엄연히 존재하는 점수인데, 자바스크립트의 || 연산자는 0을 falsy로 보고 기본값으로 바꿔버립니다
사진: UnsplashErik Mclean

||의 동작 원리: falsy면 오른쪽

||왼쪽이 falsy일 때 오른쪽 값을 반환합니다.

javascript
console.log(null || "기본값");      // "기본값"
console.log(undefined || "기본값"); // "기본값"
console.log(0 || "기본값");         // "기본값" ← 의도하지 않은 동작
console.log("" || "기본값");        // "기본값" ← 의도하지 않은 동작
console.log(false || "기본값");     // "기본값" ← 의도하지 않은 동작

자바스크립트의 falsy 값 목록:

데이터 표
타입
falseBoolean
0, -0, 0nNumber, BigInt
"", '', ``String (빈 문자열)
nullNull
undefinedUndefined
NaNNumber

이 중 0, "", false의미 있는 값일 수 있습니다. 이게 ||의 함정이에요.

nullundefined는 그렇다 쳐도, 숫자 0이 “없음"이라니. 자바스크립트가 처음 만들어질 때 이 결정을 내린 분이 지금은 어떤 심정일지 궁금합니다.

??의 동작 원리: null/undefined일 때만

??(nullish coalescing)은 왼쪽 값이 null 또는 undefined일 때만 오른쪽을 반환합니다.

javascript
console.log(null ?? "기본값");      // "기본값" ✅
console.log(undefined ?? "기본값"); // "기본값" ✅
console.log(0 ?? "기본값");         // 0        ✅ 그대로 통과
console.log("" ?? "기본값");        // ""       ✅ 그대로 통과
console.log(false ?? "기본값");     // false    ✅ 그대로 통과

nullundefined만 “값이 없는 상태"로 취급하고, 나머지는 모두 유효한 값으로 봅니다. 이 둘(nullundefined)을 묶어 부르는 이름이 nullish예요 — 연산자 이름이 여기서 왔습니다.

||와 ?? 두 필터의 비교 다이어그램 - || 필터는 0, 빈 문자열, false까지 전부 걸러내 기본값으로 바꾸지만, ?? 필터는 null과 undefined만 걸러내고 0, 빈 문자열, false는 유효한 값으로 통과시킵니다
||와 ?? 두 필터의 비교 다이어그램 - || 필터는 0, 빈 문자열, false까지 전부 걸러내 기본값으로 바꾸지만, ?? 필터는 null과 undefined만 걸러내고 0, 빈 문자열, false는 유효한 값으로 통과시킵니다

참고로, 둘을 섞으면 문법 에러입니다

||??를 괄호 없이 한 식에 쓰면 어떻게 될까요? 실행조차 안 됩니다.

javascript
const value = a || b ?? c;   // SyntaxError!
const value = (a || b) ?? c; // 괄호를 붙이면 OK

버그가 아니라 일부러 이렇게 설계됐어요. 두 연산자의 우선순위를 사람마다 다르게 기억할 게 뻔하니, 명세가 “싸우지 말고 괄호로 의도를 적어라"라고 심판을 본 겁니다. 언어가 강제하는 코드 리뷰인 셈이죠.

실무에서 자주 만나는 패턴

숫자 설정값

javascript
// 사용자가 타임아웃을 0으로 설정할 수 있는 경우
function createTimer(timeout) {
  // ❌ 0을 설정하면 기본값 3000으로 대체됨
  const delay = timeout || 3000;

  // ✅ 0도 유효한 값으로 처리
  const delay = timeout ?? 3000;
}

빈 문자열 처리

javascript
// 사용자가 설명을 의도적으로 비워둘 수 있는 경우
const description = userInput || "설명이 없습니다"; // ❌ 빈 문자열도 기본값으로
const description = userInput ?? "설명이 없습니다"; // ✅ null/undefined만 기본값으로

API 응답의 기본값

javascript
const response = await fetchUserSettings();

// ❌ fontSize가 0이면 14로 덮어씌워짐
const fontSize = response.fontSize || 14;

// ✅ 명시적으로 null/undefined인 경우만 기본값 적용
const fontSize = response.fontSize ?? 14;

Optional Chaining과 함께 쓰기

???.(optional chaining)과 함께 쓸 때 진가를 발휘합니다.

javascript
const user = {
  profile: {
    age: 0, // 0세 사용자 (드물지만 유효한 값)
  },
};

// ❌ age가 0이면 "나이 정보 없음"으로 표시됨
const age = user?.profile?.age || "나이 정보 없음";

// ✅ null/undefined인 경우만 기본값 표시
const age = user?.profile?.age ?? "나이 정보 없음";

user?.profile?.ageundefined(프로퍼티 없음)일 때만 “나이 정보 없음"을 보여주고, 0이 오면 그대로 0을 씁니다.

??= 할당 연산자

??=는 값이 null 또는 undefined일 때만 할당합니다. 설정 초기화에 유용해요.

javascript
const config = {
  theme: "dark",
  language: null,
};

// null/undefined인 경우만 기본값 설정
config.language ??= "ko";    // null이었으니 "ko" 할당
config.theme ??= "light";    // "dark"가 있으니 그대로

console.log(config);
// { theme: "dark", language: "ko" }

기존에는 이걸 이렇게 썼어야 했죠.

javascript
config.language = config.language ?? "ko";

??=로 훨씬 깔끔해집니다.

언제 ||를 써야 할까?

??가 항상 좋은 건 아닙니다. 0, "", false도 기본값으로 교체해야 하는 경우가 있어요.

javascript
// 입력값이 없거나 비어있으면 기본 검색어 사용
const query = searchInput || "전체"; // "" → "전체"로 교체 의도적

// 권한이 없는 사용자에게 기본 역할 부여
const role = userRole || "guest"; // false/undefined/null 모두 "guest"

기준은 이렇게 생각하면 됩니다:

  • 0, "", false도 “값이 없다"고 봐야 하면 → ||
  • null/undefined만 “값이 없다"고 봐야 하면 → ??
||와 ?? 선택 가이드 다이어그램 - 기본값을 넣기 전에 0이나 빈 문자열, false도 유효한 값인지 묻고, 유효하면 ??를, 없는 것으로 취급해야 하면 ||를 고르는 갈림길입니다. 확신이 없다면 ??가 데이터를 덜 잃는 안전한 기본기라는 안내가 붙어 있습니다
||와 ?? 선택 가이드 다이어그램 - 기본값을 넣기 전에 0이나 빈 문자열, false도 유효한 값인지 묻고, 유효하면 ??를, 없는 것으로 취급해야 하면 ||를 고르는 갈림길입니다. 확신이 없다면 ??가 데이터를 덜 잃는 안전한 기본기라는 안내가 붙어 있습니다

헷갈리면 ??가 안전한 기본기입니다 — 실수해도 “기본값이 안 나오는” 쪽이지, 사용자의 0점이 증발하는 쪽은 아니니까요.

정리

  • ||: falsy 값(0, "", false, null, undefined 등) 이면 기본값 반환
  • ??: null 또는 undefined일 때만 기본값 반환
  • ??=: 변수가 null/undefined일 때만 할당

코드에서 기본값을 설정할 때, “0이나 빈 문자열도 유효한 값인가?“를 먼저 생각해보세요. 그 답이 ||?? 중 어떤 걸 쓸지 알려줍니다.

그 항의 메일의 답장은 한 글자 수정이었습니다 — ||??로. 0점을 받은 그분께는 죄송하지만, 덕분에 이 글이 나왔네요.


질문으로 다시 보기

??와 ||의 차이는 무엇인가요?
||는 왼쪽 값이 falsy(0, 빈 문자열, false, null, undefined, NaN)면 오른쪽 기본값을 반환하지만, ??(nullish coalescing)는 왼쪽이 null 또는 undefined일 때만 기본값을 반환합니다. 0이나 빈 문자열을 유효한 값으로 살려야 한다면 ??를 사용하세요.
??와 ||를 한 식에서 같이 쓰면 왜 SyntaxError가 나나요?
a || b ?? c처럼 괄호 없이 섞으면 자바스크립트 명세가 문법 오류로 정의하고 있습니다. 두 연산자의 우선순위를 개발자가 오해하기 쉬워, 반드시 (a || b) ?? c처럼 괄호로 의도를 명시하도록 강제한 설계입니다.

이 시리즈의 다른 글