Skip to content

Typescript Core Features

Objects

Caveat for Excess Property Checking

Checks if an object literal contains properties that are not present in the target type. If a property does not exist in the target type, TypeScript will raise an error.

Excess property checks only trigger for object literals being created in locations that are declared to be an object type. Providing an existing object literal bypasses excess property checks. This is because TypeScript assumes that the object has already been created and any excess properties are intentional.

Optional Properties

Optional properties using ? can either exist or not exist in the object.

There is a difference between optional properties and properties whose type happens to include undefined in a type union.

A property declared as optional with ? is allowed to not exist. A property declared as required and | undefined must exist, even if the value is undefined.

Inferred Object-Type Unions

If a variable is given an initial value that could be one of multiple object types, TypeScript will infer its type to be a union of object types. That union type will have a constituent for each of the possible object shapes. Each of the possible properties on the type will be present in each of those constituents, though they’ll be ? optional types on any type that doesn’t have an initial value for them.

This poem value always has a name property of type string, and may or may not have pages and rhymes properties:

Explicit Object-Type Unions

Most notably, if a value’s type is a union of object types, TypeScript’s type system will only allow access to properties that exist on all of those union types.

Accessing names is allowed because it always exists, but pages and rhymes aren’t guaranteed to exist.

Narrowing Object Types

TypeScript’s type narrowing will apply to objects if you check their shape in code.

Note that TypeScript won’t allow truthiness existence checks like if (poem.pages). Attempting to access a property of an object that might not exist is considered a type error, even if used in a way that seems to behave like a type guard:

Discriminated Unions

Another popular form of union typed objects in JavaScript and TypeScript is to have a property on the object indicate what shape the object is. This kind of type shape is called a discriminated union, and the property whose value indicates the object’s type is a discriminant. TypeScript is able to perform type narrowing for code that type guards on discriminant properties.

Intersection Types

Intersection types are a way to combine multiple types into one type using the & operator. This creates a new type that has all the properties and methods of each individual type.

We can combine Intersection types with union types.

There are dangers of using intersection types:

  1. Long assignability errors: Intersection types can make the resulting type more complex and difficult to read, especially if the types being intersected are already complex.
  2. Conflicting types: If two types being intersected have conflicting properties, it can lead to unexpected behavior. For example, if one type has a property that's optional and another has the same property as required, the resulting type will have the property as required, which may not be what you intended. type NotPossible = number & string;

The never keyword and type is what programming languages refer to as a bottom type, or empty type. A bottom type is one that can have no possible values and can’t be reached. No types can be provided to a location whose type is a bottom type.

Functions

Optional Parameters

Optional parameters are not the same as parameters with union types that happen to include | undefined. Parameters that aren’t marked as optional with a ? must always be provided, even if the value is explicitly undefined.

Any optional parameters for a function must be the last parameters. Placing an optional parameter before a required parameter would trigger a TypeScript syntax error:

Rest Parameters

The ... spread operator may be placed on the last parameter in a function declaration to indicate any “rest” arguments passed to the function starting at that parameter should all be stored in a single array. We can use [] syntax to indicate it's an array of arguments.

Void returns

The void keyword is used to declare the return type of a function that doesn't return anything. This is useful for functions that only perform some action, like logging, and don't need to return a value indicating that any returned value from the function would be ignored.

It's important to note that void is not the same as undefined. void means that the return type of a function will be ignored, while undefined is a literal value that can be returned. Trying to assign a value of type void to a value whose type includes undefined will result in a type error.

For example built-in forEach method on arrays takes in a callback that returns void. Functions provided to forEach can return any value they want. Return value will be ignored.

Never returns

Some functions not only don’t return a value, but aren’t meant to return at all. Never-returning functions are those that always throw an error or run an infinite loop.

If a function is meant to never return, adding an explicit : never type annotation indicates that any code after a call to that function won’t run.

In below example, the convertNumberToString function takes in a number and returns a string.

However, if the input number is negative, the function throws an error and never returns a string value. To indicate this in the function's signature, we can use the never type like this:

By adding | never to the return type, we're indicating that the function will never actually return a string value if the input number is negative.


never is not the same as void. void is for a function that returns nothing. never is for a function that never returns.


Arrays

Array Inference

