CodeFixesHub
    programming tutorial

    Utility Type: ConstructorParameters<T> for Class Constructor Parameter Types

    Extract class constructor args with ConstructorParameters<T>. Learn patterns, factories, and refactors with examples. Read the full tutorial and apply today.

    article details

    Quick Overview

    TypeScript
    Category
    Aug 20
    Published
    22
    Min Read
    2K
    Words
    article summary

    Extract class constructor args with ConstructorParameters<T>. Learn patterns, factories, and refactors with examples. Read the full tutorial and apply today.

    Utility Type: ConstructorParameters for Class Constructor Parameter Types

    Introduction

    Constructor signatures are a common place where types and runtime behavior meet. When you build factories, dependency injection containers, or wrapper utilities that must construct instances of classes generically, you frequently need the exact parameter list of a class constructor — including the tuple shape, types, and ordering. Without an easy way to extract that information, you end up duplicating types or losing type-safety by using any or unknown and manual casts.

    TypeScript provides the built-in utility type ConstructorParameters to solve exactly this problem: it extracts the parameter types from a class constructor type as a tuple. This tutorial covers how ConstructorParameters works, how it differs from the related Parameters utility, how to combine it with other utilities (like ReturnType, InstanceType, and mapped types), and practical patterns you can use in real-world code: factories, DI containers, proxying/wrapping classes, and refactors.

    By the end of this long-form guide you'll be able to:

    • Confidently extract constructor argument tuples and reuse them in type-safe factories.
    • Compose ConstructorParameters with tuple transforms, mapped types, and conditional types.
    • Handle abstract classes, new signatures, overloads, and edge-cases.
    • Use Constructable helper types and write reusable utilities that accept "newable" types.

    This article assumes an intermediate understanding of TypeScript's type system and will include multiple code examples, patterns, and troubleshooting tips so you can use ConstructorParameters reliably in production code.

    Background & Context

    ConstructorParameters is a TypeScript utility type that takes a constructor type (a "newable" signature) and produces a tuple type representing the constructor's parameter list. It's related to Parameters, which extracts parameter types from functions; see our deeper guide on Parameters for comparative behavior and advanced patterns.

    The utility is most useful when you want to avoid repeating parameter tuples across your codebase: factories, wrappers, and DI containers can accept classes of different shapes while preserving correct types for construction. Under the hood, ConstructorParameters uses conditional types and the infer keyword to match new signatures and extract the argument types. If T is not a constructable type, the utility yields never or a compile-time error, depending on how you use it.

    Because it relies on tuple types and inference, ConstructorParameters works well with tuple transforms, mapped types, and other advanced utilities — enabling complex type-level manipulations without sacrificing readability or safety. For patterns that use infer in conditional types, check our guides on using infer with functions and objects to understand the mechanism this utility leverages: see Using infer with Functions in Conditional Types and Using infer with Objects in Conditional Types — Practical Guide.

    Key Takeaways

    • ConstructorParameters extracts a constructor's parameter types as a tuple.
    • It only works with constructable ("newable") types; otherwise, results can be never or error.
    • Combine it with tuple mapping, InstanceType, and ReturnType<T> to build factories and wrappers.
    • Be careful with overloaded constructors; explicit new signatures are safer.
    • Use helper types such as new (...args: ConstructorParameters<T>) => InstanceType<T> to reconstruct constructors.

    Prerequisites & Setup

    • TypeScript 3.1+ is required for ConstructorParameters and many utility types (use a recent TypeScript version; 4.x+ recommended).
    • Familiarity with tuple types, mapped types, conditional types, and the infer keyword.
    • Basic project: Node.js, npm/yarn, a TypeScript config (tsconfig.json) set to at least "target": "ES2017" and "strict": true for best results.

    To follow examples, create a minimal TypeScript project:

    bash
    mkdir ts-constructor-params && cd ts-constructor-params
    npm init -y
    npm install --save-dev typescript @types/node
    npx tsc --init

    Open the index.ts and try the code snippets as you read. Adjust tsconfig.json to enable strict to expose type errors and make the learning experience clearer.

    Main Tutorial Sections

    1) Basic Usage: Extracting Constructor Parameters

    ConstructorParameters expects a constructor type, such as the type of a class or a new signature. Example:

    ts
    class Logger {
      constructor(private prefix: string, private level: number) {}
    }
    
    type LoggerCtorArgs = ConstructorParameters<typeof Logger>; // [string, number]

    Now LoggerCtorArgs is the tuple [string, number]. You can reuse this tuple in factories or helpers without repeating the argument types manually.

    ts
    function create<T extends abstract new (...args: any) => any>(C: T, ...args: ConstructorParameters<T>) {
      return new C(...args);
    }
    
    const logger = create(Logger, 'app', 3);

    This keeps create fully generic and type-safe for any constructable type.

    2) Handling Non-Constructor Types and Guards

    ConstructorParameters only makes sense when T is a constructor type; using it on a non-newable type typically yields never. To make safe utilities, constrain generics to the new signature:

    ts
    type Newable = abstract new (...args: any) => any;
    
    function makeInstance<T extends Newable>(C: T, ...args: ConstructorParameters<T>) {
      return new C(...args);
    }

    If you receive unknown types at runtime (e.g., from a plugin registry), validate or type-guard them before using ConstructorParameters. Because ConstructorParameters is purely a type-level construct, runtime checks will still be necessary when working with dynamic values.

    For details on infer and conditional types used under the hood, the guides Using infer with Functions in Conditional Types and Using infer with Objects in Conditional Types — Practical Guide are useful references.

    3) Abstract Classes and Interfaces with 'new' Signatures

    Abstract classes and interfaces can be represented as new signatures. If you wish to extract constructor parameters from an abstract class, use typeof the class or constrain to a new signature:

    ts
    abstract class BaseService {
      constructor(public url: string) {}
    }
    
    type BaseCtorArgs = ConstructorParameters<typeof BaseService>; // [string]
    
    interface ServiceCtor {
      new (url: string): BaseService;
    }
    
    type FromInterface = ConstructorParameters<ServiceCtor>; // [string]

    Having new signatures in interfaces makes ConstructorParameters work seamlessly. This pattern is especially handy when designing plugin hosts or DI container APIs that accept abstract types instead of concrete implementations.

    4) Using ConstructorParameters in Factories & DI

    A common pattern is to implement a typed factory or DI container that constructs classes generically while preserving argument types:

    ts
    class Container {
      create<T extends abstract new (...args: any) => any>(C: T, ...args: ConstructorParameters<T>) {
        // You might resolve dependencies here and spread them into args
        return new C(...args) as InstanceType<T>;
      }
    }
    
    const container = new Container();
    const logger = container.create(Logger, 'svc', 1);

    Because create takes ...args: ConstructorParameters<T>, callers get full compile-time checks on the argument types, improving reliability of DI wiring without brittle manual type duplication.

    5) Reconstructing Constructor Types (Newable Helpers)

    Often you want to reconstruct a constructor type using ConstructorParameters. For example, to transform a constructor while keeping the same call signature:

    ts
    type Reconstruct<T extends abstract new (...args: any) => any> =
      new (...args: ConstructorParameters<T>) => InstanceType<T>;
    
    function wrap<T extends abstract new (...args: any) => any>(C: T): Reconstruct<T> {
      return class extends (C as any) {
        constructor(...args: any[]) {
          super(...args);
          // add behavior
        }
      } as any;
    }

    This wrap retains the precise constructor parameter tuple of the original class, so code that uses the wrapped constructor remains type-safe.

    6) Combining with Parameters and ReturnType

    While ConstructorParameters is for constructors, Parameters works for function parameter lists. There are cases where you combine them, for instance when class constructors accept function arguments and you want to mirror types:

    ts
    function factoryFromFunction<F extends (...args: any) => any>(fn: F) {
      type FnParams = Parameters<F>;
      // Use FnParams in other contexts
    }

    When you want to extract returns after building instances or proxies, consider using ReturnType alongside ConstructorParameters to derive full type relationships between constructor arguments and factory results.

    7) Working with Overloaded Constructors

    Overloaded constructors are tricky: TypeScript chooses a single signature when a type is used in certain contexts, typically the last overload signature in the declaration. That means ConstructorParameters may not behave as expected if your class declares multiple constructor overloads. A safe approach is to declare a single explicit new signature or use an interface with a clear new signature:

    ts
    class Multi {
      constructor(a: string);
      constructor(a: string, b: number);
      constructor(a: any, b?: any) {}
    }
    
    // ConstructorParameters<typeof Multi> -> may be [a: any, b?: any]

    To preserve exact overload semantics, prefer explicit new signatures on interfaces, or provide a strongly-typed factory wrapper that uses conditional types to narrow parameter possibilities.

    For advanced conditional and distribution behaviors, review our guide on Distributional Conditional Types in TypeScript.

    8) Transforming Constructor Tuples into Options Objects

    A practical pattern is converting a constructor parameter tuple into a named options object using tuple-to-object transforms. Use mapped types and recursive mapped transforms for this task:

    ts
    type ParamNames = ['url', 'timeout'];
    
    type TupleToObject<T extends readonly any[], Keys extends readonly PropertyKey[]> = {
      [I in keyof T as Keys[I & number]]: T[I]
    };
    
    type Args = [string, number];
    type Opts = TupleToObject<Args, ParamNames>; // { url: string; timeout: number }

    For deep tuple transformations and advanced mapped types see Recursive Mapped Types for Deep Transformations in TypeScript and Advanced Mapped Types: Key Remapping with Conditional Types.

    9) Interoperating with Arrays, Tuples, and Rest Parameters

    ConstructorParameters returns a tuple type that works with ... rest positions. When passing a tuple to spread into a constructor, use as const or explicit tuple types:

    ts
    function instantiate<T extends abstract new (...args: any) => any>(C: T, args: ConstructorParameters<T>) {
      return new C(...args);
    }
    
    const args = ['http://', 5000] as const;
    instantiate(Logger, args as unknown as ConstructorParameters<typeof Logger>);

    The as const helps keep literal types; for complex transformations of tuple elements, see Using infer with Arrays in Conditional Types — Practical Guide.

    10) Practical Wrappers: Proxies, Decorators, and Logging

    You can build decorators and proxies for classes that preserve constructor signatures by relying on ConstructorParameters:

    ts
    function withLogging<T extends abstract new (...args: any) => any>(C: T) {
      return class extends (C as any) {
        constructor(...args: ConstructorParameters<T>) {
          console.log('creating', C.name, args);
          super(...args);
        }
      } as unknown as Reconstruct<T>;
    }
    
    const LoggedLogger = withLogging(Logger);
    const l = new LoggedLogger('svc', 2);

    This pattern keeps the wrapped constructor’s signature intact so callers get full type support while adding cross-cutting concerns such as logging or metrics.

    Advanced Techniques

    Once you're comfortable with the basics, you can apply ConstructorParameters in more advanced type-level constructs. For instance, combine it with conditional types and infer to build utilities that accept only constructors whose first parameter implements a specific interface. Or create a type that maps a constructor tuple to a Promise-wrapped version of the instance, useful for asynchronous factories:

    ts
    type AsyncFactory<T extends abstract new (...args: any) => any> =
      (...args: ConstructorParameters<T>) => Promise<InstanceType<T>>;

    You can also create specialized DI registration types that store constructor argument metadata using Utility Type: Record<K, T> for Dictionary Types to hold mapping from tokens to constructor param tuples. For recursive transforms and modeling complex deep changes, consult Recursive Conditional Types for Complex Type Manipulations and Distributional Conditional Types in TypeScript: A Practical Guide for patterns and performance trade-offs.

    Performance tip: heavy use of deeply recursive conditional types can slow down TypeScript's type checker. Keep utility types focused and consider splitting very complex transforms into smaller, named helper types to reduce compile-time cost.

    Best Practices & Common Pitfalls

    • Always constrain generics that use ConstructorParameters to a new signature: T extends abstract new (...args: any) => any to avoid never surprises.
    • Prefer explicit new signatures (interfaces or type aliases) when your constructors have overloads — overloaded constructors can lead to ambiguous inferred parameter tuples.
    • Use as const when preserving literal tuple elements for argument arrays to keep the most specific types.
    • Avoid extremely deep or recursive type-level transforms unless necessary: they can slow down TS compiler. When you need deep transforms, split logic into named helper types and test incrementally.
    • Beware of runtime vs. type-level mismatch: ConstructorParameters is type-level only. If your runtime values may not be constructors, validate them before constructing.

    Troubleshooting:

    • If you get unexpected any types in tuples, check if the class was written in JavaScript or had implicit any parameters.
    • If ConstructorParameters returns never, ensure you passed a constructor type (use typeof MyClass or a new interface) and that your generic constraint allows new signatures.

    Real-World Applications

    • Dependency Injection (DI) containers: automatically infer and pass constructor arguments when wiring components while retaining type-safety.
    • Test factories: generate instances for tests by using ConstructorParameters to create argument builders and default factories.
    • Wrappers and decorators: preserve constructor signatures while augmenting behavior (logging, performance metrics, instrumentation).
    • Plugin systems: accept class implementations from third-parties while validating and typing the constructor args.

    For complex mapping of constructor tuple shapes into configuration objects for such systems, you might store those mappings in a dictionary with Utility Type: Record<K, T> for Dictionary Types to keep plugin metadata organized.

    Conclusion & Next Steps

    ConstructorParameters is a compact but powerful utility in TypeScript that removes the need to duplicate constructor argument types. It shines in factories, DI, wrappers, and refactors where preserving constructor signatures is critical. Start using it in small helper functions and gradually refactor larger factories and DI registrations to rely on it. For additional depth on related concepts, explore our articles on Parameters and ReturnType.

    Next steps: practice by converting an existing factory or container to use ConstructorParameters, wrap a small class with a typed decorator, and experiment with tuple-to-object transforms to build named configuration objects from constructor args.

    Enhanced FAQ

    Q: What exactly does ConstructorParameters return? A: ConstructorParameters returns a tuple type representing the parameter types of the constructor signature of T. For class Foo { constructor(a: string, b: number) {} }, ConstructorParameters<typeof Foo> is [string, number].

    Q: Can I use ConstructorParameters on function constructors like new Function()? A: You can only use ConstructorParameters on types that declare a construct signature (types that are "newable"). Plain functions without new signatures are not valid and will typically produce never unless you provide a new signature type for them.

    Q: How does it differ from Parameters? A: Parameters extracts parameter types from function types ((...args: any) => any). ConstructorParameters specializes that concept for constructor (new) signatures. If you need function parameters, use Parameters; for constructors, use ConstructorParameters. See our guide on Parameters for more.

    Q: What happens with overloaded constructors? A: Overloaded constructors can be ambiguous: TypeScript often selects a single signature for inference (commonly the implementation signature or the last overload depending on context). Because of that, ConstructorParameters may not reflect all overloads. Prefer explicit new signatures on interfaces or single-signature constructors for reliable inference.

    Q: Is ConstructorParameters safe to use in public library APIs? A: Yes — when used carefully. Constrain your generics (T extends abstract new (...args: any) => any) and document expected class shapes. For libraries, prefer clear new signatures in types you export to avoid overload ambiguity and to provide consistent consumer experience.

    Q: Can I transform the returned tuple into an object with named fields? A: Yes. You can use mapped types and key remapping to convert a tuple into a named object. For complex deep transformations, refer to Recursive Mapped Types for Deep Transformations in TypeScript.

    Q: How do I reconstruct a constructor type from ConstructorParameters? A: Use a helper like type Reconstruct<T extends new (...args:any) => any> = new (...args: ConstructorParameters<T>) => InstanceType<T> to build a constructor type that preserves argument and instance types. This is useful for wrappers and proxies.

    Q: What are common pitfalls when using ConstructorParameters? A: Common pitfalls include:

    • Forgetting to constrain the generic to a new signature and getting never.
    • Overloaded constructors causing ambiguous tuples.
    • Relying purely on types for runtime validation (ConstructorParameters is compile-time only).
    • Excessive complex recursive types causing slow compile times.

    Q: Can I use ConstructorParameters for classes written in JavaScript (JS)? A: Type inference depends on your declaration files or @types. If the JS class has typed declarations (d.ts) or the project uses JSDoc with type annotations, it can work. Otherwise, TypeScript may fall back to any for parameters, making the tuple less useful.

    Q: How does ConstructorParameters interact with InstanceType and ReturnType? A: Use InstanceType to obtain the instance type produced by the constructor type T. ReturnType is for function returns. They can be combined to create full factory and wrapper types: use ConstructorParameters for arguments, InstanceType for the constructed type, and ReturnType when functions produce constructors or instances. For a guide on ReturnType, see Utility Type: ReturnType for Function Return Types.

    Q: Any recommended further reading? A: To strengthen related knowledge, read about infer in conditional types (Using infer with Functions in Conditional Types), tuple and array inference (Using infer with Arrays in Conditional Types — Practical Guide), and advanced mapped/recursive transformations (Recursive Mapped Types for Deep Transformations in TypeScript, Advanced Mapped Types: Key Remapping with Conditional Types).

    article completed

    Great Work!

    You've successfully completed this TypeScript tutorial. Ready to explore more concepts and enhance your development skills?

    share this article

    Found This Helpful?

    Share this TypeScript tutorial with your network and help other developers learn!

    continue learning

    Related Articles

    Discover more programming tutorials and solutions related to this topic.

    No related articles found.

    Try browsing our categories for more content.

    Content Sync Status
    Offline
    Changes: 0
    Last sync: 11:20:07 PM
    Next sync: 60s
    Loading CodeFixesHub...