TypeScript "as const" annotation

The TypeScript "as const" annotation allows us to mark a value as deeply readonly and immutable. The effect is different from "Object.freeze" because it only runs at compile-time and Object.freeze only works on the top level of the frozen object.

In this post I would like to highlight some useful examples of "as const" annotation.

The values marked "as const" cannot be mutated

const warrior = {
  weapons: {
    sword: true,
    knife: false,
  },
} as const;

warrior.weapons.sword = false;
//      ☝️ Error: Cannot assign to "weapons" because it is a read-only property.

It also works on arrays:

const array = ["a", "b", "c"] as const;
array.push("d");
//  ☝️ Error: Property 'push' does not exist on type 'readonly ["a", "b", "c"]'

Can replace the "enum" keyword

Enum does not exist in JavaScript, it was made up by the TypeScript team in order to allow developer to define a set of named constants. It can sometimes be handy, however it comes with some drawbacks. One of them is that it is a pretty rigid typing system.

Let's take this example:

enum Roles {
    ADMIN = "admin",
    MANAGER = "manager",
    VISITOR = "visitor",
}

function isAdmin(role: Roles) {
  return role === Roles.ADMIN;
}

isAdmin(Roles.ADMIN); // ✅Valid but we had to import Roles to use it
isAdmin("admin"); // ❌Error: Argument of type '"admin"' is not assignable to parameter of type 'Roles'

Here we clearly see that we cannot simply use "admin", even if it is the "same" value as Roles.ADMIN.

To avoid that, we can use "as const":

const ROLES = {
  ADMIN: "admin",
  MANAGER: "manager",
  VISITOR: "visitor",
} as const;

// Then we can define a type to get the union types of all possible values for ROLES
type Role = typeof ROLES[keyof typeof ROLES];

function isAdmin(role: Role) {
  return role === ROLES.ADMIN; // Or role === "admin"
}

isAdmin(ROLES.ADMIN);
//  ✅ Valid
isAdmin("admin");
// ✅ Also valid, and no need to import anything

Conclusion

"As const" in TypeScript is a valuable tool for creating immutable literal types, providing developers with more accurate type inferences and enhanced type safety. By using this assertion, developers can ensure that their variables and properties retain their specific literal values, preventing unintentional modifications and catching potential bugs early in the development process. Incorporating "as const" into your TypeScript codebase can lead to more robust and reliable applications.

To go further:

  • https://blog.logrocket.com/complete-guide-const-assertions-typescript/
  • https://www.totaltypescript.com/concepts/as-const