+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 75 of 355

🔍 Extract and Exclude: Filtering Union Types with Precision

Master TypeScript's Extract and Exclude utility types to filter union types with surgical precision, creating focused type subsets 🚀

🚀Intermediate
24 min read

Prerequisites

  • Understanding TypeScript union types and literals 📝
  • Familiarity with conditional types basics ⚡
  • Knowledge of type relationships and assignability 💻

What you'll learn

  • Master Extract<T, U> for selecting union members 🎯
  • Use Exclude<T, U> to remove unwanted union members 🏗️
  • Filter function types and complex union structures 🐛
  • Apply Extract/Exclude in real-world type operations ✨

🎯 Introduction

Welcome to this essential guide on TypeScript’s Extract and Exclude utility types! 🎉 These powerful tools are like precision filters for union types - they let you surgically select exactly what you want or remove what you don’t need from complex type unions.

You’ll discover how Extract and Exclude can transform messy, complex union types into clean, focused type definitions. Whether you’re building event systems 🎪, API handlers 🌐, or component libraries 🧩, these utility types provide the precision you need for type-safe operations.

By the end of this tutorial, you’ll be filtering union types like a TypeScript surgeon! Let’s dive in! 🏊‍♂️

📚 Understanding Extract and Exclude

🤔 What are Extract and Exclude?

Extract and Exclude are like specialized sieves for union types 🔬. Think of Extract as a magnet that pulls out only the pieces you want, while Exclude is like a filter that removes the pieces you don’t want.

In TypeScript terms:

  • Extract<T, U> 🎣: Extracts from T only those types that are assignable to U
  • Exclude<T, U> 🚫: Excludes from T all types that are assignable to U
  • Both work with union types and conditional type logic

This means you can:

  • ✨ Create focused subsets from large union types
  • 🚀 Remove problematic or unwanted types from unions
  • 🛡️ Build type-safe filtering operations

💡 Why Use Extract and Exclude?

Here’s why developers love these utility types:

  1. Type Precision 🎯: Get exactly the types you need
  2. Union Filtering 🔍: Clean up complex union types
  3. Type Safety 🔒: Maintain compile-time guarantees
  4. Code Clarity 📖: Express intent clearly in type definitions

Real-world example: Imagine handling different types of events 🎪. You might have 20 different event types, but your component only handles mouse events. Extract helps you create a focused MouseEvent union!

🔧 Extract - Selecting Union Members

📝 Basic Extract Syntax

Let’s start with the fundamentals:

// 🎨 Basic union type
type AllColors = 'red' | 'blue' | 'green' | 'yellow' | 'purple' | 'orange';

// 🎯 Extract only primary colors
type PrimaryColors = Extract<AllColors, 'red' | 'blue' | 'green'>;
// Result: 'red' | 'blue' | 'green'

// 🌈 Extract colors that start with specific letters
type ColorsStartingWithR = Extract<AllColors, `r${string}`>;
// Result: 'red'

type ColorsStartingWithBOrG = Extract<AllColors, `b${string}` | `g${string}`>;
// Result: 'blue' | 'green'

🚀 Practical Extract Examples

🎪 Event Type Filtering

// 🖱️ DOM event types
type DOMEvents = 
  | 'click' 
  | 'mousedown' 
  | 'mouseup' 
  | 'mousemove'
  | 'keydown' 
  | 'keyup' 
  | 'focus' 
  | 'blur'
  | 'scroll'
  | 'resize'
  | 'load'
  | 'error';

// 🖱️ Extract only mouse events
type MouseEvents = Extract<DOMEvents, `mouse${string}` | 'click'>;
// Result: 'click' | 'mousedown' | 'mouseup' | 'mousemove'

// ⌨️ Extract only keyboard events
type KeyboardEvents = Extract<DOMEvents, `key${string}`>;
// Result: 'keydown' | 'keyup'

// 🎯 Extract focus-related events
type FocusEvents = Extract<DOMEvents, 'focus' | 'blur'>;
// Result: 'focus' | 'blur'

