• DSA with JS - Self Paced
  • JS Tutorial
  • JS Exercise
  • JS Interview Questions
  • JS Operator
  • JS Projects
  • JS Examples
  • JS Free JS Course
  • JS A to Z Guide
  • JS Formatter

TypeScript Operators

  • Typescript Keyof Type Operator
  • TypeScript Constraints
  • TypeScript Instanceof Operator
  • TypeScript Object
  • TypeScript Tuples
  • TypeScript Arrays
  • TypeScript Numbers
  • TypeScript Literal Types
  • TypeScript class
  • TypeScript Optional Parameters
  • TypeScript Map
  • TypeScript Object Types
  • TypeScript Mapped Types
  • TypeScript Assertions Type
  • Rest Parameters in TypeScript
  • Interfaces in TypeScript
  • TypeScript Generic Types
  • Next.js TypeScript
  • TypeScript in operator narrowing Type
  • How to calculate the number of days between two dates in JavaScript ?
  • Convert a String to an Integer in JavaScript
  • How to append HTML code to a div using JavaScript ?
  • How to Open URL in New Tab using JavaScript ?
  • Difference between var and let in JavaScript
  • How do you run JavaScript script through the Terminal?
  • Remove elements from a JavaScript Array
  • How to read a local text file using JavaScript?
  • JavaScript console.log() Method
  • JavaScript Number toString() Method

TypeScript operators are symbols or keywords that perform operations on one or more operands. In this article, we are going to learn various types of TypeScript Operators.

Below are the different TypeScript Operators:

Table of Content

TypeScript Arithmetic operators

Typescript logical operators, typescript relational operators, typescript bitwise operators, typescript assignment operators, typescript ternary/conditional operator, typescript type operators, typescript string operators.

In TypeScript, arithmetic operators are used to perform mathematical calculations.

In TypeScript, logical operators are used to perform logical operations on Boolean values.

In TypeScript, relational operators are used to compare two values and determine the relationship between them.

In TypeScript, bitwise operators perform operations on the binary representation of numeric values.

In TypeScript, assignment operators are used to assign values to variables and modify their values based on arithmetic or bitwise operations.

In TypeScript, the ternary operator, also known as the conditional operator, is a concise way to write conditional statements. It allows you to express a simple if-else statement in a single line.

In TypeScript, type operators are constructs that allow you to perform operations on types. These operators provide powerful mechanisms for defining and manipulating types in a flexible and expressive manner.

In TypeScript, string operators and features are used for manipulating and working with string values.

Please Login to comment...

Similar reads.

  • Web Technologies

advertisewithusBannerImg

Improve your Coding Skills with Practice

 alt=

What kind of Experience do you want to share?

Advisory boards aren’t only for executives. Join the LogRocket Content Advisory Board today →

LogRocket blog logo

  • Product Management
  • Solve User-Reported Issues
  • Find Issues Faster
  • Optimize Conversion and Adoption
  • Start Monitoring for Free

How to dynamically assign properties to an object in TypeScript

typescript in assignment

Editor’s note: This article was updated on 6 October 2023, introducing solutions like type assertions and the Partial utility type to address the TypeScript error highlighted.

How To Dynamically Assign Properties To An Object In TypeScript

JavaScript is a dynamically typed language, meaning that a variable’s type is determined at runtime and by what it holds at the time of execution. This makes it flexible but also unreliable and prone to errors because a variable’s value might be unexpected.

TypeScript, on the other hand, is a statically typed version of JavaScript — unlike JavaScript, where a variable can change types randomly, TypeScript defines the type of a variable at its declaration or initialization.

Dynamic property assignment is the ability to add properties to an object only when they are needed. This can occur when an object has certain properties set in different parts of our code that are often conditional.

In this article, we will explore some ways to enjoy the dynamic benefits of JavaScript alongside the security of TypeScript’s typing in dynamic property assignment.

Consider the following example of TypeScript code:

This seemingly harmless piece of code throws a TypeScript error when dynamically assigning name to the organization object:

An Error Is Thrown When Dynamically Assigning A Property To An Object

See this example in the TypeScript Playground .

The source of confusion, perhaps rightly justified if you’re a TypeScript beginner, is: how could something that seems so simple be such a problem in TypeScript?

The TL;DR of it all is that if you can’t define the variable type at declaration time, you can use the Record utility type or an object index signature to solve this. But in this article, we’ll go through the problem itself and work toward a solution that should work in most cases.

The problem with dynamically assigning properties to objects

Generally speaking, TypeScript determines the type of a variable when it is declared. This determined type stays the same throughout your application. There are exceptions to this rule, such as when considering type narrowing or working with the any type, but otherwise, this is a general rule to remember.

In the earlier example, the organization object is declared as follows:

There is no explicit type assigned to this variable, so TypeScript infers a type of organization based on the declaration to be {} , i.e., the literal empty object.

If you add a type alias, you can explore the type of organization :

Exploring The Literal Object Type

See this in the TypeScript Playground .

When you then try to reference the name prop on this empty object literal:

You receive the following error:

There are many ways to solve the TypeScript error here. Let’s consider the following:

Solution 1: Explicitly type the object at declaration time

This is the easiest solution to reason through. At the time you declare the object, go ahead and type it, and assign all the relevant values:

This eliminates any surprises. You’re clearly stating what this object type is and rightly declaring all relevant properties when you create the object.

However, this is not always feasible if the object properties must be added dynamically, which is why we’re here.

Solution 2: Use an object index signature

Occasionally, the properties of the object truly need to be added at a time after they’ve been declared. In this case, you can use the object index signature, as follows:

When the organization variable is declared, you can explicitly type it to the following: {[key: string] : string} .

You might be used to object types having fixed property types:

However, you can also substitute name for a “variable type.” For example, if you want to define any string property on obj :

Note that the syntax is similar to how you’d use a variable object property in standard JavaScript:

The TypeScript equivalent is called an object index signature.

typescript in assignment

Over 200k developers use LogRocket to create better digital experiences

typescript in assignment

Moreover, note that you could type key with other primitives:

Solution 3: Use the Record Utility Type

The Record utility type allows you to constrict an object type whose properties are Keys and property values are Type . It has the following signature: Record<Keys, Type> .

In our example, Keys represents string and Type . The solution here is shown below:

Instead of using a type alias, you can also inline the type:

Using The Record Utility Type

Solution 4: Use the Map data type

A Map object is a fundamentally different data structure from an object , but for completeness, you could eliminate this problem if you were using Map .

Consider the starting example rewritten to use a Map object:

With Map objects, you’ll have no errors when dynamically assigning properties to the object:

Dark Background Typescript Playground Showing Map Object No Errors

This seems like a great solution at first, but the caveat is your Map object is weakly typed. You can access a nonexisting property and get no warnings at all:

See the TypeScript Playground .

This is unlike the standard object. By default, the initialized Map has the key and value types as any — i.e., new () => Map<any, any> . Consequently, the return type of the s variable will be any :

Dark Background Typescript Playground Showing Constant S With Type Any Indicated By Red Arrow

When using Map , at the very least, I strongly suggest passing some type information upon creation. For example:

s will still be undefined, but you won’t be surprised by its code usage. You’ll now receive the appropriate type for it:

Dark Background Typescript Playground Showing Properly Typed Map Value With Const S With Type String Undefined Indicated By Red Arrow

If you truly don’t know what the keys of the Map will be, you can go ahead and represent this at the type level:

And if you’re not sure what the keys or values are, be safe by representing this at the type level:

Solution 5: Consider an optional object property

This solution won’t always be possible, but if you know the name of the property to be dynamically assigned, you can optionally provide this when initializing the object as shown below:

If you don’t like the idea of using optional properties, you can be more explicit with your typing as shown below:

Solution 6: Leveraging type assertions

TypeScript type assertion is a mechanism that tells the compiler the variable’s type and overrides what it infers from the declaration or assignment. With this, we are telling the compiler to trust our understanding of the type because there will be no type verification.

We can perform a type assertion by either using the <> brackets or the as keyword. This is particularly helpful with the dynamic property assignment because it allows the properties we want for our object to be dynamically set because TypeScript won’t enforce them.

Let’s take a look at applying type assertions to our problem case:

Dark Background Typescript Playground Showing Dynamic Property Assignment Using Type Assertion

Note that with type assertions, the compiler is trusting that we will enforce the type we have asserted. This means if we don’t, for example, set a value for organization.name , it will throw an error at runtime that we will have to handle ourselves.

Solution 7: Use the Partial utility type

TypeScript provides several utility types that can be used to manipulate types. Some of these utility types are Partial , Omit , Required , and Pick .

For dynamic property assignments, we will focus specifically on the Partial utility type. This takes a defined type and makes all its properties optional. Thus, we can initialize our object with any combination of its properties, from none to all, as each one is optional:

In our example with the Partial utility type, we defined our organization object as the type partial Org , which means we can choose not to set a phoneNumber property:

Dark Background Typescript Playground Showing Dynamic Property Assignment Using Utility Type Partial

Grouping and comparing the options for adding properties in TypeScript

In this article, we explored the different options for setting properties dynamically in TypeScript. These options can be grouped together by their similarities.

