0
0
Typescriptprogramming~15 mins

Generic factory pattern in Typescript - Deep Dive

Choose your learning style9 modes available
Overview - Generic factory pattern
What is it?
The generic factory pattern is a way to create objects in a flexible and reusable manner using TypeScript's generics. It lets you write a single factory function or class that can produce different types of objects based on input parameters. This pattern helps avoid repeating similar code for creating many related objects. It works by defining a generic type that the factory uses to know what kind of object to make.
Why it matters
Without the generic factory pattern, developers often write many separate functions or classes to create different objects, which leads to duplicated code and harder maintenance. This pattern solves that by centralizing object creation logic and making it adaptable to many types. It improves code clarity, reduces bugs, and makes adding new object types easier. In real projects, this means faster development and more reliable code.
Where it fits
Before learning this, you should understand basic TypeScript types, functions, classes, and generics. After mastering the generic factory pattern, you can explore design patterns like dependency injection, abstract factories, and advanced TypeScript features like conditional types and mapped types.
Mental Model
Core Idea
A generic factory pattern is a single, reusable creator that uses type parameters to produce many kinds of objects without repeating code.
Think of it like...
Imagine a vending machine that can dispense different snacks based on the button you press. Instead of having a separate machine for each snack, one machine adapts to your choice and gives you exactly what you want.
Factory Pattern Structure

┌───────────────┐
│ GenericFactory│
│ <T>           │
│ create(type)  │
└──────┬────────┘
       │
       ▼
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│ ProductTypeA  │   │ ProductTypeB  │   │ ProductTypeC  │
└───────────────┘   └───────────────┘   └───────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding basic factory concept
🤔
Concept: Learn what a factory function is and why it helps create objects.
A factory function is a simple function that returns a new object. For example, a function that creates a car object with specific properties. This helps avoid repeating object creation code everywhere. Example: function createCar(make: string, model: string) { return { make, model }; } const car = createCar('Toyota', 'Corolla');
Result
You get a new car object without writing the object structure multiple times.
Understanding that a factory function centralizes object creation helps you see why factories reduce repetition and errors.
2
FoundationIntroduction to TypeScript generics
🤔
Concept: Learn how generics let you write flexible functions and classes that work with many types.
Generics use type variables to create reusable code. For example, a function that returns the first element of an array can be generic: function first(arr: T[]): T { return arr[0]; } This works for arrays of any type, like numbers or strings.
Result
You can write one function that works with many types safely.
Knowing generics lets you write adaptable code that fits many situations without losing type safety.
3
IntermediateCombining generics with factory functions
🤔Before reading on: do you think a generic factory can create any object type without extra code? Commit to your answer.
Concept: Use generics in factory functions to create different object types based on input type parameters.
You can write a factory function that takes a type parameter and returns an object of that type. For example: function createInstance(ctor: new () => T): T { return new ctor(); } class Dog { bark() { return 'woof'; } } class Cat { meow() { return 'meow'; } } const dog = createInstance(Dog); const cat = createInstance(Cat);
Result
The factory creates Dog or Cat instances depending on the class passed in.
Understanding that constructors can be passed as parameters unlocks flexible object creation with generics.
4
IntermediateUsing interfaces to define factory contracts
🤔Before reading on: do you think interfaces can help factories create objects with specific methods? Commit to your answer.
Concept: Define interfaces to specify what methods or properties created objects must have, ensuring type safety.
You can define an interface for products: interface Animal { speak(): string; } class Dog implements Animal { speak() { return 'woof'; } } function createAnimal(ctor: new () => T): T { return new ctor(); } const dog = createAnimal(Dog); console.log(dog.speak()); // 'woof'
Result
The factory guarantees the created object has the speak method.
Knowing how to constrain generics with interfaces ensures factories produce objects with expected behavior.
5
IntermediateAdding parameters to generic factories
🤔Before reading on: can generic factories accept parameters to customize created objects? Commit to your answer.
Concept: Extend generic factories to accept arguments for object initialization.
Modify the factory to pass arguments to constructors: class Car { constructor(public make: string, public model: string) {} } function createWithArgs(ctor: new (...args: any[]) => T, ...args: any[]): T { return new ctor(...args); } const car = createWithArgs(Car, 'Honda', 'Civic'); console.log(car.make); // 'Honda'
Result
The factory creates objects with custom properties set via constructor arguments.
Understanding how to forward arguments to constructors makes factories more flexible and practical.
6
AdvancedGeneric factory classes for reusable creation
🤔Before reading on: do you think a factory class can manage creation logic better than a function? Commit to your answer.
Concept: Create a generic factory class that can be reused and extended for complex creation logic.
Example of a generic factory class: class Factory { constructor(private ctor: new (...args: any[]) => T) {} create(...args: any[]): T { return new this.ctor(...args); } } class User { constructor(public name: string) {} } const userFactory = new Factory(User); const user = userFactory.create('Alice'); console.log(user.name); // 'Alice'
Result
You get a reusable factory object that can create many instances with different arguments.
Knowing how to encapsulate creation logic in classes helps organize code and supports extension.
7
ExpertAdvanced typing with constraints and overloads
🤔Before reading on: can you guess how to type a factory that creates different objects with different constructor signatures? Commit to your answer.
Concept: Use TypeScript advanced types like overloads and constraints to type factories that handle multiple product types with different constructors.
Example with overloads: class MultiFactory { create(type: 'dog'): Dog; create(type: 'cat'): Cat; create(type: string): any { if (type === 'dog') return new Dog(); if (type === 'cat') return new Cat(); } } const factory = new MultiFactory(); const dog = factory.create('dog'); const cat = factory.create('cat'); // Or with generics and constraints: interface Constructor { new (...args: any[]): T; } function createGeneric(ctor: Constructor, ...args: any[]): T { return new ctor(...args); }
Result
Factories can be typed precisely to create different objects with correct constructor arguments and return types.
Understanding advanced typing prevents bugs and improves developer experience with autocomplete and error checking.
Under the Hood
At runtime, the generic factory pattern uses constructor functions or classes passed as parameters to create new instances. TypeScript's generics exist only at compile time for type checking and do not affect runtime behavior. The factory calls the constructor with 'new' and any arguments, returning the created object. This pattern leverages JavaScript's dynamic nature and TypeScript's static typing to ensure safety and flexibility.
Why designed this way?
This pattern was designed to reduce code duplication and improve maintainability by centralizing object creation. Using generics allows the factory to be type-safe and adaptable to many types without rewriting code. Alternatives like separate factory functions for each type were less scalable and more error-prone. The design balances flexibility with strong typing, which is a core goal of TypeScript.
Generic Factory Internal Flow