// ✨ Usage in event handler
const handleMouseEvent = (eventType: MouseEvents) => {
  // 🎯 Only receives mouse-related events
  console.log(`Handling mouse event: ${eventType}`);
};

// ✅ Valid calls
handleMouseEvent('click');      // ✅
handleMouseEvent('mousedown');  // ✅
// handleMouseEvent('keydown'); // ❌ Error: not a mouse event

🎮 Game State Management

// 🏆 Game action types
type GameActions =
  | 'MOVE_UP'
  | 'MOVE_DOWN'
  | 'MOVE_LEFT'
  | 'MOVE_RIGHT'
  | 'ATTACK_MELEE'
  | 'ATTACK_RANGED'
  | 'CAST_FIRE'
  | 'CAST_ICE'
  | 'CAST_HEAL'
  | 'USE_POTION'
  | 'USE_SCROLL'
  | 'SAVE_GAME'
  | 'LOAD_GAME';

// 🏃 Extract movement actions
type MovementActions = Extract<GameActions, `MOVE_${string}`>;
// Result: 'MOVE_UP' | 'MOVE_DOWN' | 'MOVE_LEFT' | 'MOVE_RIGHT'

// ⚔️ Extract attack actions
type AttackActions = Extract<GameActions, `ATTACK_${string}`>;
// Result: 'ATTACK_MELEE' | 'ATTACK_RANGED'

// 🔮 Extract magic actions
type MagicActions = Extract<GameActions, `CAST_${string}`>;
// Result: 'CAST_FIRE' | 'CAST_ICE' | 'CAST_HEAL'

// 🎒 Extract item usage actions
type ItemActions = Extract<GameActions, `USE_${string}`>;
// Result: 'USE_POTION' | 'USE_SCROLL'

// 💾 Extract game management actions
type GameManagementActions = Extract<GameActions, 'SAVE_GAME' | 'LOAD_GAME'>;
// Result: 'SAVE_GAME' | 'LOAD_GAME'

// 🎯 Focused action handlers
const handleMovement = (action: MovementActions) => {
  // 🏃 Only handles movement actions
  const direction = action.replace('MOVE_', '').toLowerCase();
  console.log(`Moving ${direction}`);
};

const handleCombat = (action: AttackActions | MagicActions) => {
  // ⚔️ Handles all combat-related actions
  if (action.startsWith('ATTACK')) {
    console.log(`Performing ${action.replace('ATTACK_', '').toLowerCase()} attack`);
  } else {
    console.log(`Casting ${action.replace('CAST_', '').toLowerCase()} spell`);
  }
};

🌐 API Endpoint Filtering

// 🌐 HTTP methods and endpoints
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';

interface APIEndpoint {
  path: string;
  method: HTTPMethod;
  authRequired: boolean;
}

// 📚 All API endpoints
type AllEndpoints = 
  | { path: '/users', method: 'GET', authRequired: false }
  | { path: '/users', method: 'POST', authRequired: true }
  | { path: '/users/:id', method: 'GET', authRequired: false }
  | { path: '/users/:id', method: 'PUT', authRequired: true }
  | { path: '/users/:id', method: 'DELETE', authRequired: true }
  | { path: '/posts', method: 'GET', authRequired: false }
  | { path: '/posts', method: 'POST', authRequired: true }
  | { path: '/posts/:id', method: 'GET', authRequired: false }
  | { path: '/posts/:id', method: 'PUT', authRequired: true }
  | { path: '/posts/:id', method: 'DELETE', authRequired: true };

// 👁️ Extract only GET endpoints (read operations)
type ReadEndpoints = Extract<AllEndpoints, { method: 'GET' }>;

// ✏️ Extract only write endpoints (POST, PUT, DELETE)
type WriteEndpoints = Extract<AllEndpoints, { method: 'POST' | 'PUT' | 'DELETE' }>;

// 🔒 Extract only authenticated endpoints
type AuthenticatedEndpoints = Extract<AllEndpoints, { authRequired: true }>;