Index/Key signatures

This group of options allows you to define the type of keys allowed without limiting what possible keys can exist. The options in this group include:

  • Using an object index signature
  • Using the Record utility type
  • Using the Map data type (with key/value typing)

With these, we can define that our object will take string indexes and decide what types to support as values, like String , Number , Boolean , or Any :

See in TypeScript Playground .

Pro: The main benefit of these methods is the ability to dynamically add properties to an object while still setting expectations for the potential types of keys and values.

Con: The main disadvantage of this way of defining objects is that you can’t predict what keys our objects will have and so some references may or may not be defined. An additional disadvantage is that if we decide to define our key signature with type Any , then the object becomes even more unpredictable.

Conditional/Optional properties

This set of object assignment methods shares a common feature: the definition of optional properties. This means that the range of possible properties are known but some may or may not be set. The options in this group include:

  • Using optional object properties
  • Using the Partial utility type
  • Using type assertions

See this example in the TypeScript Playground , or in the code block below:

Note: While these options mean that the possible keys are known and may not be set, TypeScript’s compiler won’t validate undefined states when using type assertions. This can lead to unhandled exceptions during runtime. For example, with optional properties and the Partial utility type, name has type string or undefined . Meanwhile, with type assertions, name has type string .

Pro: The advantage of this group of options is that all possible object keys and values are known.

Con: The disadvantage is that while the possible keys are known, we don’t know if those keys have been set and will have to handle the possibility that they are undefined.

Apart from primitives, the most common types you’ll have to deal with are likely object types. In cases where you need to build an object dynamically, take advantage of the Record utility type or use the object index signature to define the allowed properties on the object.

If you’d like to read more on this subject, feel free to check out my cheatsheet on the seven most-asked TypeScript questions on Stack Overflow, or tweet me any questions . Cheers!

LogRocket : Full visibility into your web and mobile apps

LogRocket Dashboard Free Trial Banner

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

Try it for free .

Share this:

  • Click to share on Twitter (Opens in new window)
  • Click to share on Reddit (Opens in new window)
  • Click to share on LinkedIn (Opens in new window)
  • Click to share on Facebook (Opens in new window)
  • #typescript

typescript in assignment

Stop guessing about your digital experience with LogRocket

Recent posts:.

A Guide To Deno.cron

A guide to Deno.cron

This guide explores how to use the cron package in Deno, `Deno.cron`, to handle scheduling tasks with specific commands.

typescript in assignment

Comparing React state tools: Mutative vs. Immer vs. reducers

Mutative processes data with better performance than both Immer and native reducers. Let’s compare these data handling options in React.

typescript in assignment

Radix UI adoption guide: Overview, examples, and alternatives

Radix UI is quickly rising in popularity and has become an excellent go-to solution for building modern design systems and websites.

typescript in assignment

Understanding the CSS revert-layer keyword

In this article, we’ll explore CSS cascade layers — and, specifically, the revert-layer keyword — to help you refine your styling strategy.

typescript in assignment

3 Replies to "How to dynamically assign properties to an object in TypeScript"

I know this is explicitly for TypeScript, and I think type declarations should always be first. But in general, you can also use a Map object. If it’s really meant to by dynamic, might as well utilize the power of Map.

Great suggestion (updated the article). It’s worth mentioning the weak typing you get by default i.e., with respect to Typescript.

Hi, thanks for your valuable article please consider ‘keyof type’ in TypeScript, and add this useful solution if you are happy have nice time

Leave a Reply Cancel reply

typescript in assignment

Home » Introduction and Basics » Type assignment in typescript

Type assignment in typescript

Type assignment in typescript.

TypeScript is a statically typed superset of JavaScript that adds optional type annotations to the language. This allows developers to catch errors and bugs at compile-time rather than runtime. One of the key features of TypeScript is its ability to assign types to variables, functions, and objects. In this article, we will explore the different ways to assign types in TypeScript and provide examples to illustrate their usage.

Basic Type Assignment

The most straightforward way to assign a type in TypeScript is by using the colon (:) syntax. This syntax is used to declare the type of a variable, function parameter, or function return value. Let’s take a look at some examples:

In the above examples, we have assigned the type “string” to the variable “name”, the type “string” to the function parameter “person”, and the type “number” to the function parameters “a” and “b” as well as the return value of the function “add”. This ensures that the assigned values or function arguments are of the specified type, and any type mismatches will result in a compile-time error.

Implicit Type Assignment

In addition to explicit type assignment, TypeScript also supports implicit type assignment. This means that TypeScript can infer the type of a variable based on its initial value. Let’s see an example:

In the above example, we have declared a variable “age” without explicitly assigning a type. TypeScript infers the type of “age” as “number” based on its initial value of 25. This allows us to write more concise code without sacrificing type safety.

Union Types

Another powerful feature of TypeScript is the ability to assign multiple types to a variable using union types. Union types are denoted by the pipe (|) symbol. Let’s consider an example:

In the above example, we have declared a variable “result” with a union type of “string” and “number”. This means that “result” can hold values of either type. We can assign a string value or a number value to “result” without any compile-time errors. This flexibility allows us to handle different types of data in a single variable.

Type Assertion

Sometimes, TypeScript may not be able to infer the correct type or we may want to override the inferred type. In such cases, we can use type assertion to explicitly specify the type of a value. Type assertion is done using the angle bracket (<>) syntax or the “as” keyword. Let’s see an example:

In the above example, we have a variable “message” with the type “any”. We want to access the “length” property of “message”, which is only available for strings. By using type assertion, we explicitly tell TypeScript that “message” is of type “string” and then access its “length” property. This allows us to perform type-specific operations on values with a broader type.

Type assignment is a fundamental aspect of TypeScript that enables developers to write safer and more maintainable code. By assigning types to variables, function parameters, and return values, we can catch errors at compile-time and improve the overall quality of our code. Additionally, TypeScript provides features like implicit type assignment, union types, and type assertion to enhance flexibility and expressiveness. Understanding and utilizing these type assignment techniques will greatly benefit TypeScript developers in their day-to-day programming tasks.

  • No Comments
  • assignment , typescript

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Save my name, email, and website in this browser for the next time I comment.

Table of Contents

Not stay with the doubts.

Typescript SOS

  • Privacy Overview
  • Strictly Necessary Cookies

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.

Strictly Necessary Cookie should be enabled at all times so that we can save your preferences for cookie settings.

If you disable this cookie, we will not be able to save your preferences. This means that every time you visit this website you will need to enable or disable cookies again.

TypeScript: Mastering Type Assignment and Type Inference

TypeScript introduced static typing, enabling developers to specify the types of variables, function parameters, and return values.

typescript-type-assignment-and-type-inference

TypeScript, a superset of JavaScript, introduces static typing, enabling developers to specify the types of variables, function parameters, and return values. This capability significantly enhances code quality and developer productivity by catching errors early in the development process. Two fundamental concepts in TypeScript's type system are type assignment and type inference. Understanding these concepts is crucial for leveraging TypeScript's full potential to create robust and error-resistant applications.

Type Assignment

Type assignment in TypeScript is straightforward: it allows developers to explicitly specify the type of a variable, function parameter, or return value. This explicit typing helps catch type-related errors during compilation, long before the code is executed.

Syntax and Usage

Type assignment is done using a colon ( : ) followed by the type. Here are a few examples:

In these examples, userName is explicitly declared as a string , isLoggedIn as a boolean , and userAge as a number . The greet function expects a string parameter and is also expected to return a string .

Type Inference

Type inference allows TypeScript to automatically deduce the types of variables and expressions when they are not explicitly provided. This feature simplifies the code by reducing the verbosity of type annotations, without sacrificing the benefits of static typing.

How Type Inference Works

TypeScript's type inference comes into play in several scenarios, such as variable initialization and default function parameters. Here's an example:

In these cases, TypeScript infers the types based on the initial values. userId is inferred to be a number , isAdmin a boolean , and the multiply function's return type is inferred as number .

Best Practices for Type Assignment and Inference

While TypeScript's type inference is powerful, relying solely on inference can sometimes lead to less readable code, especially in complex scenarios. Here are some best practices:

  • Use explicit types for public API boundaries : This includes exported functions, classes, and interfaces. Explicit types improve documentation and make your code easier to understand for other developers.
  • Leverage type inference for local variables : For simple, internal code blocks, let TypeScript infer the types of local variables to reduce verbosity.
  • Prefer const over let for immutable values : This makes your intention clear and allows TypeScript to infer the type more accurately.

TypeScript's type assignment and type inference are two pillars that support writing clear, error-free code. By combining explicit type annotations with TypeScript's intelligent type inference, developers can enjoy the best of both worlds: the flexibility of JavaScript with the robustness of a statically-typed language. Whether you're a seasoned TypeScript developer or just starting out, mastering these concepts is key to unlocking the full power of TypeScript in your projects.

For those looking to dive deeper into TypeScript and explore its vast capabilities, PullTheCode offers a wealth of resources, tutorials, and best practices. From type assignment and inference to advanced types and utility types, PullTheCode is your go-to destination for elevating your TypeScript skills.

