TypeScript: TypeScript Cross-Types and Advanced Composition

Last updated: 2026-08-26

Intersection types use & to combine multiple types into a single type—the new type possesses all the characteristics of each of the original types. It is a core tool for composite types in TypeScript.

1. Basics of Cross Types

(1) Basic Syntax

TYPESCRIPT
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;

let person: Person = {
  name: "Charlie",
  age: 20
  // Both must be present name and age
};

(2) Comparison with Union Types

Operator Meaning Analogy
A & B (OR) Satisfies both A and B AND (and)
`A B` (Conjunction) Satisfies A or B
TYPESCRIPT
type StringOrNumber = string | number;   // Joint:It could be one of them
type StringAndNumber = string & number;   // Crossing:It must be both at the same time → never!

// Meaningful Crossovers——Object Type
type HasId = { id: number };
type HasName = { name: string };
type Entity = HasId & HasName;   // ✅ At the same time, there are id and name

(3) Overlap Between Multiple Types

TYPESCRIPT
type Timestamped = { createdAt: Date; updatedAt: Date };
type SoftDeletable = { deletedAt: Date | null };
type Auditable = { createdBy: string; updatedBy: string };

type FullEntity = Timestamped & SoftDeletable & Auditable;

let article: FullEntity = {
  createdAt: new Date(),
  updatedAt: new Date(),
  deletedAt: null,
  createdBy: "admin",
  updatedBy: "editor"
};


2. Merging Rules for Cross-Type Data

(1) Properties with the Same Name—Take the Intersection of Types

When two types have properties with the same name but are of different types, the result of the intersection is the intersection of the two types:

TYPESCRIPT
type A = { value: string | number };
type B = { value: string | boolean };
type C = A & B;

// C 's value Type = (string | number) & (string | boolean) = string
let c: C = { value: "hello" };     // ✅ string It is the intersection
// let c2: C = { value: 42 };      // ❌ number Not in the intersection
// let c3: C = { value: true };    // ❌ boolean Not in the intersection

(2) Properties with the same name—incompatible types result in a "never" error

When the properties of two types with the same name have no overlap, the result is never—it is impossible for such a value to exist:

TYPESCRIPT
type A = { id: string };
type B = { id: number };
type C = A & B;

// C 's id Type = string & number = never
// No value can be both string and number
// let c: C = { id: "x" };  // ❌ You cannot string Assigned never
⚠️ Note: TypeScript does not actively throw an error when a cross-type results in never—it simply makes the type unavailable. This is the key difference between cross-types and interface extends: extends throws an error when a conflict is detected, while & cross-types silently result in never.

(3) Intersection of Function Types

When function types intersect, the parameters are taken as their intersection (which usually results in never):

TYPESCRIPT
type StringHandler = (value: string) => void;
type NumberHandler = (value: number) => void;
type MixedHandler = StringHandler & NumberHandler;

// MixedHandler parameters = string & number = never
// In fact, there is no value that can satisfy both signatures at the same time.
💡 Tip: Function overloading is rarely used in normal development. If you need a function that "can handle multiple types," use union types (overloading) instead of overloading.



3. The Difference Between Cross-Type and interface extends

(1) Syntactic Comparison

TYPESCRIPT
// interface extends
interface Person {
  name: string;
}
interface Employee extends Person {
  employeeId: string;
}

// type Crossing
type Person2 = { name: string };
type Employee2 = Person2 & { employeeId: string };

(2) Comparison of Conflict Resolution Methods

Scenario interface extends type intersection &
Compatibility of Attribute Types with the Same Name ✅ Subtype Overrides Supertype Take the Intersection
Incompatible property types with the same name ❌ Compilation error Silently generates a never
Multiple inheritance Single inheritance only Multiple cross-inheritance allowed
Statement Merged Support Do Not Support

(3) Selection Recommendations

TYPESCRIPT
// Use extends scenario — Compile-time conflict detection is required
interface BaseConfig {
  host: string;
  port: number;
}

interface DevConfig extends BaseConfig {
  debug: boolean;    // ✅ New Properties
  // host: number;   // ❌ Compilation error——Parent Type host: string Conflict
}

