================================================================================ TYPESCRIPT FUNDAMENTALS - CHALLENGE 3 SOLUTION Chapter 9: Enums & Literal Types Challenge: Const Enum Optimization ================================================================================ PROBLEM: Create a UserRole const enum with Admin, User, Guest. Build a permission checker that uses the const enum. Compare compile output with regular enum. SOLUTION: ================================================================================ // Const Enum (optimized for runtime) const enum UserRole { Admin = "admin", User = "user", Guest = "guest" } // Permission checker function function hasPermission(role: UserRole, action: string): boolean { switch (role) { case UserRole.Admin: return true; // Admins can do anything case UserRole.User: return action === "read" || action === "write"; case UserRole.Guest: return action === "read"; // Guests can only read default: return false; } } // Helper function to check specific permissions function canDelete(role: UserRole): boolean { return role === UserRole.Admin; } function canRead(role: UserRole): boolean { return role !== UserRole.Guest || true; // Everyone can read } // Testing the solution console.log("=== User Role Permission Test ===\n"); console.log("Admin permissions:"); console.log(` Read: ${hasPermission(UserRole.Admin, "read")}`); // true console.log(` Write: ${hasPermission(UserRole.Admin, "write")}`); // true console.log(` Delete: ${hasPermission(UserRole.Admin, "delete")}`); // true console.log(` Can delete: ${canDelete(UserRole.Admin)}`); // true console.log("\nUser permissions:"); console.log(` Read: ${hasPermission(UserRole.User, "read")}`); // true console.log(` Write: ${hasPermission(UserRole.User, "write")}`); // true console.log(` Delete: ${hasPermission(UserRole.User, "delete")}`); // false console.log(` Can delete: ${canDelete(UserRole.User)}`); // false console.log("\nGuest permissions:"); console.log(` Read: ${hasPermission(UserRole.Guest, "read")}`); // true console.log(` Write: ${hasPermission(UserRole.Guest, "write")}`); // false console.log(` Delete: ${hasPermission(UserRole.Guest, "delete")}`); // false console.log(` Can delete: ${canDelete(UserRole.Guest)}`); // false ================================================================================ COMPILED JAVASCRIPT OUTPUT: ================================================================================ // REGULAR ENUM - Produces JavaScript object at runtime enum UserRole { Admin = "admin", User = "user", Guest = "guest" } // Compiles to: var UserRole; (function (UserRole) { UserRole["Admin"] = "admin"; UserRole["User"] = "user"; UserRole["Guest"] = "guest"; })(UserRole || (UserRole = {})); // Size: ~200+ bytes for the enum object --- // CONST ENUM - Values inlined, no object created const enum UserRole { Admin = "admin", User = "user", Guest = "guest" } // Compiles to: // (no enum object created!) // Function calls have inline values: function hasPermission(role, action) { switch (role) { case "admin": // Inlined! return true; case "user": // Inlined! return action === "read" || action === "write"; case "guest": // Inlined! return action === "read"; default: return false; } } // Size: ~150+ bytes (no enum object overhead) // Benefit: Smaller bundle, zero runtime cost ================================================================================ WHY THIS WORKS: ================================================================================ 1. Const Enum Optimization const enum UserRole { ... } - The "const" keyword tells TypeScript: optimize this away - No runtime object is created - All uses are inlined with their literal values 2. Type Safety During Development - TypeScript still checks: UserRole.Admin exists - TypeScript still checks: you only use valid roles - You get autocomplete: UserRole.| (shows Admin, User, Guest) 3. Zero Runtime Cost - Regular enum: creates an object at runtime (uses memory) - Const enum: disappears at compile-time (zero overhead) - The values are inlined wherever the enum is used 4. Inlining Example Code: if (role === UserRole.Admin) Becomes: if (role === "admin") The enum member is replaced with its literal value! ================================================================================ COMPARISON TABLE: ================================================================================ Regular Enum Const Enum Syntax enum X { ... } const enum X { ... } Runtime Object Yes (exists) No (inlined) Bundle Size Larger Smaller Reverse Mapping Yes (for numbers) Yes Type Safety Yes Yes Autocomplete Yes Yes Decorator Support Yes No Reflection Yes No Performance Normal Slightly faster Use Case Legacy, complex Modern, optimization ================================================================================ WHEN TO USE CONST ENUM: ================================================================================ ✅ DO use const enum when: - Values are literal strings or numbers - No need for reverse mapping - Bundle size matters (web apps) - High-frequency use (many calls per second) - Public API stability important (inlined values are public) ❌ DON'T use const enum when: - Need to iterate over enum values - Need reverse mapping (value → name) - Build separate for Node.js (const enums leak implementation) - Using reflection or Object.keys() - Values might change (they're inlined, not referenced) ================================================================================ ALTERNATIVE: COMPARING APPROACHES: ================================================================================ Approach 1: Regular Enum enum UserRole { Admin = "admin", User = "user", Guest = "guest" } // Pros: Works with reverse mapping, iteration // Cons: Creates runtime object, larger bundle Approach 2: Const Enum (Optimized) const enum UserRole { Admin = "admin", User = "user", Guest = "guest" } // Pros: Zero runtime cost, smaller bundle // Cons: Values are inlined (public), no reflection Approach 3: Literal Union (Modern) type UserRole = "admin" | "user" | "guest"; // Pros: Simple, no enum needed, modern // Cons: No enum name to use in switch (use string literal instead) Approach 4: Object Const const UserRole = { Admin: "admin", User: "user", Guest: "guest" } as const; type UserRole = typeof UserRole[keyof typeof UserRole]; // Pros: Flexible, modern, can iterate // Cons: More verbose, requires type extraction ================================================================================ REAL-WORLD EXAMPLES: ================================================================================ Example 1: HTTP Methods const enum HttpMethod { Get = "GET", Post = "POST", Put = "PUT", Delete = "DELETE", Patch = "PATCH" } function makeRequest(method: HttpMethod, url: string) { // method is inlined as literal string fetch(url, { method }); } // Compiled to: // fetch(url, { method: "GET" }); Example 2: Permission Levels const enum PermissionLevel { None = 0, Read = 1, Write = 2, Admin = 4 } function checkPermission(level: PermissionLevel, required: PermissionLevel) { return (level & required) === required; } // Values inlined at compile-time Example 3: Status Codes const enum StatusCode { Ok = 200, Created = 201, BadRequest = 400, Unauthorized = 401, NotFound = 404, ServerError = 500 } function handleStatus(code: StatusCode) { switch (code) { case 200: return "OK"; case 404: return "Not Found"; // Inlined numbers } } ================================================================================ TESTING & VERIFICATION: ================================================================================ console.log("=== Const Enum Testing ===\n"); // Test 1: Type checking console.log("Test 1 - Type Safety:"); console.log(`✅ UserRole.Admin exists: ${UserRole.Admin === "admin"}`); console.log(`✅ UserRole.User exists: ${UserRole.User === "user"}`); console.log(`✅ UserRole.Guest exists: ${UserRole.Guest === "guest"}`); // Test 2: Permission checker console.log("\nTest 2 - Permission Checker:"); const adminRole = UserRole.Admin; const userRole = UserRole.User; const guestRole = UserRole.Guest; console.log(`Admin has read: ${hasPermission(adminRole, "read")}`); console.log(`User has read: ${hasPermission(userRole, "read")}`); console.log(`Guest has read: ${hasPermission(guestRole, "read")}`); console.log(`Guest has write: ${hasPermission(guestRole, "write")}`); // Test 3: Exhaustiveness checking console.log("\nTest 3 - Exhaustiveness:"); function describeRole(role: UserRole): string { switch (role) { case UserRole.Admin: return "Administrator (full access)"; case UserRole.User: return "User (read/write)"; case UserRole.Guest: return "Guest (read-only)"; } } console.log(`Admin: ${describeRole(UserRole.Admin)}`); console.log(`User: ${describeRole(UserRole.User)}`); console.log(`Guest: ${describeRole(UserRole.Guest)}`); // Test 4: In conditional console.log("\nTest 4 - Conditional Usage:"); function requiresLogin(role: UserRole): boolean { return role !== UserRole.Guest; } console.log(`Admin requires login: ${requiresLogin(UserRole.Admin)}`); console.log(`Guest requires login: ${requiresLogin(UserRole.Guest)}`); // Test 5: Type compatibility console.log("\nTest 5 - Type Compatibility:"); const roles: UserRole[] = [UserRole.Admin, UserRole.User, UserRole.Guest]; console.log(`Number of roles: ${roles.length}`); ================================================================================ OPTIMIZATION DEMONSTRATION: ================================================================================ // Bundle size comparison // Regular Enum (simulated transpiled code) var UserRole1; (function (UserRole1) { UserRole1["Admin"] = "admin"; UserRole1["User"] = "user"; UserRole1["Guest"] = "guest"; })(UserRole1 || (UserRole1 = {})); function hasPermission1(role, action) { switch (role) { case UserRole1.Admin: // References object return true; // ... } } // Const Enum (optimized) function hasPermission2(role, action) { switch (role) { case "admin": // Inline value (no object reference) return true; // ... } } // Difference: // 1. No UserRole object created // 2. No property lookups (UserRole.Admin) // 3. Direct string literal comparison // 4. Smaller bundle size ================================================================================ KEY TAKEAWAYS: ================================================================================ ✅ Const enums are optimized for bundle size and performance ✅ Values are inlined at compile-time, never referenced at runtime ✅ Type safety is preserved during development ✅ Perfect for web applications where bundle size matters ✅ Compare generated JavaScript to understand the optimization When to Use: - Const enum: High-performance, small bundle, stable values - Regular enum: Need reverse mapping or reflection - Literal union: Modern, simple, no overhead Performance Consideration: - Const enum: ~150 bytes (no object) - Regular enum: ~200+ bytes (object created) - Literal union: ~80 bytes (no type at runtime) The const enum is a TypeScript-specific optimization. The values are inlined during compilation, so they have ZERO runtime cost. This is why it's preferred for constants in performance-sensitive code. ================================================================================