Own your own Software!

Join PullTheCode and start building your own SaaS in minutes on a platform you own!

Logical assignment operators in Typescript

Portrait of Tom

Hi there, Tom here! As of writing, Typescript 4's stable release is imminent (August 2020) and with it are some very nice changes to come.

Typescript 4 ahead

One of those is the addition of logical assignment operators, which allow to use the logical operators with an assignment. As a reminder, here are the logical operators being used for the new logical assignment operators:

These new logical assignment operators themselves are part of the collection of compound assignment operators , which apply an operator to two arguments and then assign the result to the left side. They are available in many languages and now get enhanced in Typescript.

Code, please!

What does that mean? You probably already know a few compound assignment operators:

The new logical assignment operators now give you the ability to use your logical operator of choice for the same kind of assignment:

Note that these new operators were already available in JS and are also natively suppported by V8.

And that’s about it for one of Typescript 4’s great new features. Thanks for the quick read and I hope you could learn something!

expressFlow is now Lean-Forge

Definite Assignment Assertions (!)

The Definite Assignment Assertions or also called non-null assertion operator tells the TypeScript compiler that a value typed cannot be null or undefined which is a way to override the compiler's analysis and inform it that a variable will be assigned a value before it is used.

TypeScript tutorial

Typescript exercises, typescript tutorial.

TypeScript is JavaScript with added syntax for types.

Examples in Each Chapter

Our "Try it Yourself" editor makes it easy to learn TypeScript.

You can edit TypeScript code and view the result in your browser.

Click on the "Try it Yourself" button to see how it works.

We recommend reading this tutorial in the sequence listed in the left menu.

Test Yourself With Exercises

Complete the sentence:

Start the Exercise

My Learning

Track your progress with the free "My Learning" program here at W3Schools.

Log in to your account, and start earning points!

This is an optional feature. You can study at W3Schools without using My Learning.

typescript in assignment

COLOR PICKER

colorpicker

Contact Sales

If you want to use W3Schools services as an educational institution, team or enterprise, send us an e-mail: [email protected]

Report Error

If you want to report an error, or if you want to make a suggestion, send us an e-mail: [email protected]

Top Tutorials

Top references, top examples, get certified.

TypeScript ESLint: Unsafe assignment of an any value [Fix]

avatar

Last updated: Feb 29, 2024 Reading time · 5 min

banner

# TypeScript ESLint: Unsafe assignment of an any value

The error "@typescript-eslint/no-unsafe-assignment: Unsafe assignment of an any value." occurs when you assign a value with a type of any to a variable or a property.

To solve the error, set the variable to a specific type or disable the ESLint rule.

Here are some examples of when the ESLint error is raised.

All of the assignments above cause the error because the ESLint rule prevents you from assigning a value with an any type to a variable.

The any type effectively turns off type checking and should be used sparingly.

This article addresses 2 similar ESLint errors:

  • @typescript-eslint/no-unsafe-assignment: Unsafe assignment of an any value.
  • Unexpected any. Specify a different type. eslint@typescript-eslint/no-explicit-any

# Disabling the @typescript-eslint/no-unsafe-assignment ESLint rule

One way to get around the ESLint error is to disable the rule.

For example, the following comment disables the rule for 1 line.

disabling the ts eslint no unsafe assignment rule

If you need to disable the @typescript-eslint/no-explicit-any rule for a single line, use the following comment.

If you need to disable multiple rules for a line, separate them by a comma.

If you need to disable the rule for the entire file, use the following comment.

If you need to disable the @typescript-eslint/no-explicit-any rule for the entire file, use the following comment instead.

You can disable both rules for the entire file by using the following comment.

If you want to disable the rules globally, add the following 2 rules to your .eslintrc.js file.

disable the two rules

If you use a .eslintrc.json file, make sure to double-quote the keys and values.

# Setting the variable or property to unknown instead of any

Alternatively, you can set the variable or property to unknown to resolve the ESLint error.

setting the variable property to unknown instead of any

The unknown type is the type-safe counterpart of any .

When working with the unknown type, we basically tell TypeScript that we're going to get this value, but we don't know its type.

We are going to check with a couple of if statements to track the type down and use it safely.

I have written a detailed guide on how to check the type of a variable in TypeScript .

When using the unknown type, you have to use an if statement as a type guard to check the type of the variable before you are able to use any type-specific methods (e.g. string, array, object, etc).

# The error commonly occurs when parsing a JSON string

The error commonly occurs when parsing a JSON string with the JSON.parse() method.

The result variable stores a value of any type because TypeScript doesn't know the type of the value that is being parsed.

One way to resolve the issue is to use a type predicate .

using type predicate to solve the error

The value is Employee syntax is called a type predicate.

Our function basically checks if the passed-in value is compatible with an object of type Employee .

Notice that in the if block in which we called the isAnEmployee() function, the parsed variable is typed as Employee and we can access the id and name properties without getting TypeScript or ESLint errors.

I've written a detailed guide on how to check if a value is an object .

# Resolve the issue by typing the variable explicitly

You can also resolve the issue by typing the variable explicitly and removing the any type.

Here is an example of typing an object.

And here is an example of typing an array of objects.

You might have to use a type assertion, e.g. when parsing a JSON string.

In some rare cases, you might have to widen the type to unknown before using a type assertion to set a more specific type.

I've written detailed guides on:

  • How to initialize a typed Empty Object in TypeScript
  • Declare an Empty Array for a typed Variable in TypeScript
  • How to add Elements to an Array in TypeScript
  • Check if an Array contains a Value in TypeScript
  • Check if a Value is an Array (of type) in TypeScript
  • How to declare an Array of Objects in TypeScript
  • How to declare a Two-dimensional Array in TypeScript
  • Declare Array of Numbers, Strings or Booleans in TypeScript
  • Create an Object based on an Interface in TypeScript
  • Create a Type from an object's Keys or Values in TypeScript
  • ESLint: Expected property shorthand object-shorthand [Fixed]
  • 'X' should be listed in the project's dependencies, not devDependencies
  • ESLint: Unexpected lexical declaration in case block [Fixed]
  • ESLint couldn't find the config 'prettier' to extend from
  • Import in body of module reorder to top eslint import/first
  • ESLint: A form label must be associated with a control

book cover

Borislav Hadzhiev

Web Developer

buy me a coffee

Copyright © 2024 Borislav Hadzhiev

  • United States
  • United Kingdom

TypeScript 5.5 moves to beta

List of improvements includes inferred type predicates, regular expression syntax checking, and performance and size optimizations..

Paul Krill

Editor at Large, InfoWorld |

Letterpress, TypeScript

TypeScript 5.5, the latest version of Microsoft’s strongly typed JavaScript variant , has arrived in beta with improvements ranging from performance and size optimizations to regular expression checking.

The TypeScript 5.5 beta was introduced April 25 and can be accessed through Nuget or the following command: npm -D typescript@beta . A release candidate is due June 4, and the final release is planned for June 18.

TypeScript 5.5 has a long a list of improvements. For performance and size, monomorphization work has been done for the language service and public API. With monomorphism, the editor experience and build tools using the TypeScript API will get faster, TypeScript’s authors said. This was the same work previously done for Node and Symbol objects in TypeScript 5.0 to ensure they had a consistent set of properties with a consistent initialization order.

TypeScript 5.5 also features a significant reduction in overall package size. The disk footprint has been reduced from 30.2 MB to 20.4 MB, and the packed size from 5.5 MB to 3.7 MB. As part of work to enable isolatedDeclarations , Microsoft has improved how often TypeScript can directly copy input source code when producing declaration files.

TypeScript 5.5 introduces basic syntax checking on regular expressions. Until now, TypeScript typically skipped over most regular expressions in code, because regular expressions technically have an extensible grammar and TypeScript never made an effort to compile regular expressions to earlier versions of JavaScript. This meant common problems would go undiscovered in regular expressions.

With TypeScript 5.5, TypeScript will now infer that a function returns a type predicate under certain conditions . And with control flow narrowing for constant indexed accesses, TypeScript now is able to narrow expressions in for obj[key] when both obj and key are effectively constant.

TypeScript 5.5 makes API consumption of ECMAScript modules easier. Previously, if a developer was writing an ECMAScript module in Node.js, named imports were not available from the typescript package. This has been fixed. TypeScript also now supports a new @import comment tag that has the same syntax as ECMAScript imports.

TypeScript 5.5 also adds a t ranspileDeclaration API, which is designed to generate a single declaration file based on input source text. The API is similar to transpileModule for compiling a single file of TypeScript code.

TypeScript 5.5 follows TypeScript 5.4 , which became generally available in March and brought preserved narrowing within function closures.

Next read this:

  • Why companies are leaving the cloud
  • 5 easy ways to run an LLM locally
  • Coding with AI: Tips and best practices from developers
  • Meet Zig: The modern alternative to C
  • What is generative AI? Artificial intelligence that creates
  • The best open source software of 2023
  • Development Libraries and Frameworks
  • Software Development

Paul Krill is an editor at large at InfoWorld, whose coverage focuses on application development.

Copyright © 2024 IDG Communications, Inc.

typescript in assignment