// 🔓 Extract only public endpoints
type PublicEndpoints = Extract<AllEndpoints, { authRequired: false }>;

// ✨ Usage in middleware
const requireAuth = (endpoint: AuthenticatedEndpoints) => {
  // 🔒 Only receives endpoints that require authentication
  console.log(`Checking auth for ${endpoint.method} ${endpoint.path}`);
};

❌ Exclude - Removing Union Members

📝 Basic Exclude Syntax

Now let’s explore Exclude - the opposite of Extract:

// 🎨 Same color union as before
type AllColors = 'red' | 'blue' | 'green' | 'yellow' | 'purple' | 'orange';

// 🚫 Exclude primary colors, keep secondary colors
type SecondaryColors = Exclude<AllColors, 'red' | 'blue' | 'green'>;
// Result: 'yellow' | 'purple' | 'orange'

// 🚫 Exclude colors starting with specific letters
type NonRedColors = Exclude<AllColors, `r${string}`>;
// Result: 'blue' | 'green' | 'yellow' | 'purple' | 'orange'

// 🌈 Keep only warm colors (exclude cool colors)
type WarmColors = Exclude<AllColors, 'blue' | 'green' | 'purple'>;
// Result: 'red' | 'yellow' | 'orange'

🚀 Practical Exclude Examples

🔧 Function Type Filtering

// 🔧 Mixed utility functions
type UtilityFunctions = 
  | 'formatDate'
  | 'parseJson'
  | 'validateEmail'
  | 'generateId'
  | 'debugLog'    // 🐛 Debug function
  | 'devOnly'     // 🚧 Development only
  | 'testHelper'  // 🧪 Test helper
  | 'calculateTax'
  | 'formatCurrency';

// 🚫 Exclude development/debug functions for production
type ProductionFunctions = Exclude<UtilityFunctions, 'debugLog' | 'devOnly' | 'testHelper'>;
// Result: 'formatDate' | 'parseJson' | 'validateEmail' | 'generateId' | 'calculateTax' | 'formatCurrency'

// 🧮 Exclude formatting functions, keep logic functions
type LogicFunctions = Exclude<UtilityFunctions, `format${string}` | `parse${string}` | `validate${string}`>;
// Result: 'generateId' | 'debugLog' | 'devOnly' | 'testHelper' | 'calculateTax'

// ✨ Usage in production build
const allowedInProduction = (funcName: ProductionFunctions) => {
  // 🚀 Only receives production-safe functions
  console.log(`Executing production function: ${funcName}`);
};

🎮 Game Ability System

// 🏆 Character abilities
type AllAbilities = 
  | 'fireball'
  | 'heal'
  | 'teleport'
  | 'poison'
  | 'curse'
  | 'resurrect'
  | 'lightning'
  | 'shield'
  | 'blessing'
  | 'doom'
  | 'invisibility';

// 😈 Dark magic abilities
type DarkMagic = 'poison' | 'curse' | 'doom';

// ⚡ Lightning abilities
type LightningMagic = 'lightning';

// ✨ Exclude dark magic for good characters
type GoodCharacterAbilities = Exclude<AllAbilities, DarkMagic>;
// Result: 'fireball' | 'heal' | 'teleport' | 'resurrect' | 'lightning' | 'shield' | 'blessing' | 'invisibility'

// 🚫 Exclude powerful abilities for beginner characters
type BeginnerAbilities = Exclude<AllAbilities, 'resurrect' | 'doom' | 'teleport'>;
// Result: 'fireball' | 'heal' | 'poison' | 'curse' | 'lightning' | 'shield' | 'blessing' | 'invisibility'

// 🌩️ Non-lightning abilities
type NonLightningAbilities = Exclude<AllAbilities, LightningMagic>;
// Result: 'fireball' | 'heal' | 'teleport' | 'poison' | 'curse' | 'resurrect' | 'shield' | 'blessing' | 'doom' | 'invisibility'

// 🎯 Character creation with restrictions
interface Character {
  name: string;
  class: 'warrior' | 'mage' | 'cleric' | 'rogue';
  abilities: {
    good: GoodCharacterAbilities[];
    beginner: BeginnerAbilities[];
  };
}

