What is the proper way to implement the coalesce function in my code?

I'm still learning about TypeScripts and other typed languages beyond just standard types. My goal is to find a more precise way to type this function, removing the any type for both parameters and the return type.

The function is designed to return the first value that is not null, undefined, or NaN.

/** Fetches the first non-null value excluding NaN */
const coalesce = (...args: any): any => {
  for (let i = 0; i < args.length; i++) {
    // args[i] === args[i] is used to exclude NaN, as NaN !== NaN
    if (args[i] != null && args[i] === args[i]) {
      return args[i];
    }
  }
  return null;
};

This is an example of how the function is used:

test('coalesce returns the first value that is not null, undefined, NaN', () => {
  expect(coalesce(null, undefined, NaN, 'maybe')).toBe('maybe');
}); // -> returns 'maybe'

Answer №1

To simplify the process, we can define a function that captures the types of the parameters we pass and returns a union of all parameter types.

By using tuples for rest parameters to gather all parameter types as a tuple type, and utilizing type queries to create a union of all types in the tuple:

const coalesce = <T extends any[]>(...args: T): T[number] => {
    for (let i = 0; i < args.length; i++) {
        // args[i] === args[i] is used to avoid NaN comparison, as NaN !== NaN
        if (args[i] != null && args[i] === args[i]) {
            return args[i];
        }
    }
    return null;
};

// o will be string | number | null | undefined under strict null checks
// T is [null, undefined, number, string], so T[number] results in string | number | null | undefined
let o = coalesce(null, undefined, NaN, 'maybe')  

The main purpose of the function is to eliminate nulls and undefined values (including NaN, which are not denoted in the type system and hence cannot be handled). The current signature retains null and undefined types. To address this, mapped types and conditional types can be incorporated to eliminate null and undefined if any parameter cannot be null or undefined (as any non-null/non-undefined parameter will be returned).

type ExcludeNullIfAnyNotNullHelper<T> = {
    [P in keyof T]-?: (null extends T[P] ? never : null) |
        (undefined extends T[P] ? never : undefined) 
}[keyof T]

type ExcludeNullIfAnyNotNull<T extends any[]> = Exclude<T[number], ExcludeNullIfAnyNotNullHelper<T>>

// Examples of removing null and undefined types based on conditions
type t1 = ExcludeNullIfAnyNotNull<[null, undefined, number, string]> // number | string
type t2 = ExcludeNullIfAnyNotNull<[null | string, string]> // string
type t4 = ExcludeNullIfAnyNotNull<[null | undefined | string, string | null]> // string | null

function coalesce<T extends any[]>(...args: T): ExcludeNullIfAnyNotNull<T>
function coalesce<T extends any[]>(...args: T): T[number] {
    for (let i = 0; i < args.length; i++) {
        // args[i] === args[i] is to avoid NaN, because NaN !== NaN
        if (args[i] != null && args[i] === args[i]) {
            return args[i];
        }
    }
    return null;
};

// o will be string | number 
let o = coalesce(null, undefined, NaN, 'maybe')

Answer №2

Describing the type function you're attempting is quite complex.


NaN poses an issue as TypeScript lacks a representation for NaN as a literal type. The value NaN falls under the type number, making it challenging for the compiler to distinguish if a value of type number is potentially NaN. Therefore, when encountering a number argument, it must be treated as number | null for the coalesce() function. For instance:

function hmm(n: number) { return coalesce(n, "oops"); }

should yield a value of type number | "oops", rather than just number. A slight improvement can be made by explicitly recognizing that a numeric literal cannot be NaN, such as in this case:

coalesce(123, "oops");

where the result should be 123 and not 123 | "oops", as 123 is a confirmed non-NaN number.


By treating arguments to coalesce() as a tuple, the goal is to iterate through the tuple from left to right, aggregating a union of the types while removing any potential null or undefined types. If a type that is unquestionably not null, undefined, or

number</code is encountered, the process stops. Otherwise, if it reaches the end without such certainty, <code>null
is added to the union, and the process concludes.

This recursive type function is not currently supported by TypeScript, necessitating approaches like unrolling the recursive type to a fixed depth before terminating.

Types can be defined as follows:

// Definitions...

The design leads to a fixed depth with orderly progression to handle longer argument lists. The above arrangement is suitable for tuples up to a certain length before transitioning to a union for longer lists.


To include literal type arguments such as 123 without automatic widening, a hint must be given to the compiler using a type like Narrowable.

type Narrowable = string | number | boolean | symbol | object |
  null | undefined | void | ((...args: any[]) => any) | {};

Finally, typing coalesce() unveils an accurate assessment based on the defined logic:

const coalesce = <T extends Narrowable[]>(...args: T): FirstNonNull<T> => {
  // Implementation...
};

Adapting this pattern, the results demonstrate the robustness of the function:

// Examples...

The outcomes are in alignment with the expected behavior of coalesce().


Addressing this multidimensional concept in type definitions is intricate yet impactful. The provided insights and structures may prove beneficial in your endeavors. Best of luck!

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

Include a class in ul > li elements upon page load in Angular4

I attempted to add a class to each "li" element in an Angular4 page, but the class was not applied. Here is the relevant HTML code: <ul class="pagination"> <button class="previous" (click)="previous()">Previous</button> <button ...

What is the best way to call an Angular component function from a global function, ensuring compatibility with IE11?