typescript in assignment

Announcing TypeScript 5.5 Beta

typescript in assignment

Daniel Rosenwasser

April 25th, 2024 2 3

Today we are excited to announce the availability of TypeScript 5.5 Beta.

To get started using the beta, you can get it through NuGet , or through npm with the following command:

Here’s a quick list of what’s new in TypeScript 5.5!

Inferred Type Predicates

Control flow narrowing for constant indexed accesses, type imports in jsdoc, regular expression syntax checking, isolated declarations, the ${configdir} template variable for configuration files, consulting package.json dependencies for declaration file generation, editor and watch-mode reliability improvements, performance and size optimizations, easier api consumption from ecmascript modules, the transpiledeclaration api, disabling features deprecated in typescript 5.0, lib.d.ts changes, respecting file extensions and package.json in other module modes, stricter parsing for decorators, undefined is no longer a definable type name, simplified reference directive declaration emit.

This section was written by Dan Vanderkam , who implemented this feature in TypeScript 5.5 . Thanks Dan!

TypeScript’s control flow analysis does a great job of tracking how the type of a variable changes as it moves through your code:

By making you handle the undefined case, TypeScript pushes you to write more robust code.

In the past, this sort of type refinement was more difficult to apply to arrays. This would have been an error in all previous versions of TypeScript:

This code is perfectly fine: we’ve filtered all the undefined values out of the list. But TypeScript hasn’t been able to follow along.

With TypeScript 5.5, the type checker is fine with this code:

Note the more precise type for birds .

This works because TypeScript now infers a type predicate for the filter function. You can see what’s going on more clearly by pulling it out into a standalone function:

bird is Bird is the type predicate. It means that, if the function returns true , then it’s a Bird (if the function returns false then it’s undefined ). The type declarations for Array.prototype.filter know about type predicates, so the net result is that you get a more precise type and the code passes the type checker.

TypeScript will infer that a function returns a type predicate if these conditions hold:

  • The function does not have an explicit return type or type predicate annotation.
  • The function has a single return statement and no implicit returns.
  • The function does not mutate its parameter.
  • The function returns a boolean expression that’s tied to a refinement on the parameter.

Generally this works how you’d expect. Here’s a few more examples of inferred type predicates:

Previously, TypeScript would have just inferred that these functions return boolean . It now infers signatures with type predicates like x is number or x is NonNullable<T> .

Type predicates have "if and only if" semantics. If a function returns x is T , then it means that:

  • If the function returns true then x is has type T .
  • If the function returns false then x does not have type T .

If you’re expecting a type predicate to be inferred but it’s not, then you may be running afoul of the second rule. This often comes up with "truthiness" checks:

TypeScript did not infer a type predicate for score => !!score , and rightly so: if this returns true then score is a number . But if it returns false , then score could be either undefined or a number (specifically, 0 ). This is a real bug: if any student got a zero on the test, then filtering out their score will skew the average upwards. Fewer will be above average and more will be sad!

As with the first example, it’s better to explicitly filter out undefined values:

A truthiness check will infer a type predicate for object types, where there’s no ambiguity. Remember that functions must return a boolean to be a candidate for an inferred type predicate: x => !!x might infer a type predicate, but x => x definitely won’t.

Explicit type predicates continue to work exactly as before. TypeScript will not check whether it would infer the same type predicate. Explicit type predicates ("is") are no safer than a type assertion ("as").

It’s possible that this feature will break existing code if TypeScript now infers a more precise type than you want. For example:

The fix is to tell TypeScript the type that you want using an explicit type annotation:

For more information, check out the implementing pull request and Dan’s blog post about implementing this feature .

TypeScript is now able to narrow expressions of the form obj[key] when both obj and key are effectively constant.

In the above, neither obj nor key are ever mutated, so TypeScript can narrow the type of obj[key] to string after the typeof check. For more information, see the implementing pull request here .

Today, if you want to import something only for type-checking in a JavaScript file, it is cumbersome. JavaScript developers can’t simply import a type named SomeType if it’s not there at runtime.

SomeType won’t exist at runtime, so the import will fail. Developers can instead use a namespace import instead.

But ./some-module is still imported at runtime – which might also not be desirable.

To avoid this, developers typically had to use import(...) types in JSDoc comments.

If you wanted to reuse the same type in multiple places, you could use a typedef to avoid repeating the import.

This helps with local uses of SomeType , but it gets repetitive for many imports and can be a bit verbose.

That’s why TypeScript now supports a new @import comment tag that has the same syntax as ECMAScript imports.

Here, we used named imports. We could also have written our import as a namespace import.

Because these are just JSDoc comments, they don’t affect runtime behavior at all.

We would like to extend a big thanks to Oleksandr Tarasiuk who contributed this change !

Until now, TypeScript has typically skipped over most regular expressions in code. This is because regular expressions technically have an extensible grammar and TypeScript never made any effort to compile regular expressions to earlier versions of JavaScript. Still, this meant that lots of common problems would go undiscovered in regular expressions, and they would either turn into errors at runtime, or silently fail.

But TypeScript now does basic syntax checking on regular expressions!

This is a simple example, but this checking can catch a lot of common mistakes. In fact, TypeScript’s checking goes slightly beyond syntactic checks. For instance, TypeScript can now catch issues around backreferences that don’t exist.

The same applies to named capturing groups.

TypeScript’s checking is now also aware of when certain RegExp features are used when newer than your target version of ECMAScript. For example, if we use named capturing groups like the above in an ES5 target, we’ll get an error.

The same is true for certain regular expression flags as well.

Note that TypeScript’s regular expression support is limited to regular expression literals . If you try calling new RegExp with a string literal, TypeScript will not check the provided string.

We would like to thank GitHub user graphemecluster who iterated a ton with us to get this feature into TypeScript .

This section was co-authored by Rob Palmer who supported the design of isolated declarations.

Declaration files (a.k.a. .d.ts files) describe the shape of existing libraries and modules to TypeScript. This lightweight description includes the library’s type signatures and excludes implementation details such as the function bodies. They are published so that TypeScript can efficiently check your usage of a library without needing to analyse the library itself. Whilst it is possible to handwrite declaration files, if you are authoring typed code, it’s much safer and simpler to let TypeScript generate them automatically from source files using --declaration .

The TypeScript compiler and its APIs have always had the job of generating declaration files; however, there are some use-cases where you might want to use other tools, or where the traditional build process doesn’t scale.

Use-case: Faster Declaration Emit Tools

Imagine if you wanted to create a faster tool to generate declaration files, perhaps as part of a publishing service or a new bundler. Whilst there is a thriving ecosystem of blazing fast tools that can turn TypeScript into JavaScript, the same is not true for turning TypeScript into declaration files. The reason is that TypeScript’s inference allows us to write code without explicitly declaring types, meaning declaration emit can be complex.

Let’s consider a simple example of a function that adds two imported variables.

Even if the only thing we want to do is generate add.d.ts , TypeScript needs to crawl into another imported file ( util.ts ), infer that the type of one and two are strings, and then calculate that the + operator on two strings will lead to a string return type.

While this inference is important for the developer experience, it means that tools that want to generate declaration files would need to replicate parts of the type-checker including inference and the ability to resolve module specifiers to follow the imports.

Use-case: Parallel Declaration Emit and Parallel Checking

Imagine if you had a monorepo containing many projects and a multi-core CPU that just wished it could help you check your code faster. Wouldn’t it be great if we could check all those projects at the same time by running each project on a different core?

Unfortunately we don’t have the freedom to do all the work in parallel. The reason is that we have to build those projects in dependency order, because each project is checking against the declaration files of their dependencies. So we must build the dependency first to generate the declaration files. TypeScript’s project references feature works the same way, building the set of projects in "topological" dependency order.

As an example, if we have two projects called backend and frontend , and they both depend on a project called core , TypeScript can’t start type-checking either frontend or backend until core has been built and its declaration files have been generated.

frontend and backend point to core, other stuff might point to each of those

In the above graph, you can see that we have a bottleneck. Whilst we can build frontend and backend in parallel, we need to first wait for core to finish building before either can start.

How could we improve upon this? Well, if a fast tool could generate all those declaration files for core in parallel , TypeScript then could immediately follow that by type-checking core , frontend , and backend also in parallel .

Solution: Explicit Types!

The common requirement in both use-cases is that we need a cross-file type-checker to generate declaration files. Which is a lot to ask from the tooling community.

As a more complex example, if we want a declaration file for the following code…

…we would need to generate a signature for foo . Well that requires looking at the implementation of foo . foo just returns x , so getting the type of x requires looking at the implementation of add . But that might require looking at the implementation of add ‘s dependencies, and so on. What we’re seeing here is that generating declaration files requires a whole lot of logic to figure out the types of different places that might not even be local to the current file.

Still, for developers looking for fast iteration time and fully parallel builds, there is another way of thinking about this problem. A declaration file only requires the types of the public API of a module – in other words, the types of the things that are exported. If, controversially, developers are willing to explicitly write out the types of the things they export, tools could generate declaration files without needing to look at the implementation of the module – and without reimplementing a full type-checker.

