Dan D Kim

Let's share stories

Advanced Typescript Questions - Conditional Types

2021-12-27 Dan D. Kimtypescript

In this post, we will go through some advanced Typescript Type concepts.


Hello, this is the fourth of my Typescript interview series. I also wrote a post of beginner questions, and two posts for intermediate questions - one, and two.


Q1 - Conditional Types

Given the following code

type Car = {
  model: string
}

type House = {
  sqft: number
}

function houseOrCar<K extends 'Car' | 'House'>(s: K): Car | House {
  return s === 'Car' ? { model: 'hyundai' } : { sqft: 100_000 } as any
}

When we try to use the houseOrCar() function, the resulting type is Car | House as shown:

const car = houseOrCar('Car') // Car | House
const house = houseOrCar('House') // Car | House

How can we modify the type definitions such that the function houseOrCar() returns an object of type Car when input is 'Car' and House when the input is 'House'?

/* DEFINE A TYPE HERE */

function houseOrCar<K extends 'Car' | 'House'>: /* CHANGE CODE HERE */ {
  return s === 'Car' ? { model: 'hyundai' } : { sqft: 100_000 } as any
}

const car = houseOrCar('Car') // Car
const house = houseOrCar('House') // House
Answer

Use conditional types utility type.

type StringToType<T> =  T extends 'Car' ? Car :  T extends 'House' ? House :  never
function houseOrCar<K extends 'Car' | 'House'>(s: K): StringToType<K> {  return s === 'Car' ? { model: 'hyundai' } : { sqft: 100_000 } as any
}

Q2 never filter

Given the following code

type Employee = {
  name: string,
  age: number,
  weaknesses: never
}

How can we extract all the possible different types of possible value-types except never?

type PossibleTypes = /* YOUR CODE HERE * /
// type PossibleTypes = string | number
Answer

Typescript removes never whenever it has a union, so a simple keyof operation will suffice.

type PossibleTypes = Employee[keyof Employee] // string | number

Q3 How to get properties of a specific type

Given the following code

type House = {
  sqft: number,
  address: string,
  floors: number,
  bedrooms: number,
  builtAt: Date
}

We want to create a generic type such that we can extract properties of a specific type, as shown:

type NumberProperties = PropsOfType<House, number> // "sqft" | "floors" | "bedrooms"
type StringProperties = PropsOfType<House, string> // "address"
type DateProperties = PropsOfType<House, Date> // "builtAt"

Write a generic type PropsOfType that can achieve the above.

Hint: the answers from the previous two questions above will help

Hint 1

Here is what the following code gets me.

type PropsOfTypeNumber = {
  [K in keyof House]: House[K] extends number ? K : never
}

// type PropsOfTypeNumber = {
//     sqft: "sqft";
//     address: never;
//     floors: "floors";
//     bedrooms: "bedrooms";
//     builtAt: never;
// }

We have a type where the keys are the same but the values are now the string representation of the key IF they are a number. 🤔 Otherwise they are never. If only we could filter out those nevers…

And then make it generic.

Hint 2

Here is what the following code gets me.

type ValueOf<T> = T[keyof T]

type PropsOfTypeNumber = ValueOf<{
  [K in keyof House]: House[K] extends number ? K : never
}>

// type PropsOfTypeNumber = "sqft" | "floors" | "bedrooms

We are now able to successfully filter out the nevers, as they are filtered out by Typescript unions.

Now we just need to make it generic… 🤔

Answer
type ValueOf<T> = T[keyof T]

type PropsOfType<T, P> =  ValueOf<{
  [K in keyof T]: T[K] extends P ? K : never
}>
type NumberProperties = PropsOfType<House, number> // "sqft" | "floors" | "bedrooms"
type StringProperties = PropsOfType<House, string> // "address"
type DateProperties = PropsOfType<House, Date> // "builtAt"

Great job! Hope you learned something from this.

Happy learning!