The new operator is failing because it’s being used with a function that returns void, meaning it doesn’t produce a value that can be instantiated.

Common Causes and Fixes

  1. Accidentally calling a void function with new: This is the most straightforward cause. You might have a function that’s intended for side effects only, but you’re trying to create an instance of it.

    • Diagnosis: Look at the function signature. If it explicitly returns void or has no return type annotation (which defaults to void in TypeScript), this is your culprit.
      function doSomething(): void {
          console.log("Performing an action.");
      }
      
    • Fix: Remove the new keyword or change the function to return a value. If doSomething is meant to be a constructor, it should return this or an instance of the class it belongs to.
      // Option 1: If it's not meant to be a constructor
      doSomething();
      
      // Option 2: If it *is* meant to be a constructor (e.g., a class method)
      class MyClass {
          constructor() {
              console.log("Instance created.");
          }
          // ... other methods
      }
      const instance = new MyClass();
      
    • Why it works: The new operator in JavaScript (and by extension, TypeScript) expects the constructor function to return an object. If it returns undefined (which is what a void function implicitly does), the new operator returns the newly created object (this) instead. However, TypeScript flags this at compile time because it’s usually a sign of a logical error.
  2. Misunderstanding Class vs. Function Constructors: You might be trying to use new on a regular function that you intended to be a class constructor, but it’s missing the structure of a class or a factory function designed for instantiation.

    • Diagnosis: Check if the function in question is defined using the class keyword or if it’s a standalone function. If it’s a standalone function, examine its contents. Does it initialize properties on this? Does it have a clear return value intended for instantiation?
      // Incorrect
      function OldStyleConstructor(name: string) {
          this.name = name;
          // No explicit return, defaults to undefined
      }
      // const person = new OldStyleConstructor("Alice"); // TS2350 error
      
      // Corrected (as a class)
      class Person {
          name: string;
          constructor(name: string) {
              this.name = name;
          }
      }
      const person = new Person("Alice");
      
    • Fix: Convert the function into a class or ensure the function is designed as a factory that returns an object. If it’s a legacy constructor function, ensure it implicitly returns this by not having an explicit return statement that returns a non-object value.
      // For legacy functions that *should* work with new
      function LegacyConstructor(name: string) {
          this.name = name;
          // Implicitly returns 'this'
      }
      const legacyPerson = new LegacyConstructor("Bob");
      
    • Why it works: Classes in JavaScript are syntactic sugar over constructor functions. When new is used with a class, the JavaScript engine automatically handles the creation of this and its return. For older-style constructor functions, the new operator itself ensures that this (the newly created object) is returned, unless the function explicitly returns a different, non-primitive value.
  3. Incorrectly Typing a Constructor Function: You might have a function that is a constructor, but its type definition in TypeScript is incorrect, leading the compiler to believe it returns void.

    • Diagnosis: Examine the type annotation for the function or the variable holding the constructor. It should be a typeof ClassName or a function type that expects new (...) => InstanceType.
      class MyService {
          constructor(private id: number) {}
          getId() { return this.id; }
      }
      
      // Incorrect typing
      const serviceConstructor: () => MyService = MyService; // TS2350
      // const instance = new serviceConstructor(123);
      
      // Correct typing
      const correctServiceConstructor: new (id: number) => MyService = MyService;
      const instance = new correctServiceConstructor(123);
      
    • Fix: Ensure the type of the constructor variable correctly reflects its nature as a constructor. Use new (...) => Type for function types or typeof ClassName for class types.
      const correctServiceConstructor: new (id: number) => MyService = MyService;
      
    • Why it works: The new (...) => Type type explicitly tells TypeScript that this function is intended to be called with the new keyword and will produce an instance of Type. typeof ClassName is a shorthand for the constructor signature of ClassName.
  4. Using new with a Module or Namespace: Sometimes, people confuse modules or namespaces with classes or constructors. You cannot instantiate a module or a namespace with new.

    • Diagnosis: Check if the identifier you’re using with new refers to a module (import ... from '...') or a namespace (namespace MyNamespace { ... }).
      // Example module
      // import * as fs from 'fs';
      // const fileSystem = new fs(); // TS2350 error
      
      // Example namespace
      namespace Utils {
          export function format(s: string): string {
              return s.toUpperCase();
          }
      }
      // const utilInstance = new Utils(); // TS2350 error
      
    • Fix: Remove the new operator. If you intended to use functions or classes within the module or namespace, import them directly or access them via their namespace.
      // For modules
      import * as fs from 'fs';
      const fileContent = fs.readFileSync('path/to/file', 'utf-8');
      
      // For namespaces
      const formattedString = Utils.format("hello");
      
    • Why it works: Modules and namespaces are fundamentally different constructs from classes. They are used for organizing code and managing scope, not for creating individual instances of data structures.
  5. Library Functionality Misinterpretation: You might be using a library that exports functions or objects, and you’re mistakenly trying to instantiate one of them with new when it’s designed to be called directly or is already an instance.

    • Diagnosis: Consult the library’s documentation. See how the specific function or object you’re trying to use is intended to be invoked. Look for examples of its usage.
      // Hypothetical library usage
      import { createLogger } from 'some-logging-library';
      
      // Incorrect: Assuming createLogger is a constructor
      // const logger = new createLogger({ level: 'info' }); // TS2350 error
      
      // Correct: Based on typical library patterns
      const logger = createLogger({ level: 'info' });
      
    • Fix: Call the function or use the object as documented by the library. This usually means removing the new keyword.
      const logger = createLogger({ level: 'info' });
      
    • Why it works: Libraries often provide factory functions (functions that create and return objects) or singleton instances. Using new on a factory function that doesn’t expect it, or on a pre-existing object, will lead to type errors like TS2350 because these entities aren’t designed to be constructors.
  6. Circular Dependencies with Constructors: In complex scenarios, circular dependencies involving class constructors can sometimes manifest in confusing ways. If Class A’s constructor somehow depends on an instance of Class B, and Class B’s constructor depends on an instance of Class A, and one of these dependencies is being incorrectly resolved or typed as void during the instantiation chain, you might see this error.

    • Diagnosis: This is rare and harder to pinpoint. Use TypeScript’s tsc --explainFiles and carefully trace the instantiation chain. Look for implements clauses or direct constructor arguments that might form a loop.
    • Fix: Refactor the code to break the circular dependency. This might involve using dependency injection, introducing a third mediating class, or re-architecting the relationship between the classes.
    • Why it works: Breaking the cycle ensures that each constructor receives valid, fully initialized dependencies, preventing the new operator from encountering an unexpected void return type in the chain.

After fixing TS2350, you might encounter TS2554: Expected N arguments, but got M if your constructor now correctly expects arguments that were previously not being passed due to the initial error.

Want structured learning?

Take the full Typescript course →