This is where the new --isolatedDeclarations option comes in. --isolatedDeclarations reports errors when a module can’t be reliably transformed without a type-checker. More plainly, it makes TypeScript report errors if you have a file that isn’t sufficiently annotated on its exports.

That means in the above example, we would see an error like the following:

Why are errors desirable?

Because it means that TypeScript can

  • Tell us up-front whether other tools will have issues with generating declaration files
  • Provide a quick fix to help add these missing annotations.

This mode doesn’t require annotations everywhere though. For locals, these can be ignored, since they don’t affect the public API. For example, the following code would not produce an error:

There are also certain expressions where the type is "trivial" to calculate.

Using isolatedDeclarations

isolatedDeclarations requires that either the declaration or composite flags are also set.

Note that isolatedDeclarations does not change how TypeScript performs emit – just how it reports errors. Importantly, and similar to isolatedModules , enabling the feature in TypeScript won’t immediately bring about the potential benefits discussed here. So please be patient and look forward to future developments in this space. Keeping tool authors in mind, we should also recognize that today, not all of TypeScript’s declaration emit can be easily replicated by other tools wanting to use it as a guide. That’s something we’re actively working on improving.

We also feel it is worth calling out that isolatedDeclarations should be adopted on a case-by-case basis. There are some developer ergonomics that are lost when using isolatedDeclarations , and thus it may not be the right choice if your setup is not leveraging the two scenarios mentioned earlier. For others, the work on isolatedDeclarations has already uncovered many optimizations and opportunities to unlock different parallel build strategies. In the meantime, if you’re willing to make the trade-offs, we believe isolatedDeclarations can be a powerful tool to speed up your build process once external tooling becomes available.

Work on isolatedDeclarations has been a long-time collaborative effort between the TypeScript team and the infrastructure and tooling teams within Bloomberg and Google. Individuals like Hana Joo from Google who implemented the quick fix for isolated declaration errors (more on that soon), as well as Ashley Claymore, Jan Kühle, Lisa Velden, Rob Palmer, and Thomas Chetwin have been involved in discussion, specification, and implementation for many months. But we feel it is specifically worth calling out the tremendous amount of work provided by Titian Cernicova-Dragomir from Bloomberg. Titian has been instrumental in driving the implementation of isolatedDeclarations and has been a contributor to the TypeScript project for years prior.

While the feature involved many changes, you can see the core work for Isolated Declarations here .

It’s common in many codebases to reuse a shared tsconfig.json file that acts as a "base" for other configuration files. This is done by using the extends field in a tsconfig.json file.

One of the issues with this is that all paths in the tsconfig.json file are relative to the location of the file itself. This means that if you have a shared tsconfig.base.json file that is used by multiple projects, relative paths often won’t be useful in the derived projects. For example, imagine the following tsconfig.base.json :

If author’s intent was that every tsconfig.json that extends this file should

  • output to a dist directory relative to the derived tsconfig.json , and
  • have a custom-types directory relative to the derived tsconfig.json ,

then this would not work. The typeRoots paths would be relative to the location of the shared tsconfig.base.json file, not the project that extends it. Each project that extends this shared file would need to declare its own outDir and typeRoots with identical contents. This could be frustrating and hard to keep in sync between projects, and while the example above is using typeRoots , this is a common problem for paths and other options.

To solve this, TypeScript 5.5 introduces a new template variable ${configDir} . When ${configDir} is written in certain path fields of a tsconfig.json or jsconfig.json files, this variable is substituted with the containing directory of the configuration file in a given compilation. This means that the above tsconfig.base.json could be rewritten as:

Now, when a project extends this file, the paths will be relative to the derived tsconfig.json , not the shared tsconfig.base.json file. This makes it easier to share configuration files across projects and ensures that the configuration files are more portable.

If you intend to make a tsconfig.json file extendable, consider if a ./ should instead be written with ${configDir} .

For more information, see the proposal issue and the implementing pull request .

Previously, TypeScript would often issue an error message like

This was often due to TypeScript’s declaration file generation finding itself in the contents of files that were never explicitly imported in a program. Generating an import to such a file could be risky if the path ended up being relative. Still, for codebases with explicit dependencies in the dependencies (or peerDependencies and optionalDependencies ) of a package.json , generating such an import should be safe under certain resolution modes. So in TypeScript 5.5, we’re more lenient when that’s the case, and many occurrences of this error should disappear.

See this pull request for more details on the change.

TypeScript has either added some new functionality or fixed existing logic that makes --watch mode and TypeScript’s editor integration feel more reliable. That should hopefully translate to fewer TSServer/editor restarts.

Correctly Refresh Editor Errors in Configuration Files

TypeScript can generate errors for tsconfig.json files; however, those errors are actually generated from loading a project, and editors typically don’t directly request those errors for tsconfig.json files. While this sounds like a technical detail, it means that when all errors issued in a tsconfig.json are fixed, TypeScript doesn’t issue a new fresh empty set of errors, and users are left with stale errors unless they reload their editor.

TypeScript 5.5 now intentionally issues an event to clear these out. See more here .

Better Handling for Deletes Followed by Immediate Writes

Instead of overwriting files, some tools will opt to delete them and then create new files from scratch. This is the case when running npm ci , for instance.

While this can be efficient for those tools, it can be problematic for TypeScript’s editor scenarios where deleting a watched might dispose of it and all of its transitive dependencies. Deleting and creating a file in quick succession could lead to TypeScript tearing down an entire project and then rebuilding it from scratch.

TypeScript 5.5 now has a more nuanced approach by keeping parts of a deleted project around until it picks up on a new creation event. This should make operations like npm ci work a lot better with TypeScript. See more information on the approach here .

Symlinks are Tracked in Failed Resolutions

When TypeScript fails to resolve a module, it will still need to watch for any failed lookup paths in case the module is added later. Previously this was not done for symlinked directories, which could cause reliability issues in monorepo-like scenarios when a build occurred in one project but was not witnessed in the other. This should be fixed in TypeScript 5.5, and means you won’t need to restart your editor as often.

See more information here .

Project References Contribute to Auto-Imports

Auto-imports no longer requires at least one explicit import to dependent projects in a project reference setup. Instead, auto-import completions should just work across anything you’ve listed in the references field of your tsconfig.json .

See more on the implementing pull request .

Monomorphized Objects in Language Service and Public API

In TypeScript 5.0, we ensured that our Node and Symbol objects had a consistent set of properties with a consistent initialization order. Doing so helps reduce polymorphism in different operations, which allows runtimes to fetch properties more quickly.

By making this change, we witnessed impressive speed wins in the compiler; however, most of these changes were performed on internal allocators for our data structures. The language service, along with TypeScript’s public API, uses a different set of allocators for certain objects. This allowed the TypeScript compiler to be a bit leaner, as data used only for the language service would never be used in the compiler.

In TypeScript 5.5, the same monomorphization work has been done for the language service and public API. What this means is that your editor experience, and any build tools that use the TypeScript API, will get a decent amount faster. In fact, in our benchmarks, we’ve seen a 5-8% speedup in build times when using the public TypeScript API’s allocators, and language service operations getting 10-20% faster . While this does imply an increase in memory, we believe that tradeoff is worth it and hope to find ways to reduce that memory overhead. Things should feel a lot snappier now.

For more information, see the change here .

Monomorphized Control Flow Nodes

In TypeScript 5.5, nodes of the control flow graph have been monomorphized so that they always hold a consistent shape. By doing so, check times will often be reduced by about 1%.

See this change here .

Optimizations on Control Flow Graph

In many cases, control flow analysis will traverse nodes that don’t provide any new information. We observed that in the absence of any early termination or effects in the antecedents (or "dominators") of certain nodes meant that those nodes could always be skipped over. As such, TypeScript now constructs its control flow graphs to take advantage of this by linking to an earlier node that does provide interesting information for control flow analysis. This yields a flatter control flow graph, which can be more efficient to traverse. This optimization has yielded modest gains, but with up to 2% reductions in build time on certain codebases.

You can read more here .

TypeScript Package Size Reduction

Further leveraging our transition to modules in 5.0 , we’ve significantly reduced TypeScript’s overall package size by making tsserver.js and typingsInstaller.js import from a common API library instead of having each of them produce standalone bundles .

This reduces TypeScript’s size on disk from 30.2 MB to 20.4 MB, and reduces its packed size from 5.5 MB to 3.7 MB!

Node Reuse in Declaration Emit

As part of the work to enable isolatedDeclarations , we’ve substantially improved how often TypeScript can directly copy your input source code when producing declaration files.

For example, let’s say you wrote

Note that the union types are equivalent, but the order of the union is different. When emitting the declaration file, TypeScript has two equivalent output possibilities.

The first is to use a consistent canonical representation for each type:

The second is to re-use the type annotations exactly as written:

The second approach is generally preferable for a few reasons:

  • Many equivalent representations still encode some level of intent that is better to preserve in the declaration file
  • Producing a fresh representation of a type can be somewhat expensive, so avoiding is better
  • User-written types are usually shorter than generated type representations