const createGoodCharacter = (abilities: GoodCharacterAbilities[]) => {
  // ✨ Cannot receive dark magic abilities
  return {
    name: 'Hero',
    class: 'cleric' as const,
    abilities: abilities.filter(ability => !['poison', 'curse', 'doom'].includes(ability))
  };
};

📊 Data Type Sanitization

// 📊 Form field types
type FormFieldTypes = 
  | 'text'
  | 'password'
  | 'email'
  | 'number'
  | 'tel'
  | 'url'
  | 'search'
  | 'hidden'     // 🚫 Internal use only
  | 'file'
  | 'checkbox'
  | 'radio'
  | 'select'
  | 'textarea'
  | 'date'
  | 'time'
  | 'datetime-local'
  | 'submit'     // 🚫 Not a data field
  | 'reset'      // 🚫 Not a data field
  | 'button';    // 🚫 Not a data field

// 📝 Exclude non-data fields
type DataFieldTypes = Exclude<FormFieldTypes, 'hidden' | 'submit' | 'reset' | 'button'>;
// Result: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search' | 'file' | 'checkbox' | 'radio' | 'select' | 'textarea' | 'date' | 'time' | 'datetime-local'

// 📱 Exclude complex fields for simple forms
type SimpleFieldTypes = Exclude<DataFieldTypes, 'file' | 'date' | 'time' | 'datetime-local'>;
// Result: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search' | 'checkbox' | 'radio' | 'select' | 'textarea'

// 🔤 Text-based fields only
type TextFieldTypes = Exclude<DataFieldTypes, 'checkbox' | 'radio' | 'select' | 'file'>;
// Result: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search' | 'textarea' | 'date' | 'time' | 'datetime-local'

// ✨ Form builder with restrictions
interface FormField {
  name: string;
  type: DataFieldTypes;
  required: boolean;
  validation?: RegExp;
}

const createSimpleForm = (fields: { name: string; type: SimpleFieldTypes }[]) => {
  // 🎯 Only allows simple field types
  return fields.map(field => ({
    ...field,
    required: false,
    validation: field.type === 'email' ? /^[^\s@]+@[^\s@]+\.[^\s@]+$/ : undefined
  }));
};

💡 Advanced Patterns and Combinations

🔗 Combining Extract and Exclude

// 🎪 Event system with complex filtering
type AllEventTypes = 
  | 'user:login'
  | 'user:logout' 
  | 'user:register'
  | 'admin:create_user'
  | 'admin:delete_user'
  | 'admin:view_logs'
  | 'system:startup'
  | 'system:shutdown'
  | 'system:error'
  | 'api:request'
  | 'api:response'
  | 'api:error';

// 👤 Extract user events, then exclude logout
type ActiveUserEvents = Exclude<Extract<AllEventTypes, `user:${string}`>, 'user:logout'>;
// Result: 'user:login' | 'user:register'

// 👨‍💼 Extract admin events, then exclude dangerous ones
type SafeAdminEvents = Exclude<Extract<AllEventTypes, `admin:${string}`>, 'admin:delete_user'>;
// Result: 'admin:create_user' | 'admin:view_logs'

// ⚙️ Extract system events, exclude errors
type HealthySystemEvents = Exclude<Extract<AllEventTypes, `system:${string}`>, 'system:error'>;
// Result: 'system:startup' | 'system:shutdown'

// 🌐 API events without errors
type SuccessfulAPIEvents = Exclude<Extract<AllEventTypes, `api:${string}`>, 'api:error'>;
// Result: 'api:request' | 'api:response'

🏗️ Building Type Filters

// 🎯 Generic type filters
type ExtractByPrefix<T, P extends string> = Extract<T, `${P}${string}`>;
type ExcludeByPrefix<T, P extends string> = Exclude<T, `${P}${string}`>;
type ExtractBySuffix<T, S extends string> = Extract<T, `${string}${S}`>;
type ExcludeBySuffix<T, S extends string> = Exclude<T, `${string}${S}`>;

