Stating the problem As typescript is growing and gaining popularity recently, more and more javascript developers appreciate type safety. The list of features Typescript provides is huge and might be overwhelming, so in this post, I will focus on one of them which is easy to grasp and has a neat practical impact. Let's start with an example. Imagine you are developing an application with many user roles. It is pretty common for an application to be consumed by different users, isn't it? The exact roles are not really important here, but let's say they are admin, consumer and guest. In typescript, we can declare users holding those roles as follows: type Admin = {} type Consumer = {} type Guest = {} Now, let's consider a set of attributes each user role has. Usually, they are email, firstName and lastName or something like that. But, wait, Guest users probably won't have those (they are guests after all), so let's just leave this type empty for now. type Admin = { firstName: string lastName: string email: string } type Consumer = { firstName: string lastName: string email: string } type Guest = {} The user of an application could only be of one role. The way to represent this through types is to use a union type. type User = Admin | Consumer | Guest Admins are famous for their exclusive abilities, and in our application, they are able to invite consumers. Let's add a field indicating how many invitations an admin could send. type Admin = { firstName: string lastName: string email: string numberOfInvitesLeft: number // <-- added } To make things more interesting and closer to a real application, let's add a property exclusive to a Consumer type. type Consumer = { firstName: string lastName: string email: string premium: boolean // <-- added } This is a very simple example, and in reality, users could have dozens of disparate properties, which considerably complicates the codebase when you need to access certain properties. const doSomethingBasedOnRole = (user: User) => { // how do you check here that user is really an admin if (user) { // ...and do something with the `numberOfInvitesLeft` property? } } One option is to check on the existence of the property. const doSomethingBasedOnRole = (user: User) => { if (user && user.numberOfInvitesLeft) { // safely access `numberOfInvitesLeft` property } } But this is a tedious and not a scalable solution. And what to do when `numberOfInvitesLeft` becomes an optional property? Introducing Discriminated Union Types This is where discriminated union types come into play. We just need to put an additional field in every user type indicating the role. type Admin = { firstName: string lastName: string email: string numberOfInvitesLeft: number role: "admin" // <-- added } type Consumer = { firstName: string lastName: string email: string role: "consumer" // <-- added } type Guest = { role: "guest" // <-- added } Notice how I am putting a specific string as a type; this is called string literal type. What this gives you is that now you can use native JS language operators, e.g., switch case, if, else to discriminate on the role. const user: Admin = { firstName: "John", lastName: "Smith", email: "johnsmith@examplemail.com", numberOfInvitesLeft: 3, role: "admin", } const doSomethingBasedOnRole = (user: User) => { if (user.role === "admin") { // now typescript knows that INSIDE of this block user is of type `Admin` // now you can safely call `user.numberOfInvitesLeft` within this block } } The same applies to a switch case statement. // ... const doSomethingBasedOnRole = (user: User) => { switch (user.role) { case "admin": { // now typescript knows that INSIDE of this block user is of type `Admin` // now you can safely call `user.numberOfInvitesLeft` within this block } case "consumer": { // do something with a `Consumer` user // if you try to call `user.numberOfInvitesLeft` here, TS compiler errors in // // "Property 'numberOfInvitesLeft' does not exist on type 'Consumer'." // } } } The benefits of discriminated union types are apparent because the type checking is based on explicit role property and not on ad-hoc properties which might or might not be related to a specific user. Stating the problem As typescript is growing and gaining popularity recently, more and more javascript developers appreciate type safety. The list of features Typescript provides is huge and might be overwhelming, so in this post, I will focus on one of them which is easy to grasp and has a neat practical impact. Let's start with an example. Imagine you are developing an application with many user roles. It is pretty common for an application to be consumed by different users, isn't it? The exact roles are not really important here, but let's say they are admin , consumer and guest . In typescript, we can declare users holding those roles as follows: admin consumer guest type Admin = {} type Consumer = {} type Guest = {} type Admin = {} type Consumer = {} type Guest = {} Now, let's consider a set of attributes each user role has. Usually, they are email , firstName and lastName or something like that. But, wait, Guest users probably won't have those (they are guests after all), so let's just leave this type empty for now. email firstName lastName Guest type Admin = { firstName: string lastName: string email: string } type Consumer = { firstName: string lastName: string email: string } type Guest = {} type Admin = { firstName: string lastName: string email: string } type Consumer = { firstName: string lastName: string email: string } type Guest = {} The user of an application could only be of one role. The way to represent this through types is to use a union type. union type User = Admin | Consumer | Guest type User = Admin | Consumer | Guest Admins are famous for their exclusive abilities, and in our application, they are able to invite consumers. Let's add a field indicating how many invitations an admin could send. type Admin = { firstName: string lastName: string email: string numberOfInvitesLeft: number // <-- added } type Admin = { firstName: string lastName: string email: string numberOfInvitesLeft: number // <-- added } To make things more interesting and closer to a real application, let's add a property exclusive to a Consumer type. Consumer type Consumer = { firstName: string lastName: string email: string premium: boolean // <-- added } type Consumer = { firstName: string lastName: string email: string premium: boolean // <-- added } This is a very simple example, and in reality, users could have dozens of disparate properties, which considerably complicates the codebase when you need to access certain properties. const doSomethingBasedOnRole = (user: User) => { // how do you check here that user is really an admin if (user) { // ...and do something with the `numberOfInvitesLeft` property? } } const doSomethingBasedOnRole = (user: User) => { // how do you check here that user is really an admin if (user) { // ...and do something with the `numberOfInvitesLeft` property? } } One option is to check on the existence of the property. const doSomethingBasedOnRole = (user: User) => { if (user && user.numberOfInvitesLeft) { // safely access `numberOfInvitesLeft` property } } const doSomethingBasedOnRole = (user: User) => { if (user && user.numberOfInvitesLeft) { // safely access `numberOfInvitesLeft` property } } But this is a tedious and not a scalable solution. And what to do when `numberOfInvitesLeft` becomes an optional property? Introducing Discriminated Union Types This is where discriminated union types come into play. We just need to put an additional field in every user type indicating the role. type Admin = { firstName: string lastName: string email: string numberOfInvitesLeft: number role: "admin" // <-- added } type Consumer = { firstName: string lastName: string email: string role: "consumer" // <-- added } type Guest = { role: "guest" // <-- added } type Admin = { firstName: string lastName: string email: string numberOfInvitesLeft: number role: "admin" // <-- added } type Consumer = { firstName: string lastName: string email: string role: "consumer" // <-- added } type Guest = { role: "guest" // <-- added } Notice how I am putting a specific string as a type; this is called string literal type . What this gives you is that now you can use native JS language operators, e.g., switch case , if , else to discriminate on the role. string literal type switch case if else discriminate const user: Admin = { firstName: "John", lastName: "Smith", email: "johnsmith@examplemail.com", numberOfInvitesLeft: 3, role: "admin", } const doSomethingBasedOnRole = (user: User) => { if (user.role === "admin") { // now typescript knows that INSIDE of this block user is of type `Admin` // now you can safely call `user.numberOfInvitesLeft` within this block } } const user: Admin = { firstName: "John", lastName: "Smith", email: "johnsmith@examplemail.com", numberOfInvitesLeft: 3, role: "admin", } const doSomethingBasedOnRole = (user: User) => { if (user.role === "admin") { // now typescript knows that INSIDE of this block user is of type `Admin` // now you can safely call `user.numberOfInvitesLeft` within this block } } The same applies to a switch case statement. // ... const doSomethingBasedOnRole = (user: User) => { switch (user.role) { case "admin": { // now typescript knows that INSIDE of this block user is of type `Admin` // now you can safely call `user.numberOfInvitesLeft` within this block } case "consumer": { // do something with a `Consumer` user // if you try to call `user.numberOfInvitesLeft` here, TS compiler errors in // // "Property 'numberOfInvitesLeft' does not exist on type 'Consumer'." // } } } // ... const doSomethingBasedOnRole = (user: User) => { switch (user.role) { case "admin": { // now typescript knows that INSIDE of this block user is of type `Admin` // now you can safely call `user.numberOfInvitesLeft` within this block } case "consumer": { // do something with a `Consumer` user // if you try to call `user.numberOfInvitesLeft` here, TS compiler errors in // // "Property 'numberOfInvitesLeft' does not exist on type 'Consumer'." // } } } The benefits of discriminated union types are apparent because the type checking is based on explicit role property and not on ad-hoc properties which might or might not be related to a specific user.