In 5.5, we’ve greatly improved the number of places where TypeScript can correctly identify places where it’s safe and correct to print back types exactly as they were written in the input file. Many of these cases are invisible performance improvements – TypeScript would generate fresh sets of syntax nodes and serialize them into a string. Instead, TypeScript can now operate over the original syntax nodes directly, which is much cheaper and faster.

Previously, if you were writing an ECMAScript module in Node.js, named imports were not available from the typescript package.

This is because cjs-module-lexer did not recognize the pattern of TypeScript’s generated CommonJS code. This has been fixed, and users can now use named imports from the TypeScript npm package with ECMAScript modules in Node.js.

TypeScript’s API exposes a function called transpileModule . It’s intended to make it easy to compile a single file of TypeScript code. Because it doesn’t have access to an entire program , the caveat is that it may not produce the right output if the code violates any errors under the isolatedModules option.

In TypeScript 5.5, we’ve added a new similar API called transpileDeclaration . This API is similar to transpileModule , but it’s specifically designed to generate a single declaration file based on some input source text. Just like transpileModule , it doesn’t have access to a full program, and a similar caveat applies: it only generates an accurate declaration file if the input code is free of errors under the new isolatedDeclarations option.

If desired, this function can be used to parallelize declaration emit across all files under isolatedDeclarations mode. Note that while you might experience some of the performance overhead of transpileModule in transpileDeclaration , we’re working on ways to optimize this further .

For more information, see the implementation here .

Notable Behavioral Changes

This section highlights a set of noteworthy changes that should be acknowledged and understood as part of any upgrade. Sometimes it will highlight deprecations, removals, and new restrictions. It can also contain bug fixes that are functionally improvements, but which can also affect an existing build by introducing new errors.

TypeScript 5.0 deprecated the following options and behaviors:

  • target: ES3
  • importsNotUsedAsValues
  • noImplicitUseStrict
  • noStrictGenericChecks
  • keyofStringsOnly
  • suppressExcessPropertyErrors
  • suppressImplicitAnyIndexErrors
  • preserveValueImports
  • prepend in project references
  • implicitly OS-specific newLine

To continue using the deprecated options above, developers using TypeScript 5.0 and other more recent versions have had to specify a new option called ignoreDeprecations with the value "5.0" .

In TypeScript 5.5, these options no longer have any effect. To help with a smooth upgrade path, you may still specify them in your tsconfig, but these will be an error to specify in TypeScript 6.0. See also the Flag Deprecation Plan which outlines our deprecation strategy.

More information around these deprecation plans is available on GitHub , which contains suggestions in how to best adapt your codebase.

Types generated for the DOM may have an impact on type-checking your codebase. For more information, see the DOM updates for TypeScript 5.5 .

Before Node.js implemented support for ECMAScript modules in v12, there was never a good way for TypeScript to know whether .d.ts files it found in node_modules represented JavaScript files authored as CommonJS or ECMAScript modules. When the vast majority of npm was CommonJS-only, this didn’t cause many problems – if in doubt, TypeScript could just assume that everything behaved like CommonJS. Unfortunately, if that assumption was wrong it could allow unsafe imports:

In practice, this didn’t come up very often. But in the years since Node.js started supporting ECMAScript modules, the share of ESM on npm has grown. Fortunately, Node.js also introduced a mechanism that can help TypeScript determine if a file is an ECMAScript module or a CommonJS module: the .mjs and .cjs file extensions and the package.json "type" field. TypeScript 4.7 added support for understanding these indicators, as well as authoring .mts and .cts files; however, TypeScript would only read those indicators under --module node16 and --module nodenext , so the unsafe import above was still a problem for anyone using --module esnext and --moduleResolution bundler , for example.

To solve this, TypeScript 5.5 reads and stores module format information encoded by file extensions and package.json "type" in all module modes, and uses it to resolve ambiguities like the one in the example above in all modes (except for amd , umd , and system ).

A secondary effect of respecting this format information is that the format-specific TypeScript file extensions ( .mts and .cts ) or an explicitly set package.json "type" in your own project will override your --module option if it’s set to commonjs or es2015 through esnext . Previously, it was technically possible to produce CommonJS output into a .mjs file or vice versa:

Now, .mts files (or .ts files in scope of a package.json with "type": "module" ) never emit CommonJS output, and .cts files (or .ts files in scope of a package.json with "type": "commonjs" ) never emit ESM output.

More details are available on the change here .

Since TypeScript originally introduced support for decorators, the specified grammar for the proposal has been tightened up. TypeScript is now stricter about what forms it allows. While rare, existing decorators may need to be parenthesized to avoid errors.

See more information on the change here .

TypeScript has always disallowed type alias names that conflict with built-in types:

Due to a bug, this logic didn’t also apply to the built-in type undefined . In 5.5, this is now correctly identified as an error:

Bare references to type aliases named undefined never actually worked in the first place. You could define them, but you couldn’t use them as an unqualified type name.

When producing a declaration file, TypeScript would synthesize a reference directive when it believed one was required. For example, all Node.js modules are declared ambiently, so cannot be loaded by module resolution alone. A file like:

Would emit a declaration file like:

Even though the reference directive never appeared in the original source.

Similarly, TypeScript also removed reference directives that it did not believe needed to be a part of the output. For example, let’s imagine we had a reference directive to jest ; however, imagine the reference directive isn’t necessary to generate the declaration file. TypeScript would simply drop it. So in the following example:

TypeScript would still emit:

In the course of working on isolatedDeclarations , we realized that this logic was untenable for anyone attempting to implement a declaration emitter without type checking or using more than a single file’s context. This behavior is also hard to understand from a user’s perspective; whether or not a reference directive appeared in the emitted file seems inconsistent and difficult to predict unless you understand exactly what’s going on during typechecking. To prevent declaration emit from being different when isolatedDeclarations was enabled, we knew that our emit needed to change.

Through experimentation , we found that nearly all cases where TypeScript synthesized reference directives were just to pull in node or react . These are cases where the expectation is that a downstream user already references those types through tsconfig.json "types" or library imports, so no longer synthesizing these reference directives would be unlikely to break anyone. It’s worth noting that this is already how it works for lib.d.ts ; TypeScript doesn’t synthesize a reference to lib="es2015" when a module exports a WeakMap , instead assuming that a downstream user will have included that as part of their environment.

For reference directives that had been written by library authors (not synthesized), further experimentation showed that nearly all were removed, never showing up in the output. Most reference directives that were preserved were broken and likely not intended to be preserved.

Given those results, we decided to greatly simplfy reference directives in declaration emit in TypeScript 5.5. A more consistent strategy will help library authors and consumers have better control of their declaration files.

Reference directives are no longer synthesized. User-written reference directives are no longer preserved, unless annotated with a new preserve="true" attribute. Concretely, an input file like:

Adding preserve="true" is backwards compatible with older versions of TypeScript as unknown attributes are ignored.

This change also improved performance; in our benchmarks, the emit stage saw a 1-4% improvement in projects with declaration emit enabled.

What’s Next?

At this point, TypeScript 5.5 is what we’d call "feature-stable". The focus on TypeScript 5.5 will be bug fixes, polish, and certain low-risk editor features. We’ll have a release candidate available in a bit over month, followed by a stable release soon after. If you’re interested in planning around the release, be sure to keep an eye on our iteration plan which has target release dates and more.

As a note: while beta is a great way to try out the next version of TypeScript, you can also try a nightly build to get the most up-to-date version of TypeScript 5.5 up until our release candidate. Our nightlies are well-tested and can even be tested solely in your editor .

So please try out the beta or a nightly release today and let us know what you think!

Happy Hacking!

– Daniel Rosenwasser and the TypeScript Team

typescript in assignment

Daniel Rosenwasser Senior Program Manager, TypeScript

typescript in assignment

Leave a comment Cancel reply

Log in to join the discussion or edit/delete existing comments.

There is a typo two vars have the same name

Great to see inference of type predicate making its way into the compiler!

That being said, it would have been even better if the inferred type predicate could have been checked against any user specified type predicate in the function type, i.e. if the following example would have returned a compilation error:

light-theme-icon

Insert/edit link

Enter the destination URL

Or link to existing content

This page has been deprecated

This handbook page has been replaced, go to the new page

Advanced Types

This page lists some of the more advanced ways in which you can model types, it works in tandem with the Utility Types doc which includes types which are included in TypeScript and available globally.

Type Guards and Differentiating Types

Union types are useful for modeling situations when values can overlap in the types they can take on. What happens when we need to know specifically whether we have a Fish ? A common idiom in JavaScript to differentiate between two possible values is to check for the presence of a member. As we mentioned, you can only access members that are guaranteed to be in all the constituents of a union type.

To get the same code working via property accessors, we’ll need to use a type assertion:

This isn’t the sort of code you would want in your codebase however.

User-Defined Type Guards

It would be much better if once we performed the check, we could know the type of pet within each branch.

It just so happens that TypeScript has something called a type guard . A type guard is some expression that performs a runtime check that guarantees the type in some scope.

Using type predicates

To define a type guard, we simply need to define a function whose return type is a type predicate :

pet is Fish is our type predicate in this example. A predicate takes the form parameterName is Type , where parameterName must be the name of a parameter from the current function signature.