The following firstCharAndSize function is inferred as returning (string | number)[], not [string, number], because that’s the type inferred for its returned array literal. It assumes a flexible size array rather than a fixed size tuple.

Explicit tuple type

Tuple types may be used in type annotations. If the function is declared as returning a tuple type and returns an array literal, that array literal will be inferred to be a tuple instead of a more general variable-length array

Const asserted tuples

TypeScript provides an as const operator known as a const assertion that can be placed after a value. Const assertions tell TypeScript to use the most literal, read-only possible form of the value when inferring its type.

If one is placed after an array literal, it will indicate that the array should be treated as a tuple:

Note that as const assertions go beyond switching from flexible sized arrays to fixed size tuples: they also indicate to TypeScript that the tuple is read-only and cannot be used in a place that expects it should be allowed to modify the value.

In practice, read-only tuples are convenient for function returns. Returned values from functions that return a tuple are often destructured immediately anyway, so the tuple being read-only does not get in the way of using the function.

Interfaces

Interfaces are another way to declare an object shape with an associated name. Interfaces are in many ways similar to aliased object types but are generally preferred for their more readable error messages, speedier compiler performance, and better interoperability with classes.

Key differences between interfaces and type aliases:

  1. Declaration merging: Interfaces can "merge" together, which allows you to combine multiple interface declarations into a single definition.

  2. Type checking class declarations: Interfaces can be used to type check the structure of class declarations while type aliases cannot.

  3. Speed: Interfaces are generally speedier for the TypeScript type checker to work with. They declare a named type that can be cached more easily internally, rather than a dynamic copy-and-paste of a new object literal the way type aliases do.

  4. Readablity of errors: Because interfaces are considered named objects rather than an alias for an unnamed object literal, their error messages are more likely to be readable i n hard edge cases.

Read-Only Properties

TypeScript allows you to add a readonly modifier before a property name to indicate that once set, that property should not be set to a different value. These readonly properties can be read from normally, but not reassigned to anything new.

Noote that they’re a type system construct only and don’t exist in the compiled JavaScript output code. They only protect from modification during development with the TypeScript type checker.

Functions and Methods

TypeScript provides two ways of declaring interface members as functions:

  • Method syntax: declaring that a member of the interface is a function intended to be called as a member of the object, like member(): void.
  • Property syntax: declaring that a member of the interface is equal to a standalone function, like member: () => void

The two declaration forms are an analog for the two ways you can declare a JavaScript object as having a function. There are some differences but let's not get into that it’ll rarely impact your code.

Call Signatures

Interfaces and object types can declare call signatures, which is a type system description of how a value may be called like a function.

Interfaces can be used to define not only the shape of objects but also the shape of function types, including call signatures. Call signatures are used to describe the parameters and return type of a function type.

Index signatures

Index signatures allow you to define the types of properties that are not known ahead of time.

In JavaScript, you can use bracket notation to access properties of an object. For example, you can use myObj["myProp"] to access the value of a property named myProp on an object named myObj.

In TypeScript, you can use index signatures to define the type of these unknown properties. An index signature has the following syntax: [propertyName: type]: valueType

Interface Extensions

TypeScript allows an interface to extend another interface, which declares it as copying all the members of another. This means that the new interface will inherit all the properties and methods of the base interface, and you can add new properties or methods to it.

Overridden Properties

Derived interfaces may override, or replace, properties from their base interface by declaring the property again with a different type. This is useful when you want to customize or extend the behavior of an existing interface.

Interface Merging

One of the important features of interfaces is their ability to merge with each other. Interface merging means if two interfaces are declared in the same scope with the same name, they’ll join into one bigger interface under that name with all declared fields.

Interface merging isn’t a feature used very often in day-to-day TypeScript development. I would recommend avoiding it when possible, as it can be difficult to understand code where an interface is declared in multiple places.

However, interface merging is particularly useful when working with third-party libraries, as it enables developers to extend the functionality of existing interfaces without having to modify their source code. For example, when using the default TypeScript compiler options, declaring a Window interface in a file with a myEnvironmentVariable property makes a window.myEnvironmentVariable available:

Member Naming Conflicts

If there are member naming conflicts between the interfaces, TypeScript will raise an error.

Profile picture

I have a passion for all things web.