Currently, I am facing a challenge while integrating the Mastercard payment gateway api into an Angular-based application. The api requires a callback for success and error handling, which is passed through the data-error and data-success attributes of the ...

Issues with Angular2 causing function to not run as expected

After clicking a button to trigger createPlaylist(), the function fails to execute asd(). I attempted combining everything into one function, but still encountered the same issue. The console.log(resp) statement never logs anything. What could be causing ...

Functional programming: Retrieve the initial truthy output from executing an array of diverse functions using a particular parameter

As I delve into the world of functional programming in TypeScript, I find myself contemplating the most idiomatic way to achieve a specific task using libraries like ramda, remeda, or lodash-fp. My goal is to apply a series of functions to a particular dat ...

Ensure all promises are resolved inside of for loops before moving on to the next

Within my angular 11 component, I have a process that iterates through elements on a page and converts them from HTML to canvas to images, which are then appended to a form. The problem I am encountering is that the promise always resolves after the ' ...

Creating callback functions that vary based on input variables

Consider the following code snippet, which may seem somewhat contrived: arbitraryFunction( // Input that is dynamically generated [ returnValue("key1", "a"), returnValue("key2", 1), returnValue ...

When the next() function of bcrypt.hash() is called, it does not activate the save method in mongoose

Attempting to hash a password using the .pre() hook: import * as bcrypt from 'bcrypt'; // "bcrypt": "^1.0.2" (<any>mongoose).Promise = require('bluebird'); const user_schema = new Schema({ email: { type: String, required: tru ...

Generate a new array, specifying its type, and populate it by utilizing Angular

My current situation involves a variable that is an array with the type Club. Within this array, a function is responsible for populating it. clubs: [Club]; This function is as follows: this.authService.getAllClubs().subscribe( clubs => { ...

Are there any comparable features in Angular 8 to Angular 1's $filter('orderBy') function?

Just starting out with Angular and curious about the alternative for $filter('orderBy') that is used in an AngularJS controller. AngularJS example: $scope.itemsSorted = $filter('orderBy')($scope.newFilteredData, 'page_index&apos ...

Issue with displaying Typescript sourcemaps on Android device in Ionic 2

Encountering a strange behavior with Ionic2. After deploying my app to a simulator, I am able to view the .ts file sourceMap in the Chrome inspect debugger. In both scenarios, I use: ionic run android https://i.sstatic.net/JarmI.png However, when depl ...

What is the method for comparing a value in TypeScript that does not match a given value?

I am new to scripting languages and encountered an issue while using enums with if-else statements in TypeScript. To work around this problem, I have decided to use switch-case instead of if-else conditions. Despite trying !== and !===, they do not seem t ...

Guide on converting a JSON object into a TypeScript Object

I'm currently having issues converting my JSON Object into a TypeScript class with matching attributes. Can someone help me identify what I'm doing wrong? Employee Class export class Employee{ firstname: string; lastname: string; bi ...

To ensure the next line only runs after the line above has finished executing, remember that the function is invoked in HTML

my.component.html <button (click)="refresh()">Refresh</button> my.component.ts refresh() { let self = this; self.isRefresh = true; //1st time self.getfun().then(() => { self.isRefresh = false; ...

Bring in all Functions and Interfaces from the Types Definition

How can I call the function below in TypeScript: nlp.text("Hi Dr. Miller the price is 4.59 for the U.C.L.A. Ph.Ds.").sentences.length // 1 To make this function call work, what would be the correct import statement needed from this types definition? It& ...

I'm struggling to get a specific tutorial to work for my application. Can anyone advise me on how to map a general URL to the HTTP methods of an API endpoint that is written in C

I am struggling to retrieve and display data from a C# Web API using Typescript and Angular. As someone new to Typescript, I followed a tutorial to create a service based on this guide: [https://offering.solutions/blog/articles/2016/02/01/consuming-a-rest- ...

Sorting in the TypeScript/Angular table is functioning properly on every column except for the initial one

Even after carefully following the information provided in the official documentation and implementing the example as suggested, I'm still struggling to sort my first column in descending order. Whenever I attempt to sort by another column and then cl ...

Is There a Comparable Feature to *ngIf in DevExtreme?

Currently, I am diving into the world of webapp development using DevExtreme. As a novice in coding, this is my first time exploring the functionalities of DevExtreme. Essentially, I am seeking guidance on how to display certain elements based on specific ...

Both undefined and null are sometimes allowed as values in conditional types, even when they should not be

Do you think this code should trigger a compiler error? type Test<T extends number | string> = { v: T extends number ? true : false } const test: Test<1> = { v: undefined } Is there something I am overlooking? Appreciate your help! ...

Tips for utilizing intellisense from monaco.d.ts

Is there a way for me to incorporate monaco.d.ts in order to utilize intellisense with the monaco-editor package? I've recently integrated this package into a JavaScript project and everything is functioning properly. However, as I transition to Type ...

What is the best way to declare and initialize a global variable in a TypeScript Node application?

When using Webpack: const WebpackConfig = { // ... plugins: [ new Webpack.DefinePlugin({ __IS_DEVELOPMENT_BUILDING_MODE__: isDevelopmentBuildingMode, __IS_TESTING_BUILDING_MODE__: isTestingBuildingMode, __IS_PRODUCTION_BUILDING_MO ...