Let and Const in TypeScript

TypeScript infers different types for let and const. Learn how block scoping changes type inference, why const gets literal types, and when to choose each.

5 min read

TypeScript infers different types depending on whether you use let or const. A const variable gets a precise literal type because its value cannot change. A let variable gets a wider general type because reassignment is allowed.

This is the fundamental difference, and it affects type safety across your code.

typescripttypescript
const name = "Alice";
// Type: "Alice"
 
let name2 = "Alice";
// Type: string

With const, the compiler knows the name is always the exact string "Alice". With let, it infers the general string type because the variable could be reassigned to any string later. The same principle applies to numbers and booleans: const gives you the literal, let gives you the general type.

Block Scoping vs Function Scoping

Both let and const are block-scoped. A variable declared inside curly braces is only visible inside that block. The older var keyword is function-scoped and leaks outside blocks.

This matters for preventing accidental name collisions.

typescripttypescript
function checkScope(flag: boolean) {
  if (flag) {
    const a = 10;
    let b = 20;
    var c = 30;
  }
  console.log(c); // 30 (var leaks out)
  console.log(a); // Error: Cannot find name 'a'
  console.log(b); // Error: Cannot find name 'b'
}

The var declaration escapes the if block because it is scoped to the entire function. Both let and const stay inside the block. This is the main reason var is rarely used in modern TypeScript.

It also fixes a classic loop bug: with let, each iteration gets its own variable binding, so callbacks capture the correct value.

The following table compares how var, let, and const differ in their behavior:

Featurevarletconst
ScopeFunctionBlockBlock
ReassignmentYesYesNo
Redeclaration in same scopeAllowedErrorError
HoistedYes (undefined)Yes (TDZ)Yes (TDZ)
Property on global objectYesNoNo

How Const Changes Type Inference

When you declare a variable with const, TypeScript infers a literal type instead of a general one. For a string, that means the exact string value rather than the broad string type. The compiler can do this because const guarantees the value will never change.

With let, TypeScript must allow for future reassignment. If it inferred a literal type for a let variable, reassigning to a different value of the same general type would produce a compiler error. That would be impractical, so the compiler widens to the general type.

This difference has practical effects. A const with a literal type works naturally in places that expect a specific string union:

typescripttypescript
function setAlignment(direction: "left" | "right") {
  console.log(`Aligned ${direction}`);
}
 
const dir = "left";
setAlignment(dir); // OK -- type is "left"
 
let dir2 = "left";
setAlignment(dir2);
// Error: 'string' not assignable to '"left" | "right"'

The const version compiles because the literal type "left" is assignable to the union. The let version fails because the general string type is too wide.

For more on this behavior, see TypeScript literal types explained.

Const Does Not Deeply Freeze Objects

A common misconception is that const makes an object fully immutable. It only prevents reassignment of the variable itself. The object's internal properties remain writable.

typescripttypescript
const user = { name: "Alice", age: 30 };
 
user.name = "Bob"; // OK -- properties are mutable
user.age = 31;     // OK
user = { name: "Charlie", age: 25 };
// Error: Cannot assign to 'user' because it is a constant

TypeScript infers the type as an object with writable string and number properties. It does not infer readonly literal types for the nested values. The object reference is locked, but the contents are free to change.

To make the entire object readonly at the type level, use the as const assertion. This tells TypeScript to treat every property as readonly and infer literal types for every value. For more on this, see Use as const in TypeScript.

The Temporal Dead Zone

Both let and const share a rule that var does not follow: you cannot access a variable before its declaration line. The region from the start of a block to the declaration is called the temporal dead zone, or TDZ. Var, by contrast, is hoisted and accessible immediately with the value undefined.

TypeScript catches TDZ violations at compile time, preventing a whole category of runtime bugs.

typescripttypescript
console.log(a); // undefined (var is hoisted)
var a = 10;
 
console.log(b); // Error: Cannot access 'b' before initialization
let b = 20;

The error message is clear and points directly at the offending line. This is another reason to avoid var: hoisting hides initialization order mistakes. With let and const, you always know a variable is declared before it is used.

When to Use Each

Prefer const by default. It gives TypeScript more information, which leads to narrower types, better autocomplete, and fewer mistakes. The literal types that const provides are especially useful when passing values to functions that expect specific string or number unions.

Use let only when you genuinely need to reassign the variable. Common cases include loop counters, accumulators, and flags that change state. If you find yourself using let everywhere out of habit, try switching to const and see how few variables actually need to change.

For more on how TypeScript decides between literal and widened types, see Type widening in TypeScript.

Rune AI

Rune AI

Key Insights

  • let and const are block-scoped, unlike var which is function-scoped.
  • TypeScript infers literal types for const and wider types for let.
  • const prevents reassignment but does not make objects deeply immutable.
  • Prefer const by default for more precise type inference.
  • Temporal dead zones prevent using let and const variables before declaration.
RunePowered by Rune AI

Frequently Asked Questions

Does const make objects fully immutable in TypeScript?

No. const only prevents reassignment of the variable. The object's internal properties can still be changed. Use readonly properties or as const for deep immutability.

Why does let infer wider types than const?

let variables can be reassigned, so TypeScript infers the general type (like string) instead of a specific literal type (like "hello"). const cannot be reassigned, so the literal type is safe to infer.

Should I always use const instead of let?

Use const by default. Only use let when you know the variable needs to be reassigned. This makes your intent clear and helps TypeScript infer more precise types.

Conclusion

let and const are not just about reassignment rules. They change how TypeScript infers types. const gives you literal types because the value cannot change. let widens to a general type because reassignment is allowed. Prefer const by default for more precise types, better autocomplete, and clearer intent.