// Using Intercut Scenes——Requires flexible combinations of multiple types
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithAudit = { createdBy: string; updatedBy: string };
type FullRecord = BaseConfig & WithTimestamps & WithAudit;
// Quick Combinations,No need to define an intermediate step interface


4. Common Patterns for Type Combinations

(1) Pattern 1: Mixin

"Mixing in" additional capabilities into an object using interface types:

TYPESCRIPT
type WithId = { id: number };
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithSoftDelete = { deletedAt: Date | null };
type WithAudit = { createdBy: string; updatedBy: string };

// Freely combine different abilities
type BaseEntity = WithId & WithTimestamps;
type FullEntity = WithId & WithTimestamps & WithSoftDelete & WithAudit;

interface Article extends BaseEntity {
  title: string;
  content: string;
}

let article: Article = {
  id: 1,
  createdAt: new Date(),
  updatedAt: new Date(),
  title: "TypeScript Getting Started",
  content: "TypeScript is a superset of JavaScript..."
};

(2) Model 2: Combination of Conditions

Select whether to cross a specific type based on the conditions:

TYPESCRIPT
type EntityWithOptional<T, TExtra> = T & Partial<TExtra>;

interface User {
  id: number;
  name: string;
}

interface UserProfile {
  avatar: string;
  bio: string;
}

// User + Optional Profile
type UserWithOptionalProfile = EntityWithOptional<User, UserProfile>;
// { id: number; name: string; avatar?: string; bio?: string }

(3) Model Three: Branded Types

Use cross-types to "label" primitive types and prevent their misuse:

TYPESCRIPT
type USD = number & { __brand: "USD" };
type EUR = number & { __brand: "EUR" };

function createUSD(amount: number): USD {
  return amount as USD;
}

function createEUR(amount: number): EUR {
  return amount as EUR;
}

let price: USD = createUSD(100);
let cost: EUR = createEUR(80);

// price = cost;          // ❌ EUR Cannot be assigned to USD
// price + cost;          // ❌ Cannot be combined

function addUSD(a: USD, b: USD): USD {
  return (a + b) as USD;  // Calculations can be made within the same currency
}

let total = addUSD(price, createUSD(50));
console.log(total);  // 150

▶ Example: Type-Safe Configuration Combinations

Output:

TEXT 📖 Display only
Development: localhost:3000 (mock: true)
Production: api.example.com:443 (ssl: true)
TYPESCRIPT
// Basic Configuration
type BaseConfig = {
  host: string;
  port: number;
};

// Additional Development Environment Configuration
type DevConfig = BaseConfig & {
  debug: true;
  mockApi: boolean;
};

// Additional Configuration for the Production Environment
type ProdConfig = BaseConfig & {
  debug: false;
  ssl: boolean;
  maxConnections: number;
};

function createDevConfig(): DevConfig {
  return { host: "localhost", port: 3000, debug: true, mockApi: true };
}

function createProdConfig(): ProdConfig {
  return { host: "api.example.com", port: 443, debug: false, ssl: true, maxConnections: 100 };
}

let dev = createDevConfig();
let prod = createProdConfig();

console.log(`Development:${dev.host}:${dev.port} (mock: ${dev.mockApi})`);
console.log(`Production:${prod.host}:${prod.port} (ssl: ${prod.ssl})`);

Output:

TEXT 📖 Display only
Development:localhost:3000 (mock: true)
Production:api.example.com:443 (ssl: true)


5. Advanced Techniques for Cross-Type Operations

(1) Recursive Type Conversion

TYPESCRIPT
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object
    ? T[K] extends Array<any>
      ? T[K]
      : DeepPartial<T[K]>
    : T[K];
};

interface Config {
  server: { host: string; port: number };
  database: { url: string; pool: { min: number; max: number } };
}

type PartialConfig = DeepPartial<Config>;
let cfg: PartialConfig = {};  // All levels are available

(2) Intersections in Generic Functions

TYPESCRIPT
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

let person = merge({ name: "Charlie" }, { age: 20 });
// person Type:{ name: string } & { age: number }

(3) Intersections in Conditional Types

