• Home
  • Writing
  • Resume
  • Home
  • Writing
  • Resume

Writing

  • Why Some Managers Look Disingenuous

    January 3rd, 2024
  • Do’s and Don’ts For Technical Interviews

    December 27th, 2023
  • The Book That Made Me Want To Be A Leader

    October 30th, 2023
  • How to Fail An Interview As the Interviewer

    October 25th, 2023
  • Should Managers Be Prescriptive?

    October 16th, 2023
  • How To Help Unmotivated Developers

    July 12th, 2023
  • What to do when nothing seems good enough

    July 10th, 2023
  • Why Do We Burn Out?

    July 7th, 2023
  • What I Taught You, I Don’t Know

    June 21st, 2023
  • How To Delegate Effectively Without Feeling You Are Losing Control

    June 12th, 2023
  • Let’s Accept It. Technical Interviews Are Broken

    June 7th, 2023
  • Why Managers Need Empathy to Manage Low Performers

    May 31st, 2023
  • The Slow Decline of Highly Motivated Developers

    May 24th, 2023
  • Why Writing Explicit Code Matters

    May 18th, 2023
  • Why Is It So Difficult to Assess Expertise in an Interview?

    May 15th, 2023
  • The Real Value of a Senior Developer When it Comes to Dealing With Uncertainty

    May 11th, 2023
  • Why You Should Use Feature Flags to Deploy with Confidence

    April 28th, 2023
  • Over-Engineering Is Not (Just) a Technical Problem

    March 20th, 2023
  • The proactivity fallacy

    January 25th, 2023
  • Extending typescript intersection with optional properties

    January 18th, 2023
  • Setting up Google Tag Manager in a Nextjs application with a strict content security policy

    December 27th, 2022
  • How to build a scalable folder structure for a nextjs app

    December 11th, 2022
  • Why I have stopped writing comments

    December 6th, 2022
  • How to efficiently type nextjs page's props

    December 6th, 2022

Extending typescript intersection with optional properties

January 18th, 2023

Typescript provides us very powerful operators to extend our existing types: union type and intersection type. Let's quickly cover what they are and how are different. Of course, for more in depth examples i encourage you to read the official documentation.

Union

A union type is a type formed from two or more other types, representing values that may be any one of those types. We refer to each of these types as the union’s members.

Intersection

An intersection type combines multiple types into one. This allows you to add together existing types to get a single type that has all the features you need

Let's say we have to interfaces and we want to combine them to create a new type.

One option is to use the intersection:

interface A {
    firstName: string
    lastName: string
}

interface B {
    age: number
}

type C = A & B

As a result, C will inherit all properties from A and B, and they all will required.

Another option is to use union

interface A {
    firstName: string
    lastName: string
}

interface B {
    age: number
}

type C = A | B

As a result, C will inherit only the common properties between A and B, and they will also be required. In the case of C type, that will be no properties.

Combining them for something more advanced

What if we need something in between these two results? Recently i had the need to create a type that contained all common properties between two interfaces as required properties and the rest as optional properties. I played for a little while with the tools that typescript provides and i was able to come up with something that did what i needed. Let's see how we can build such type.

First, let's create a type with only common keys between the two interfaces. This line is saying that the resulting type C will have a key for each one that appears in A and B.

interface A {
    firstName: string
    lastName: string
}

interface B {
    age: number
}

type C = {
    [K in keyof A & keyof B]: A[K] | B[K]
}

Then, we use the typescript's utility Exclude to take out from A all the keys that also appear in B, leaving out the uniques to A and we specify the type that key had in A. By adding the question mark we make them optional.

interface A {
    firstName: string
    lastName: string
}

interface B {
    age: number
}

type C = {
    [K in Exclude<keyof A, keyof A & keyof B>]?: A[K]
}

We can do the same thing for B now

interface A {
    firstName: string
    lastName: string
}

interface B {
    age: number
}

type C = {
    [K in Exclude<keyof B, keyof A & keyof B>]?: b[K]
}

Now, using the intersection operator, we can combine them all into one type that will fulfill our requirement: the common keys to A and B will be required and the rest optional.

interface A {
    firstName: string
    lastName: string
}

interface B {
    age: number
}

type C = {
    [K in keyof A & keyof B]: A[K] | B[K]
} & {
    [K in Exclude<keyof A, keyof A & keyof B>]?: A[K]
} & {
    [K in Exclude<keyof B, keyof A & keyof B>]?: b[K]
}

Next steps

To wrap up, we can create our own utility type so we can reuse that we have done.

/**
 * Construct a type with the properties common to T and U as required properties and the rest as optional properties
 */
type SoftIntersection<T, U> = {
    [K in keyof T & keyof U]: T[K] | U[K]
} & {
    [K in Exclude<keyof T, keyof T & keyof U>]?: T[K]
} & {
    [K in Exclude<keyof U, keyof T & keyof U>]?: U[K]
}

I've decided to name this utility SoftIntersection as i wanted to differentiate it from the normal intersection operator, but better names are welcomed!