As large-scale JavaScript applications require strong typing, better maintainability, and tooling support, TypeScript has become the industry standard for building robust, scalable, and error-free front-end and back-end applications. Recruiters must identify professionals skilled in TypeScript syntax, type system, and integration with frameworks like React, Angular, or Node.js.
This resource, "100+ TypeScript Interview Questions and Answers," is tailored for recruiters to simplify the evaluation process. It covers topics from TypeScript fundamentals to advanced type manipulations and real-world application design, including generics, decorators, and type inference.
Whether hiring for Front-End Developers, Full-Stack Engineers, or Node.js Developers, this guide enables you to assess a candidate’s:
For a streamlined assessment process, consider platforms like WeCP, which allow you to:
✅ Create customized TypeScript assessments aligned to your framework and project stack.
✅ Include hands-on coding tasks, such as writing strongly typed functions, implementing generics, or building React components with TypeScript.
✅ Proctor assessments remotely with AI-powered integrity checks.
✅ Leverage automated grading to evaluate code correctness, type safety, and adherence to TypeScript best practices.
Save time, ensure code quality, and confidently hire TypeScript professionals who can build maintainable, scalable applications with fewer runtime errors from day one.
TypeScript is a strongly typed superset of JavaScript that adds optional static typing, interfaces, and other advanced features to the language. Developed and maintained by Microsoft, TypeScript compiles down to standard JavaScript, which can then run anywhere JavaScript is supported, such as in browsers or Node.js.
Key Differences between TypeScript and JavaScript:
In summary, while TypeScript is a superset of JavaScript that provides additional features, it allows developers to write more reliable and maintainable code, especially in large, complex projects, and ensures that errors related to types are caught early in the development lifecycle.
Using TypeScript over JavaScript offers several substantial benefits, particularly in larger codebases or when working in teams. Here are some of the key advantages:
Type annotations in TypeScript refer to the process of explicitly specifying the types of variables, function parameters, return values, and other data structures. By providing type annotations, you help TypeScript’s type checker understand the expected types in your code, which in turn allows TypeScript to catch potential errors during development before the code is executed.
For example:
let age: number = 25;
let name: string = "Alice";
let isActive: boolean = true;
In the above example:
Function Annotations: Type annotations are also used when defining functions. You can specify the types of parameters and the return type of the function:
function greet(name: string): string {
return `Hello, ${name}`;
}
In this case, name: string specifies that the greet function expects a string as an argument, and the : string after the parentheses specifies that the function returns a string.
Benefits of Type Annotations:
In TypeScript, you declare a variable with a specific type by using the : syntax followed by the type. TypeScript uses type annotations to ensure that variables hold values of the correct type.
Here’s the syntax for declaring a variable with a type:
let variableName: type = value;
Example:
let num: number = 10; // Declares a variable 'num' with type 'number'
let name: string = "Alice"; // Declares a variable 'name' with type 'string'
let isActive: boolean = true; // Declares a variable 'isActive' with type 'boolean'
Type annotations can also be used with complex data types:
Arrays:
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Alice", "Bob"];
Objects:
let user: { name: string, age: number } = { name: "Alice", age: 30 };
Tuples:
let tuple: [number, string] = [1, "Alice"];
By declaring the types explicitly, TypeScript can check that variables hold the expected types, helping catch errors at compile time.
The any type in TypeScript is a dynamic type that can represent any value, essentially opting out of TypeScript's strict type system for that variable. When you assign the any type to a variable, TypeScript allows that variable to hold any kind of data — be it a number, string, object, function, or even null.
Example:
let value: any = 5;
value = "Hello, TypeScript"; // No error, as 'value' can hold any type
value = { name: "Alice" }; // Still valid
When to Use any:
However, any should be used sparingly. Overusing it undermines the benefits of TypeScript’s type safety, making the code more prone to errors. It's generally recommended to use unknown when you need flexibility but still want to enforce type checking before using the value.
In TypeScript, you can define a function with specific argument types and a return type by annotating the types of the parameters and the return type after the function signature. Here’s the general syntax:
typescript
function functionName(parameterName: type): returnType {
// Function body
}
Example:
function add(a: number, b: number): number {
return a + b;
}
console.log(add(5, 10)); // Outputs: 15
In this example:
You can also define functions with optional or default parameters:
function greet(name: string, age?: number): string {
return `Hello ${name}, you are ${age ? age : 'unknown'} years old.`;
}
console.log(greet("Alice")); // Outputs: Hello Alice, you are unknown years old.
Type inference in TypeScript refers to the compiler's ability to automatically deduce the type of a variable or expression based on its assigned value, without needing explicit type annotations. TypeScript uses inference to determine the type of variables, function return types, and other expressions at compile time.
Example:
let num = 10; // TypeScript infers that 'num' is of type 'number'
let greeting = "Hello"; // TypeScript infers that 'greeting' is of type 'string'
In the above example, TypeScript automatically infers that num is a number and greeting is a string, based on their assigned values.
Type inference helps reduce the verbosity of TypeScript code by eliminating the need for explicit type annotations in many cases. However, you can always override inferred types by using type annotations.
In TypeScript (and JavaScript), there are three ways to declare variables: let, const, and var. These keywords differ in how they handle scoping, mutability, and hoisting.
let name = "Alice";
name = "Bob"; // Reassignment is allowed
const:
const pi = 3.14;
// pi = 3.14159; // Error: Cannot reassign a constant variable
var:
var name = "Alice";
var name = "Bob"; // Redeclaration is allowed in the same scope
Recommendation: Always prefer let and const over var in TypeScript, as they provide better scoping and prevent potential bugs related to variable redeclaration.
TypeScript provides several built-in primitive types. These types represent the simplest form of data in the language and cannot be broken down further:
number: Represents both integer and floating-point numbers.
let num: number = 42;
string: Represents text, enclosed in single, double, or backticks for template literals.
let name: string = "Alice";
boolean: Represents a logical value, either true or false.
let isActive: boolean = true;
null: Represents the absence of a value.
let x: null = null;
undefined: Represents a variable that has been declared but not initialized.
let y: undefined = undefined;
symbol: A unique and immutable primitive value used to create anonymous and unique object properties.
let sym: symbol = Symbol("description");
bigint: A large integer type that can represent values larger than the maximum value of the number type.
let bigNum: bigint = 12345678901234567890n;
Both undefined and null represent "empty" or "no value" in TypeScript, but they are used in different contexts:
undefined: This is the default value for uninitialized variables. It is assigned to a variable when it is declared but not given a value.
let x: number | undefined;
console.log(x); // undefined, as no value is assigned yet
null: Represents the intentional absence of any object value. null is often used when you want to explicitly indicate that a variable should not reference any object.
let person: { name: string } | null = null; // Person is explicitly set to 'null'
Key Differences:
null represents an object that is intentionally empty or missing.
In TypeScript, you can define arrays in a few different ways, each allowing you to specify the type of elements the array will hold.
1. Using the Array<T> syntax:
This is the generic form where you specify the type of elements the array will hold inside the angle brackets.
Example:
let numbers: Array<number> = [1, 2, 3, 4];
let strings: Array<string> = ["apple", "banana", "cherry"];
2. Using the type[] syntax:
This is a shorthand version where you specify the element type followed by square brackets.
Example:
let numbers: number[] = [1, 2, 3, 4];
let strings: string[] = ["apple", "banana", "cherry"];
Mixed-Type Arrays:
You can also define arrays that hold mixed types, using tuples or union types, depending on your use case:
let mixedArray: (string | number)[] = [1, "hello", 2, "world"];
Readonly Arrays:
If you want an array to be immutable (i.e., you don't want to modify it after it's created), you can define it as a readonly array:
let numbers: readonly number[] = [1, 2, 3, 4];
A tuple in TypeScript is an ordered collection of elements where each element can have a different type. Tuples are similar to arrays, but unlike arrays where all elements must be of the same type, tuples allow for heterogenous types (i.e., different types in the same collection).
Example:
let tuple: [number, string, boolean] = [1, "hello", true];
In this example:
Tuples also support optional elements, meaning you can define a tuple where some elements are optional:
let tuple: [number, string?, boolean?] = [1, "hello"];
Tuple with spread:
Tuples can also include a spread operator to allow for an arbitrary number of elements in the array:
let tuple: [string, ...number[]] = ["apple", 1, 2, 3];
An enum in TypeScript is a special "object" that allows you to define a set of named constants. Enums can be used to represent a collection of related values, making your code more readable and less error-prone.
Types of Enums:
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction.Up); // Output: 0
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
Differences Between Enums and Objects:
Example:
const DirectionObject = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
};
While you can achieve similar functionality with objects, enums provide better support for reverse mapping, clarity, and integration with TypeScript’s type system.
In TypeScript, you can define the type of an object using interface or type alias, but the simplest way is to define an inline object type directly in the code.
Using object type annotation:
let person: { name: string; age: number } = {
name: "Alice",
age: 30
};
Here, person is an object where:
You can also define objects with optional properties using ?:
let person: { name: string; age?: number } = { name: "Alice" };
In this case, age is optional.
Using interfaces for complex object types:
interface Person {
name: string;
age: number;
address?: string; // Optional property
}
let person: Person = {
name: "Bob",
age: 25
};
Both interfaces and type aliases in TypeScript can be used to define object shapes, function signatures, and other types. However, they have some differences in terms of usage, extensibility, and syntax.
Key Differences:
interface Shape {
area: number;
}
interface Circle extends Shape {
radius: number;
}
type Shape = { area: number };
type Circle = Shape & { radius: number };
interface Person {
name: string;
}
interface Person {
age: number;
}
const person: Person = { name: "Alice", age: 25 };
The void type in TypeScript represents the absence of any value, typically used for functions that do not return a value. In JavaScript, functions can implicitly return undefined, but in TypeScript, you can explicitly declare a function’s return type as void.
Example:
function logMessage(message: string): void {
console.log(message);
}
In this example, the function logMessage takes a string as an argument and does not return anything, so its return type is void.
The void type is also used when you expect a function to return nothing, indicating that the function is purely for its side effects (e.g., logging, updating state).
The never type represents values that never occur. It is used
to define the return type of a function that will never successfully return a value, such as functions that always throw an error or enter an infinite loop.
Examples of never:
Functions that throw errors:
function throwError(message: string): never {
throw new Error(message);
}
Infinite loops:
function infiniteLoop(): never {
while (true) {
console.log("Running...");
}
}
The never type is useful when you want to ensure that a certain function cannot complete normally, providing better type safety.
TypeScript uses structural typing to handle type compatibility, meaning that two types are compatible if they have the same structure (i.e., the same properties with matching types), rather than relying on their names or definitions.
Example:
interface Person {
name: string;
age: number;
}
let employee: Person = { name: "Alice", age: 30 };
// Employee object is compatible with Person type because they have the same structure
TypeScript uses a duck typing approach, where the type compatibility is determined by the presence of properties and their types, not by the specific types' names. If two objects or types have the same shape, they are compatible regardless of whether they are explicitly declared as the same type.
A type alias in TypeScript allows you to create a custom name for any type. You can use type aliases to simplify complex types, provide clarity, or define reusable type definitions.
Example:
type ID = string | number;
let userId: ID = "abc123"; // Can be either a string or a number
Type aliases can be used to represent primitive types, objects, function types, and even unions and intersections.
Complex example:
type Point = { x: number, y: number };
type Point3D = Point & { z: number }; // Intersection type
let point: Point3D = { x: 1, y: 2, z: 3 };
Type aliases provide flexibility and are especially useful when working with complex types that are used repeatedly across your codebase.
In TypeScript, you can define an optional property in an object or interface by appending a question mark (?) to the property name.
Example:
interface Person {
name: string;
age?: number; // age is optional
}
let person1: Person = { name: "Alice" }; // Valid
let person2: Person = { name: "Bob", age: 30 }; // Also valid
The ? indicates that the property is not required, and objects of that type may or may not include that property. When the property is not provided, it is treated as undefined.
The readonly modifier in TypeScript is used to make properties or variables immutable, meaning their values cannot be changed once they are initialized. This can be applied to object properties, arrays, and class members.
1. readonly on object properties:
When applied to an object property, it prevents the property from being reassigned after it is initially set.
Example:
interface Person {
readonly name: string;
readonly age: number;
}
let person: Person = { name: "Alice", age: 25 };
person.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property
2. readonly on arrays:
The readonly modifier can also be applied to arrays to prevent modification of the array elements.
Example:
let numbers: readonly number[] = [1, 2, 3];
numbers[0] = 4; // Error: Index signature in type 'readonly number[]' only permits reading
numbers.push(5); // Error: Property 'push' does not exist on type 'readonly number[]'
The readonly modifier is useful for enforcing immutability and creating more predictable, bug-resistant code, especially when dealing with data that should not change after it is created.
In TypeScript, you can define a function with optional parameters by appending a question mark (?) to the parameter's name in the function signature. This means that the parameter is not required when the function is called.
Example:
function greet(name: string, age?: number): string {
return age ? `Hello ${name}, you are ${age} years old.` : `Hello ${name}, age is unknown.`;
}
console.log(greet("Alice")); // Hello Alice, age is unknown.
console.log(greet("Bob", 30)); // Hello Bob, you are 30 years old.
In this example, age is an optional parameter, and TypeScript will allow the function to be called with or without it. If not provided, age will be undefined inside the function.
In TypeScript, you can define default values for function parameters by assigning the default value directly in the function signature. This allows you to provide a fallback value if no argument is passed for that parameter.
Example:
function greet(name: string, age: number = 30): string {
return `Hello ${name}, you are ${age} years old.`;
}
console.log(greet("Alice")); // Hello Alice, you are 30 years old.
console.log(greet("Bob", 25)); // Hello Bob, you are 25 years old.
In this example, age has a default value of 30. If no value is passed for age when the function is called, TypeScript will use 30 as the default value.
The as keyword in TypeScript is used for type assertion, which tells the TypeScript compiler to treat a value as a specific type, overriding its inferred or existing type. It doesn’t perform any runtime type checking, it only affects TypeScript’s type system.
Example:
let someValue: unknown = "This is a string";
// Type assertion with 'as'
let stringLength: number = (someValue as string).length;
console.log(stringLength); // Outputs: 16
In this example:
Difference between as and <type> (angle bracket syntax):
Both as and <type> can be used for type assertions, but the as syntax is preferred in TypeScript (especially in JSX, where angle brackets are already used for JSX tags).
let stringLength: number = (<string>someValue).length; // Less common in modern TypeScript
In TypeScript, the typeof operator is used to obtain the type of a variable or expression. It can be used in both expressions and type annotations.
1. typeof in expressions:
It works similarly to JavaScript to retrieve the type of a value.
let message = "Hello, TypeScript!";
console.log(typeof message); // Output: "string"
2. typeof in type annotations:
You can use typeof to create a type based on the type of an existing variable or expression.
let user = { name: "Alice", age: 25 };
// Using 'typeof' to create a type from the 'user' object
let anotherUser: typeof user = { name: "Bob", age: 30 };
In this example, the type of anotherUser will automatically match the structure of user.
A namespace in TypeScript is a way to group related code, such as functions, classes, or variables, under a common name to avoid name collisions. Namespaces were previously used for module systems in TypeScript, but with the introduction of ES6 modules, their use has become less common. However, namespaces can still be useful in certain scenarios, especially when organizing code in large applications.
Example:
namespace MathUtils {
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
}
console.log(MathUtils.add(5, 3)); // Outputs: 8
In this example:
Namespaces provide a simple way to organize code in a global scope, but when using ES6 modules, it’s often better to rely on import/export for code organization.
TypeScript is statically typed, meaning that type checking is done at compile time. However, there are times when you might need to perform type checks at runtime as well. You can perform type checks using the typeof operator (for primitive types) or instanceof (for class instances).
1. Using typeof for primitive types:
let value: any = "Hello, world!";
if (typeof value === "string") {
console.log("It's a string!");
} else {
console.log("It's not a string.");
}
2. Using instanceof for class instances:
class Person {
constructor(public name: string, public age: number) {}
}
let person = new Person("Alice", 25);
if (person instanceof Person) {
console.log("It's a Person object!");
}
3. User-defined type guards:
For more complex types, you can create custom type guards to check if an object matches a specific type:
function isPerson(obj: any): obj is Person {
return obj && obj.name && obj.age;
}
let someObject = { name: "Bob", age: 30 };
if (isPerson(someObject)) {
console.log(`${someObject.name} is a person.`);
}
In TypeScript, interfaces and classes serve different purposes:
Example:
interface Shape {
area: number;
calculateArea(): number;
}
class Circle implements Shape {
constructor(public radius: number) {}
area: number = 0;
calculateArea(): number {
this.area = Math.PI * this.radius * this.radius;
return this.area;
}
}
Example:
class Circle {
constructor(public radius: number) {}
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
TypeScript supports several ways to import and export modules, which align with ES6 module syntax.
1. Named exports and imports:
Export: You can export multiple named items from a module.
export const pi = 3.14;
export function calculateArea(radius: number): number {
return pi * radius * radius;
}
Import: You import specific members from a module using curly braces.
import { pi, calculateArea } from './math';
console.log(calculateArea(5)); // Uses imported function
2. Default export and import:
Export: You can export a single default value from a module.
export default class Circle {
constructor(public radius: number) {}
}
Import: You import the default value directly without curly braces.
import Circle from './Circle';
let circle = new Circle(5);
Declaration merging is a feature in TypeScript that allows multiple declarations with the same name to be merged into a single definition. This is particularly useful for augmenting types and interfaces, allowing for flexible extension of existing code.
Example with interfaces:
interface Person {
name: string;
}
interface Person {
age: number;
}
let person: Person = { name: "Alice", age: 30 };
In this case, TypeScript merges the two Person interfaces into a single interface, effectively adding age to the original Person type.
Example with modules:
You can also augment modules using declaration merging:
// In a separate module
declare module 'myModule' {
export function foo(): void;
}
// Later in the same or another module
declare module 'myModule' {
export function bar(): void;
}
// Now 'myModule' has both 'foo' and 'bar' functions
This allows you to extend the functionality of existing modules or libraries without modifying their original source code.
In TypeScript, strict mode is a set of compiler options that enable a wide range of type-checking features to help catch potential errors and enforce better coding practices. It makes TypeScript's type system more rigorous by enabling a number of strict checks, such as disallowing null or undefined in certain places, requiring explicit types, and ensuring non-nullable types.
To enable strict mode, you need to add the following to your tsconfig.json file:
{
"compilerOptions": {
"strict": true
}
}
When strict is set to true, TypeScript enables the following strict checks:
Strict mode is highly recommended for better type safety and to catch potential issues early in development.
The unknown type in TypeScript is a safer alternative to any. It represents any value, but unlike any, you cannot perform operations on an unknown value until you have narrowed its type through some form of checking.
Example of using unknown:
let value: unknown = 42;
if (typeof value === "number") {
console.log(value * 2); // Now it's safe to use 'value' as a number
} else {
console.log("Not a number");
}
not know in advance, but you want to enforce type checking before using the value.
Type guards in TypeScript are functions or expressions that allow you to narrow the type of a variable at runtime. Type guards help the TypeScript compiler understand the type of a variable more precisely within a specific scope.
Examples of common type guards:
function isString(value: unknown): value is string {
return typeof value === "string";
}
let myValue: unknown = "Hello, TypeScript!";
if (isString(myValue)) {
console.log(myValue.toUpperCase()); // Safe to call string methods
}
class Dog {
bark() {
console.log("Woof!");
}
}
let animal: unknown = new Dog();
if (animal instanceof Dog) {
animal.bark(); // Safe to call Dog methods
}
interface Cat {
meow: boolean;
}
function isCat(value: unknown): value is Cat {
return (value as Cat).meow !== undefined;
}
let animal: unknown = { meow: true };
if (isCat(animal)) {
console.log(animal.meow); // Safe to use `meow` property
}
Type guards improve type safety by enabling the narrowing of types based on runtime checks.
A union type in TypeScript allows a variable to hold values of more than one type. It is denoted using the pipe (|) symbol between types.
Example:
let value: string | number;
value = "Hello, TypeScript"; // Valid
value = 42; // Valid
value = true; // Error: Type 'boolean' is not assignable to type 'string | number'
Union types are useful when a variable can have multiple possible types, such as in situations where a function might return different types depending on the input.
Example with functions:
function printId(id: string | number) {
console.log(id);
}
printId("12345"); // Valid
printId(12345); // Valid
printId(true); // Error: Argument of type 'boolean' is not assignable to parameter of type 'string | number'
You can also combine union types with other type features like null or undefined, which can create more complex type structures.
The never type is used in TypeScript to represent values that never occur, such as the return type of a function that always throws an error, or enters an infinite loop. When a function has a return type of never, TypeScript knows that the function will never successfully complete and return a value.
Example with a function that throws an error:
function throwError(message: string): never {
throw new Error(message);
}
throwError("Something went wrong!"); // This function never returns.
The never type is useful for cases like:
A literal type in TypeScript refers to a specific, exact value of a given type. Literal types can be used to specify a variable or parameter that can only accept specific values, rather than a broader type.
Example with string literal types:
let direction: "up" | "down"; // `direction` can only be "up" or "down"
direction = "up"; // Valid
direction = "left"; // Error: Type '"left"' is not assignable to type '"up" | "down"'
Literal types can also be used with numbers and booleans:
let age: 18 | 21 | 30;
age = 18; // Valid
age = 25; // Error: Type '25' is not assignable to type '18 | 21 | 30'
Literal types are especially useful when you need to constrain values to a specific set of values, like in function parameters or enum-like structures.
The keyof keyword in TypeScript is used to create a union type of the keys of a given type. It can be applied to an object type, and it returns a union type that includes the keys of that object.
Example:
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person; // "name" | "age"
let key: PersonKeys;
key = "name"; // Valid
key = "age"; // Valid
key = "address"; // Error: Type '"address"' is not assignable to type '"name" | "age"'
keyof is particularly useful when you want to reference or work with keys dynamically, and it allows for better type safety when accessing object properties.
In TypeScript, when using third-party JavaScript libraries, there are several ways to ensure type safety:
Install Type Definitions: Many JavaScript libraries come with TypeScript type definitions either built-in or through DefinitelyTyped. You can install these types via npm using @types scope.
npm install @types/jquery
Importing a JavaScript library without type definitions: If the library doesn't have type definitions, you can declare it as any or unknown to bypass TypeScript's type checking.
declare var myLibrary: any;
myLibrary.someFunction(); // No type checking, but code will compile
Creating Custom Type Definitions: If no type definitions are available, you can create your own type declarations in a .d.ts file.
// myLibrary.d.ts
declare module 'myLibrary' {
export function someFunction(): void;
}
By using type definitions, you can get IntelliSense, type checking, and better tooling when working with JavaScript libraries in TypeScript.
declare is used to tell TypeScript about the existence of variables, functions, or modules that are defined outside the TypeScript code (such as in JavaScript libraries or external scripts). It is used in declaration files (.d.ts) and allows TypeScript to understand the types or interfaces without generating any code.Example:
declare const myGlobalVar: string;
import is used to bring in modules or specific exports from external TypeScript or JavaScript files. It is used for actual imports, and the TypeScript compiler will include the necessary code in the output.Example:
import { myFunction } from './myModule';
In summary, declare is used for declarations where TypeScript doesn't generate any code, and import is used for actual module loading.
To enable TypeScript in a JavaScript project, follow these steps:
Install TypeScript:
npm install --save-dev typescript
Create a tsconfig.json file: This configuration file tells TypeScript how to transpile your code. You can generate it automatically with the tsc --init command.
npx tsc --init
Install type definitions: If you are using third-party libraries, you should install type definitions for them. For example:
npm install @types/react
Compile the code: Run the TypeScript compiler to transpile .ts files into .js:
npx tsc
By following these steps, you can begin using TypeScript in a JavaScript project.
The Partial<T> utility type in TypeScript is used to create a new type where all properties of the type T are optional. It is commonly used when you want to represent an object where some of the properties might not be provided, such as when updating an existing object.
Example:
interface User {
name: string;
age: number;
email: string;
}
const updateUser = (user: User, updates: Partial<User>) => {
return { ...user, ...updates };
};
const user = { name: "John", age: 30, email: "john@example.com" };
const updatedUser = updateUser(user, { age: 31 });
console.log(updatedUser); // { name: "John", age: 31, email: "john@example.com" }
In the above example, Partial<User> makes all properties of the User interface optional, allowing you to pass any subset of the properties to the updateUser function.
The Readonly<T> utility type in TypeScript is used to make all properties of a type T immutable (i.e., readonly). This means that you cannot reassign values to properties once they are initialized.
Example:
interface User {
name: string;
age: number;
}
const user: Readonly<User> = { name: "Alice", age: 25 };
user.age = 26; // Error: Cannot assign to 'age' because it is a read-only property
The key difference between Readonly<T> and readonly properties:
Example of readonly in a class:
class User {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
const user = new User("Alice");
user.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property
The Pick<T, K> utility type is used to create a new type by picking a subset of properties K from an existing type T. This is useful when you want to create a new type that only includes specific properties from an existing type.
Example:
interface User {
name: string;
age: number;
email: string;
}
type UserInfo = Pick<User, "name" | "email">;
const userInfo: UserInfo = { name: "Alice", email: "alice@example.com" };
In the above example, Pick<User, "name" | "email"> creates a new type that only includes the name and email properties from the User type.
Generics in TypeScript allow you to create reusable components or functions that work with any data type while preserving type safety. A generic type is defined using the angle bracket syntax (<T>), where T is a placeholder for a specific type.
Example:
function identity<T>(value: T): T {
return value;
}
const num = identity(123); // num has type number
const str = identity("hello"); // str has type string
In this example, the identity function is a generic function. It can accept and return any type T, which is determined based on the argument passed when the function is called.
Using generics in interfaces:
interface Box<T> {
value: T;
}
const numberBox: Box<number> = { value: 123 };
const stringBox: Box<string> = { value: "Hello" };
Generics enable you to write functions and components that are flexible and type-safe, allowing them to work with multiple types without sacrificing type checking.
The extends keyword in TypeScript is used for two main purposes:
Extending a class: When creating a new class, you can use extends to inherit properties and methods from another class (just like in other object-oriented languages).Example:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks`);
}
}
const dog = new Dog("Rex");
dog.speak(); // Outputs: Rex barks
Constrained Generics: The extends keyword is also used to constrain generic types, ensuring that a given type T must satisfy certain conditions (e.g., it must be a subclass of a specific class or have specific properties).Example:
function printLength<T extends { length: number }>(value: T): number {
return value.length;
}
printLength("Hello"); // Works, because string has a length property
printLength([1, 2, 3]); // Works, because array has a length property
printLength(123); // Error: number doesn't have a length property
You can constrain a generic type in TypeScript by using the extends keyword. This ensures that the type argument passed to a generic function or class must adhere to a certain structure or type.
Example:
function getProperty<T extends { name: string }>(obj: T): string {
return obj.name;
}
const person = { name: "Alice", age: 25 };
console.log(getProperty(person)); // Works: "Alice"
In this example, the T type is constrained to objects that have a name property, so you can safely access obj.name.
You can also constrain a generic to a specific type, class, or interface:
function printDate<T extends Date>(date: T): void {
console.log(date.toDateString());
}
printDate(new Date()); // Works
printDate("2022-01-01"); // Error: Type 'string' is not assignable to type 'Date'
Mapped types in TypeScript are a way to create new types by transforming properties of an existing type. You can create new types by iterating over the properties of an existing type and modifying them (e.g., making them optional, readonly, etc.).
Example: Making properties optional with a mapped type:
interface User {
name: string;
age: number;
}
type PartialUser = {
[K in keyof User]?: User[K];
};
const user: PartialUser = { name: "Alice" }; // `age` is optional
In this example, PartialUser is a new type where all properties of User are optional. This is equivalent to using the Partial<T> utility type.
Example: Making properties readonly with a mapped type:
type ReadonlyUser = {
readonly [K in keyof User]: User[K];
};
const user: ReadonlyUser = { name: "Alice", age: 25 };
user.age = 26; // Error: Cannot assign to 'age' because it is a read-only property
Mapped types are useful for creating dynamic types, especially when you need to transform or derive new types based on existing ones.
TypeScript handles asynchronous code similarly to JavaScript, using Promise<T> for asynchronous operations and async/await syntax for handling asynchronous code in a more readable and synchronous-looking way.
1. Promise<T>:
A Promise<T> is used to represent a value that will be available at some point in the future, where T is the type of the value the promise will resolve to.
function fetchData(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched");
}, 1000);
});
}
fetchData().then(data => console.log(data)); // Outputs: "Data fetched"
2. async and await:
The async keyword is used to declare an asynchronous function, and await is used to pause the execution of the function until the promise is resolved.
async function getData(): Promise<string> {
const data = await fetchData();
console.log(data); // Outputs: "Data fetched"
}
getData();
async/await provides a more concise and synchronous-like syntax for handling asynchronous code, making it easier to work with promises and avoid callback hell.
The Record<K, T> utility type in TypeScript is used to create an object type with specific keys (K) and corresponding values of type T. It's especially useful when you want to create objects with specific keys that are mapped to values of a certain type.
Example:
type UserRoles = "admin" | "editor" | "viewer";
const roles: Record<UserRoles, string> = {
admin: "Administrator",
editor: "Editor",
viewer: "Viewer",
};
In this example, Record<UserRoles, string> creates an object type where the keys are "admin", "editor", and "viewer", and the values are strings.
In TypeScript, the this keyword behaves similarly to JavaScript, referring to the current context or object within which the function is being executed. However, TypeScript provides more explicit typing for this within classes and functions.
In a function:
In JavaScript, this depends on how the function is called, which can lead to bugs. In TypeScript, you can specify the type of this to avoid errors.
function greet(this: { name: string }) {
console.log(`Hello, ${this.name}`);
}
const person = { name: "Alice", greet };
person.greet(); // Works fine, this refers to the person object
In a class:
class User {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}`);
}
}
const user = new User("Bob");
user.greet(); // 'this' refers to the User instance
In TypeScript, you can also use this in interfaces and classes to ensure that the correct context is used when accessing properties or methods.
Both interface and abstract class are used to define the structure of objects, but they serve different purposes and have different behaviors.
Key Differences:
Example:
interface Shape {
area: number;
calculateArea(): void;
}
abstract class ShapeBase {
abstract area: number;
abstract calculateArea(): void;
displayArea() {
console.log(`Area: ${this.area}`);
}
}
class Circle extends ShapeBase {
area: number = 0;
calculateArea() {
this.area = Math.PI * 2 * 2; // Example for radius 2
}
}
const circle = new Circle();
circle.calculateArea();
circle.displayArea(); // Output: Area: 12.566370614359172
Inheritance in TypeScript is implemented using the extends keyword. A derived class inherits properties and methods from a base class.
Example:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name); // Call the base class constructor
}
speak() {
console.log(`${this.name} barks`);
}
}
const dog = new Dog("Rex");
dog.speak(); // Output: Rex barks
Here, Dog extends Animal, and the speak method is overridden to provide a different implementation for dogs.
Both interface and type alias can be used to define the structure of objects, but they differ in syntax, capabilities, and usage.
Key Differences:
Example:
// Interface
interface Shape {
width: number;
height: number;
}
// Type Alias
type ShapeType = {
width: number;
height: number;
color: string;
};
const shape1: Shape = { width: 10, height: 20 };
const shape2: ShapeType = { width: 10, height: 20, color: "red" };
The super keyword is used in TypeScript to refer to the base (parent) class and is particularly useful when calling methods or accessing properties from the parent class.
Uses of super:
Example:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name); // Call the base class constructor
}
speak() {
super.speak(); // Call the base class speak method
console.log(`${this.name} barks`);
}
}
const dog = new Dog("Buddy");
dog.speak();
// Output: Buddy makes a sound
// Buddy barks
In TypeScript, the constructor method is used to initialize a newly created instance of a class. It is called automatically when an object is instantiated.
Example:
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const person = new Person("Alice", 25);
person.greet(); // Output: Hello, my name is Alice and I am 25 years old.
The constructor initializes the name and age properties when a new Person object is created.
Mixins in TypeScript are a pattern for combining multiple classes or objects into one, allowing you to share functionality across different classes. This allows you to apply multiple behaviors to a single class, especially when single inheritance is limiting.
Example:
class CanSwim {
swim() {
console.log("Swimming!");
}
}
class CanFly {
fly() {
console.log("Flying!");
}
}
class Duck implements CanSwim, CanFly {
swim: () => void;
fly: () => void;
}
Object.assign(Duck.prototype, CanSwim.prototype, CanFly.prototype);
const duck = new Duck();
duck.swim(); // Output: Swimming!
duck.fly(); // Output: Flying!
In this example, the Duck class can both swim and fly because it mixes the behaviors of CanSwim and CanFly.
You can declare a read-only property in a class using the readonly modifier. Once set, a readonly property cannot be modified outside of the constructor.
Example:
class Person {
readonly name: string;
readonly age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const person = new Person("Alice", 30);
person.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property
In this example, name and age are read-only properties, meaning they cannot be reassigned after the object is constructed.
Custom decorators in TypeScript allow you to add behavior to classes, methods, or properties. They are functions prefixed with @ and can be used for various purposes, such as logging, validation, or metadata handling.
Example: Creating a Method Decorator
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling method ${key} with args: ${args}`);
return originalMethod.apply(this, args);
};
}
class Person {
@log
sayHello(name: string) {
console.log(`Hello, ${name}!`);
}
}
const person = new Person();
person.sayHello("Alice"); // Logs: Calling method sayHello with args: [ 'Alice' ]
In this example, the log decorator intercepts the method call and logs the arguments before calling the original method.
The infer keyword in TypeScript is used within conditional types to infer a type within the true branch of the condition. It's often used in type manipulation to extract types from other types dynamically.
Example:
type ReturnTypeOfFunction<T> = T extends (...args: any[]) => infer R ? R : never;
function getName() {
return "Alice";
}
type NameType = ReturnTypeOfFunction<typeof getName>; // Type is 'string'
In this example, ReturnTypeOfFunction uses infer R to extract the return type of a function and use it elsewhere in the code.
A conditional type in TypeScript allows you to define types based on a condition. It has the syntax T extends U ? X : Y, where:
Example:
type IsString<T> = T extends string ? "Yes" : "No";
type Test1 = IsString<string>; // "Yes"
type Test2 = IsString<number>; // "No"
In this example, IsString is a conditional type that returns "Yes" if T is a string, and "No" otherwise.
TypeScript supports JSX/TSX (the syntax used in React) with full type-checking capabilities. When writing React code in TypeScript, you use .tsx files instead of .ts files to denote that the file contains JSX syntax.
Key features:
Example:
import React from "react";
interface ButtonProps {
label: string;
}
const Button: React.FC<ButtonProps> = ({ label }) => {
return <button>{label}</button>;
};
export default Button;
In this example, TypeScript ensures that the label prop is always a string when used within the Button component.
Type assertions in TypeScript allow you to tell the compiler to treat a variable as a different type. Type assertions do not perform any runtime checks—they are purely a compile-time construct.
There are two ways to perform type assertions:
Example:
let someValue: unknown = "Hello, TypeScript";
// Using angle bracket syntax
let strLength1: number = (<string>someValue).length;
// Using 'as' syntax
let strLength2: number = (someValue as string).length;
Both assertions above tell TypeScript to treat someValue as a string, which allows you to safely access properties like .length.
function throwError(message: string): never {
throw new Error(message);
}
let x: any = 5;
x = "Hello"; // No error
x = true; // No error
The main difference between the traditional function declaration (function) and the arrow function (=>) lies in how they handle the this keyword.
function traditionalFunction() {
console.log(this); // `this` is determined by how the function is called
}
const arrowFunction = () => {
console.log(this); // `this` is inherited from the surrounding context
};
TypeScript handles module resolution using two strategies:
Example:
// tsconfig.json example for module resolution
{
"compilerOptions": {
"moduleResolution": "node",
"baseUrl": "./src", // Set base directory for imports
"paths": {
"*": ["./*"] // Allows you to define custom path mappings
}
}
}
With module resolution, TypeScript ensures that the correct module is found based on how the module is imported and the configuration in tsconfig.json.
You can use async/await with generics in TypeScript in the same way you use them with any other type. The key is that the return type of an async function is always a Promise, so you can use generics to specify the type of the resolved value.
Example:
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
const data = await response.json();
return data as T; // Returning data of type T
}
interface User {
id: number;
name: string;
}
fetchData<User>("https://api.example.com/user")
.then(user => console.log(user.name)); // Type is User
In this example, the generic type T ensures that fetchData returns a Promise<User> when called with a URL that fetches user data.
The main difference between type and interface when used with generics is their syntax and capabilities.
Example:
// Interface with generics
interface Wrapper<T> {
value: T;
}
// Type Alias with generics
type WrapperType<T> = { value: T };
// Both can be used to describe objects of type `T`
const wrapper1: Wrapper<number> = { value: 42 };
const wrapper2: WrapperType<number> = { value: 42 };
The key takeaway is that type is more versatile for complex types, while interface is better suited for object structures and can be extended or merged.
The declare keyword in TypeScript is used to declare variables, classes, functions, or modules that are defined elsewhere, often in external libraries or in globally available code. This is particularly useful for adding type definitions for code that doesn't have them.
Use cases:
Example:
declare var myGlobalVar: string;
declare function myExternalFunction(a: number): void;
myGlobalVar = "Hello"; // TypeScript knows it's a string
myExternalFunction(5);
In this example, declare tells TypeScript that myGlobalVar and myExternalFunction exist, even though they are not explicitly defined in the TypeScript code.
The asserts keyword is used in TypeScript to narrow down types within a function. It allows you to assert that a condition is true, and if the assertion is correct, the type of the variable is refined.
Example:
function isString(value: any): asserts value is string {
if (typeof value !== "string") {
throw new Error("Not a string");
}
}
let unknownValue: any = "Hello, TypeScript!";
isString(unknownValue); // Narrowed to string
console.log(unknownValue.toUpperCase()); // Safe to call methods on string
Here, the asserts value is string tells TypeScript that after the isString function call, value will be of type string, enabling type-safe access to string properties.
Destructuring allows you to unpack values from arrays or properties from objects into distinct variables. It’s a shorthand syntax for extracting values and is fully supported in TypeScript.
Example (Array Destructuring):
const numbers = [1, 2, 3];
const [a, b, c] = numbers;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
Example (Object Destructuring):
const person = { name: "Alice", age: 25 };
const { name, age } = person;
console.log(name); // Alice
console.log(age); // 25
Destructuring is useful for extracting specific values from arrays or objects in a concise and readable manner.
In TypeScript, you can define interfaces with optional properties using the ? operator. This marks a property as optional, meaning it may or may not be provided when an object is created.
Example:
interface Person {
name: string;
age?: number; // 'age' is an optional property
}
const person1: Person = { name: "Alice" }; // valid, age is optional
const person2: Person = { name: "Bob", age: 25 }; // valid, age is provided
In this example, the age property is optional, so person1 does not need to include it, while person2 includes both properties.
Error handling in TypeScript is done using the try, catch, and finally statements, just like in JavaScript. TypeScript enhances error handling by allowing you to type the errors in the catch block.
Example:
function throwError(message: string): never {
throw new Error(message);
}
try {
throwError("Something went wrong!");
} catch (error) {
if (error instanceof Error) {
console.error(error.message); // Access the error message safely
}
}
In this example:
Example:
// file1.ts
export const greet = (name: string) => `Hello, ${name}`;
// file2.ts
import { greet } from './file1';
console.log(greet("Alice"));
Example:
namespace MyNamespace {
export const greet = (name: string) => `Hello, ${name}`;
}
console.log(MyNamespace.greet("Alice"));
TypeScript's type system is primarily used at compile time, and there is no built-in runtime type checking. However, you can implement runtime type validation using custom checks or libraries such as zod or io-ts.
Example using custom validation:
interface Person {
name: string;
age: number;
}
function isPerson(obj: any): obj is Person {
return typeof obj.name === 'string' && typeof obj.age === 'number';
}
const obj = { name: "Alice", age: 30 };
if (isPerson(obj)) {
console.log(`${obj.name} is ${obj.age} years old`);
} else {
console.error("Invalid person object");
}
In this example, isPerson is a user-defined type guard that validates whether the object conforms to the Person interface.
Exclude<T, U> is a TypeScript utility type that constructs a type by excluding from T all types that are assignable to U.
Example:
type A = string | number | boolean;
type B = string | boolean;
type C = Exclude<A, B>; // Excludes 'string' and 'boolean' from 'A', so C is 'number'
In this example, C will be of type number, because string and boolean are excluded from type A.
NonNullable<T> is a TypeScript utility type that removes null and undefined from the type T. It ensures that a value cannot be null or undefined.
Example:
type MaybeString = string | null | undefined;
type NonNullableString = NonNullable<MaybeString>; // string
const value: NonNullableString = "Hello"; // Valid
// const invalid: NonNullableString = null; // Error: Type 'null' is not assignable to type 'string'.
When to use:
To avoid common pitfalls in TypeScript type compatibility, keep the following principles in mind:
{
"compilerOptions": {
"strict": true
}
}
By enabling strict mode, TypeScript will enforce stricter checks, helping to prevent subtle bugs.
The void type in TypeScript is used to represent the absence of a value. It is commonly used as the return type for functions that do not return a value.
Example:
function logMessage(message: string): void {
console.log(message);
}
const result = logMessage("Hello, TypeScript!");
// result is of type 'void', so you cannot use it further
When to use:
To create a type-safe event system in TypeScript, you can use interfaces and generics to define events and listeners with strongly typed parameters.
Example:
type EventListener<T> = (event: T) => void;
interface MyEvent {
type: "click";
x: number;
y: number;
}
class EventEmitter {
private listeners: Map<string, EventListener<any>[]> = new Map();
on<T>(eventType: string, listener: EventListener<T>) {
const listeners = this.listeners.get(eventType) || [];
listeners.push(listener);
this.listeners.set(eventType, listeners);
}
emit<T>(eventType: string, event: T) {
const listeners = this.listeners.get(eventType);
if (listeners) {
listeners.forEach(listener => listener(event));
}
}
}
const emitter = new EventEmitter();
// Register a listener for 'click' event
emitter.on<MyEvent>("click", (event) => {
console.log(`Clicked at (${event.x}, ${event.y})`);
});
// Emit a 'click' event
emitter.emit("click", { type: "click", x: 100, y: 200 });
In this example, TypeScript ensures that event listeners and emitted events are type-safe, reducing the likelihood of runtime errors.
typeof: The typeof operator is used to obtain the type of a variable or expression at compile time.Example:
const x = 42;
type XType = typeof x; // XType is 'number'
keyof: The keyof operator is used to obtain the union type of the keys (property names) of an object type.Example:
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person; // PersonKeys is 'name' | 'age'
Summary:
Type-safe dependency injection (DI) in TypeScript involves using classes, interfaces, and generics to ensure that dependencies are injected in a way that preserves type safety throughout your application.
Basic Implementation:
You can use TypeScript’s classes and interfaces to define services that are injected via a constructor, along with generics to ensure types are consistent.
Example:
interface Service {
doSomething(): void;
}
class MyService implements Service {
doSomething() {
console.log('Service is doing something');
}
}
class Consumer {
private service: Service;
constructor(service: Service) {
this.service = service;
}
useService() {
this.service.doSomething();
}
}
const myService = new MyService();
const consumer = new Consumer(myService);
consumer.useService(); // Output: Service is doing something
Using Generics for More Flexibility:
You can extend this with generics to create a more flexible DI system, ensuring the correct dependencies are injected.
class Injector {
private services = new Map<string, any>();
register<T>(name: string, service: T) {
this.services.set(name, service);
}
resolve<T>(name: string): T {
const service = this.services.get(name);
if (!service) {
throw new Error(`Service not found: ${name}`);
}
return service;
}
}
const injector = new Injector();
injector.register("myService", new MyService());
const myServiceInstance = injector.resolve<MyService>("myService");
myServiceInstance.doSomething(); // Type-safe dependency injection
This ensures that dependencies are injected correctly with their respective types, offering type safety while resolving dependencies.
Both never and unknown are special types in TypeScript, but they have different purposes:
Example:
function throwError(message: string): never {
throw new Error(message);
}
Example:
let value: unknown = 42;
if (typeof value === 'number') {
console.log(value.toFixed(2)); // Now it's safe to treat `value` as a number
}
Example:
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const merged = merge({ name: "Alice" }, { age: 25 });
Example:
interface Node<T> {
value: T;
children: Node<T>[];
}
const tree: Node<number> = {
value: 1,
children: [
{ value: 2, children: [] },
{ value: 3, children: [{ value: 4, children: [] }] }
]
};
Declaration files (.d.ts) provide TypeScript with type information about JavaScript code, allowing TypeScript to check types in code that doesn't have built-in type definitions (e.g., third-party libraries, external JavaScript code).
Example:
// person.d.ts
declare module 'person' {
export interface Person {
name: string;
age: number;
}
export function getPerson(): Person;
}
Why important:
Here are some key strategies to optimize a TypeScript application:
Use strict mode: Enable strict type-checking options in the tsconfig.json to catch more errors at compile-time.
{
"compilerOptions": {
"strict": true
}
}
Type guards in TypeScript are functions or expressions that narrow the type of a variable within a specific scope, ensuring that operations on the variable are type-safe.
typeof for primitive types:
function isString(value: unknown): value is string {
return typeof value === 'string';
}
instanceof for class instances:
function isDate(value: unknown): value is Date {
return value instanceof Date;
}
User-defined type guards for more complex checks:
function isPerson(obj: any): obj is Person {
return obj && typeof obj.name === 'string' && typeof obj.age === 'number';
}
Type guards enhance type safety by ensuring that you can safely perform operations on variables once their type has been narrowed.
Conditional types in TypeScript are used to define types that depend on a condition. They follow the pattern:
T extends U ? X : Y
Where if type T extends type U, the type will be X; otherwise, it will be Y.
Example:
type IsString<T> = T extends string ? "Yes" : "No";
type Test1 = IsString<string>; // "Yes"
type Test2 = IsString<number>; // "No"
Advanced Example with Conditional Types:
type Flatten<T> = T extends (infer U)[] ? U : T;
type TestArray = Flatten<number[]>; // number
type TestString = Flatten<string>; // string
Conditional types allow for more flexible and dynamic type definitions, useful for complex type transformations.
Strict null checks enforce that null and undefined are not assignable to any type except null and undefined, unless explicitly stated. This improves safety by preventing runtime errors where null or undefined values might be accessed incorrectly.
To enable strict null checks:
Set strictNullChecks to true in the tsconfig.json:
{
"compilerOptions": {
"strictNullChecks": true
}
}
After enabling this, null and undefined will be treated as distinct types. For example:
let name: string = null; // Error: Type 'null' is not assignable to type 'string'.
Why is it important:
The as const assertion in TypeScript is used to convert values into their most specific literal type, preventing them from being widened to broader types.
Example:
const colors = ["red", "green", "blue"] as const;
// colors is now of type ["red", "green", "blue"], not string[]
This allows you to define literal types more effectively:
type Colors = typeof colors[number]; // 'red' | 'green' | 'blue'
The as const assertion is useful when you want to treat a value as a specific constant type, rather than a broader type like string or number.