Any time isFish is called with some variable, TypeScript will narrow that variable to that specific type if the original type is compatible.

Notice that TypeScript not only knows that pet is a Fish in the if branch; it also knows that in the else branch, you don’t have a Fish , so you must have a Bird .

You may use the type guard isFish to filter an array of Fish | Bird and obtain an array of Fish :

Using the in operator

The in operator also acts as a narrowing expression for types.

For a n in x expression, where n is a string literal or string literal type and x is a union type, the “true” branch narrows to types which have an optional or required property n , and the “false” branch narrows to types which have an optional or missing property n .

typeof type guards

Let’s go back and write the code for a version of padLeft which uses union types. We could write it with type predicates as follows:

However, having to define a function to figure out if a type is a primitive is kind of a pain. Luckily, you don’t need to abstract typeof x === "number" into its own function because TypeScript will recognize it as a type guard on its own. That means we could just write these checks inline.

These typeof type guards are recognized in two different forms: typeof v === "typename" and typeof v !== "typename" , where "typename" can be one of typeof operator’s return values ( "undefined" , "number" , "string" , "boolean" , "bigint" , "symbol" , "object" , or "function" ). While TypeScript won’t stop you from comparing to other strings, the language won’t recognize those expressions as type guards.

instanceof type guards

If you’ve read about typeof type guards and are familiar with the instanceof operator in JavaScript, you probably have some idea of what this section is about.

instanceof type guards are a way of narrowing types using their constructor function. For instance, let’s borrow our industrial strength string-padder example from earlier:

The right side of the instanceof needs to be a constructor function, and TypeScript will narrow down to:

  • the type of the function’s prototype property if its type is not any
  • the union of types returned by that type’s construct signatures

in that order.

Nullable types

TypeScript has two special types, null and undefined , that have the values null and undefined respectively. We mentioned these briefly in the Basic Types section .

By default, the type checker considers null and undefined assignable to anything. Effectively, null and undefined are valid values of every type. That means it’s not possible to stop them from being assigned to any type, even when you would like to prevent it. The inventor of null , Tony Hoare, calls this his “billion dollar mistake” .

The strictNullChecks flag fixes this: when you declare a variable, it doesn’t automatically include null or undefined . You can include them explicitly using a union type:

Note that TypeScript treats null and undefined differently in order to match JavaScript semantics. string | null is a different type than string | undefined and string | undefined | null .

From TypeScript 3.7 and onwards, you can use optional chaining to simplify working with nullable types.

Optional parameters and properties

With strictNullChecks , an optional parameter automatically adds | undefined :

The same is true for optional properties:

Type guards and type assertions

Since nullable types are implemented with a union, you need to use a type guard to get rid of the null . Fortunately, this is the same code you’d write in JavaScript:

The null elimination is pretty obvious here, but you can use terser operators too:

In cases where the compiler can’t eliminate null or undefined , you can use the type assertion operator to manually remove them. The syntax is postfix ! : identifier! removes null and undefined from the type of identifier :

Type Aliases

Type aliases create a new name for a type. Type aliases are sometimes similar to interfaces, but can name primitives, unions, tuples, and any other types that you’d otherwise have to write by hand.

Aliasing doesn’t actually create a new type - it creates a new name to refer to that type. Aliasing a primitive is not terribly useful, though it can be used as a form of documentation.

Just like interfaces, type aliases can also be generic - we can just add type parameters and use them on the right side of the alias declaration:

We can also have a type alias refer to itself in a property:

Together with intersection types, we can make some pretty mind-bending types:

Interfaces vs. Type Aliases

As we mentioned, type aliases can act sort of like interfaces; however, there are some subtle differences.

Almost all features of an interface are available in type , the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

Because an interface more closely maps how JavaScript objects work by being open to extension , we recommend using an interface over a type alias when possible.

On the other hand, if you can’t express some shape with an interface and you need to use a union or tuple type, type aliases are usually the way to go.

Enum Member Types

As mentioned in our section on enums , enum members have types when every member is literal-initialized.

Much of the time when we talk about “singleton types”, we’re referring to both enum member types as well as numeric/string literal types, though many users will use “singleton types” and “literal types” interchangeably.

Polymorphic this types

A polymorphic this type represents a type that is the subtype of the containing class or interface. This is called F -bounded polymorphism, a lot of people know it as the fluent API pattern. This makes hierarchical fluent interfaces much easier to express, for example. Take a simple calculator that returns this after each operation:

Since the class uses this types, you can extend it and the new class can use the old methods with no changes.

Without this types, ScientificCalculator would not have been able to extend BasicCalculator and keep the fluent interface. multiply would have returned BasicCalculator , which doesn’t have the sin method. However, with this types, multiply returns this , which is ScientificCalculator here.

Index types

With index types, you can get the compiler to check code that uses dynamic property names. For example, a common JavaScript pattern is to pick a subset of properties from an object:

Here’s how you would write and use this function in TypeScript, using the index type query and indexed access operators:

The compiler checks that manufacturer and model are actually properties on Car . The example introduces a couple of new type operators. First is keyof T , the index type query operator . For any type T , keyof T is the union of known, public property names of T . For example:

keyof Car is completely interchangeable with "manufacturer" | "model" | "year" . The difference is that if you add another property to Car , say ownersAddress: string , then keyof Car will automatically update to be "manufacturer" | "model" | "year" | "ownersAddress" . And you can use keyof in generic contexts like pluck , where you can’t possibly know the property names ahead of time. That means the compiler will check that you pass the right set of property names to pluck :

The second operator is T[K] , the indexed access operator . Here, the type syntax reflects the expression syntax. That means that taxi["manufacturer"] has the type Car["manufacturer"] — which in our example is just string . However, just like index type queries, you can use T[K] in a generic context, which is where its real power comes to life. You just have to make sure that the type variable K extends keyof T . Here’s another example with a function named getProperty .

In getProperty , o: T and propertyName: K , so that means o[propertyName]: T[K] . Once you return the T[K] result, the compiler will instantiate the actual type of the key, so the return type of getProperty will vary according to which property you request.

Index types and index signatures

keyof and T[K] interact with index signatures. An index signature parameter type must be ‘string’ or ‘number’. If you have a type with a string index signature, keyof T will be string | number (and not just string , since in JavaScript you can access an object property either by using strings ( object["42"] ) or numbers ( object[42] )). And T[string] is just the type of the index signature:

If you have a type with a number index signature, keyof T will just be number .

Mapped types

A common task is to take an existing type and make each of its properties optional:

Or we might want a readonly version:

This happens often enough in JavaScript that TypeScript provides a way to create new types based on old types — mapped types . In a mapped type, the new type transforms each property in the old type in the same way. For example, you can make all properties optional or of a type readonly . Here are a couple of examples:

And to use it:

Note that this syntax describes a type rather than a member. If you want to add members, you can use an intersection type:

Let’s take a look at the simplest mapped type and its parts:

The syntax resembles the syntax for index signatures with a for .. in inside. There are three parts:

  • The type variable K , which gets bound to each property in turn.
  • The string literal union Keys , which contains the names of properties to iterate over.
  • The resulting type of the property.

In this simple example, Keys is a hard-coded list of property names and the property type is always boolean , so this mapped type is equivalent to writing:

Real applications, however, look like Readonly or Partial above. They’re based on some existing type, and they transform the properties in some way. That’s where keyof and indexed access types come in:

But it’s more useful to have a general version.

In these examples, the properties list is keyof T and the resulting type is some variant of T[P] . This is a good template for any general use of mapped types. That’s because this kind of transformation is homomorphic , which means that the mapping applies only to properties of T and no others. The compiler knows that it can copy all the existing property modifiers before adding any new ones. For example, if Person.name was readonly, Partial<Person>.name would be readonly and optional.

Here’s one more example, in which T[P] is wrapped in a Proxy<T> class:

Note that Readonly<T> and Partial<T> are so useful, they are included in TypeScript’s standard library along with Pick and Record :

Readonly , Partial and Pick are homomorphic whereas Record is not. One clue that Record is not homomorphic is that it doesn’t take an input type to copy properties from:

Non-homomorphic types are essentially creating new properties, so they can’t copy property modifiers from anywhere.

Note that keyof any represents the type of any value that can be used as an index to an object. In otherwords, keyof any is currently equal to string | number | symbol .

Inference from mapped types

Now that you know how to wrap the properties of a type, the next thing you’ll want to do is unwrap them. Fortunately, that’s pretty easy:

Note that this unwrapping inference only works on homomorphic mapped types. If the mapped type is not homomorphic you’ll have to give an explicit type parameter to your unwrapping function.

Conditional Types

A conditional type selects one of two possible types based on a condition expressed as a type relationship test:

The type above means when T is assignable to U the type is X , otherwise the type is Y .

A conditional type T extends U ? X : Y is either resolved to X or Y , or deferred because the condition depends on one or more type variables. When T or U contains type variables, whether to resolve to X or Y , or to defer, is determined by whether or not the type system has enough information to conclude that T is always assignable to U .

As an example of some types that are immediately resolved, we can take a look at the following example:

Another example would be the TypeName type alias, which uses nested conditional types:

