TypeScript: A few things I picked up today

I spent some time today going through the official Typescript docs. Even though I use TypeScript daily, I realized I barely understand how some concepts actually work. I don’t just want to write TypeScript that works. I want to fully understand my typescript code (okay, maybe not fully, but enough to make sense of it lol). Below are some of the things i learnt.

1. Narrowing

Narrowing is the process of refining broad types into more specific ones based on runtime checks. Let’s look at a simple example:

function function printLength(value: string | number): voidprintLength(value: string | numbervalue: string | number) {
  var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(value: string | numbervalue.length);
Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'.
}

Our printLength function accepts a parameter that can be either a string or a number. But when we try to use it, TypeScript throws an error.

Why? While strings have a .length property, numbers don’t, so TypeScript can’t guarantee that .length will always be available. We can fix this by adding a type check:

function printLength(value: string | number) {
  if (typeof value === "string") {
    console.log(value.length);
  } else {
    console.log(value);
  }
}

Now, something interesting happens. Inside the if block, TypeScript automatically narrows value to a string because we’ve confirmed it is one. That’s why value.length no longer causes an error.

The same applies to the else block. TypeScript knows that if value isn’t a string, then it must be a number, so it treats it as such inside that block. Outside the if-else, however, value remains string | number since no further checks have been made.

Here’s another simple example:

function processInput(value: string | number | boolean) {
  let result: string | number | boolean;

  if (typeof value === "string") {
    result = value.toUpperCase(); // Narrowed to string
  } else if (typeof value === "number") {
    result = value * 2; // Narrowed to number
  } else {
    result = value ? 1 : 0; // Narrowed to boolean -> mapped to number
  }

  return result; // TypeScript correctly infers result as string | number
}

I found this one even more interesting because it makes so much sense. At the start, result is initialized with the type string | number | boolean. But when we check the type of result at the return statement, it’s inferred as string | number.

Why? TypeScript examines all possible execution paths and determines the final possible types. Since result is always transformed into either a string or a number, the boolean type is discarded from the union. Regardless of how the function executes, result will never be returned as a boolean, so TypeScript removes it from the final inferred type.

Other Ways to Narrow Types

Up until this point, we have been using the typeof type guard for checks. But there are other ways to narrow types in TypeScript.

Using instanceof (Type Guard for Objects)

function formatDate(value: Date | string) {
  if (value instanceof Date) {
    console.log(value.toISOString()); // TypeScript knows value is a Date here
  } else {
    console.log(value.toUpperCase()); // TypeScript knows value is a string here
  }
}

Using in Operator (Type Guard for Object Properties)

type Car = { brand: string; drive: () => void };
type Bike = { brand: string; pedal: () => void };

function moveVehicle(vehicle: Car | Bike) {
  if ("drive" in vehicle) {
    vehicle.drive(); // TypeScript knows vehicle is a Car here
  } else {
    vehicle.pedal(); // TypeScript knows vehicle is a Bike here
  }
}

Using Equality Narrowing

function processInput(value: string | number | null) {
  if (value === null) {
    console.log("No value provided."); // TypeScript knows value is null here
  } else if (value === 0) {
    console.log("Zero detected."); // TypeScript knows value is exactly 0 here
  } else {
    console.log(`Valid input: ${value}`); // TypeScript now knows value is string | number
  }
}

There are other ways to narrow types in TypeScript, but these are the most commonly used.

2. Discrimated unions

A discriminated union is a type of union. Union types describe a value that can be one of several types.

type NumberOrString = number | string;

function printNumberOrString(value: NumberOrString) {
  console.log(value);
}

In the example above, NumberOrString is a union type because it is formed from two existing types and represents values that can be either a number or a string.

A discriminated union is a special kind of union type where all members share a common property with a literal type that identifies the specific type in the union. This property is known as the discriminator or tag. Let’s look at an example:

type Circle = {
  type: "circle";
  radius: number;
};

type Square = {
  type: "square";
  length: number;
};

type Shape = Circle | Square;

function getArea(shape: Shape) {
  if (shape.type === "circle") {
    return Math.PI * shape.radius ** 2;
  }
}

In this example, Shape is a discriminated union because it combines Circle and Square, both of which share a common property: type. The type property acts as the discriminator, allowing TypeScript to determine whether a given shape is a circle or a square. Inside the getArea function, checking shape.type === "circle" confirms that shape is a Circle, so TypeScript allows access to the radius property without any errors.

Let’s look at another example:

