Are optional parameters in TypeScript distinct from parameters that have the ability to be undefined?

Is there a distinction between the following two code snippets?

function sayHello(name?: string) {
  if (name) { return 'Hello ' + name; }
  return 'Hello!';
}

and

function sayHello(name: string | undefined) {
  if (name) { return 'Hello ' + name; }
  return 'Hello!';
}

(I am aware that adding another non-optional argument after 'name' is not allowed as it should be the last or one of the last arguments)

This question crossed my mind today, and I believe the main difference lies in what message you are conveying to the function's user.

The first implies flexibility, stating "you have the choice to input this parameter, but it's not mandatory." The second states "input a string, even if it's undefined, I can handle it."

A similar concept applies to interfaces and types as well.

interface Foo {
   thing?: string;
}

compared to

interface Foo {
   thing: string | undefined;
}

Do you think I'm on the right path here? Anything else I should take into account?

Answer №1

You're on the right path and quite accurate.


For the purposes ahead, let's assume you have enabled the --strict or at least the --strictNullChecks compiler option to disallow implicit permission for undefined and null:

let oops: string = undefined; // error! 
// Type 'undefined' is not assignable to type 'string'

In TypeScript, when a function/method parameter or object type field is denoted as optional using the ? modifier, it indicates that it can be absent:

function opt(x?: string) { }

interface Opt {
    x?: string;
}

const optObj: Opt = {}; // okay
opt(); // okay

However, these optional parameters/fields can also be present but undefined:

const optObj2: Opt = { x: undefined } // okay
opt(undefined); // okay

When examining the types of such optional parameters/fields using IntelliSense, you'll notice that the compiler automatically considers undefined as a possibility:

function opt(x?: string) { }
// function opt(x?: string | undefined): void

interface Opt {
    x?: string;
}
type AlsoOpt = Pick<Opt, "x">;
/* type AlsoOpt = {
    x?: string | undefined;
} */

From both the function implementer's and the object type consumer's perspective, the optional element can be handled as if always present but potentially undefined:

function opt(x?: string) {
    console.log(typeof x !== "undefined" ? x.toUpperCase() : "undefined");
}

function takeOpt(v: Opt) {
    const x = v.x;
    console.log(typeof x !== "undefined" ? x.toUpperCase() : "undefined");
}

Differentiating this scenario from a required (non-optional) field or parameter that incorporates | undefined:

function req(x: string | undefined) { }

interface Req {
x: string | undefined
}

Similar to their optional counterparts, required fields with | undefined accept an explicit undefined. However, they cannot operate without passing the value entirely missing:

req(); // error, Expected 1 arguments, but got 0!
req(undefined); // okay
const reqObj: Req = {}; // error, property x is missing!
const reqObj2: Req = { x: undefined } // okay

Like before, the implementer of the function or the consumer of the object type will view the required elements as definitely present but possibly undefined:

function req(x: string | undefined) {
console.log(typeof x !== "undefined" ? x.toUpperCase() : "undefined");
}

function takeReq(v: Req) {
const x = v.x;
console.log(typeof x !== "undefined" ? x.toUpperCase() : "undefined");
}

Additional points to consider:


Tuple types also include optional elements, working in a similar manner to optional object fields. Nonetheless, they follow the restriction where if any tuple element is optional, all subsequent ones must also be optional:

type OptTuple = [string, number?];
const oT: OptTuple = ["a"]; // okay
const oT2: OptTuple = ["a", undefined]; // okay

type ReqTuple = [string, number | undefined];
const rT: ReqTuple = ["a"]; // error! Source has 1 element(s) but target requires 2
const rT2: ReqTuple = ["a", undefined]; // okay

With function parameters, the void type can sometimes signify "missing," hence utilizing | void to denote "optional." This was introduced in microsoft/TypeScript#27522. Thus, x?: string and x: string | void are handled similarly:

function orVoid(x: string | void) {
console.log((typeof x !== "undefined") ? x.toUpperCase() : "undefined");
}
orVoid(); // okay

While applicable for function parameters, this feature has not been extended to object fields yet. It was proposed in microsoft/TypeScript#40823 but remains pending implementation into the language:

interface OrVoid {
x: string | void;
}
const o: OrVoid = {} // error! x is missing

Lastly, I recommend avoiding scenarios where differentiating between missing and undefined holds significance.


If interested, explore more about the upcoming changes concerning optional property types in TypeScript through this --exactOptionalPropertyTypes compiler flag soon to be included in TypeScript 4.4, providing distinctions regarding optional properties' presence or absence.

Access the code in Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The Typescript generics are unable to be assigned to type T

