Cannot access 'x' before initialization — TDZ 원인·해결·예방
자바스크립트에서 "ReferenceError: Cannot access 'x' before initialization"이 뜨면, let이나 const로 선언한 변수를 그 선언 줄보다 먼저 사용했다는 뜻이다. 여기서 낯선 단어가 하나 나온다. TDZ(Temporal Dead Zone, 일시적 사각지대)다. let·const 변수는 블록 안 어디서든 이름은 존재하지만, 선언 줄이 실행되기 전까지는 "아직 값을 쓸 수 없는" 구간에 머문다. 그 구간에서 변수를 건드리면 이 에러가 난다. 이름 자체가 없어서 나는 "x is not defined"와도, var가 undefined로 나오는 것과도 다르다. 이 글은 이 에러의 의미, not defined·var와의 차이, 원인과 해결을 정리한다.

이 글의 구성
🔎 에러 메시지와 TDZ의 의미
이 에러는 ReferenceError로, let·const 변수를 초기화되기 전에 접근했을 때 발생한다. 흔히 "let·const는 호이스팅되지 않는다"고 알려져 있지만, 정확히는 조금 다르다. let·const도 선언은 블록 맨 위로 끌어올려진다(호이스팅). 다만 var처럼 자동으로 undefined가 채워지지 않고, "초기화되지 않은" 상태로 남는다. 그래서 선언 줄이 실행돼 값이 정해지기 전까지는 그 이름을 읽거나 쓸 수 없다.
이 "블록 시작부터 선언 줄 직전까지"의 구간을 TDZ라 부른다. 이 구간 안에서 변수를 건드리면 "아직 초기화 전이라 접근할 수 없다"며 이 에러가 난다. 핵심은 이름은 이미 존재한다는 점이다. 그래서 "이름이 없다"는 not defined와 성격이 다르다. 참고로 파이어폭스는 같은 상황을 "can't access lexical declaration 'x' before initialization"처럼 조금 다른 문구로 보여주지만 의미는 같다.
핵심 — 이름은 있지만 아직 못 쓴다
let·const 변수는 블록 안에서 이름이 존재하되, 선언 줄이 실행되기 전까지 "쓸 수 없는" TDZ에 머문다. 그래서 이 에러는 "선언을 사용보다 아래에 뒀다"는 신호다. 해결은 단순하다. 변수를 쓰기 전에 선언·초기화되도록 순서를 맞추면 된다.
⚖️ TDZ vs not defined vs var
비슷해 보이는 세 상황을 구분하면 진단이 빨라진다. 무엇이 다른지 표로 정리했다.
| 상황 | 결과 | 이유 |
|---|---|---|
| let·const 선언 전 접근 | Cannot access before initialization | 이름은 있으나 초기화 전(TDZ) |
| 선언 없는 이름 접근 | x is not defined | 이름 자체가 없음 |
| var 선언 전 접근 | undefined (에러 아님) | 호이스팅돼 undefined로 초기화됨 |
핵심 차이는 "초기화 여부"다. var는 선언이 끌어올려지면서 undefined로 채워지므로, 선언 전에 접근해도 에러 없이 undefined가 나온다. let·const는 끌어올려지되 초기화가 안 돼 TDZ에 머물기 때문에, 선언 전에 접근하면 에러가 난다. 그리고 아예 선언하지 않은 이름은 not defined다. 즉 var는 "빈 채로라도 있음", let·const 선언 전은 "이름은 있지만 아직 못 씀", 미선언은 "이름이 없음"이다.
🧩 자주 나오는 발생 원인 4가지
원인 1 — 선언보다 위에서 변수 사용
가장 흔하다. console.log(x)를 먼저 쓰고 아래에서 let x = 1로 선언한 경우다. var 시절 습관으로 "어디서 선언하든 되겠지" 하고 쓰다 걸린다. 사용이 선언보다 위에 있으면 TDZ에 걸린다.
원인 2 — 블록 안 변수 섀도잉
바깥에 같은 이름의 변수가 있는데, 블록 안에서 그 이름을 let으로 다시 선언한 경우다. 블록 안에서는 안쪽 선언이 우선이라, 그 선언 줄 전에 이름을 쓰면 바깥 값이 아니라 TDZ에 걸린다.
원인 3 — 클래스를 선언 전에 사용
class도 let·const처럼 TDZ의 적용을 받는다. new MyClass()를 class 선언보다 위에서 호출하면 같은 에러가 난다. 함수 선언과 달리 클래스는 선언 전에 쓸 수 없다.
원인 4 — 초기화식이 자기 자신을 참조
let x = x + 1처럼 선언과 동시에 자기 자신을 읽으려는 경우다. 오른쪽의 x를 읽는 시점엔 아직 초기화 전이라 TDZ에 걸린다. 모듈 간 순환 참조에서 이른 시점 접근도 비슷한 형태다.
💻 재현과 진단
전형적인 재현이다. 선언 아래에서 위로 변수를 당겨 쓰면 막힌다.
// 선언 전에 접근 (TDZ)
console.log(count);
let count = 10;
// ✗ ReferenceError: Cannot access 'count' before initialization
// var 라면 에러 없이 undefined
console.log(v); // undefined
var v = 10;
진단의 핵심은 에러가 난 변수의 선언 위치를 찾는 것이다. 콘솔의 스택 추적으로 접근한 줄을 확인하고, 같은 이름의 let·const 선언이 그 아래에 있는지 본다. 아래에 있다면 TDZ가 원인이다. 한 가지 알아 둘 점은 typeof도 안전하지 않다는 것이다. 미선언 이름에는 typeof가 에러를 안 내지만, TDZ 변수에는 typeof도 같은 에러를 던진다.
// TDZ 변수엔 typeof 도 에러 (미선언과 다른 점)
typeof temp;
let temp = 1;
// ✗ Cannot access 'temp' before initialization
🛠 해결 방법과 예방
해결은 순서를 맞추는 것이 전부다. 변수를 쓰기 전에 선언·초기화되도록 코드 순서를 바로잡는다.
// ✓ 선언을 사용보다 위로
let count = 10;
console.log(count); // 10
// ✓ 클래스도 선언 후 사용
class User {}
const u = new User();
상황별로 정리하면 이렇다. 대부분은 선언 순서 조정과 섀도잉 점검으로 풀린다.
상황별 해결 방법
예방의 핵심은 도구와 습관이다. ESLint의 no-use-before-define 규칙을 켜면 선언 전 사용을 저장 시점에 잡아 준다. 그리고 "변수는 쓰기 직전에 선언한다"는 습관을 들이면 TDZ에 걸릴 일이 거의 없어진다. TDZ는 사실 var 시절의 "선언 전에도 몰래 쓸 수 있던" 애매함을 없애 실수를 일찍 드러내려는 장치이기도 하다.
💬 자주 묻는 질문 5가지
Q1let·const는 호이스팅이 안 되는 거 아닌가요?
선언은 호이스팅됩니다. 다만 var처럼 undefined로 자동 초기화되지 않고 "초기화 전" 상태로 남죠. 그래서 이름은 블록 안에 존재하지만 선언 줄 전까지는 쓸 수 없습니다. 이 구간이 TDZ입니다. "호이스팅이 안 된다"기보다 "끌어올려지되 초기화가 미뤄진다"가 정확합니다.
Q2"x is not defined"랑 뭐가 다른가요?
not defined는 이름 자체가 어디에도 선언되지 않은 것이고, 이 에러는 이름은 있지만 아직 초기화 전(TDZ)이라 못 쓰는 것입니다. 둘 다 ReferenceError지만, 전자는 선언·오타를, 후자는 선언과 사용의 순서를 점검합니다.
Q3var로 바꾸면 해결되나요?
에러는 사라지지만 권하지 않습니다. var는 선언 전 접근이 undefined로 조용히 넘어가 버그를 숨기죠. TDZ 에러는 오히려 "순서가 잘못됐다"고 일찍 알려주는 신호입니다. var로 덮기보다 선언을 사용 위로 올려 순서를 바로잡는 게 맞습니다.
Q4typeof로 확인하면 안전하지 않나요?
미선언 이름에는 typeof가 안전하지만, TDZ 변수에는 typeof도 같은 에러를 던집니다. 이름은 존재하되 초기화 전이기 때문이죠. 그래서 typeof로 "선언됐는지" 우회 검사하는 방법은 let·const에는 통하지 않습니다.
Q5함수는 선언 전에 호출해도 되던데요?
함수 선언문(function foo() {})은 통째로 호이스팅돼 선언 전 호출이 됩니다. 하지만 const foo = () => {} 같은 함수 표현식은 const가 TDZ의 적용을 받아 선언 전 호출 시 이 에러가 납니다. 클래스도 마찬가지고요. "함수는 다 된다"가 아니라 선언문만 그렇습니다.
📌 결론
"Cannot access 'x' before initialization"은 let·const(또는 클래스)를 선언 줄보다 먼저 사용했을 때 나는 ReferenceError다. 이름은 존재하지만 아직 초기화 전인 TDZ 구간에 있어 접근이 막힌 것이다. 이름이 아예 없는 not defined, undefined로 넘어가는 var와는 다르다.
실무 원칙은 다음과 같다.
🕐 TDZ를 이해한다. let·const는 끌어올려지되 초기화가 미뤄져, 선언 줄 전까지 못 쓴다. 이 구간에서 접근하면 에러다.
📍 순서를 바로잡는다. 선언을 사용보다 위로 올리거나 접근을 선언 아래로 내린다. 섀도잉·자기 참조 초기화도 점검한다.
🔀 var로 덮지 않는다. var는 버그를 숨긴다. TDZ 에러는 순서 실수를 일찍 드러내는 신호이니, ESLint no-use-before-define으로 예방한다.
TDZ 에러 체크리스트
본 글은 자바스크립트 ReferenceError(TDZ)의 일반적 원인과 해결 방법을 정리한 자료다. 코드 수정은 영향 범위를 확인한 뒤 신중히 적용한다.
#JavaScript #ReferenceError #TDZ #TemporalDeadZone #일시적사각지대 #자바스크립트에러 #호이스팅 #let #const #var #클래스 #ESLint #프론트엔드 #JS디버깅 #웹개발
'IT' 카테고리의 다른 글
| Invalid array length — 배열 길이 오류 원인·해결·예방 (0) | 2026.07.02 |
|---|---|
| Cannot convert undefined or null to object — 원인·해결·예방 (0) | 2026.07.02 |
| x is not iterable — 원인·해결·예방 (0) | 2026.07.01 |
| Unexpected token < in JSON — HTML 응답을 JSON으로 파싱할 때 원인·해결 (0) | 2026.07.01 |
| Assignment to constant variable — const 재할당 원인·해결·예방 (0) | 2026.07.01 |
댓글