┌───────────────┐
│ Caller        │
│ calls factory │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ GenericFactory│
│ receives ctor │
│ and args      │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Calls 'new'   │
│ on ctor with  │
│ args          │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Returns new   │
│ instance      │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does TypeScript generics affect runtime object creation? Commit to yes or no.
Common Belief:Generics change how objects are created at runtime.
Tap to reveal reality
Reality:Generics only exist at compile time for type checking; they do not affect runtime behavior or how objects are created.
Why it matters:Believing generics affect runtime can lead to confusion about errors and debugging, causing wasted effort chasing phantom issues.
Quick: Can a generic factory create objects without knowing their constructors? Commit to yes or no.
Common Belief:A generic factory can create any object without needing its constructor or creation logic.
Tap to reveal reality
Reality:The factory needs the constructor or a creation method to instantiate objects; it cannot create unknown types without this information.
Why it matters:Assuming otherwise leads to runtime errors or undefined behavior when the factory tries to create objects without proper constructors.
Quick: Does using a generic factory always improve code clarity? Commit to yes or no.
Common Belief:Using a generic factory always makes code clearer and simpler.
Tap to reveal reality
Reality:Overusing generic factories can make code harder to read and debug, especially if types and constructors become complex or unclear.
Why it matters:Misusing the pattern can reduce maintainability and increase bugs, so it's important to balance flexibility with simplicity.
Quick: Can a generic factory handle different constructor signatures without extra typing? Commit to yes or no.
Common Belief:Generic factories automatically handle any constructor signature without special typing.
Tap to reveal reality
Reality:You must explicitly type constructors and overloads to handle different signatures correctly; otherwise, type errors or runtime bugs occur.
Why it matters:Ignoring this leads to incorrect type assumptions and potential runtime crashes.
Expert Zone
1
Generic factories can be combined with dependency injection to manage complex object graphs in large applications.
2
TypeScript's conditional types can create factories that adapt return types based on input parameters, enabling more precise typing.
3
Using mapped types with factories allows automatic creation of multiple related objects with consistent interfaces.
When NOT to use
Avoid generic factories when object creation logic is simple and unlikely to change, as the added abstraction can complicate code. For very complex creation processes, consider specialized builder patterns or dependency injection frameworks instead.
Production Patterns
In real-world apps, generic factories are used to create UI components, service instances, or data models dynamically. They often integrate with IoC containers to manage lifecycles and dependencies, improving modularity and testability.
Connections
Dependency Injection
Builds-on
Understanding generic factories helps grasp how dependency injection containers create and manage object lifecycles dynamically.
Abstract Factory Pattern
Related pattern
Generic factories generalize the abstract factory pattern by using generics to reduce boilerplate and increase flexibility.
Manufacturing Processes (Industrial Engineering)
Same pattern
Just like factories in industry produce different products from raw materials efficiently, generic factories produce different objects from input parameters, showing a universal creation principle.
Common Pitfalls
#1Trying to create objects without passing the correct constructor.
Wrong approach:function create() { return new T(); // Error: 'T' only used as a type here }
Correct approach:function create(ctor: new () => T): T { return new ctor(); }
Root cause:Confusing TypeScript types with runtime values; types are erased at runtime and cannot be instantiated directly.
#2Not typing constructor arguments properly, causing type errors.
Wrong approach:function create(ctor: new () => T, arg: any): T { return new ctor(arg); // Error if ctor expects no args or different args }
Correct approach:function create(ctor: new (...args: any[]) => T, ...args: any[]): T { return new ctor(...args); }
Root cause:Assuming all constructors have the same signature leads to incorrect typing and runtime errors.
#3Overusing generic factories for simple object creation.
Wrong approach:const createNumber = (value: T): T => value; // Unnecessary generic factory for simple value
Correct approach:const createNumber = (value: number): number => value; // Simple function without generics
Root cause:Misunderstanding when to apply patterns; not all creation needs a generic factory.
Key Takeaways
The generic factory pattern uses TypeScript generics to create flexible, reusable object creators that reduce code duplication.
Generics exist only at compile time and do not affect how objects are created at runtime, which relies on constructors passed as parameters.
Constraining generics with interfaces ensures factories produce objects with expected methods and properties, improving type safety.
Advanced typing techniques like overloads and conditional types allow factories to handle multiple object types with different constructors precisely.
Using generic factories wisely improves maintainability and scalability, but overuse or misuse can complicate code and introduce bugs.