TypeScript 4.2
Smarter Type Alias Preservation
TypeScript has a way to declare new names for types called type aliases.If you’re writing a set of functions that all work onstring | number | boolean, you can write a type alias to avoid repeating yourself over and over again.
tstypeBasicPrimitive =number |string |boolean;
TypeScript has always used a set of rules and guesses for when to reuse type aliases when printing out types.For example, take the following code snippet.
tsexporttypeBasicPrimitive =number |string |boolean;exportfunctiondoStuff(value:BasicPrimitive) {letx =value;returnx;}
If we hover our mouse overx in an editor like Visual Studio, Visual Studio Code, orthe TypeScript Playground, we’ll get a quick info panel that shows the typeBasicPrimitive.Likewise, if we get the declaration file output (.d.ts output) for this file, TypeScript will say thatdoStuff returnsBasicPrimitive.
However, what happens if we return aBasicPrimitive orundefined?
tsexporttypeBasicPrimitive =number |string |boolean;exportfunctiondoStuff(value:BasicPrimitive) {if (Math.random() <0.5) {returnundefined;}returnvalue;}
We can see what happensin the TypeScript 4.1 playground.While we might want TypeScript to display the return type ofdoStuff asBasicPrimitive | undefined, it instead displaysstring | number | boolean | undefined!What gives?
Well this has to do with how TypeScript represents types internally.When creating a union type out of one or more union types, it will alwaysnormalize those types into a new flattened union type - but doing that loses information.The type-checker would have to find every combination of types fromstring | number | boolean | undefined to see what type aliases could have been used, and even then, there might be multiple type aliases tostring | number | boolean.
In TypeScript 4.2, our internals are a little smarter.We keep track of how types were constructed by keeping around parts of how they were originally written and constructed over time.We also keep track of, and differentiate, type aliases to instances of other aliases!
Being able to print back the types based on how you used them in your code means that as a TypeScript user, you can avoid some unfortunately humongous types getting displayed, and that often translates to getting better.d.ts file output, error messages, and in-editor type displays in quick info and signature help.This can help TypeScript feel a little bit more approachable for newcomers.
For more information, check outthe first pull request that improves various cases around preserving union type aliases, along witha second pull request that preserves indirect aliases.
Leading/Middle Rest Elements in Tuple Types
In TypeScript, tuple types are meant to model arrays with specific lengths and element types.
ts// A tuple that stores a pair of numbersleta: [number,number] = [1,2];// A tuple that stores a string, a number, and a booleanletb: [string,number,boolean] = ["hello",42,true];
Over time, TypeScript’s tuple types have become more and more sophisticated, since they’re also used to model things like parameter lists in JavaScript.As a result, they can have optional elements and rest elements, and can even have labels for tooling and readability.
tsTry// A tuple that has either one or two strings.letc : [string,string?] = ["hello"];c = ["hello","world"];// A labeled tuple that has either one or two strings.letd : [first :string,second ?:string] = ["hello"];d = ["hello","world"];// A tuple with a *rest element* - holds at least 2 strings at the front,// and any number of booleans at the back.lete : [string,string, ...boolean[]];e = ["hello","world"];e = ["hello","world",false];e = ["hello","world",true,false,true];
In TypeScript 4.2, rest elements specifically been expanded in how they can be used.In prior versions, TypeScript only allowed...rest elements at the very last position of a tuple type.
However, now rest elements can occuranywhere within a tuple - with only a few restrictions.
tsTryletfoo : [...string[],number];foo = [123];foo = ["hello",123];foo = ["hello!","hello!","hello!",123];letbar : [boolean, ...string[],boolean];bar = [true,false];bar = [true,"some text",false];bar = [true,"some","separated","text",false];
The only restriction is that a rest element can be placed anywhere in a tuple, so long as it’s not followed by another optional element or rest element.In other words, only one rest element per tuple, and no optional elements after rest elements.
tsTryinterfaceClown {/*...*/}interfaceJoker {/*...*/}letA rest element cannot follow another rest element.1265A rest element cannot follow another rest element.StealersWheel : [...Clown [],"me", ...Joker []];letAn optional element cannot follow a rest element.1266An optional element cannot follow a rest element.StringsAndMaybeBoolean : [...string[],boolean?];
These non-trailing rest elements can be used to model functions that take any number of leading arguments, followed by a few fixed ones.
tsTrydeclarefunctiondoStuff (...args : [...names :string[],shouldCapitalize :boolean]):void;doStuff (/*shouldCapitalize:*/false)doStuff ("fee","fi","fo","fum",/*shouldCapitalize:*/true);
Even though JavaScript doesn’t have any syntax to model leading rest parameters, we were still able to declaredoStuff as a function that takes leading arguments by declaring the...args rest parameter witha tuple type that uses a leading rest element.This can help model lots of existing JavaScript out there!
For more details,see the original pull request.
Stricter Checks For Thein Operator
In JavaScript, it is a runtime error to use a non-object type on the right side of thein operator.TypeScript 4.2 ensures this can be caught at design-time.
tsTry"foo"inType 'number' is not assignable to type 'object'.2322Type 'number' is not assignable to type 'object'.42 ;
This check is fairly conservative for the most part, so if you have received an error about this, it is likely an issue in the code.
A big thanks to our external contributorJonas Hübotter fortheir pull request!
--noPropertyAccessFromIndexSignature
Back when TypeScript first introduced index signatures, you could only get properties declared by them with “bracketed” element access syntax likeperson["name"].
tsTryinterfaceSomeType {/** This is an index signature. */[propName :string]:any;}functiondoStuff (value :SomeType ) {letx =value ["someProperty"];}
This ended up being cumbersome in situations where we need to work with objects that have arbitrary properties.For example, imagine an API where it’s common to misspell a property name by adding an extras character at the end.
tsTryinterfaceOptions {/** File patterns to be excluded. */exclude ?:string[];/*** It handles any extra properties that we haven't declared as type 'any'.*/[x :string]:any;}functionprocessOptions (opts :Options ) {// Notice we're *intentionally* accessing `excludes`, not `exclude`if (opts .excludes ) {console .error ("The option `excludes` is not valid. Did you mean `exclude`?");}}
To make these types of situations easier, a while back, TypeScript made it possible to use “dotted” property access syntax likeperson.name when a type had a string index signature.This also made it easier to transition existing JavaScript code over to TypeScript.
However, loosening the restriction also meant that misspelling an explicitly declared property became much easier.
tsTryfunctionprocessOptions (opts :Options ) {// ...// Notice we're *accidentally* accessing `excludes` this time.// Oops! Totally valid.for (constexcludePattern ofopts .excludes ) {// ...}}
In some cases, users would prefer to explicitly opt into the index signature - they would prefer to get an error message when a dotted property access doesn’t correspond to a specific property declaration.
That’s why TypeScript introduces a new flag callednoPropertyAccessFromIndexSignature.Under this mode, you’ll be opted in to TypeScript’s older behavior that issues an error.This new setting is not under thestrict family of flags, since we believe users will find it more useful on certain codebases than others.
You can understand this feature in more detail by reading up on the correspondingpull request.We’d also like to extend a big thanks toWenlu Wang who sent us this pull request!
abstract Construct Signatures
TypeScript allows us to mark a class asabstract.This tells TypeScript that the class is only meant to be extended from, and that certain members need to be filled in by any subclass to actually create an instance.
tsTryabstractclassShape {abstractgetArea ():number;}newCannot create an instance of an abstract class.2511Cannot create an instance of an abstract class.Shape ();classSquare extendsShape {#sideLength:number;constructor(sideLength :number) {super();this.#sideLength =sideLength ;}getArea () {returnthis.#sideLength **2;}}// Works fine.newSquare (42);
To make sure this restriction innew-ing upabstract classes is consistently applied, you can’t assign anabstract class to anything that expects a construct signature.
tsTryinterfaceHasArea {getArea ():number;}letType 'typeof Shape' is not assignable to type 'new () => HasArea'. Cannot assign an abstract constructor type to a non-abstract constructor type.2322Type 'typeof Shape' is not assignable to type 'new () => HasArea'. Cannot assign an abstract constructor type to a non-abstract constructor type.:new ()=> Ctor HasArea =Shape ;
This does the right thing in case we intend to run code likenew Ctor, but it’s overly-restrictive in case we want to write a subclass ofCtor.
tsTryabstractclassShape {abstractgetArea ():number;}interfaceHasArea {getArea ():number;}functionmakeSubclassWithArea (Ctor :new ()=>HasArea ) {returnclassextendsCtor {getArea () {return42}};}letArgument of type 'typeof Shape' is not assignable to parameter of type 'new () => HasArea'. Cannot assign an abstract constructor type to a non-abstract constructor type.2345Argument of type 'typeof Shape' is not assignable to parameter of type 'new () => HasArea'. Cannot assign an abstract constructor type to a non-abstract constructor type.MyShape =makeSubclassWithArea (); Shape
It also doesn’t work well with built-in helper types likeInstanceType.
tsTrytypeMyInstance =InstanceType <typeofShape >;
That’s why TypeScript 4.2 allows you to specify anabstract modifier on constructor signatures.
tsTryinterfaceHasArea {getArea ():number;}// Works!letCtor :abstractnew ()=>HasArea =Shape ;
Adding theabstract modifier to a construct signature signals that you can pass inabstract constructors.It doesn’t stop you from passing in other classes/constructor functions that are “concrete” - it really just signals that there’s no intent to run the constructor directly, so it’s safe to pass in either class type.
This feature allows us to writemixin factories in a way that supports abstract classes.For example, in the following code snippet, we’re able to use the mixin functionwithStyles with theabstract classSuperClass.
tsTryabstractclassSuperClass {abstractsomeMethod ():void;badda () {}}typeAbstractConstructor <T > =abstractnew (...args :any[])=>T functionwithStyles <T extendsAbstractConstructor <object>>(Ctor :T ) {abstractclassStyledClass extendsCtor {getStyles () {// ...}}returnStyledClass ;}classSubClass extendswithStyles (SuperClass ) {someMethod () {this.someMethod ()}}
Note thatwithStyles is demonstrating a specific rule, where a class (likeStyledClass) that extends a value that’s generic and bounded by an abstract constructor (likeCtor) has to also be declaredabstract.This is because there’s no way to know if a class withmore abstract members was passed in, and so it’s impossible to know whether the subclass implements all the abstract members.
You can read up more on abstract construct signatureson its pull request.
Understanding Your Project Structure With--explainFiles
A surprisingly common scenario for TypeScript users is to ask “why is TypeScript including this file?“.Inferring the files of your program turns out to be a complicated process, and so there are lots of reasons why a specific combination oflib.d.ts got used, why certain files innode_modules are getting included, and why certain files are being included even though we thought specifyingexclude would keep them out.
That’s why TypeScript now provides anexplainFiles flag.
shtsc --explainFiles
When using this option, the TypeScript compiler will give some very verbose output about why a file ended up in your program.To read it more easily, you can forward the output to a file, or pipe it to a program that can easily view it.
sh# Forward output to a text filetsc --explainFiles > explanation.txt# Pipe output to a utility program like `less`, or an editor like VS Codetsc --explainFiles | lesstsc --explainFiles | code -
Typically, the output will start out by listing out reasons for includinglib.d.ts files, then for local files, and thennode_modules files.
TS_Compiler_Directory/4.2.2/lib/lib.es5.d.tsLibrary referenced via 'es5' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.tsLibrary referenced via 'es2015' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2016.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2016.d.tsLibrary referenced via 'es2016' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2017.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2017.d.tsLibrary referenced via 'es2017' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2018.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2018.d.tsLibrary referenced via 'es2018' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2019.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2019.d.tsLibrary referenced via 'es2019' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2020.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2020.d.tsLibrary referenced via 'es2020' from file 'TS_Compiler_Directory/4.2.2/lib/lib.esnext.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.esnext.d.tsLibrary 'lib.esnext.d.ts' specified in compilerOptions... More Library References...foo.tsMatched by include pattern '**/*' in 'tsconfig.json'
Right now, we make no guarantees about the output format - it might change over time.On that note, we’re interested in improving this format if you have any suggestions!
For more information,check out the original pull request!
Improved Uncalled Function Checks in Logical Expressions
Thanks to further improvements fromAlex Tarasyuk, TypeScript’s uncalled function checks now apply within&& and|| expressions.
UnderstrictNullChecks, the following code will now error.
tsfunctionshouldDisplayElement(element:Element) {// ...returntrue;}functiongetVisibleItems(elements:Element[]) {returnelements.filter((e)=>shouldDisplayElement &&e.children.length);// ~~~~~~~~~~~~~~~~~~~~// This condition will always return true since the function is always defined.// Did you mean to call it instead.}
For more details,check out the pull request here.
Destructured Variables Can Be Explicitly Marked as Unused
Thanks to another pull request fromAlex Tarasyuk, you can now mark destructured variables as unused by prefixing them with an underscore (the_ character).
tslet [_first,second] =getValues();
Previously, if_first was never used later on, TypeScript would issue an error undernoUnusedLocals.Now, TypeScript will recognize that_first was intentionally named with an underscore because there was no intent to use it.
For more details, take a look atthe full change.
Relaxed Rules Between Optional Properties and String Index Signatures
String index signatures are a way of typing dictionary-like objects, where you want to allow access with arbitrary keys:
tsTryconstmovieWatchCount : { [key :string]:number } = {};functionwatchMovie (title :string) {movieWatchCount [title ] = (movieWatchCount [title ] ??0) +1;}
Of course, for any movie title not yet in the dictionary,movieWatchCount[title] will beundefined (TypeScript 4.1 added the optionnoUncheckedIndexedAccess to includeundefined when reading from an index signature like this).Even though it’s clear that there must be some strings not present inmovieWatchCount, previous versions of TypeScript treated optional object properties as unassignable to otherwise compatible index signatures, due to the presence ofundefined.
tsTrytypeWesAndersonWatchCount = {"Fantastic Mr. Fox"?:number;"The Royal Tenenbaums"?:number;"Moonrise Kingdom"?:number;"The Grand Budapest Hotel"?:number;};declareconstwesAndersonWatchCount :WesAndersonWatchCount ;constmovieWatchCount : { [key :string]:number } =wesAndersonWatchCount ;// ~~~~~~~~~~~~~~~ error!// Type 'WesAndersonWatchCount' is not assignable to type '{ [key: string]: number; }'.// Property '"Fantastic Mr. Fox"' is incompatible with index signature.// Type 'number | undefined' is not assignable to type 'number'.// Type 'undefined' is not assignable to type 'number'. (2322)
TypeScript 4.2 allows this assignment. However, it doesnot allow the assignment of non-optional properties withundefined in their types, nor does it allow writingundefined to a specific key:
tsTrytypeBatmanWatchCount = {"Batman Begins":number |undefined;"The Dark Knight":number |undefined;"The Dark Knight Rises":number |undefined;};declareconstbatmanWatchCount :BatmanWatchCount ;// Still an error in TypeScript 4.2.constType 'BatmanWatchCount' is not assignable to type '{ [key: string]: number; }'. Property '"Batman Begins"' is incompatible with index signature. Type 'number | undefined' is not assignable to type 'number'. Type 'undefined' is not assignable to type 'number'.2322Type 'BatmanWatchCount' is not assignable to type '{ [key: string]: number; }'. Property '"Batman Begins"' is incompatible with index signature. Type 'number | undefined' is not assignable to type 'number'. Type 'undefined' is not assignable to type 'number'.: { [ movieWatchCount key :string]:number } =batmanWatchCount ;// Still an error in TypeScript 4.2.// Index signatures don't implicitly allow explicit `undefined`.Type 'undefined' is not assignable to type 'number'.2322Type 'undefined' is not assignable to type 'number'.movieWatchCount ["It's the Great Pumpkin, Charlie Brown"] =undefined ;
The new rule also does not apply to number index signatures, since they are assumed to be array-like and dense:
tsTrydeclareletsortOfArrayish : { [key :number]:string };declareletnumberKeys : {42?:string };Type '{ 42?: string | undefined; }' is not assignable to type '{ [key: number]: string; }'. Property '42' is incompatible with index signature. Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'.2322Type '{ 42?: string | undefined; }' is not assignable to type '{ [key: number]: string; }'. Property '42' is incompatible with index signature. Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'.= sortOfArrayish numberKeys ;
You can get a better sense of this changeby reading up on the original PR.
Declare Missing Helper Function
Thanks toa community pull request fromAlexander Tarasyuk, we now have a quick fix for declaring new functions and methods based on the call-site!

Breaking Changes
We always strive to minimize breaking changes in a release.TypeScript 4.2 contains some breaking changes, but we believe they should be manageable in an upgrade.
lib.d.ts Updates
As with every TypeScript version, declarations forlib.d.ts (especially the declarations generated for web contexts), have changed.There are various changes, thoughIntl andResizeObserver’s may end up being the most disruptive.
noImplicitAny Errors Apply to Looseyield Expressions
When the value of ayield expression is captured, but TypeScript can’t immediately figure out what type you intend for it to receive (i.e. theyield expression isn’t contextually typed), TypeScript will now issue an implicitany error.
tsTryfunction*g1 () {const'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation.7057'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation.value =yield 1;}function*g2 () {// No error.// The result of `yield 1` is unused.yield1;}function*g3 () {// No error.// `yield 1` is contextually typed by 'string'.constvalue :string =yield1;}function*g4 ():Generator <number,void,string> {// No error.// TypeScript can figure out the type of `yield 1`// from the explicit return type of `g4`.constvalue =yield1;}
See more details inthe corresponding changes.
Expanded Uncalled Function Checks
As described above, uncalled function checks will now operate consistently within&& and|| expressions when usingstrictNullChecks.This can be a source of new breaks, but is typically an indication of a logic error in existing code.
Type Arguments in JavaScript Are Not Parsed as Type Arguments
Type arguments were already not allowed in JavaScript, but in TypeScript 4.2, the parser will parse them in a more spec-compliant way.So when writing the following code in a JavaScript file:
tsf<T>(100);
TypeScript will parse it as the following #"https://github.com/microsoft/TypeScript/pull/41928">the pull request for more details on what’s checked.
Tuple size limits for spreads
Tuple types can be made by using any sort of spread syntax (...) in TypeScript.
ts// Tuple types with spread elementstypeNumStr = [number,string];typeNumStrNumStr = [...NumStr, ...NumStr];// Array spread expressionsconstnumStr = [123,"hello"]asconst;constnumStrNumStr = [...numStr, ...numStr]asconst;
Sometimes these tuple types can accidentally grow to be huge, and that can make type-checking take a long time.Instead of letting the type-checking process hang (which is especially bad in editor scenarios), TypeScript has a limiter in place to avoid doing all that work.
You cansee this pull request for more details.
.d.ts Extensions Cannot Be Used In Import Paths
In TypeScript 4.2, it is now an error for your import paths to contain.d.ts in the extension.
ts// must be changed to something like// - "./foo"// - "./foo.js"import {Foo }from"./foo.d.ts";
Instead, your import paths should reflect whatever your loader will do at runtime.Any of the following imports might be usable instead.
tsimport {Foo }from"./foo";import {Foo }from"./foo.js";import {Foo }from"./foo/index.js";
Reverting Template Literal Inference
This change removed a feature from TypeScript 4.2 beta.If you haven’t yet upgraded past our last stable release, you won’t be affected, but you may still be interested in the change.
The beta version of TypeScript 4.2 included a change in inference to template strings.In this change, template string literals would either be given template string types or simplify to multiple string literal types.These types would thenwiden tostring when assigning to mutable variables.
tsdeclareconstyourName:string;// 'bar' is constant.// It has type '`hello ${string}`'.constbar =`hello${yourName}`;// 'baz' is mutable.// It has type 'string'.letbaz =`hello${yourName}`;
This is similar to how string literal inference works.
ts// 'bar' has type '"hello"'.constbar ="hello";// 'baz' has type 'string'.letbaz ="hello";
For that reason, we believed that making template string expressions have template string types would be “consistent”;however, from what we’ve seen and heard, that isn’t always desirable.
In response, we’ve reverted this feature (and potential breaking change).If youdo want a template string expression to be given a literal-like type, you can always addas const to the end of it.
tsdeclareconstyourName:string;// 'bar' has type '`hello ${string}`'.constbar =`hello${yourName}`asconst;// ^^^^^^^^// 'baz' has type 'string'.constbaz =`hello${yourName}`;
TypeScript’slift Callback invisitNode Uses a Different Type
TypeScript has avisitNode function that takes alift function.lift now expects areadonly Node[] instead of aNodeArray<Node>.This is technically an API breaking change which you can read more onhere.
The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request ❤
Last updated: Nov 25, 2025