But as an example of a place where conditional types are deferred - where they stick around instead of picking a branch - would be in the following:

In the above, the variable a has a conditional type that hasn’t yet chosen a branch. When another piece of code ends up calling foo , it will substitute in U with some other type, and TypeScript will re-evaluate the conditional type, deciding whether it can actually pick a branch.

In the meantime, we can assign a conditional type to any other target type as long as each branch of the conditional is assignable to that target. So in our example above we were able to assign U extends Foo ? string : number to string | number since no matter what the conditional evaluates to, it’s known to be either string or number .

Distributive conditional types

Conditional types in which the checked type is a naked type parameter are called distributive conditional types . Distributive conditional types are automatically distributed over union types during instantiation. For example, an instantiation of T extends U ? X : Y with the type argument A | B | C for T is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y) .

In instantiations of a distributive conditional type T extends U ? X : Y , references to T within the conditional type are resolved to individual constituents of the union type (i.e. T refers to the individual constituents after the conditional type is distributed over the union type). Furthermore, references to T within X have an additional type parameter constraint U (i.e. T is considered assignable to U within X ).

Notice that T has the additional constraint any[] within the true branch of Boxed<T> and it is therefore possible to refer to the element type of the array as T[number] . Also, notice how the conditional type is distributed over the union type in the last example.

The distributive property of conditional types can conveniently be used to filter union types:

Conditional types are particularly useful when combined with mapped types:

Note, conditional types are not permitted to reference themselves recursively. For example the following is an error.

Type inference in conditional types

Within the extends clause of a conditional type, it is now possible to have infer declarations that introduce a type variable to be inferred. Such inferred type variables may be referenced in the true branch of the conditional type. It is possible to have multiple infer locations for the same type variable.

For example, the following extracts the return type of a function type:

Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:

The following example demonstrates how multiple candidates for the same type variable in co-variant positions causes a union type to be inferred:

Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:

When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types.

It is not possible to use infer declarations in constraint clauses for regular type parameters:

However, much the same effect can be obtained by erasing the type variables in the constraint and instead specifying a conditional type:

Predefined conditional types

TypeScript adds several predefined conditional types, you can find the full list and examples in Utility Types .

Nightly Builds

How to use a nightly build of TypeScript

The TypeScript docs are an open source project. Help us improve these pages by sending a Pull Request ❤

Daniel Rosenwasser  (65)

Last updated: Apr 29, 2024  

typescript in assignment

IMAGES

  1. A beginner’s guide to TypeScript (with some history of the TypeScript

    typescript in assignment

  2. TypeScript Function Types: A Beginner's Guide

    typescript in assignment

  3. TypeScript Tutorial

    typescript in assignment

  4. TypeScript Tutorial #1

    typescript in assignment

  5. TypeScript Array of Objects: Syntax, Examples & Rules

    typescript in assignment

  6. Typescript Assignment-4-Declaring a variable and typing a simple quote

    typescript in assignment

VIDEO

  1. Typescript Type System explained by Anders Hejlsberg

  2. Assignment Operators in Typescript

  3. Typescript Assignment Q no 11 to 15

  4. How to use new 'using' keyword in typescript/javascript?

  5. Typescript

  6. 24. Creating classes with constructors in Typescript. How class is compiled by Typescript compiler

COMMENTS

  1. TypeScript: Playground Example

    Logical Operators and Assignment are new features in JavaScript for 2020. These are a suite of new operators which edit a JavaScript object. ... How TypeScript infers types based on runtime behavior. Variable Declarations. How to create and type JavaScript variables. TypeScript in 5 minutes. An overview of building a TypeScript web app ...

  2. What is the difference Between '!:' and '?:' in TypeScript object

    They are well hidden in the documentation of TypeScript.? is described on interfaces, it marks an optional property.! is the definite assertion operator. It tells the compiler that the property is set (not null or undefined) even if TypeScript's analyses cannot detect so.

  3. Assignment Operators in TypeScript

    TypeScript Tutorial. An assignment operator requires two operands. The value of the right operand is assigned to the left operand. The sign = denotes the simple assignment operator. The typescript also has several compound assignment operators, which is actually shorthand for other operators. List of all such operators are listed below.

  4. TypeScript Operators

    TypeScript operators are symbols or keywords that perform operations on one or more operands. In this article, we are going to learn various types of TypeScript Operators. ... TypeScript Assignment operators. In TypeScript, assignment operators are used to assign values to variables and modify their values based on arithmetic or bitwise ...

  5. How to dynamically assign properties to an object in TypeScript

    TypeScript, on the other hand, is a statically typed version of JavaScript — unlike JavaScript, where a variable can change types randomly, TypeScript defines the type of a variable at its declaration or initialization. Dynamic property assignment is the ability to add properties to an object only when they are needed.

  6. Type assignment in typescript

    Type Assignment in TypeScript TypeScript is a statically typed superset of JavaScript that adds optional type annotations to the language. This allows developers to catch errors and bugs at compile-time rather than runtime. One of the key features of TypeScript is its ability to assign types to variables, functions, and objects. In this article, we […]

  7. TypeScript: Mastering Type Assignment and Type Inference

    Type assignment in TypeScript is straightforward: it allows developers to explicitly specify the type of a variable, function parameter, or return value. This explicit typing helps catch type-related errors during compilation, long before the code is executed. Syntax and Usage. Type assignment is done using a colon (:) followed by the type.

  8. Typescript 4 enhances the compound assignment operators

    Hi there, Tom here! As of writing, Typescript 4's stable release is imminent (August 2020) and with it are some very nice changes to come. Typescript 4 ahead. One of those is the addition of logical assignment operators, which allow to use the logical operators with an assignment. As a reminder, here are the logical operators being used for the ...

  9. TypeScript Tutorial

    This video is about Assignment Operators which are generally used in TypeScript.🔴TypeScript Tutorial : Beginner to Advanced🔴https://www.youtube.com/watch?v...

  10. Definite Assignment Assertions (!)

    Definite Assignment Assertions (!) The Definite Assignment Assertions or also called non-null assertion operator tells the TypeScript compiler that a value typed cannot be null or undefined which is a way to override the compiler's analysis and inform it that a variable will be assigned a value before it is used. type Person = {. name: string; };

  11. Best practice conditional variable assignment in typescript

    A very easy typescript question, which I need assistance with. Without using if statements, how would I conditional assign something to a constant variable. const myConstant = myProduct.productId; I want it so that if the productId in myProduct is empty (""), it will assign the variable of myConstant to "No Product", I wish to do this without ...

  12. TypeScript Tutorial

    Example Get your own TypeScript Server. console.log('Hello World!'); Try it Yourself ». Click on the "Try it Yourself" button to see how it works. We recommend reading this tutorial in the sequence listed in the left menu.

  13. TypeScript ESLint: Unsafe assignment of an any value [Fix]

    Our function basically checks if the passed-in value is compatible with an object of type Employee.. Notice that in the if block in which we called the isAnEmployee() function, the parsed variable is typed as Employee and we can access the id and name properties without getting TypeScript or ESLint errors.. I've written a detailed guide on how to check if a value is an object.

  14. TypeScript 5.5 moves to beta

    TypeScript 5.5 also adds a transpileDeclaration API, which is designed to generate a single declaration file based on input source text. The API is similar to transpileModule for compiling a ...

  15. Specify type of variable after initial assignment in TypeScript

    Specify type of variable after initial assignment in TypeScript. Ask Question Asked 6 years, 11 months ago. Modified 6 years, 11 months ago. Viewed 2k times 1 I have an interface Base, which interfaces A and B extend, and A and B both have properties that are unique and don't exist in the other. In my code, I have some data that will change ...

  16. Announcing TypeScript 5.5 Beta

    TypeScript 4.7 added support for understanding these indicators, as well as authoring .mts and .cts files; however, TypeScript would only read those indicators under --module node16 and --module nodenext, so the unsafe import above was still a problem for anyone using --module esnext and --moduleResolution bundler, for example.

  17. TypeScript: Documentation

    TypeScript has two special types, null and undefined, that have the values null and undefined respectively. We mentioned these briefly in the Basic Types section. By default, the type checker considers null and undefined assignable to anything. Effectively, null and undefined are valid values of every type.

  18. Why are logical assignment (&=) operators disallowed in Typescript

    Your variable is a boolean and these values are combined using && (logical AND). In TypeScript you could conceivably create an &&= operator but the && operator uses short-circuit evaluation where evaluation stops as soon the result is known which means that the semantic of x &&= y becomes a bit clouded. edited Jul 31, 2017 at 14:51.

  19. Extending Microsoft 365's Copilot: JavaScript and TypeScript

    With tools like JavaScript and TypeScript at their disposal, developers have robust avenues to enhance Copilot's capabilities, making it a more powerful tool in Microsoft's productivity suite. Azure Developers JavaScript Day Cloud Skills Challenge .

  20. javascript

    Stack Overflow Public questions & answers; Stack Overflow for Teams Where developers & technologists share private knowledge with coworkers; Talent Build your employer brand ; Advertising Reach developers & technologists worldwide; Labs The future of collective knowledge sharing; About the company