As Const
Sometimes default infered type is not strict enough, or maybe typescript will infer a type as too general.
Defining
const numbersArray = [1, 2, 3];
// numbersArray is inferred as number[]
 
const numbersArrayAsConst = [1, 2, 3] as const;
// numbersArrayAsConst is inferred as readonly [1, 2, 3]
 
const object = {
  a: 1,
  b: 2,
  c: 3,
};
// object is inferred as { a: number, b: number, c: number }
 
const objectAsConst = {
  a: 1,
  b: 2,
  c: 3,
} as const;
// objectAsConst is inferred as { readonly a: 1, readonly b: 2, readonly c: 3 }
 
const stringArray = ["a", "b", "c"];
// stringArray is inferred as string[]
 
const stringArrayAsConst = ["a", "b", "c"] as const;
// stringArrayAsConst is inferred as readonly ["a", "b", "c"]
 
const nestedObject = {
  a: {
    b: {
      c: 1,
    },
  },
};
// nestedObject is inferred as { a: { b: { c: number } } }
 
const nestedObjectAsConst = {
  a: {
    b: {
      c: 1,
    },
  },
} as const;
// nestedObjectAsConst is inferred as { readonly a: { readonly b: { readonly c: 1 } } }As you can see, as const changes the type of the variable to be more strict, and it also makes the variable readonly.
Usage
Lets try to make some practical usage of as const. Imagine we have a function that takes some routes as an argument, but we dont want to allow any string to be passed as a route, we want to allow only routes that we have defined in our app.
const routes = {
  home: "/",
  about: "/about",
  contact: "/contact",
} as const;
 
type Routes = typeof routes;
// type Routes = {
//   readonly home: "/";
//   readonly about: "/about";
//   readonly contact: "/contact";
// }
 
type Route = Routes[keyof Routes];
// type Route = "/" | "/about" | "/contact"
 
function navigateTo(route: Route) {
  // ...
}
 
navigateTo(routes.home);
navigateTo("/");
// this is also fine, because "/" is the same as routes.home
 
navigateTo("/foo");
//Argument of type '"/foo"' is not assignable to parameter of type '"/" | "/about" | "/contact"'.Getting rid of readonly
Sometimes we want to get rid of readonly, for any rason, sometimes its even some quirks required by some library. We have two options to deal with it.
foo as foo
const routes = {
  home: "/",
  about: "/about",
  contact: "/contact",
} as {
  home: "/";
  about: "/about";
  contact: "/contact";
};This is the simplest way, but it requires us to write the type twice, which is not very DRY.
Writtable
We can create a type that will make all properties of an object writable, by using -readonly mapped type.
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
 
const routes = {
  home: "/",
  about: "/about",
  contact: "/contact",
} as const;
 
type Routes = Writeable<typeof routes>;
// type Routes = {
//   home: "/";
//   about: "/about";
//   contact: "/contact";
// }If we want to recursively make all properties of an object writable, we can use this type:
type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> };Bonus
Its beyond the scope of this article, but lets try to figure out how Writeable type works.
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
// desired functionality
const testObj = {
  a: 1,
  b: 2,
} as const;
// object is readonly
 
type typeOfTestObj = typeof testObj;
// type typeOfTestObj = {
//    readonly a: 1;
//    readonly b: 2;
// }Lets try to describe what Writeable does:
- it takes a generic type 
T - it maps all keys of 
Tto a new type - it stores all keys of 
Tunder a genericP - it returns a new type under each of the keys of 
T, usingPto access the value ofT - (to this point, it basically returns the same type as 
T) - it removes 
readonlymodifier from each of the keys ofT 
Lets try to recreate Writeable type step by step.
type mappedToNull = { [key in "a" | "b"]: null };
// type mappedToNull = { a: null; b: null; }
 
type mappedToKey = { [key in "a" | "b"]: key };
// type mappedToKey = { a: "a"; b: "b"; }
 
type keys<T> = keyof T;
type testObjKeys = keys<typeOfTestObj>;
// type testObjKeys = "a" | "b"
 
type mappedObj = { [key in testObjKeys]: key };
// type mappedObj = { a: "a"; b: "b"; }
 
type mappedObjOneLiner = { [key in keys<typeOfTestObj>]: key };
// type mappedObjOneLiner = { a: "a"; b: "b"; }
 
type mappedObjWithGeneric<T> = { [key in keys<T>]: key };
type newMappedObj = mappedObjWithGeneric<typeOfTestObj>;
// type newMappedObj = { a: "a"; b: "b"; }
 
type usingKeysToAccessObj<T> = { [key in keys<T>]: T[key] };
type newMappedObj2 = usingKeysToAccessObj<typeOfTestObj>;
// type newMappedObj2 = {
//    readonly a: 1;
//    readonly b: 2;
// }
 
type removingKeysHelper<T> = { [key in keyof T]: T[key] };
type newMappedObj3 = removingKeysHelper<typeOfTestObj>;
// type newMappedObj3 = {
//    readonly a: 1;
//    readonly b: 2;
// }
 
type removingReadonly<T> = { -readonly [key in keyof T]: T[key] };
type newMappedObj4 = removingReadonly<typeOfTestObj>;
// type newMappedObj4 = {
//    a: 1;
//    b: 2;
// }
 
type replacingKeyWithP<T> = { -readonly [P in keyof T]: T[P] };
type removedReadonly = replacingKeyWithP<typeOfTestObj>;
// type removedReadonly = {
//    a: 1;
//    b: 2;
// }Now, lets break it down:
typeOfTestObjis the type of our object, with readonly propertiesmappedToNullis a mapped type, that maps all keys of our object to null usinginsyntax.keyis a generic, that can have any name we want.mappedToKeyis a mapped type, that maps all keys of our object to the key itself.testObjKeysis a type that contains all keys of our objectmappedObjis a mapped type, that maps all keys of our object to the key itself, by usingtestObjKeystypemappedObjOneLineris the same as all stepes abouve, but written in one linemappedObjWithGenericis the same asmappedObjOneLiner, but using generic to pass the type of our objectnewMappedObjis our new type, mapped withmappedObjWithGenericusingKeysToAccessObjis a mapped type, that maps all keys of our object to the value that is under that keynewMappedObj2is our new type, mapped withusingKeysToAccessObjremovingKeysHelperis the same asusingKeysToAccessObj, but we replacedkeys<T>withkeyof T, same built in that we used inkeys<T>newMappedObj3is our new type, mapped withremovingKeysHelperremovingReadonlyis the same asremovingKeysHelper, but we added-readonlyto the key. Minus sign is a special syntax that tells typescript to remove a modifier from the key. We can also add+to add a modifier to the key.newMappedObj4is our new type, mapped withremovingReadonlyreplacingKeyWithPis the same asremovingReadonly, but we replacedkeywithP.Pis a generic, that can have any name we want.removedReadonlyis our new type, mapped withreplacingKeyWithP
This way, step by step we created exact same type as Writeable type. DeepWriteable works the same way, but it uses recursion to call itself on nested objects.