Take a look at my code snippet: interface IDoc { doSomething(): void } class IDocFoo implements IDoc { doSomething(): void { console.log('Do doSomething'); } } class IDocFactory { getIDocInstance<T extends IDoc>(): T { re ...

Receiving feedback from an http.post request and transferring it to the component.ts file in an Angular

Currently, I am facing an issue with passing the response from an http.post call in my TypeScript service component to an Angular 2 component for display on the frontend. Below are the code structures of my service.ts and component.ts: getSearchProfileRes ...

The property 'filter' is not recognized on the 'Object' type. An attempt to filter the response was made

Trying to fetch data from a JSON file that matches the player's name in the URL, such as localhost:4200/players/Febiven, should only retrieve information about Febiven. The code is written in Angular 6. The current code snippet is as follows: player ...

From transitioning from AngularJS to the latest version Angular 8

I have an Angular application where I need to update some old AngularJS code to work with Angular table.html <table ngFor="let group of vm.groups" style="float: left"> <thead> <tr> <th><b>Sl. No</b ...

Using the keyof lookup in a Typescript interface is a powerful way to

I'm looking for a solution similar to: interface Operation<T, K extends keyof T> { key: keyof T; operation: 'add' | 'remove'; value: T[K]; } but without the necessity of passing K as a template. Essentially, I want to ...

Issue with ReactTS Route Triggering Invalid Hook Call

My implementation of the PrivateRoute component is as follows: interface Props { path: string, exact: boolean, component: React.FC<any>; } const PrivateRoute: React.FC<Props> = ({ component, path, exact }) => { return ( ...

Retrieve the non-empty attributes of a JSON object

I have a function in Typescript that extracts specific values from a JSON data object, some of which may be empty. How can I retrieve only certain data fields? Here is the function: let datosCod; for (let get in Object.keys(transfConsData)) { co ...

The ngAfterContentInit lifecycle hook is not triggered when the parent component updates the child component

I am trying to understand the functionality of the ngOnChanges callback in Angular. I have implemented it to observe changes in a property annotated with the Input decorator as shown below: @Input() postsToAddToList: Post[] = []; However, after compiling ...

To switch to desktop mode, double click; for mobile view, just tap once

I am looking to implement 2 different gestures for a specific feature in my application. Ideally, I want users to be able to double click on a card to open it in desktop view, but if they are using a phone, a single tap should suffice. How can I achieve th ...

Angular 4 allows for dynamically applying the active class to a clicked button, enhancing interactivity

Issue: <button *ngFor="let button of buttons" [ngClass]="{'active': isClicked}" (click)="isClicked = !isClicked" Description: A total of 10 buttons are displayed on the screen. When I click on button number 1, each button receives the clas ...

What is the best way to limit the types of function parameters in TypeScript based on whether the parameter index is even or odd?

My goal is to create a function with an unlimited number of parameters, where the type of each parameter is determined by whether its index is odd or even. For example: flow(isMachineReady(), 'and', isWaterHot(), 'or', isMilkHot(), &ap ...

Issue encountered when working with interface and Observable during the http parsing process

Visual Studio File Structure Error app(folder) -->employee-list(folder) -->employee-list.component.html -->employee-list.component.ts -->app.component.html -->app.component.ts -->app.module.ts -->employee.json ...

Building and executing an Angular 2 program on the Windows platform: Step-by-step guide

After successfully installing npm and related packages including TypeScript and Angular 2, I am encountering difficulties running Angular 2 in my browser on a Windows platform. Can someone provide a step-by-step guide to help me create and run Angular 2 ...

Manipulating data with Angular 2 services: accessing and updating values

This code snippet is all about managing an array in Angular. The Injectable decorator is used to define a service called Svc with methods for setting and getting column definitions. import { Injectable } from '@angular/core'; @Injectable() ...

Accessing the property of an object with TypeScript

I am working with an array of objects, where each object contains two properties: {key:count} When configuring my chart, I need to set the data source in this format: {meta: "unknown", value: [the count of unknown]}, {meta: "male", value: [the count of ...

Tips for altering a key within a tree-view:

I am working with a potentially infinite tree-view array: type Tree = { id: number; name: string; email: string; children: Tree[]; }; const tree: Tree[] = [ { id: 1, name: 'Truck', email: '@mail', children ...

Adding types to computed properties in Vue 3's Composition API is a seamless process

Having an issue where I am trying to add type to computed but keep encountering this error: Overload 1 of 2, '(getter: ComputedGetter<AuthFormType>, debugOptions?: DebuggerOptions | undefined): ComputedRef<AuthFormType>', gave the fol ...

Error: WebStorm's Language Service has timed out while executing TSLint operations

While working on my Mac running MacOS Sierra with WebStorm (version 2017.2.4), I encounter a pop-up notification sporadically, as shown in the following image: My coworkers and I all have the same TSLint configuration and code style set up in WebStorm. Ho ...

Start incrementing from the value of x

I'm trying to create an incremental column in Node.js with TypeORM, similar to this: @Column() @Generated('increment') public orderNumber: number; Is there a method to set TypeORM to begin counting from 9000 instead of the default starting ...

Performing an insertion in TypeORM with a foreign key connection

In my database schema, I have set up a relationship where each Chatroom can have multiple Messages linked to it. However, when I try to insert a Message (or a batch of Messages), the foreign key for ChatRoom is not being assigned properly and remains null. ...