🎯

types-as-sets

🎯Skill

from marius-townhouse/effective-typescript-skills

VibeIndex|
What it does

Reasons about TypeScript types using set theory, clarifying type relationships, unions, intersections, and assignability through a mathematical lens.

πŸ“¦

Part of

marius-townhouse/effective-typescript-skills(83 items)

types-as-sets

Installation

Quick InstallInstall with npx
npx skills add marius-townhouse/effective-typescript-skills --all
Quick InstallInstall with npx
npx skills add marius-townhouse/effective-typescript-skills -s prefer-unknown-over-any exhaustiveness-checking
Quick InstallInstall with npx
npx skills add marius-townhouse/effective-typescript-skills -a opencode claude-code
Quick InstallInstall with npx
npx skills add marius-townhouse/effective-typescript-skills -l
git cloneClone repository
git clone https://github.com/marius-townhouse/effective-typescript-skills.git
πŸ“– Extracted from docs: marius-townhouse/effective-typescript-skills
1Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Use when reasoning about type relationships. Use when confused by union or intersection types. Use when extends feels counterintuitive.

Overview

# Think of Types as Sets of Values

Overview

A type is a set of possible values. Assignability means subset.

Understanding types as sets helps you reason about unions, intersections, extends, and never. This mental model makes TypeScript's behavior intuitive.

When to Use This Skill

  • Confused why A & B has MORE properties than A or B
  • Don't understand why extends means "subset"
  • Reasoning about union and intersection types
  • Working with never or unknown types
  • Debugging "not assignable to" errors

The Iron Rule

```

ALWAYS think "subset" when you see "extends" or "assignable to".

```

Remember:

  • Union (|) = larger set (union of domains)
  • Intersection (&) = smaller set (intersection of domains)
  • extends = "is a subset of"
  • never = empty set (no values)
  • unknown = universal set (all values)

Detection: The "Assignability" Confusion

If you're confused by assignability errors, think in terms of sets:

```typescript

type AB = 'A' | 'B';

type AB12 = 'A' | 'B' | 12;

const ab: AB = 'A'; // OK: 'A' is in {'A', 'B'}

const ab12: AB12 = ab; // OK: {'A', 'B'} βŠ† {'A', 'B', 12}

declare let twelve: AB12;

const back: AB = twelve; // Error!

// {'A', 'B', 12} is NOT a subset of {'A', 'B'}

```

The Set Theory Mental Model

TypeScript to Set Theory Translation

| TypeScript | Set Theory | Meaning |

|------------|------------|---------|

| never | βˆ… (empty set) | No values |

| Literal type "A" | Single element {A} | One value |

| T1 \| T2 | T1 βˆͺ T2 (union) | Values in either |

| T1 & T2 | T1 ∩ T2 (intersection) | Values in both |

| unknown | Universal set | All values |

| extends | βŠ† (subset) | "Is contained in" |

| "assignable to" | βŠ† (subset) | "Is contained in" |

Why Intersection `&` ADDS Properties

This seems counterintuitive at first:

```typescript

interface Person { name: string; }

interface Lifespan { birth: Date; death?: Date; }

type PersonSpan = Person & Lifespan;

// PersonSpan has MORE properties, not fewer!

const ps: PersonSpan = {

name: 'Alan Turing',

birth: new Date('1912/06/23'),

death: new Date('1954/06/07'),

}; // OK

```

Why? Because we're intersecting SETS OF VALUES, not properties.

  • Person = all objects with a name property
  • Lifespan = all objects with birth (and optional death)
  • Person & Lifespan = objects that have BOTH sets of properties

The set of values is SMALLER, but each value has MORE properties.

Why Union `|` Has FEWER Guaranteed Properties

```typescript

type K = keyof (Person | Lifespan);

// ^? type K = never

// No keys are guaranteed on BOTH Person AND Lifespan

```

The set of values is LARGER, but we can rely on FEWER properties.

The `extends` Keyword

In TypeScript, extends means "subset of":

```typescript

interface Vector1D { x: number; }

interface Vector2D extends Vector1D { y: number; }

interface Vector3D extends Vector2D { z: number; }

// Vector3D βŠ† Vector2D βŠ† Vector1D (as sets of values)

// A Vector3D IS-A Vector2D IS-A Vector1D

```

This also applies to generic constraints:

```typescript

function getKey(val: any, key: K) { / ... / }

// K can be any subset of string:

getKey({}, 'x'); // OK: 'x' βŠ† string

getKey({}, Math.random() < 0.5 ? 'a' : 'b'); // OK: 'a'|'b' βŠ† string

getKey({}, 12); // Error: number βŠ„ string

```

