All Articles

The TypeScript 5.3 Feature They Didn't Tell You About

Matt Pocock
Matt PocockMatt is a well-regarded TypeScript expert known for his ability to demystify complex TypeScript concepts.

Last week, the TypeScript team released TS 5.3.

As usual, I scanned the announcement post, but I quickly noticed something interesting.

One of the most important changes in TypeScript 5.3 wasn't mentioned in the release notes.

Quick Code Sample

ts
// This would error in 5.2, but is allowed in 5.3!
const array = ["a", "b", "c"] as const satisfies string[];
 
const returnWhatIPassIn = <const T extends any[]>(t: T) => {
return t;
};
 
// result is any[] in TS 5.2, but ['a', 'b', 'c'] in 5.3
const result = returnWhatIPassIn(["a", "b", "c"]);

Full Explanation

Working with readonly arrays in TS can be occasionally a bit of a pain.

Let's say you want to declare an array of routes as const.

This allows you to reuse the paths declared for a type.

ts
const arrayOfRoutes = [
{ path: "/home", component: Home },
{ path: "/about", component: About },
] as const;
 
type Route = (typeof arrayOfRoutes)[number]["path"];
type Route = "/home" | "/about"

But what if you want to make sure that the arrayOfRoutes conforms to a certain type?

For that, you can use satisfies.

ts
const arrayOfRoutes = [
{ path: "/home", component: Home },
{ path: "/about", component: About },
] as const satisfies {
path: string;
component: React.FC;
}[];
// Type is 'readonly' and cannot be
// assigned to a mutable type

The only trouble is that in TypeScript 5.2, this would give an error! But... Why?

Readonly Arrays vs Mutable Arrays

Well, it's because arrayOfRoutes is readonly, and you can't satisfy a mutable array with a readonly one.

So, the fix was to make the type we were satisfying a readonly array:

ts
const arrayOfRoutes = [
{ path: "/home", component: Home },
{ path: "/about", component: About },
] as const satisfies readonly {
path: string;
component: React.FC;
}[];
 
// No more error!

Now that we're using a readonly array, TypeScript is happy.

Const Type Parameters

The same was true when using const type parameters, but even more pernicious.

In this position, const infers the thing passed into T as if it were as const.

But if you try to constrain it with an array, it doesn't work!

ts
const returnWhatIPassIn = <const T extends any[]>(t: T) => {
return t;
};
 
// result is any[] in TS 5.2!
const result = returnWhatIPassIn(["a", "b", "c"]);

Before TS 5.3, the fix was to add readonly to the type parameter:

ts
const returnWhatIPassIn = <const T extends readonly any[]>(
t: T
) => {
return t;
};
 
// result is ['a', 'b', 'c']!
const result = returnWhatIPassIn(["a", "b", "c"]);

But this fix was hard to find and required some deep knowledge of how const type parameters worked.

How TypeScript 5.3 Fixed It

Since 5.3, TypeScript has relaxed the rules around readonly arrays.

In these two situations, TypeScript now acts more helpfully.

The satisfies keyword now lets you pass in readonly arrays:

ts
// This would error in 5.2, but is allowed in 5.3!
const array = ["a", "b", "c"] as const satisfies string[];
const array: ["a", "b", "c"]

Const type parameters now infer the type passed in instead of defaulting to their constraints:

ts
const returnWhatIPassIn = <const T extends any[]>(t: T) => {
return t;
};
 
// result is any[] in TS 5.2, but ['a', 'b', 'c'] in 5.3
const result = returnWhatIPassIn(["a", "b", "c"]);
const result: ["a", "b", "c"]

Note that there is a small difference! If you were to specify readonly string[] instead of string[], you would get a readonly array back.

So you still need to specify readonly if you want a readonly array back.

ts
// This would error in 5.2, but is allowed in 5.3!
const array = [
const array: readonly ["a", "b", "c"]
"a",
"b",
"c",
] as const satisfies readonly string[];
 
const returnWhatIPassIn = <const T extends readonly any[]>(
t: T
) => {
return t;
};
 
// result is any[] in TS 5.2, but ['a', 'b', 'c'] in 5.3
const result = returnWhatIPassIn(["a", "b", "c"]);
const result: readonly ["a", "b", "c"]

But this is a massive improvement, making both const type parameters and satisfies much easier to work with.

TypeScript - you gotta shout about these things! I'll be updating my course with this new behavior very soon.

Matt's signature

Share this article with your friends

`any` Considered Harmful, Except For These Cases

Discover when it's appropriate to use TypeScript's any type despite its risks. Learn about legitimate cases where any is necessary.

Matt Pocock
Matt Pocock

No, TypeScript Types Don't Exist At Runtime

Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.

Matt Pocock
Matt Pocock

Deriving vs Decoupling: When NOT To Be A TypeScript Wizard

In this book teaser, we discuss deriving vs decoupling your types: when building relationships between your types or segregating them makes sense.

Matt Pocock
Matt Pocock

NoInfer: TypeScript 5.4's New Utility Type

Learn how TypeScript's new utility type, NoInfer, can improve inference behavior by controlling where types are inferred in generic functions.

Matt Pocock
Matt Pocock