TYPESCRIPT
type AddTimestamps<T> = T & { createdAt: Date; updatedAt: Date };

interface User {
  name: string;
  email: string;
}

type TimestampedUser = AddTimestamps<User>;
// { name: string; email: string; createdAt: Date; updatedAt: Date }

▶ Example: Utility Types Built with Intersection

Output:

TEXT 📖 Display only
Development: localhost:3000 (mock: true)
Production: api.example.com:443 (ssl: true)
TYPESCRIPT
type WithId = { id: number };
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithSoftDelete = { deletedAt: Date | null };

type Entity = WithId & WithTimestamps & WithSoftDelete;

type Creatable<T> = Omit<T, "id" | "createdAt" | "updatedAt" | "deletedAt">;
type Updatable<T> = Partial<Omit<T, "id">>;

interface Product extends Entity {
  name: string;
  price: number;
}

type CreateProduct = Creatable<Product>;
// { name: string; price: number }

type UpdateProduct = Updatable<Product>;
// { name?: string; price?: number; createdAt?: Date; ... }

let newProduct: CreateProduct = { name: "Widget", price: 9.99 };
let update: UpdateProduct = { price: 12.99 };
console.log(update.price);  // 12.99

Output:

TEXT 📖 Display only
Development: localhost:3000 (mock: true)
Production: api.example.com:443 (ssl: true)

▶ Example: Branded Types for Domain Safety

Output:

TEXT 📖 Display only
Development: localhost:3000 (mock: true)
Production: api.example.com:443 (ssl: true)
TYPESCRIPT
type UserId = number & { __brand: "UserId" };
type OrderId = number & { __brand: "OrderId" };

function createUserId(n: number): UserId { return n as UserId; }
function createOrderId(n: number): OrderId { return n as OrderId; }

let uid = createUserId(42);
let oid = createOrderId(99);

// uid = oid;  // ❌ OrderId is not assignable to UserId

function findUser(id: UserId): string {
  return `User #${id}`;
}

console.log(findUser(uid));        // "User #42"
// console.log(findUser(oid));     // ❌ prevents accidental misuse

Output:

TEXT 📖 Display only
Development: localhost:3000 (mock: true)
Production: api.example.com:443 (ssl: true)

❓ FAQ

Q How do I troubleshoot a "never" error caused by type intersections?
A Gradually remove the intersecting members to see which two types are conflicting. A common cause is incompatible property types with the same name (e.g., string & number). We recommend using interface extends instead of intersections—extends throws an error immediately when there’s a conflict, making it easier to pinpoint the problem.
Q Can cross-typing replace inheritance?
A It can in most cases, but they are not entirely equivalent. interface extends provides compile-time conflict detection, declaration merging, and support for class implements. Cross-typing is more flexible but lacks conflict detection. We recommend using extends for combining object types, and cross-typing for simple, quick combinations.
Q Are branded types useful in actual development?
A They are very useful when you need to distinguish between types that have the same structure but different meanings. Typical examples include currencies (USD vs. EUR), IDs (UserId vs. OrderId), and units of measurement (Meters vs. Feet). Branded types prevent accidental mix-ups and catch errors—such as treating euros as dollars—at compile time.
Q Can disjunctive types and conjunctive types be used together?
A Yes. type T = (A & B) | (C & D) means "either A and B are satisfied, or C and D are satisfied." Parentheses determine precedence—& has higher precedence than |, so A & B | C & D is equal to (A & B) | (C & D).

📖 Summary

📝 Exercises

  1. Basic Problem (Difficulty ⭐): Define two types, WithId and WithTimestamps, combine them using a cross-type to form BaseEntity, and create an object that satisfies BaseEntity.
  2. Advanced Problem (Difficulty ⭐⭐): Implement the brand types UserId = number & { __brand: "UserId" } and OrderId = number & { __brand: "OrderId" }. Write functions to create each type of ID, and verify that they cannot be assigned to each other.
  3. Challenge Problem (Difficulty ⭐⭐⭐): Implement the Overwrite<T, U> tool type—override properties in T with the same name using properties from U, while preserving properties with different names. Hint: Use a mapping type + a cross type.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