The `never` Type (Empty Set)

never is the empty set - it contains no values:

```typescript

const x: never = 12;

// ~ Type 'number' is not assignable to type 'never'.

// never is useful for exhaustiveness checking

function assertNever(x: never): never {

throw new Error('Unexpected value: ' + x);

}

```

The `unknown` Type (Universal Set)

unknown is the universal set - all values are assignable to it:

```typescript

const x: unknown = 'hello'; // OK

const y: unknown = 42; // OK

const z: unknown = null; // OK

// But you can't use unknown without narrowing

const str: string = x; // Error: must narrow first

```

Arrays vs Tuples

```typescript

const list = [1, 2];

// ^? const list: number[]

const tuple: [number, number] = list;

// Error! number[] is not assignable to [number, number]

// Because: there exist number[] that aren't pairs ([], [1], [1,2,3])

```

The set number[] is NOT a subset of [number, number].

Practical Applications

Understanding Generic Constraints

```typescript

// T must be a subset of objects with an 'id' property

function getById(items: T[], id: string): T | undefined {

return items.find(item => item.id === id);

}

```

Understanding Conditional Types

```typescript

// "If A is a subset of B, then X, else Y"

type IsString = T extends string ? true : false;

type A = IsString<'hello'>; // true ('hello' βŠ† string)

type B = IsString; // false (number βŠ„ string)

```

Pressure Resistance Protocol

1. "Intersection Should Have Fewer Properties"

Pressure: "A & B should have properties common to both"

Response: We're intersecting sets of VALUES, not properties.

Action: Think: "What objects satisfy BOTH interfaces?"

2. "extends Means Inheritance"

Pressure: "extends is about class inheritance"

Response: In types, extends means "subset of".

Action: Replace "extends" with "is a subset of" when reading.

Red Flags - STOP and Reconsider

  • Thinking & removes properties
  • Thinking | adds properties
  • Confusing extends with classical inheritance
  • Forgetting that object types are "open" (allow extra properties)

Common Rationalizations (All Invalid)

| Excuse | Reality |

|--------|---------|

| "Intersection means common parts" | It's about VALUES, not properties |

| "extends means inherits" | In types, it means "is subset of" |

| "Union has all properties" | Union only guarantees common properties |

Quick Reference

| Type Expression | Set Interpretation | Size |

|-----------------|-------------------|------|

| never | Empty set | 0 values |

| "A" | Singleton | 1 value |

| "A" \| "B" | Union | 2 values |

| string | All strings | ∞ values |

| unknown | Universal set | All values |

| A & B | Intersection | Usually smaller |

| A \| B | Union | Usually larger |

The Bottom Line

Types are sets of values. Assignability is subset checking.

Understanding this makes TypeScript's behavior intuitive. extends means "subset of", & intersects value sets (resulting in more required properties), and | unions value sets (resulting in fewer guaranteed properties).

Reference

Based on "Effective TypeScript" by Dan Vanderkam, Item 7: Think of Types as Sets of Values.

More from this repository10

🎯
tsdoc-comments🎯Skill

Generates TypeScript documentation comments (TSDoc) to explain public APIs, complex types, and provide comprehensive code documentation with IDE tooltips.

🎯
async-over-callbacks🎯Skill

Transforms callback-based asynchronous code into clean, readable async/await patterns for better type flow and error handling.

🎯
type-safe-monkey-patching🎯Skill

Enables type-safe runtime extension of global objects and DOM elements in TypeScript without sacrificing type checking or using `as any`.

🎯
create-objects-all-at-once🎯Skill

Efficiently initializes multiple TypeScript objects simultaneously using concise object literal syntax and spread operators.

🎯
module-by-module-migration🎯Skill

Guides developers through systematic TypeScript module migration, breaking down complex refactoring into manageable, incremental steps.

🎯
ts-js-relationship🎯Skill

Explains TypeScript's relationship to JavaScript, highlighting how it adds static typing and catches errors before runtime while remaining fully compatible with JavaScript code.

🎯
code-gen-independent🎯Skill

Generates JavaScript code despite TypeScript type errors and demonstrates that TypeScript types are erased at runtime, requiring alternative type checking strategies.

🎯
context-type-inference🎯Skill

Helps restore precise type context when extracting values, preventing type inference errors through annotations, const assertions, and type preservation techniques.

🎯
precise-string-types🎯Skill

Enforces strict string type constraints and prevents unintended string type conversions in TypeScript projects.

🎯
type-display-attention🎯Skill

Displays and simplifies complex TypeScript types to improve IDE readability and developer experience.