TypeScript Glossary
Terminology helps us to describe a complex context and prevents us from confusion when talking about code. It plays an important role in understanding TypeScript and JavaScript.
As important as it is to know the control structures of a programming language, it is just as important to be able to name their contexts and surroundings. Using the right vocabulary is particularly effective in code reviews as it supports us to put our thoughts into words.
- Algorithms & Functions
- Node.js
- TypeScript
- Ambient Context
- Ambient Modules
- Array Destructuring
- Assertion Functions
- Assertion Signatures
- Boxed Types
- Built-in Types
- Collective Types
- Compiler
- Conditional Types
- Construct Signature
- Declaration Files
- Destructuring Assignment
- Discriminated Unions
- Downleveling
- Exports
- Function Argument
- Function Overloading
- Function Signature
- Function
- Generators
- Generics
- Imports
- Interfaces
- Intersection Types
- Iterables
- Literal Types
- Lookup Types
- Mapped Types
- Module Augmentation
- Module
- Non-null Assertion Operator
- Non-primitive types
- Primitive Types
- String Literal Type
- Structural Typing
- Tagged template
- Template Literal Type
- Template Literal
- Top Types
- Transpiler
- Triple-Slash Directives
- Tuple Type
- Type Alias
- Type Annotation
- Type Argument Inference
- Type Argument
- Type Assertion
- Type Coercion
- Type Erasure
- Type Guards
- Type Predicates
- Type Inference
- Type Narrowing
- Type Variable
- Type Widening
- Union Types
- Weak Types
Algorithms & Functions
Algorithm
An algorithm is a set of instructions to solve specific problems or to perform a computation. In TypeScript algorithms can be implemented with functions.
Algorithm Characteristics
Algorithms have defining characteristics such as:
- Generality: The algorithm must apply to a set of defined inputs.
- Definiteness / Uniqueness: At any point in time, there is at most one possibility of continuation. Each instruction step is well-defined.
- Deterministic: Given a particular input, the algorithm will always produce the same output.
- Effectiveness: The algorithm terminates in a finite amount of time / steps. Every execution of the algorithm delivers a result.
- Finiteness: The algorithm is described in a finite amount of steps.
- Feasibility: It should be feasible to execute the algorithm with the available resources.
Declarative Programming
Declarative programming is a programming paradigm that defines the desired state of an application without explicitly listing statements that must be executed. In a nutshell, declarative programming defines an application's execution from finish to start.
Example:
- Change UI behaviour by defining how it should look like based on its props
Imperative Programming
Imperative programming is a programming paradigm that uses statements to change an application's state. In a nutshell, imperative programming defines a set of instructions from start to finish.
Example:
- Change UI behaviour by defining how to turn something off before turning it on
Function Declaration
A function declaration gets hoisted and is written in the following way:
Function Expression
A function expression is part of an assignment and does not get hoisted:
Arrow Function Expression
Compact form of writing a function expression which got introduced in ES6:
Referentially Opaque Expressions
An expression is referentially transparent when it cannot be replaced with its return value.
Example:
At the time of writing the execution of today()
returned '2021-09-22T12:45:25.657Z'
. This result will change over time, so we cannot replace const isoDate = today()
with const isoDate = '2021-09-22T12:45:25.657Z'
which makes this expression referentially opaque.
Referentially Transparent Expressions
An expression is referentially transparent when it can be replaced with its return value.
Example:
The sayHello
function always returns the same text, so we can safely replace our expression with const message = Hello!
which makes it referentially transparent.
Block Scope
To enforce block-scoping in JavaScript, the let
keyword can be used. It makes variables inaccessible from the outside of their blocks:
Function Scope
By default, JavaScript is function scoped which means that variables are accessible within a function:
Method vs. Function
Sometimes the term "method" is used as a synonym for a "function". In JavaScript and TypeScript there is a clear
distinction: When a function is a property of an object or class, then it is called a method. This regulation is made because
functions can have a different value for this
depending on their association with an object or class instance.
Function:
Method:
If you turn an arrow function expression into a method, you will notice that the value of this
switches to the global object:
Deterministic Functions
A deterministic function will always produce the same output given the same input. This makes the output of a deterministic function predictable as it does not rely on a dynamic state.
Identity Function
An identity function returns the identical value that was given to it:
Example:
Pure Functions
Pure functions are a subset of deterministic functions. A pure function always produces the same result given a particular input. In addition, it does not cause side effects by avoiding I/O operations like printing to the console or writing to the disk.
A pure function does not mutate its passed parameters and is referentially transparent.
Node.js
Core Modules
Core modules are defined within Node.js and can be identified by the node:
prefix.
Example:
TypeScript
Ambient Context
By default, the TypeScript compiler does not know in which runtime environment (for instance Node.js v16, Electron v16, Chrome v94) our code will be executed later. That's why we can help the compiler knowing that by defining an ambience / ambient context.
Example:
If you run your code in an environment where there is a "world" object that TypeScript does not know about, you can define that context using declare var
:
Ambient Modules
A package that contains declaration files using the declare module
syntax is called an ambient module. The main purpose of ambient modules is to provide typings for code that has been purely written in JavaScript, so that TypeScript can make use of it.
You can find a lot of ambient modules in the Definitely Typed repository which provides them under the @types namespace on the npm Registry. A very popularΒΉ example is the @types/node package which declares multiple modules like the buffer
module:
If you plan to use some plain JavaScript code for testing, but you don't want to invest the time to write type definitions for it, you can use a shorthand ambient module declaration:
Here is an example how you can patch the typings for a package called jsonpatch
. The patch exposes the InvalidPatch
and PatchApplyError
classes:
ΒΉ 83,086,518 weekly downloads from 2022-12-06 to 2022-12-12
Array Destructuring
Extracting values from an array into distinct variables is called array destructuring.
Example:
Assertion Functions
Assertion functions are similar to user-defined type guards that allow developers to express their assumptions about the type and shape of input data. These functions can be used to check the type and value of variables. TypeScript's compiler will use this information at design-time to give fast feedback how a type should be used. An assertion function will also be executed at runtime to throw an error if a type check is not passed.
Example:
Assertion Signatures
An assertion signature is a special syntax used to tell the compiler the expected outcome of an assertion. It is a constituent element of an assertion function.
Example:
Boxed Types
A primitive data type has no methods or properties. to enable the use of methods on primitives, a mechanism called auto boxing comes into play.
Auto boxing involves automatically converting primitive data types like number
, string
, and boolean
into their respective wrapper objects (Number
, String
, Boolean
) when an object-specific property or method is accessed on them. This allows primitives to temporarily behave like objects with access to additional functionality.
Here's an example to illustrate auto boxing:
The boxed type Number
can be also expressed as:
Built-in Types
TypeScript offers numerous built-in types that you can use in your TypeScript code without the need for importing them. One such example is Array
:
You can use the Array
constructor directly without the requirement to import Array
from a module. Most built-in types can be located in the "node_modules/typescript/lib" directory after installing TypeScript. The built-in type definitions are applied when adding libraries to your lib configuration in tsconfig.json
.
Built-in types can also be categorized in "built-in utility types", "built-in type guards", "built-in primitives", etc.
Collective Types
A collective type unites all literals of its kind. Collective types can be used to widen a type.
Example: The type number
is a collective type of all integers as it includes different integer values:
More collective types:
bigint
boolean
number
string
Compiler
Compilers transform high-level programming language code to low-level machine code or some low-level intermediate representation (e.g. Bytecode in Java).
Conditional Types
TypeScript's conditional types can be likened to JavaScript's conditional ternary operator. Depending on a condition followed by a question mark, the types evaluate to either a truthy or falsy condition.
Example:
Construct Signature
When a function in JavaScript is called with the new
keyword, the function will be used as a constructor. In order for this behavior to be reflected in TypeScript, you must provide a construct signature for your constructor function:
Example:
Declaration Files
Declaration files (also known as .d.ts
files) are used to provide type information for existing JavaScript code that doesn't have any type annotations.
When you import a JavaScript library, TypeScript may not be able to understand the types and functions defined in the JavaScript code. This can lead to issues with type safety and make it difficult to use the imported code in a TypeScript project.
Declaration files provide a way to address this issue by defining the types and functions for the JavaScript code so that TypeScript can understand them. They essentially tell TypeScript what the shape of the code is, without information about the concrete implementation.
Example:
Declaration vs. Definition
A declaration introduces an identifier (such as a variable or a function) to the compiler, while a definition provides the implementation or value for that identifier.
Example:
Destructuring Assignment
Destructuring assignments allow to extract data from arrays and objects into individual variables. It is a shorthand for creating a new variable for each value of an array or object.
Example:
Discriminated Unions
Discriminated Unions (or tagged union types) can narrow a type based on a shared property.
In the following example, the type of Dog
and Person
have a shared property called type
. Depending on the value of this property, TypeScript can narrow down the type from Dog | Person
to either Dog
or Person
:
Because this allows the type to be discriminated, this technique is referred to as discriminating unions. This concept also exists in F# (Discriminated Unions in F#).
If multiple properties are shared, it is recommended to create a base type from which your other types can inherit:
Downleveling
Downleveling in TypeScript refers to the process of transpiling source code to an older version of ECMAScript/JavaScript.
Please be aware that syntax (like ?.
) is downleveled but new APIs (like Array.toSorted
) are not polyfilled (source).
Exports
A module export is a mechanism used to make functions, classes, variables, or other entities available for use in other modules. Exporting allows you to encapsulate functionality into separate modules and make them reusable by importing them in other files or projects.
Function Argument
What you pass to a function is called an argument. What the function expects to receive is called a parameter.
-
Parameter: This is a variable used in a function or method definition. It's a placeholder for the actual value (argument) that will be passed into the function. Parameters are specified in the function signature.
-
Argument: This is the actual value that is passed to the function when it is called. It corresponds to the parameter of the function. Arguments are the specific data that is provided to the function during its invocation.
In summary, the parameter is what the function expects to receive, and the argument is the actual value that is passed to satisfy that expectation.
Function Overloading
With function overloading you can define multiple functions with the same name but with different input and output types.
It is a useful technique when there are a limited number of possible input types for a function and the return type depends on the input type. When calling an overloaded function, the TypeScript compiler will look for the function signature that best matches the input parameters and return the corresponding type. This saves the users of your function from having to specify the type argument and improves the Developer Experience (DX).
Example:
Video Tutorial:
Generics vs. Function Overloading
When the possible types are many and you don't know exactly what type your users put in, then Generis are better suited. Generics allow you to specify the type argument which can make it easier to work with functions that can handle a wide range of input types.
Nevertheless, there are situations in which function overloads are more suitable. For example, to make functions that use operators like +
reusable (learn how).
Function Signature
A function signature defines the shape or structure of a function in TypeScript. It specifies the parameter list, the return type, and optionally the this
type of a function. A function signature can be also expressed with a function type.
Example:
Function
A function consists of several building blocks that define its anatomy. Here's a breakdown of the different parts:
- Function Name: This is the identifier used to call the function.
- Function Signature: Defines the shape of the function, including parameter list,
this
context and return type. - Function Body: This is the block of code enclosed within curly braces (
{}
). It contains the instructions and statements that define the behavior of the function. The code within the function body is executed when the function is called.
Generators
Generators are functions that can return multiple successive values (called yields). Generator functions are declared using the "function" keyword followed by an asterisk (function*
). When a generator function is called, it returns an object which implements the iterable protocol and iterator protocol.
The iterator protocol defines a next
function that can be used to retrieve the latest value of the generator function.
The iterable protocol allows the objects to be looped over in a for...of
construct. To align with the iterable protocol, an object must define an iterator function in a property called Symbol.iterator
.
Example:
Generics
Generics allow you to create templates for your types.
Example:
By using Generics you can relate an input parameter to an output parameter. The following code sets the type variable (T
) to number
when calling the generic function combine
:
TypeScript supports type argument inference when passing the input values:
You can also specify multiple type variables:
Default type variables are supported as well:
It is also possible to enforce a certain structure on your generic types using the extends
keyword:
Generics also work in classes:
Imports
A module import is a mechanism used to bring functionality from one TypeScript code into another TypeScript code. This feature allows you to organize your code into smaller, manageable pieces and facilitates code reuse and maintenance.
For clarity, we will use the term "provider" to refer to someone exporting functionality (such as classes, functions, variables, etc.), and the term "consumer" for someone importing functionality.
When code is being exported, it can be imported in the following ways.
Named Imports
A named import refers to the concept of importing functionality that was given a specific name by the provier. In such cases you have to use the provided name when importing code.
Example:
Default Imports
When you write a default import, you can use any name after "import" and still get the same default export:
Import Assertions
Import assertions in TypeScript allow to specify additional metadata about the modules being imported.
Example:
Note: The import assertion syntax and its assert clause (assert { ... }
) have been introduced in TypeScript 4.5 and got deprecated with the release of Import Attributes in TypeScript 5.3 (source).
Import Attributes
Import Attributes have been emerged from the TC39 Import Attributes proposal.
TypeScript 5.3 supports import attributes with the following syntax:
Import Assignments
Import assignments are typical in TypeScript when importing CommonJS modules. An import assignment looks like this:
When using ECMAScript modules the import assignments cannot be used and need to be transformed. If the CommonJS module has a default export, then it can be imported the following way:
Import Elision
In TypeScript, "import elision" refers to the behavior of analyzing imports and removing them from the emitted JavaScript code if they are only used for type checking and have no runtime impact. This optimization process aims to eliminate unnecessary imports and optimize the output JavaScript code.
The compiler option verbatimModuleSyntax
simplifies the rules for import elision as it keeps imports and exports without a type
modifier intact. Imports and exports with the type
modifier will be dropped completely ensuring a lean and predictable outcome.
Dynamic Imports
A dynamic import is a function-like expression that allows loading an ECMAScript module inside of a CommonJS module or browser enviroment. It makes use of the import() syntax and can be used the following way:
Interfaces
An interface is a way to define a contract for the shape of an object. It specifies the names and types of properties and methods that an object must have to be considered an instance of that interface.
Interfaces can be used to specify the shape of objects, define the structure of classes, and describe the signatures of functions.
Example:
Interfaces are compile-time constructs, meaning that the TypeScript compiler does not generate any JavaScript code for interfaces at runtime.
Best Practice:
Interfaces are considered to be the better version of "types" as they have improved performance over intersection types (source), allow declaration merging and support module augmentation.
Intersection Types
An intersection type combines multiple types into a single type.
Example:
Iterables
An object is considered iterable if it has an implementation for the Symbol.iterator
property that returns an iterator object.
An object is an iterator when it implements the iterator protocol which requires a next() method.
Example:
It's also possible to write it in a object-oriented style:
An object can also become an iterable iterator:
Similarly in an OOP-style:
There are also generator objects which represent iterable iterators. A generator object is returned by a generator function as follows:
Literal Types
A literal type is a more specific subtype of a collective type. A literal type can represent an exact value, such as a specific number or string, instead of a general value that can be any number or string.
Literal Narrowing
Narrowing refers to the process of reducing the set of possible values that a variable can hold. TypeScript applies literal narrowing, when declaring a variable with the const
keyword:
On the other hand, when using the let
keyword, TypeScript will infer a collective type such as number
for the variable:
Lookup Types
A lookup type, also referred to as indexed access type, is a way to retrieve the type of a property of an object by its name or key. This is useful when you want to write code that works with dynamic property names, where the name of the property is not known until runtime.
The syntax for a lookup type is as follows: Type[Key]
, where Type
is the type of the object and Key
is the name of the property being looked up.
Example:
Mapped Types
A mapped type is derived from an existing type by modifying the properties' names or accessibility (e.g. readonly
) in the new type.
Example:
Mapped types can be also used with template literals. Template literals are a powerful feature of TypeScript that allow you to create strings that contain expressions:
Module Augmentation
Module Augmentation is the process of extending type definitions by merging declarations from an existing module with your own type definitions. It is particularly useful when you want to modify interfaces from external packages. Such cases occur when type definitions from external packages are broken and need to be patched.
Example:
In the following example the existing definition for the Configuration
from the webpack
module gets extended by a property called devServer
:
Video Tutorial:
Module
In TypeScript a module is a file with at least one top level import
or export
statement.
Non-null Assertion Operator
You can use the "non-null assertion operator" to tell the compiler that you're certain that an expression won't be null
or undefined
. This operator is represented by an exclamation mark (!
) placed after the expression.
In the code snippet below, name!
tells the TypeScript compiler to assume that the name
property is not null
or undefined
. This allows to access the length
property without the compiler raising an error:
Synonyms: Definite Assignment Assertions
Non-primitive types
All non-primitive data types, also referred to as complex types, are passed by reference:
- object (and array, which is also an object)
Primitive Types
All primitive data types are passed by value. They are immutable:
bigint
boolean
null
number
string
symbol
undefined
String Literal Type
A string literal is a specific string (e.g. "click") and a string literal type is the type describing a specific string:
Structural Typing
Structural typing defines a type system where types are defined based on the structure of the data rather than explicit declarations. If two types have the same set of properties they are considered to be of the same type, even if they were defined separately and given different names.
Example:
Even though Point
and Vector
are different types, they are compatible. We can pass a Vector
object where a Point
is expected, because it has a similar structure and matches all required properties of a Point
.
Tagged template
Template Literal Type
A template literal type is a combination of Template literal and string literal type. It is capable of resolving string interpolations within a template string to use it for strong typing:
Template Literal
A template literal is a literal which has placeholders for variables (i.e. ${placeholder}
), so it can act as a template for texts:
Top Types
The top type, also known as the universal type, refers to a type that serves as the parent or supertype for all other types within a specific type system. In TypeScript, the types any
and unknown
are considered top types as they encompass all other types in the type system.
The opposite of top types are bottom types. In TypeScript, the bottom type is never
. Along with the top type, the bottom type defines the range of types, with all other types falling between never
and any
.
Transpiler
Transpilers transform high-level programming language code (e.g. TypeScript) into another high-level programming language code (e.g. JavaScript).
Triple-Slash Directives
Triple-slash directives are special comments that provide instructions to the TypeScript compiler. These directives begin with three slashes (///
) and are placed at the top of a file or just before a declaration to control the compilation process.
The most common use of triple-slash directives is to reference external dependencies or declaration files. For example, the /// <reference path="..." />
directive is used to reference external TypeScript declaration files (*.d.ts
) that provide type information for libraries or modules used in the current file.
Triple-slash directives can also be used to configure certain compiler features. For instance, the AMD module directive (/// <amd-module />
) specifies that the current file is an AMD module and allows passing an optional module name to the compiler.
It's important to note that starting from TypeScript 3.0, the preferred way to manage dependencies and module resolution is through import statements. Triple-slash directives are mainly used for legacy scenarios or compatibility reasons.
Tuple Type
A tuple is a sub-type of an array. It is similar to an array in that it can hold elements of different data types, but it differs in that it has a finite number of elements.
Example:
Variadic Tuple Types
Variadic tuple types are a feature in TypeScript that allow an indefinite number of elements. The syntax for defining a variadic tuple type is to use the rest syntax (...
) to condense multiple elements into one:
In the code example above, a benefit of using a variadic tuple type over an array (Array<string | number>
) is that it enforces that the first element of the tuple must be of type string
.
Labeled Tuple Elements
As of TypeScript 4.0 you can assign labels to the elements of a tuple type for improved readability:
Tuples vs. Arrays
A tuple has a fixed number of elements and arrays have a variable number of elements.
Example:
Tuple types are denoted by square brackets placed outside of the data type (e.g. [number]
), while array types have square brackets following the data type (e.g. number[]
):
Type Alias
A type alias is a way to create a new name for an existing type.
Example:
To express that a string is a user ID, you can create a UserID
type alias:
Type Annotation
A type annotation is when you explicitly define the type that a variable can take on:
Video Tutorial:
Type Argument Inference
TypeScript can infer types but also type arguments. This usually happens when the arguments of your generic functions are connected to the generic type of your function. In this case TypeScript can infer the type of your type variable by inspecting the input values of your function call:
Type Argument
In the following code, <string>
is the type argument (sometimes called type parameter):
Type Assertion
A type assertion is a way to tell the compiler the type of a variable when TypeScript is unable to infer it automatically:
It is important to be cautious with type assertions because they can potentially lead to dangerous errors. This is because type assertions override TypeScript's type inference, and if used incorrectly, may result in assigning an incorrect type to a variable:
Best Practice:
Type assertions can be particularly handy when writing test code. They enable you to test whether your TypeScript code can handle incorrect input by asserting a correct type to incorrect data. This enables you to pass the incorrect data to your code under test, which would otherwise be disallowed by TypeScript's type system.
When you need to override TypeScript's type inference, it is recommended to use an assertion function instead of a regular type assertion.
Video Tutorial:
Related:
Type Coercion
When a type is implicitly turned into another type, it's called type coercion.
Example:
The + operator
coerces its operands, so it can work properly. When adding a number type and a string type, the number type is turned (coerced) into a string:
Type Erasure
During compilation, TypeScript removes type annotations, interfaces, type aliases, and other type constructs from the output. This effect is called "Type Erasure".
Example:
The type assertion as 'Rudolf'
only exists in TypeScript's type system and will be removed when the code is transpiled to JavaScript:
Type Guards
A type guard is a boolean expression that does a runtime check to guarantee the type in a certain scope.
Built-in Type Guards
typeof
- checks for primitive typesin
- Checks for object propertiesinstanceof
- checks for class instances
Type Guard with typeof
A typeof
type guard can protect primitive types.
When dealing with complex types, a typeof
type guard may not be very useful since the type of a complex type will always be "object"
(see here). In such cases, an in
type guard would be more effective.
Type Guard with in
While the in
type guard is effective for checking plain objects, it is recommended to use the instanceof
type guard when checking a class
.
Type Guard with instanceof
Custom Type Guard
Type Predicates
A type predicate is a syntax construct used to define the return type of a type guard. It allows the type guard to assert a specific type to its input. Here is an example of a type predicate: error is AxiosError
Type Inference
When there is no explicit type annotation then TypeScript will infer the type for you:
Type Narrowing
Refining wide types to more specific types is called type narrowing.
Concept:
Type Variable
A type variable is the placeholder for a generic type in your generic code:
Type variables are written by using the angle brackets and defining a name for the variable (e.g. <T>
). This construct is often referred to as the diamond operator because the angle brackets look like a diamond (<>
, π).
Type Widening
You widen a type when you assign a value to a variable where the variable has a supertype of the value.
Concept:
Union Types
A union type lets you specify a range of possible types for a value:
It is called a union because it unites the amount of possible types. The term union comes from set theory where it is used when two (or more) sets are combined.
Union types become pretty powerful in the context of discriminated Unions.
Literal Union Types
Literal union types can be useful when you want to represent a set of specific values (literals). For example, consider this string literal union type:
In conjunction with type guards a literal union type can make your code more expressive and type-safe.
Weak Types
If all properties of your type or interfaces are optional, then this type or interface is considered to be weak:
Example: