Can the inclusion of additional parameters compromise the type safety in TypeScript?

For demonstration purposes, let's consider this example: (playground)

type F0 = (x?: string) => void
type F1 = () => void
type F2 = (x: number) => void

const f0: F0 = (x) => console.log(x, typeof(x))
const f1: F1 = f0
const f2: F2 = f1

f0('s')
f2(1) // f0 outputs 1, 'number', but number is not (string | undefined)

In the above code snippet, we see that type F0 expects the first parameter x to be of type string | undefined, making it assignable to type F1 which takes no parameters. This is acceptable as calling a function of type F0 would pass an implicit undefined for the first parameter.

Within TypeScript, type F1 can be assigned to type

F2</code since extra parameters are allowed. The premise here is that a function of type <code>F1
would simply disregard any additional parameters passed to it.

The issue arises when assigning F1 to F2, as a call to a function of type

F2</code is incompatible with the expected type in a call to a function of type <code>F0
. Even though F0 is assignable to F1, it is not compatible with F2. Despite having strict mode enabled, the code compiles without any errors.


Real-life Scenario

Let's walk through a real-world context where this behavior can pose a problem:

function processData (optionalData?: DataType) {
  // implementation assumes optionalData to be DataType or undefined
}

// somewhere along the way, processData is converted into () => void
const handler: () => void = processData

// later on, handler is utilized in a DOM event handler, passing an Event object as the initial argument, while processData doesn't expect it
element.addEventListener('click', handler) // Although improper, it is permitted
element.addEventListener('click', () => handler()) // Correct approach

Answer №1

The prevailing answer to this query indicates the existence of a type safety gap stemming from optional parameters, rather than extra parameters, which is acknowledged as a design constraint in TypeScript. For more information, refer to microsoft/TypeScript#13043.


It is initially advisable to examine the concepts of soundness and completeness within a static type system. A sound type system never permits unsafe operations, while a complete type system never disallows safe operations. Creating a decidable type system for JavaScript that fulfills both requirements is deemed impossible; however, TypeScript is at least considered sound, right? And although it cannot be completely sound, does TypeScript only restrict potentially unsafe operations? Right? 😬

Surprisingly, TypeScript deliberately permits certain operations that are not type-safe. There exist vulnerabilities in the type system, allowing code to bypass these gaps and result in runtime errors without any warning from the compiler to safeguard you. The intentional unsoundness of TypeScript's type system persists because patching these holes would likely generate numerous errors in functioning real-world code, rendering the additional safety impractical. It stands as one of the official TypeScript Design Non-Goals so:

Apply a sound or "provably correct" type system. Instead, strike a balance between correctness and productivity.

Conversely, there are times when TypeScript intentionally prevents certain type-safe operations. These unnecessary obstacles may lead to scenarios where compiling the code leads to errors despite no runtime issues occurring. TypeScript's deliberate incompleteness manifests itself through linter-like rules that serve as warnings to developers, even if they adhere to fundamental type constraints.

Hence, intentionally unsound and incomplete define the TypeScript type system.


In TypeScript, functions with fewer parameters are deemed compatible with functions possessing more parameters (see relevant handbook doc and FAQ entry) since it typically harmless for a function to receive more arguments than anticipated. Any surplus arguments lacking corresponding parameters are simply disregarded during runtime.

Henceforth, permitting this assignment is regarded as sound:

type F1 = () => void
const f1: F1 = () => console.log("👍");

type F2 = (x: number) => void
const f2: F2 = f1

Given that f1 neglects any input received, calling f2 with an argument poses no issue:

f1(); // "👍"
f2(1); // "👍"

However, why does the compiler prevent this?

f1(1); // compiler error, Expected 0 arguments, but got 1.

This restriction falls under intentional incompleteness. Knowing that function f1() discards any inputs it may receive, passing in an argument is likely a developer oversight. While I sought official confirmation of this rationale through documentation and GitHub issues, its apparent obviousness may render such elucidation redundant; perhaps, nobody aims to deliberately invoke a function directly with superfluous arguments. (Please share any reliable sources corroborating this insight.)

If indeed users opt against such actions, why does the aforementioned assignment qualify as sound? Essentially, individuals desire to execute tasks like:

const arr = [1, 2, 3];
arr.forEach(x => console.log(x.toFixed(1))); //  "1.0" "2.0" "3.0"    
arr.forEach(() => console.log("👉")); // "👉" "👉" "👉"

arr.forEach(f2); // "👍" "👍" "👍"
arr.forEach(f1); // "👍" "👍" "👍" 

While direct invocation of the callback with excess arguments remains off limits, forEach()'s implementation accomplishes this task seamlessly, signifying it isn't problematic.

The alternative method would require:

arr.forEach((val, ind, arr) => f2(val)) // "👍" "👍" "👍"
arr.forEach((val, ind, arr) => f1()) // "👍" "👍" "👍"

which proves cumbersome (as per documented reasoning behind this rule).

Thus, this permitted assignment proves beneficial and sound, while direct calls are prohibited due to being ultimately futile though still sound.


When assessing compatibility between functions in TypeScript, interchangeable status applies to optional and required parameters. Extra optional parameters hailing from the source type do not trigger an error (relevant handbook doc). This flexibility enables universally treating optional parameters akin to missing ones, provvided operations pertain solely to direct function execution:

type F0 = (x?: string) => void
const f0: F0 = (x) => console.log(x?.toUpperCase())
f0('abc') // "ABC"

type F1 = () => void
const f1: F1 = f0
f1(); // undefined

Sadly, this approach lacks soundness, as scenario below ought to be type-safe:

[1, 2, 3].forEach(f1) // 💥 RUNTIME ERROR! 
// x.toUpperCase is not a function

Herein lies a loophole in the type system. Provided operation transpires directly, the compiler impedes inadvertent miscues:

[1, 2, 3].forEach(f0) // compiler error
//                ~~ <-- number not assignable to string

Hence, as unearthed, TypeScript's assignability does not demonstrate transitivity (referenced via microsoft/TypeScript#47499 and cited follow-up issues), subsequently undermining the overall soundness of the type system.

This conundrum emanates from conflating "optional" and "missing," perpetually witnessed across various scenarios. Optional properties reflect identical loopholes, expounded in this microsoft/TypeScript#47331 comment, essentially manifesting in situations like {x: number} extends {} accurately allowed, whereas {x: number} extends {x?: string} rightfully meets rejection. However, {} extends {x?: string} encounters undue allowance albeit immensely useful.


To recap, given:

type F0 = (x?: string) => void
declare let f0: F0;
type F1 = () => void
declare let f1: F1;
type F2 = (x: number) => void
declare let f2: F2;

This assignment ranks as sound and acceptable:

f2 = f1; // sound, true negative

Contrarily, this assignment deviates into unsound territory yet warrants approval:

f1 = f0; // unsound, false negative    

Mixing both scenarios delves us into unforeseen complications culminating in runtime errors.

Playground link to code

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

What is the best way to set the first option in a mat-select to be

My approach to date selection involves using 3 mat-select components for day, month, and year. You can view a demo of this setup here. In an attempt to improve the code, I decided to set the initial options as null by modifying the following lines: allDat ...

Give the Row ID as a parameter to a personalized component within MUI Datagrid Pro

I am currently exploring the idea of incorporating an intermediate state to row checkboxes based on the selection status of other checkboxes within a detailed panel. My approach involves crafting a custom checkbox component and implementing some logical ...

A guide on combining multiple arrays within the filter function of arrays in Typescript