// 📄 File types
type FileExtensions = 
  | '.jpg' | '.jpeg' | '.png' | '.gif' | '.svg'
  | '.mp4' | '.avi' | '.mov' | '.wmv'
  | '.pdf' | '.doc' | '.docx' | '.txt'
  | '.js' | '.ts' | '.jsx' | '.tsx'
  | '.css' | '.scss' | '.sass';

// 🖼️ Image files only
type ImageExtensions = ExtractByPrefix<FileExtensions, '.'> & Extract<FileExtensions, '.jpg' | '.jpeg' | '.png' | '.gif' | '.svg'>;

// 🎬 Video files only  
type VideoExtensions = Extract<FileExtensions, '.mp4' | '.avi' | '.mov' | '.wmv'>;

// 📝 Document files (exclude code files)
type DocumentExtensions = Exclude<
  Extract<FileExtensions, '.pdf' | '.doc' | '.docx' | '.txt'>,
  never>;

// 💻 Code files only
type CodeExtensions = Extract<FileExtensions, '.js' | '.ts' | '.jsx' | '.tsx' | '.css' | '.scss' | '.sass'>;

// ✨ File upload validator
const validateFileType = (filename: string, allowedTypes: FileExtensions[]) => {
  const extension = filename.substring(filename.lastIndexOf('.')) as FileExtensions;
  return allowedTypes.includes(extension);
};

// 🎯 Specific upload handlers
const uploadImage = (file: File) => {
  const imageTypes: ImageExtensions[] = ['.jpg', '.jpeg', '.png', '.gif', '.svg'];
  // 🖼️ Only accepts image files
};

const uploadDocument = (file: File) => {
  const docTypes: DocumentExtensions[] = ['.pdf', '.doc', '.docx', '.txt'];
  // 📄 Only accepts document files
};

🔄 Dynamic Union Filtering

// 🏪 E-commerce product categories
type ProductCategories = 
  | 'electronics'
  | 'clothing'
  | 'books'
  | 'home'
  | 'sports'
  | 'automotive'
  | 'beauty'
  | 'toys'
  | 'food'
  | 'garden';

// 🚫 Restricted categories for certain regions
type RestrictedCategories = 'automotive' | 'beauty';
type RegionallyBannedCategories = 'food' | 'toys';

// 🌍 Available categories by region
type GlobalCategories = Exclude<ProductCategories, never>; // All categories
type EUCategories = Exclude<ProductCategories, RestrictedCategories>;
type AsiaCategories = Exclude<ProductCategories, RestrictedCategories | RegionallyBannedCategories>;

// 🎯 Region-specific product filtering
interface Product {
  id: string;
  name: string;
  category: ProductCategories;
  price: number;
}

const getAvailableProducts = <T extends ProductCategories>(
  products: Product[],
  allowedCategories: T[]
): Product[] => {
  return products.filter(product => 
    allowedCategories.includes(product.category as T)
  );
};

// ✨ Usage with type safety
const euProducts = getAvailableProducts(
  allProducts, 
  ['electronics', 'clothing', 'books'] as EUCategories[]
);

⚠️ Common Pitfalls and Solutions

❌ Wrong: Confusing Extract and Exclude

type Numbers = 1 | 2 | 3 | 4 | 5;

// ❌ BAD: Confusing which does what
type OnlyEvens = Exclude<Numbers, 1 | 3 | 5>; // Actually correct, but confusing intent
type OnlyOdds = Extract<Numbers, 2 | 4>;      // ❌ Wrong: Extract doesn't work this way

✅ Right: Clear Intent with Proper Usage

type Numbers = 1 | 2 | 3 | 4 | 5;

// ✅ GOOD: Clear intent
type EvenNumbers = Extract<Numbers, 2 | 4>;           // Extract evens: 2 | 4
type OddNumbers = Exclude<Numbers, 2 | 4>;            // Exclude evens: 1 | 3 | 5
type HighNumbers = Extract<Numbers, 4 | 5>;           // Extract high: 4 | 5
type LowNumbers = Exclude<Numbers, 4 | 5>;            // Exclude high: 1 | 2 | 3

