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

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

||는 왼쪽 값이 falsy면 오른쪽을 반환합니다. falsy는 조건문에서 거짓처럼 취급되는 값을 부르는 말인데(MDN 용어집 참고), 자바스크립트에서는 0도 여기에 포함됩니다. 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처럼 괄호로 의도를 명시하도록 강제한 설계입니다.

이 시리즈의 다른 글