type 
type Bike = {
    kind: "bike";
    model: string;
    color: string;
    wheels: number;
}
Bike
= {
kind: "bike"kind: "bike"; model: stringmodel: string; color: stringcolor: string; wheels: numberwheels: number; }; type
type Car = {
    kind: "car";
    model: string;
    color: string;
    wheels: number;
    doors: 5;
}
Car
= {
kind: "car"kind: "car"; model: stringmodel: string; color: stringcolor: string; wheels: numberwheels: number; doors: 5doors: 5; }; type type Vehicle = Bike | CarVehicle =
type Car = {
    kind: "car";
    model: string;
    color: string;
    wheels: number;
    doors: 5;
}
Car
|
type Bike = {
    kind: "bike";
    model: string;
    color: string;
    wheels: number;
}
Bike
;
const const kawasaki: Vehiclekawasaki: type Vehicle = Bike | CarVehicle = { kind: "bike"kind: "bike", model: stringmodel: "Ninja ZX", color: stringcolor: "black", wheels: numberwheels: 2, doors: 0,
Object literal may only specify known properties, and 'doors' does not exist in type 'Bike'.
};

When we try to assign the doors property to kawasaki, TypeScript throws an error. Even though 0 doors is technically correct, the Bike type does not include a doors property, so TypeScript prevents us from adding it.

But how does TypeScript know kawasaki is a bike? The answer lies in the kind property, the discriminator. Since we explicitly set kind: "bike", TypeScript understands that kawasaki must follow the Bike type definition. Adding a doors property contradicts this definition, which is why the error occurs.

3. Structural typing in Typescript

Structural typing is a way of deterniming compatibility of two types based on their atual structure rather than their names. Here’s a simple example:

type Cat = { name: string; meow: () => void };
type Dog = { name: string; bark: () => void };

let pet: Cat = { name: "Whiskers", meow: () => console.log("Meow!") };

let randomPet = { name: "Buddy", meow: () => console.log("Meow!") };
pet = randomPet; // Allowed, because it has the same structure

In the example above even though randomPet is not explicitly a Cat, it fits the structure, so TypeScript allows the assignment.This contrasts with nominal typing, where types are only compatible if explicitly declared as the same type.

Structural typing also allows objects to have additional properties beyond those initially defined in a type. Let’s modify our example:

// ...rest of the code
let randomPet = {
  name: "Buddy",
  meow: () => console.log("Meow!"),
  owner: "John Doe",
};

pet = randomPet;

You might expect this to throw an error since randomPet has an extra owner property that Cat does not define. However, TypeScript still allows the assignment because randomPet contains all the required properties to be considered a Cat. Extra properties do not affect compatibility when assigning an object to a type with fewer properties.

Structural typing makes it easier and more flexible to work with different types that share the same structure.

4. Type aliases and Intersection Types

A type alias is simply a name for a type. It can represent any type, making it easier to reuse and maintain:

type Name = string;
type Age = number;

Here, Name and Age are type aliases for string and number, respectively.

Intersection types allow us to combine multiple types into one using the & operator. This is useful when an object needs to satisfy multiple type definitions at once.

type 
type Person = {
    name: string;
    age: number;
}
Person
= { name: stringname: string; age: numberage: number };
type
type Employee = {
    company: string;
    role: string;
}
Employee
= { company: stringcompany: string; role: stringrole: string };
type type Worker = Person & EmployeeWorker =
type Person = {
    name: string;
    age: number;
}
Person
&
type Employee = {
    company: string;
    role: string;
}
Employee
; // intersection type
const const worker: Workerworker: type Worker = Person & EmployeeWorker = { name: stringname: "John", age: numberage: 23, company: stringcompany: "TechCorp", role: stringrole: "Developer", };

In this example, Worker is an intersection of Person and Employee. This means worker must have all the properties from both types.

5. Generics

Generics in TypeScript allow us to write reusable and flexible code that can work with multiple data types while maintaining type safety.

Take this basic function:

function function printItem(item: any): voidprintItem(item) {
Parameter 'item' implicitly has an 'any' type.
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(item: anyitem);
}

This function works with any value, but TypeScript doesn’t enforce type safety. We could use any to explicitly allow any type, but that defeats the purpose of TypeScript’s type checking. This is where generics come in:

function printItem<Type>(item: Type): Type {
  console.log(item);
  return item;
}

// Using the generic function
let str = printItem<string>("hello");
let num = printItem<number>(42);

A type parameter <Type> acts as a placeholder for the actual type that will be used. When we define item: Type, it means item will take on whatever type is provided when calling the function. The return type Type ensures that the function returns a value of the same type that was passed in.

For example, calling printItem<string>("hello") tells TypeScript that Type is string, so item must be a string. Similarly, printItem<number>(42) infers that Type is number, meaning item must be a number. This approach allows us to write flexible yet type-safe code that adapts to different data types while maintaining correctness.

A more practical example is:

function function getFirstItem<T>(arr: T[]): TgetFirstItem<function (type parameter) T in getFirstItem<T>(arr: T[]): TT>(arr: T[]arr: function (type parameter) T in getFirstItem<T>(arr: T[]): TT[]): function (type parameter) T in getFirstItem<T>(arr: T[]): TT {
  return arr: T[]arr[0];
}

