🎯

dry-types

🎯Skill

from marius-townhouse/effective-typescript-skills

VibeIndex|
What it does

Derives and transforms TypeScript types using built-in type operations to eliminate type duplication and maintain a single source of truth.

πŸ“¦

Part of

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

dry-types

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 duplicating type definitions. Use when interfaces share common fields. Use when types can be derived from other types.

Overview

# Use Type Operations and Generic Types to Avoid Repeating Yourself

Overview

Apply DRY (Don't Repeat Yourself) to types, not just code.

Type duplication causes the same problems as code duplication: inconsistency, maintenance burden, and bugs. Use TypeScript's type operations to derive types from other types.

When to Use This Skill

  • Copying fields between interfaces
  • Multiple types share common properties
  • Want one type to be a subset of another
  • Types should stay in sync automatically
  • Need to create optional/partial versions of types

The Iron Rule

```

NEVER copy-paste type definitions. Derive types from a single source of truth.

```

Remember:

  • extends for adding fields
  • Pick for selecting fields
  • Partial for making fields optional
  • keyof for getting key types
  • typeof for deriving types from values

Detection: The Copied Type Problem

If you see similar types diverging:

```typescript

// ❌ Duplicated type definitions

interface Person {

firstName: string;

lastName: string;

}

interface PersonWithBirthDate {

firstName: string; // Duplicated!

lastName: string; // Duplicated!

birth: Date;

}

```

What if you add middleName to Person? Now they're out of sync.

Basic Techniques

Use `extends` to Add Fields

```typescript

// βœ… Derive from base type

interface Person {

firstName: string;

lastName: string;

}

interface PersonWithBirthDate extends Person {

birth: Date;

}

```

Use `Pick` to Select Fields

```typescript

interface State {

userId: string;

pageTitle: string;

recentFiles: string[];

pageContents: string;

}

// βœ… Select only the fields you need

type TopNavState = Pick;

```

Use `Partial` for Optional Versions

```typescript

interface Options {

width: number;

height: number;

color: string;

}

class UIWidget {

constructor(init: Options) { / ... / }

// βœ… All fields optional for updates

update(options: Partial) { / ... / }

}

```

Use `keyof` for Key Types

```typescript

type OptionsKeys = keyof Options;

// ^? type OptionsKeys = "width" | "height" | "color"

```

Use `typeof` to Derive from Values

```typescript

const DEFAULTS = {

width: 640,

height: 480,

color: '#00FF00',

};

// βœ… Type derived from value

type Options = typeof DEFAULTS;

// ^? type Options = { width: number; height: number; color: string; }

```

Standard Library Utility Types

| Utility | Purpose | Example |

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

| Pick | Select properties | Pick |

| Omit | Remove properties | Omit |

| Partial | Make all optional | Partial |

| Required | Make all required | Required |

| Readonly | Make all readonly | Readonly |

| ReturnType | Function return type | ReturnType |

| Parameters | Function params | Parameters |

Advanced Patterns

Mapped Types

```typescript

// Create optional version manually

type OptionsUpdate = {

[K in keyof Options]?: Options[K]

};

// Equivalent to Partial

```

Indexing into Union Types

```typescript

interface SaveAction { type: 'save'; / ... / }

interface LoadAction { type: 'load'; / ... / }

type Action = SaveAction | LoadAction;

// βœ… Extract discriminant type

type ActionType = Action['type'];

// ^? type ActionType = "save" | "load"

```

ReturnType for Function Results

```typescript

function getUserInfo(userId: string) {

return {

userId,

name,

age,

// ... many fields

};

}

// βœ… Derive type from function

type UserInfo = ReturnType;

```

Key Remapping with `as`

```typescript

interface ShortToLong {

q: 'search';

n: 'numberOfResults';

}

// Invert the mapping

type LongToShort = {

[K in keyof ShortToLong as ShortToLong[K]]: K

};

// ^? type LongToShort = { search: "q"; numberOfResults: "n"; }

```

Homomorphic Mapped Types

Mapped types preserve modifiers when using keyof:

```typescript

interface Customer {

/* How the customer would like to be addressed. /

title?: string;

/* Complete name as entered in the system. /

readonly name: string;

}

// βœ… Preserves optional and readonly

type PickTitle = Pick;

// ^? type PickTitle = { title?: string; }

type PickName = Pick;

// ^? type PickName = { readonly name: string; }

```

When NOT to Apply DRY

Don't factor out types that are only coincidentally similar:

```typescript

// ❌ Don't do this - coincidental similarity

interface NamedAndIdentified {

id: number;

name: string;

}

interface Product extends NamedAndIdentified {

priceDollars: number;

}

interface Customer extends NamedAndIdentified {

address: string;

}

```

Why not? Product.id and Customer.id are semantically different:

  • Customer.id might become a UUID
  • Product.name and Customer.name might evolve differently

Rule of thumb: If you can't name it meaningfully, it's probably premature abstraction.

Common Patterns

Base + Extensions

```typescript

// Shared base

interface Vertebrate {

weightGrams: number;

color: string;

isNocturnal: boolean;

}

// Specific extensions

interface Bird extends Vertebrate {

wingspanCm: number;

}

interface Mammal extends Vertebrate {

eatsGardenPlants: boolean;

}

```

Input/Output Types

```typescript

// Full type

interface User {

id: string;

email: string;

name: string;

createdAt: Date;

}

// Input type derived

type CreateUserInput = Pick;

// Or with Omit

type UpdateUserInput = Partial>;

```

Function Signatures

```typescript

// βœ… Factor out common signatures

type HTTPFunction = (url: string, opts: Options) => Promise;

const get: HTTPFunction = (url, opts) => { / ... / };

const post: HTTPFunction = (url, opts) => { / ... / };

```

Pressure Resistance Protocol

1. "Copy-Paste Is Faster"

Pressure: "Just duplicate the type, it's quicker"

Response: Technical debt accumulates. Types will drift apart.

Action: Take the time to use extends, Pick, or other derivations.

2. "The Types Are Different Enough"

Pressure: "They share fields by coincidence"

Response: Good point - verify they're semantically the same first.

Action: Only factor out types that represent the same concept.

Red Flags - STOP and Reconsider

  • Copy-pasting interface fields
  • Multiple types with identical property subsets
  • Updating one type but forgetting another
  • Types named "...WithX" that duplicate base type

Common Rationalizations (All Invalid)

| Excuse | Reality |

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

| "It's just a few fields" | A few fields Γ— many types = maintenance nightmare |

| "I'll remember to update both" | You won't, or your teammates won't |

| "The derivation is confusing" | Less confusing than debugging drift |

Quick Reference

```typescript

// Adding fields

interface Extended extends Base { newField: T; }

// Selecting fields

type Subset = Pick;

// Removing fields

type WithoutPassword = Omit;

// Making optional

type Updates = Partial;

// Getting return type

type Result = ReturnType;

// Getting key union

type Keys = keyof MyInterface;

```

The Bottom Line

Derive types from a single source of truth.

Use TypeScript's type operations (extends, Pick, Partial, keyof, typeof, mapped types) to express relationships between types. This keeps types in sync and reduces maintenance burden. But only apply DRY when types are semantically related, not just structurally similar.

Reference

Based on "Effective TypeScript" by Dan Vanderkam, Item 15: Use Type Operations and Generic Types to Avoid Repeating Yourself.

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.