❌ Wrong: Complex Nested Operations

// ❌ BAD: Hard to read and understand
type ComplexFilter = Exclude<Extract<Exclude<AllEventTypes, `system:${string}`>, `user:${string}`>, 'user:logout'>;
// 😱 What is this even doing?

✅ Right: Step-by-Step Filtering

// ✅ GOOD: Clear, step-by-step filtering
type NonSystemEvents = Exclude<AllEventTypes, `system:${string}`>;
type UserEvents = Extract<NonSystemEvents, `user:${string}`>;
type ActiveUserEvents = Exclude<UserEvents, 'user:logout'>;

// 🔄 Or use intermediate types
type UserEventsOnly = Extract<AllEventTypes, `user:${string}`>;
type ActiveUserEventsOnly = Exclude<UserEventsOnly, 'user:logout'>;

🛠️ Best Practices

1. 🎯 Use Clear, Descriptive Names

// ✅ GOOD: Self-documenting type names
type MouseEvents = Extract<DOMEvents, `mouse${string}` | 'click'>;
type NonKeyboardEvents = Exclude<DOMEvents, `key${string}`>;
type SafeEvents = Exclude<DOMEvents, 'error'>;

// ❌ BAD: Unclear names
type Events1 = Extract<DOMEvents, `mouse${string}`>;
type Events2 = Exclude<DOMEvents, `key${string}`>;

2. 🏗️ Build Reusable Filter Patterns

// 🧱 Reusable filter utilities
type FilterByPrefix<T, P extends string> = Extract<T, `${P}${string}`>;
type RemoveByPrefix<T, P extends string> = Exclude<T, `${P}${string}`>;
type FilterBySuffix<T, S extends string> = Extract<T, `${string}${S}`>;
type RemoveBySuffix<T, S extends string> = Exclude<T, `${string}${S}`>;

// 🎯 Usage
type ActionEvents = FilterByPrefix<AllEventTypes, 'user:'>;
type NonSystemEvents = RemoveByPrefix<AllEventTypes, 'system:'>;

3. 📝 Document Complex Filtering Logic

// ✨ Well-documented filtering
type AllHTTPMethods = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';

// 📖 Read-only operations (safe for caching)
type ReadOnlyMethods = Extract<AllHTTPMethods, 'GET' | 'HEAD' | 'OPTIONS'>;

// ✏️ Write operations (require authentication)
type WriteMethods = Exclude<AllHTTPMethods, ReadOnlyMethods>;

// 🔄 Idempotent operations (safe to retry)
type IdempotentMethods = Extract<AllHTTPMethods, 'GET' | 'PUT' | 'DELETE' | 'HEAD' | 'OPTIONS'>;

🧪 Hands-On Exercise

Let’s build a permission system! 🔒

📋 Step 1: Define Permission Structure

// 🔐 All possible permissions
type AllPermissions = 
  | 'read:profile'
  | 'write:profile'
  | 'delete:profile'
  | 'read:posts'
  | 'write:posts'
  | 'delete:posts'
  | 'read:comments'
  | 'write:comments'
  | 'delete:comments'
  | 'read:users'
  | 'write:users'
  | 'delete:users'
  | 'admin:manage_users'
  | 'admin:view_logs'
  | 'admin:system_config'
  | 'system:backup'
  | 'system:restore'
  | 'system:maintenance';

🎯 Step 2: Create Permission Filters

// 📖 Extract read permissions
type ReadPermissions = Extract<AllPermissions, `read:${string}`>;

// ✏️ Extract write permissions
type WritePermissions = Extract<AllPermissions, `write:${string}`>;

// 🗑️ Extract delete permissions
type DeletePermissions = Extract<AllPermissions, `delete:${string}`>;

// 👨‍💼 Extract admin permissions
type AdminPermissions = Extract<AllPermissions, `admin:${string}`>;

