loading...

Type widening on strings

dwjohnston profile image David Johnston ・2 min read

The Problem

type Roles = "student" | "teacher"; 

type Person = {
    role: Roles; 
    name: string; 
    age: number; 
}

function doSomethingWithPerson(person: Person) {
    console.log(person); 
}

const andy = {
    role: "teacher", 
    name: "andy", 
    age: 31
};

doSomethingWithPerson(andy); //Argument of type '{ role: string; name: string; age: number; }' is not assignable to parameter of type 'Person'.

Playground Link

This code produces the error:

Argument of type '{ role: string; name: string; age: number; }' is not assignable to parameter of type 'Person'.
  Types of property 'role' are incompatible.
    Type 'string' is not assignable to type 'Roles'.

What's going on here?

The andy object we declare does match the type Person that the function wants, so why is TypeScript complaining?

Quick Solution

Declare the string "teacher" as const

const andy = {
    role: "teacher" as const, 
    name: "andy", 
    age: 31
};

doSomethingWithPerson(andy);

Explanation

In this scenario when we declare the object andy TypeScript infers the type of the role property as string, any string!

While "teacher" and "student" are strings, they are a subset of strings.

The Person type doesn't want any string, it wants just those two particular strings.

That TypeScript infers the type as string and not as "teacher" is called type widening.

What TypeScript is doing, is allowing for that you might later write some code like:

   andy.role = "foobar"; 

(That is, you're mutating the andy object).

What the as const keyword does is just prevent the type widening. See the documentation here.

What you're also doing is telling TypeScript 'don't worry, I promise I'm not going to reassign this property', and if you try, TypeScript will give you an error:

const andy = {
    role: "teacher" as const, 
    name: "andy", 
    age: 31
};

andy.role = "foobar"; //Type '"foobar"' is not assignable to type '"teacher"'.(2322) 

Alternative solutions

Declare the string "teacher" as "teacher"

const andy = {
    role: "teacher" as "teacher", 
    name: "andy", 
    age: 31
};

doSomethingWithPerson(andy);

What this does is instead of using the as const keyword to prevent type widening, it just explicitly sets the type of the role property to "teacher".

Both as const and as "teacher" have the same end effect, the role property's type is "teacher".

Discussion

pic
Editor guide