Type Widening in TypeScript

Type widening is how TypeScript expands a narrow inferred type into a wider, more general one. Learn when widening happens, why let widens but const does not, and how to control it.

5 min read

Type widening is the process where TypeScript expands a narrow inferred type into a wider, more general one. When you write a let declaration with a string value, TypeScript infers the general string type, not the exact string literal. Widening exists because most mutable values should accept a range of values, not just the initial one.

Without widening, you would need explicit type annotations on almost every let declaration. TypeScript widens to make the common case convenient while keeping types safe. Understanding when and why widening happens helps you predict what types the compiler infers.

typescripttypescript
let color = "blue";
// Type: string
 
color = "green"; // OK

If TypeScript inferred the literal type "blue", reassigning to "green" would fail. Since let allows reassignment, the compiler widens to string. This is the fundamental reason widening exists.

Where Widening Happens

Widening affects let declarations, object properties, and function return types. Each case has a different reason.

With let declarations, the variable is reassignable, so TypeScript infers the general type. This is straightforward and expected.

With object properties, even on a const object, each property is still mutable and TypeScript widens accordingly. The const only freezes the variable binding, not the object contents.

Function return types are a subtle case. TypeScript widens inferred return types because it does not know whether callers need the literal. A function that returns "active" is inferred as returning string unless you add an explicit return type annotation.

This is conservative but safe: the compiler assumes callers might not care about the exact value.

typescripttypescript
function getStatus() {
  return "active";
}
// Return type: string
 
function getStatusExact(): "active" {
  return "active";
}
// Return type: "active"

The first version returns string. The second version, with an explicit return type annotation, returns the literal. Choose based on whether callers depend on the exact value.

How Const Prevents Widening

Const is the simplest way to stop widening. Because const guarantees the value will not change, TypeScript keeps the literal type.

But const only stops widening at the variable level. Object properties still widen unless you use as const.

typescripttypescript
const color = "blue";
// Type: "blue"
 
const settings = {
  theme: "dark",
  version: 2,
};
// Type: { theme: string; version: number }

The settings variable cannot be reassigned, but its properties can. TypeScript reflects that by widening each property to its general type. The variable is constant, but the contents are not.

Using As Const to Block Widening Completely

The as const assertion stops widening at every level of an object or array. All properties become readonly and keep their literal types. Arrays become readonly tuples with literal element types.

typescripttypescript
const settings = {
  theme: "dark",
  version: 2,
} as const;
// Type: { readonly theme: "dark"; readonly version: 2 }

This is the most common pattern for configuration objects and string constants that need both runtime access and compile-time precision. You write the object once and get both the runtime value and the narrow compile-time type. For more on as const, see Use as const in TypeScript.

Explicit Type Annotations for Control

You can also control widening with an explicit type annotation, in either direction. An annotation on a const widens the accepted type beyond the literal.

An annotation on a let narrows it to a specific set of values.

typescripttypescript
const status: string = "active";
// Type: string (annotation widens away from "active")
 
let mode: "light" | "dark" = "light";
mode = "dark"; // OK
mode = "blue";
// Error: Type '"blue"' is not assignable to type '"light" | "dark"'

Without the annotation, status would infer the literal type "active". The explicit string annotation overrides that and widens it on purpose.

The mode variable works the other way. let would normally widen to string, but the union annotation narrows the accepted values to two options, so assigning "blue" is a compiler error.

Widening in Arrays

Arrays widen their element types. Even const arrays infer string[], not a tuple of literals, because you can still push and reassign elements on the array. Use as const to lock the array into a readonly tuple:

typescripttypescript
const colors = ["red", "green", "blue"] as const;
// Type: readonly ["red", "green", "blue"]

Without as const, the array type is string[], and elements are freely mutable. With as const, the array is a readonly tuple with literal element types. Push and index assignment are blocked.

The following table summarizes when widening happens for common declarations:

DeclarationInferred typeWidens?
let with a stringstringYes
const with a stringliteral (e.g. "hello")No
const object{ prop: general type }Properties: Yes
const object with as const{ readonly prop: literal }No
const arraygeneral type[]Elements: Yes
const array with as constreadonly tupleNo

When to Control Widening

Widening is usually the right default. It keeps types practical for mutable code.

Sometimes widening hides useful information. A function that returns a known literal should declare it as the return type if callers use that exact value. A configuration object should use as const if its values must never change at the type level.

For the reverse process, where TypeScript refines a wide type back to a narrow one based on runtime checks, see Type narrowing basics in TypeScript. For how const and let influence the initial type inference, see Let and const in TypeScript.

Rune AI

Rune AI

Key Insights

  • Widening expands a narrow inferred type like "GET" to a wider type like string.
  • let variables widen because they can be reassigned; const variables do not.
  • Object properties widen even on const objects unless you use as const.
  • Use explicit type annotations to prevent widening when you need a specific type.
  • Function return types widen unless you add an explicit return type annotation.
RunePowered by Rune AI

Frequently Asked Questions

Why does let widen types but const does not?

let allows reassignment, so TypeScript widens to the general type (string instead of "hello"). const cannot be reassigned, so the literal type is safe to use.

How do I stop TypeScript from widening a type?

Use const instead of let, add an explicit type annotation, or use as const. For function return values, add an explicit return type annotation.

Does as const affect runtime behavior?

No. as const is purely a type-level directive. It has zero runtime effect and is erased during compilation.

Conclusion

Type widening is TypeScript's way of balancing precision with practicality. It widens let variables, mutable object properties, and unannotated function returns to general types because those values can change. Use const for fixed values, as const for objects and arrays, and explicit type annotations when you need to lock in a wider type. Understanding widening helps you predict what types TypeScript infers without guessing.