// ⚙️ Extract system permissions
type SystemPermissions = Extract<AllPermissions, `system:${string}`>;

// 👤 Regular user permissions (exclude admin and system)
type UserPermissions = Exclude<AllPermissions, AdminPermissions | SystemPermissions>;

// 🔒 Safe permissions (exclude delete operations)
type SafePermissions = Exclude<AllPermissions, DeletePermissions>;

// 📄 Content permissions (posts and comments only)
type ContentPermissions = Extract<AllPermissions, `${'read' | 'write' | 'delete'}:${'posts' | 'comments'}`>;

🚀 Step 3: Implement Role-Based Access

// 🏷️ User roles with specific permissions
interface Role {
  name: string;
  permissions: AllPermissions[];
}

// 👤 Define roles using our filtered types
const roles = {
  guest: {
    name: 'Guest',
    permissions: [
      ...(['read:posts', 'read:comments'] as Extract<AllPermissions, 'read:posts' | 'read:comments'>[])
    ]
  },
  
  user: {
    name: 'User', 
    permissions: [
      // 📖 All read permissions
      ...(['read:profile', 'read:posts', 'read:comments'] as ReadPermissions[]),
      // ✏️ Own content write permissions
      ...(['write:profile', 'write:posts', 'write:comments'] as WritePermissions[])
    ]
  },

  moderator: {
    name: 'Moderator',
    permissions: [
      // 👤 All user permissions plus content management
      ...roles.user.permissions,
      ...(['delete:posts', 'delete:comments'] as Extract<DeletePermissions, 'delete:posts' | 'delete:comments'>[])
    ]
  },

  admin: {
    name: 'Admin',
    permissions: [
      // 🔓 All non-system permissions
      ...(Object.values(roles.user.permissions) as Exclude<AllPermissions, SystemPermissions>[]),
      ...(['admin:manage_users', 'admin:view_logs', 'admin:system_config'] as AdminPermissions[])
    ]
  },

  superAdmin: {
    name: 'Super Admin',
    permissions: [
      // 🌟 All permissions
      ...(Object.values(AllPermissions) as AllPermissions[])
    ]
  }
} as const;

// ✅ Permission checker
const hasPermission = (userRole: keyof typeof roles, permission: AllPermissions): boolean => {
  return roles[userRole].permissions.includes(permission);
};

// 🎯 Usage examples
const canDeletePost = hasPermission('moderator', 'delete:posts');     // ✅ true
const canManageUsers = hasPermission('user', 'admin:manage_users');   // ❌ false
const canReadProfile = hasPermission('guest', 'read:profile');        // ❌ false

🎓 Challenge Solution

Excellent! 🎉 You’ve built a sophisticated permission system using Extract and Exclude. Notice how these utility types helped create clean, focused permission sets for different roles.

🎓 Key Takeaways

Outstanding work! 🎉 You’ve mastered Extract and Exclude utility types. Here’s what you’ve learned:

🏆 Core Concepts

  • Extract<T, U> 🎣: Selects from T only types assignable to U
  • Exclude<T, U> 🚫: Removes from T all types assignable to U
  • Both work with conditional type logic and union filtering

💡 Best Practices

  • 🎯 Use clear, descriptive type names
  • 🏗️ Build reusable filter patterns
  • 📝 Document complex filtering logic
  • 🔄 Use step-by-step filtering for clarity

🚀 Real-World Applications

  • 🎪 Event system filtering and handling
  • 🔒 Permission and role-based access control
  • 🌐 API endpoint categorization and routing
  • 📊 Data type sanitization and validation

🤝 Next Steps

Ready to explore more utility types? Here are your next adventures:

  1. NonNullable 🛡️: Remove null and undefined from types
  2. Parameters and ReturnType 🔧: Extract function type information
  3. ConstructorParameters 🏗️: Work with class constructor types
  4. Conditional Types Deep Dive 🤔: Master advanced conditional logic

Keep building amazing, type-safe applications with Extract and Exclude! 🚀✨

You’re becoming a TypeScript union filtering expert! 🧙‍♂️🔍