Currently, I am incorporating Typescript into an Angular/Ionic project where I have a list of users with corresponding skill sets. My goal is to filter these users based on their online status and skill proficiency. [ { "id": 1, ...

A Typescript Function for Generating Scalable and Unique Identifiers

How can a unique ID be generated to reduce the likelihood of overlap? for(let i = 0; i < <Arbitrary Limit>; i++) generateID(); There are several existing solutions, but they all seem like indirect ways to address this issue. Potential Solu ...

Navigating through embedded arrays in Angular

JSON Object const users = [{ "name":"Mark", "age":30, "isActive" : true, "cars":{ Owned : ["Ford", "BMW", "Fiat"], Rented : ["Ford", "BMW", "Fiat" ...

Extending parent context in dependencies through OOP/Typescript as an alternative to using "extends"

Introducing a custom class called EventBus has been a game-changer for me. This class allows for easy attachment of on/off/once methods to any class that extends it, enabling the creation of an array of events that can be listened to. Currently, I find my ...

Angular's browser animation feature allows for smooth slide-up animations from the bottom

I have been experimenting with creating a simple animation, and it's working, but in the opposite direction of what I intended. I want the div to open from bottom to top and close from top to bottom. Here is my animation code in Angular: animations: ...

Integrating Immutable.js with Angular 2

Looking to optimize performance in your Angular 2 app with immutable.js? Although my app is functioning properly, I am aiming to enhance its performance through optimization and refactoring. I recently discovered immutable.js and want to convert the data ...

VS Code fails to identify Typescript internal modules

I am currently facing issues with separating my TypeScript classes into distinct files using internal modules. Unfortunately, the main.ts file is not loading or recognizing the sub-modules. main.ts /// <reference path="Car.ts" /> module Vehicles { ...

What is the best way to incorporate TypeScript into a simple JavaScript project and release it as a JavaScript library with JSDoc for TypeScript users?

Issue: I have encountered difficulties finding an efficient solution for this. Here's a summary of what I attempted: To start, ensure that allowJs and checkJs are both set to true in the tsconfig.json, then include the project files accordingly. Ty ...

Avoid the import of @types definition without exports in TypeScript to prevent the error TS2306 (not a module)

I have spent a considerable amount of time trying to load a NodeJS library that has what I believe is a faulty type definition in the @types repository. The library in question is geolib and its types can be found in @types/geolib Although I am aware tha ...

Exploring the Node environment setup within Nest.js

Currently, I am in the midst of setting up a Nest.js project and seeking an efficient solution for defining the Node environment used by the ConfigService to load environment variables: import { Module } from '@nestjs/common'; import { ConfigSer ...

Using {children} in NextJS & Typescript for layout components

I am looking to develop a component for my primary admin interface which will act as a wrapper for the individual screens. Here is the JavaScript code I have: import Header from '../Header' function TopNavbarLayout({ children }) { return ...

Is it possible to meet the requirements of a specific interface using an enum field as the criteria?

I've been struggling to create a versatile function that can return a specific interface based on an enum argument, but all my attempts have failed. Could it be possible that I missed something or am simply approaching it the wrong way? If I try to ...

What is the best way to set up TSLint to apply specific rules with one line and different rules with another line

There is a unique method in which I can specify the code to format, such as forcing the else statement to be on the same line as the ending brace of an if statement. "one-line": [ true, "check-open-brace", "check-catch", "check-else", "check-fin ...

Dayjs is failing to retrieve the current system time

Hey everyone, I'm facing an issue with using Dayjs() and format to retrieve the current time in a specific format while running my Cypress tests. Despite using the correct code, I keep getting an old timestamp as the output: const presentDateTime = da ...

Tips for preventing the ngbTypeahead input field from automatically opening when focused until all data is fully mapped

When clicking on the input field, I want the typeahead feature to display the first 5 results. I have created a solution based on the ngbTypeahead documentation. app.component.html <div class="form-group g-0 mb-3"> <input id="typ ...

Error: Unable to locate specified column in Angular Material table

I don't understand why I am encountering this error in my code: ERROR Error: Could not find column with id "continent". I thought I had added the display column part correctly, so I'm unsure why this error is happening. <div class="exa ...

PhantomJS version 2.1.1 encountered an error on a Windows 7 system, displaying "ReferenceError: Map variable not found."

I've been utilizing the "MVC ASP.NET Core with Angular" template. I'm attempting to incorporate phantomJS and execute the tests, but encountering the following errors: ERROR in [at-loader] ..\\node_modules\zone.js\dist&bs ...

Utilizing Angular 14 and Typescript to fetch JSON data through the URL property in an HTML

Is there a way to specify a local path to a JSON file in HTML, similar to how the src attribute works for an HTML img tag? Imagine something like this: <my-component data-source="localPath"> Here, localPath would point to a local JSON fil ...