let let firstString: stringfirstString = function getFirstItem<string>(arr: string[]): stringgetFirstItem<string>(["apple", "banana", "cherry"]);
let let firstNumber: numberfirstNumber = function getFirstItem<number>(arr: number[]): numbergetFirstItem<number>([10, 20, 30]);

var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(let firstString: stringfirstString); // "apple"
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(let firstNumber: numberfirstNumber); // 10

Here, T[] ensures that the function takes an array of any type, and T guarantees that the returned value matches the type of the array elements.

We are not limited to using a single type parameter. Generics allow us to define multiple type variables for even greater flexibility.

function function transform<Input, Output>(value: Input, transformer: (val: Input) => Output): Outputtransform<function (type parameter) Input in transform<Input, Output>(value: Input, transformer: (val: Input) => Output): OutputInput, function (type parameter) Output in transform<Input, Output>(value: Input, transformer: (val: Input) => Output): OutputOutput>(
  value: Inputvalue: function (type parameter) Input in transform<Input, Output>(value: Input, transformer: (val: Input) => Output): OutputInput,
  transformer: (val: Input) => Outputtransformer: (val: Inputval: function (type parameter) Input in transform<Input, Output>(value: Input, transformer: (val: Input) => Output): OutputInput) => function (type parameter) Output in transform<Input, Output>(value: Input, transformer: (val: Input) => Output): OutputOutput
): function (type parameter) Output in transform<Input, Output>(value: Input, transformer: (val: Input) => Output): OutputOutput {
  return transformer: (val: Input) => Outputtransformer(value: Inputvalue);
}

const const lengthOfString: numberlengthOfString = function transform<string, number>(value: string, transformer: (val: string) => number): numbertransform<string, number>(
  "TypeScript",
  (str: stringstr) => str: stringstr.String.length: number
Returns the length of a String object.
length
); var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(const lengthOfString: numberlengthOfString); // 10
const const booleanToString: stringbooleanToString = function transform<boolean, string>(value: boolean, transformer: (val: boolean) => string): stringtransform<boolean, string>(true, (bool: booleanbool) => bool: booleanbool ? "Yes" : "No" ); var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(const booleanToString: stringbooleanToString); // "Yes"

In this example, transform<Input, Output> takes two type variables:

  1. Input represents the type of the input value.
  2. Output represents the type of the returned value.

The function accepts a value of type Input and a transformer function that converts it into Output. This ensures type safety while keeping the function highly adaptable.

Generics constraints

Sometimes, we want a function to work with any type, but only if that type meets certain criteria. For example, let’s say we want to return the length of an item:

function function getLength<T>(val: T): numbergetLength<function (type parameter) T in getLength<T>(val: T): numberT>(val: Tval: function (type parameter) T in getLength<T>(val: T): numberT): number {
  return val: Tval.length;
Property 'length' does not exist on type 'T'.
}

The issue here is that TypeScript doesn’t know if T has a .length property. To fix this, we add a constraint using the extends keyword:

function 
function getLength<T extends {
    length: number;
}>(val: T): number
getLength
<
function (type parameter) T in getLength<T extends {
    length: number;
}>(val: T): number
T
extends { length: numberlength: number }>(val: T extends { length: number; }val:
function (type parameter) T in getLength<T extends {
    length: number;
}>(val: T): number
T
): number {
return val: T extends { length: number; }val.length: numberlength; // Now TypeScript knows 'length' exists }

This fixes the error and ensures type safety, allowing only items with a .length property, such as arrays and strings, to be passed as arguments. The example below throws an error because numbers do not have a .length property.

function 
function getLength<T extends {
    length: number;
}>(val: T): number
getLength
<
function (type parameter) T in getLength<T extends {
    length: number;
}>(val: T): number
T
extends { length: numberlength: number }>(val: T extends { length: number; }val:
function (type parameter) T in getLength<T extends {
    length: number;
}>(val: T): number
T
): number {
return val: T extends { length: number; }val.length: numberlength; } const const numLength: numbernumLength =
function getLength<{
    length: number;
}>(val: {
    length: number;
}): number
getLength
(65);
Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.

As i dived deeper into the official documentation on generics, i thought i was getting the hang of it, then suddenly, nothing made sense. I took that as my cue to call it a day. Generics are vast and have a steep learning curve, but I’m looking forward to making more sense of them over time.

Wrapping up

Overall, I got a better understanding of the reasoning behind some of the concepts I use. It also made me realize just how much I still have to learn. But that’s all part of the process, figuring things out one step at a time.

Further reading

These sources proved helpful while I was covering the topics in this article:

2,098 words

© 2023. All rights reserved.