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.
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.
const name = "Alice";
// Type: "Alice"
let name2 = "Alice";
// Type: stringWith 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.
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:
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Reassignment | Yes | Yes | No |
| Redeclaration in same scope | Allowed | Error | Error |
| Hoisted | Yes (undefined) | Yes (TDZ) | Yes (TDZ) |
| Property on global object | Yes | No | No |
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:
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.
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 constantTypeScript 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.
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
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.
Frequently Asked Questions
Does const make objects fully immutable in TypeScript?
Why does let infer wider types than const?
Should I always use const instead of let?
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.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.