Prevent ID switching mistake

Oluwafemi Shobande
Typescript Tidbits
Published in
2 min readFeb 15, 2024

Note: Basic understanding of generics and intersection types are assumed in this piece.

Problem

Ever faced a bug that’s caused by feeding in the wrong id to a function? For instance, a function expects studentId, but you pass in courseId, given that both ids are of type number, the function’s signature isn’t violated, and by extension, the compiler allows this.

This issue is particularly pertinent in functions that accept multiple ids, but the developer mistakenly switches the order of these ids. Here’s an example:

function recordStudentScore(studentId: number, courseId: number, score: number) {
// do something
}

const studentId: number = 1;
const courseId: number = 2;
const score: number = 3;

// courseId and studentId are switched
recordStudentScore(courseId, studentId, score); //✅︎

Notice that courseId and studentId have been switched. Although this code is logically incorrect, it is syntactically valid Typescript code.

What if there was a way to inform the compiler that the first parameter of recordStudentScore function should accept studentIds only. By so doing, the cognitive load of tracking the specific order of arguments can be offloaded to the compiler.

Solution

We can create a generic BrandedId type as shown below. This type is an intersection of 2 types; the first is number, while the second is a type that has a string name property. Then, we may create branded types for the different ids we are interested in:

type BrandedId<Name extends string> = number & {
name: Name;
}

type StudentId = BrandedId<"student_id">;
type CourseId = BrandedId<"course_id">;
type Score = BrandedId<"score">;

Wherever a student id is expected, instead of using number as the param type in the function signature, type StudentId will be used instead. Same goes for CourseId and Score.

Now, let’s rewrite the initial code snippet in an even more type safe manner.

function recordStudentScore(
studentId: StudentId, // StudentId instead of number
courseId: CourseId, // CourseId instead of number
score: Score // CourseId instead of number
) {
// do something
}

const studentId = 1 as StudentId;
const courseId = 2 as CourseId;
const score = 3 as Score;

// courseId and studentId are switched
recordStudentScore(courseId, studentId, score); //❌

// courseId and studentId are in the right order
recordStudentScore(studentId, courseId, score); //✅︎

Due to this name constraint added to the ids, it’s no longer possible to switch them without having compilation errors. As seen above, if courseId and studentId are switched, compilation fails.

If you like this content, kindly subscribe to this publication as I will be sharing many more neat tricks. Also here’s my Twitter, if you’d like to reach out.

--

--