Typescript Core Features
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 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.
names is allowed because it always exists, but
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:
Intersection types are a way to combine multiple types into one type using the
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:
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.
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;
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.
Optional parameters are not the same as parameters with union types that happen to include
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:
... 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 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
void means that
the return type of a function will be ignored,
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.
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:
| 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 is for a function that returns nothing.
never is for a function that never returns.
The following firstCharAndSize function is inferred as returning
(string | number),
[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 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:
Declaration merging: Interfaces can "merge" together, which allows you to combine multiple interface declarations into a single definition.
Type checking class declarations: Interfaces can be used to type check the structure of class declarations while type aliases cannot.
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.
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.
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.
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
- Property syntax: declaring that a member of the interface is equal to a
standalone function, like
member: () => void
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 allow you to define the types of properties that are not known ahead of time.
For example, you can use
myObj["myProp"] to access the value of a property
myProp on an object named
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
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.
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.
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.