[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-weekly-typescript-generics":32,"quiz-article-weekly-typescript-generics":48},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"id":33,"slug":34,"kind":35,"title":36,"description":37,"config":38,"verticalId":5,"vertical":43,"course":40,"_count":44,"access":45,"attempts":47,"questionCount":39},"019fe776-6408-72ff-8ac3-baa2e75b42c6","weekly-typescript-generics","PRACTICE_QUIZ","TypeScript Generics","Check what stuck from the deep-dive: what a type parameter actually is, how constraints and defaults work, and where generics stop mattering because the types are gone.",{"questionCount":39,"timeLimitSec":40,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":41,"maxAttempts":40,"revealAnswers":42,"allowFlagging":18,"allowBacktracking":17},7,null,70,"IMMEDIATE",{"slug":6,"name":7},{"questions":39},{"allowed":17,"reason":46},"FREE",[],{"id":49,"slug":34,"title":50,"subtitle":40,"excerpt":51,"coverUrl":52,"locale":13,"readingMinutes":53,"publishedAt":54,"viewCount":55,"likeCount":19,"commentCount":19,"author":56,"vertical":61,"topic":62,"tags":65,"_count":77,"playground":79,"body":81,"bodyMd":449,"seo":450,"translationGroupId":452,"thread":453,"assessments":455,"translations":458,"quiz":460},"019fe660-57eb-7258-b452-d66ff04d2380","TypeScript Generics: The Complete Guide (with Cheat Sheet)","Learn TypeScript generics from the ground up — generic functions, constraints, defaults, and classes — with worked examples and a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fweekly-typescript-generics.png",15,"2026-07-20T09:23:23.621Z",30,{"id":57,"name":58,"username":59,"avatarUrl":40,"headline":60},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":63,"name":64},"typescript","TypeScript",[66,68,71,74],{"slug":63,"name":67,"color":40},"Typescript",{"slug":69,"name":70,"color":40},"javascript","Javascript",{"slug":72,"name":73,"color":40},"tutorial","Tutorial",{"slug":75,"name":76,"color":40},"webdev","Webdev",{"assessments":78},1,{"slug":34,"title":80},"TypeScript Generics: any vs &lt;T&gt;",{"blocks":82,"version":78},[83,87,92,95,104,107,110,125,128,131,137,140,143,147,150,153,156,159,163,166,169,172,175,179,183,186,189,192,195,198,201,204,208,211,214,217,221,224,227,231,234,237,240,244,247,251,254,257,261,264,267,270,274,277,280,283,287,290,294,297,300,308,311,314,317,320,323,326,330,333,337,340,343,346,350,353,356,359,362,402,406,409,412,415,418,421,424,432,435,438,441],{"id":84,"html":85,"type":86},"b1","\u003Cp>You write a function that works on numbers. Then you need the same function for strings. You copy-paste it and change the type annotation. Then arrays. Then objects. Now you have four functions with identical bodies and different type signatures — or you gave up and typed everything \u003Ccode>any\u003C\u002Fcode>, and the compiler stopped helping you at the one place you needed it most: the boundary between what you pass in and what you get back.\u003C\u002Fp>","paragraph",{"id":88,"html":89,"text":90,"type":91,"level":31},"b2","What you&#39;ll learn","What you'll learn","heading",{"id":93,"html":94,"type":86},"b3","\u003Cp>By the end of this guide you&#39;ll be able to:\u003C\u002Fp>",{"id":96,"type":97,"items":98,"ordered":18},"b4","list",[99,100,101,102,103],"Write generic functions that preserve the exact type of their input in their output, instead of widening it to \u003Ccode>any\u003C\u002Fcode> or \u003Ccode>unknown\u003C\u002Fcode>","Constrain a type parameter with \u003Ccode>extends\u003C\u002Fcode> so the compiler enforces a shape without collapsing to one concrete type","Give a type parameter a sensible default, the way you&#39;d default a function argument","Read and write generic interfaces, types, and classes with more than one type parameter","Diagnose the most common generic inference failures instead of reaching for \u003Ccode>any\u003C\u002Fcode> to silence them",{"id":105,"html":106,"type":86},"b5","\u003Cp>\u003Cstrong>Who this is for:\u003C\u002Fstrong> you&#39;ve written TypeScript with interfaces and function signatures, and you&#39;ve seen \u003Ccode>&lt;T&gt;\u003C\u002Fcode> in library code, but you want the model that makes it predictable instead of memorized syntax.\u003C\u002Fp>",{"id":108,"html":109,"text":109,"type":91,"level":31},"b6","Contents",{"id":111,"type":97,"items":112,"ordered":18},"b7",[113,114,115,116,117,118,119,120,121,122,123,124],"\u003Ca href=\"#why-generics-exist\">Why generics exist\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-a-type-parameter-is-a-function-argument-for-types\">The mental model\u003C\u002Fa>","\u003Ca href=\"#stage-1-your-first-generic-function\">Stage 1: your first generic function\u003C\u002Fa>","\u003Ca href=\"#stage-2-generic-interfaces-and-types\">Stage 2: generic interfaces and types\u003C\u002Fa>","\u003Ca href=\"#stage-3-constraining-a-type-parameter-with-extends\">Stage 3: constraining a type parameter with extends\u003C\u002Fa>","\u003Ca href=\"#stage-4-default-type-parameters\">Stage 4: default type parameters\u003C\u002Fa>","\u003Ca href=\"#stage-5-multiple-type-parameters-and-generic-classes\">Stage 5: multiple type parameters and generic classes\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices-when-not-to-reach-for-generics\">Best practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key takeaways\u003C\u002Fa>",{"id":126,"html":127,"text":127,"type":91,"level":31},"b8","Why generics exist",{"id":129,"html":130,"type":86},"b9","\u003Cp>Here&#39;s the naive fix for &quot;I need this function to work on more than one type&quot; — widen the parameter to \u003Ccode>any\u003C\u002Fcode>:\u003C\u002Fp>",{"id":132,"code":133,"type":134,"language":135,"highlight":136},"b10","function firstElement(arr: any): any {\n  return arr[0];\n}\n\nconst n = firstElement([1, 2, 3]);      \u002F\u002F typed as `any` — no autocomplete, no error checking\nconst s = firstElement([\"a\", \"b\"]);     \u002F\u002F also `any` — TypeScript has no idea it's a string\nn.toUpperCase();                        \u002F\u002F compiles fine, crashes at runtime: not a string","code","ts",[],{"id":138,"html":139,"type":86},"b11","\u003Cp>The function works at runtime for both calls. But the moment you use the result, TypeScript has nothing to say — \u003Ccode>any\u003C\u002Fcode> erases the connection between what went in and what came out. You&#39;ve traded a compile-time error for a runtime crash, which is the one trade TypeScript exists to prevent.\u003C\u002Fp>",{"id":141,"html":142,"type":86},"b12","\u003Cp>The alternative most people try next is copy-pasting one function per type:\u003C\u002Fp>",{"id":144,"code":145,"type":134,"language":135,"highlight":146},"b13","function firstNumber(arr: number[]): number { return arr[0]; }\nfunction firstString(arr: string[]): string { return arr[0]; }\n\u002F\u002F ...one more function for every type you ever call this with",[],{"id":148,"html":149,"type":86},"b14","\u003Cp>This is type-safe, but it doesn&#39;t scale — you&#39;re maintaining N identical function bodies, and a bug fix means updating all of them. Generics solve exactly this: one function body, and a type that adapts to whatever you call it with, while the compiler still checks that the relationship holds.\u003C\u002Fp>",{"id":151,"html":152,"text":152,"type":91,"level":31},"b15","The mental model: a type parameter is a function argument for types",{"id":154,"html":155,"type":86},"b16","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> a generic \u003Ccode>&lt;T&gt;\u003C\u002Fcode> is a parameter, exactly like a function parameter — except instead of a value, you&#39;re passing in a \u003Cem>type\u003C\u002Fem>, and the compiler substitutes it everywhere \u003Ccode>T\u003C\u002Fcode> appears and checks that everything stays consistent.\u003C\u002Fp>",{"id":157,"html":158,"type":86},"b17","\u003Cp>Compare the two side by side:\u003C\u002Fp>",{"id":160,"code":161,"type":134,"language":135,"highlight":162},"b18","\u002F\u002F a regular function: `x` is a placeholder for a VALUE, filled in at the call site\nfunction double(x: number): number {\n  return x * 2;\n}\ndouble(5); \u002F\u002F x = 5\n\n\u002F\u002F a generic function: `T` is a placeholder for a TYPE, filled in at the call site\nfunction identity\u003CT>(x: T): T {\n  return x;\n}\nidentity(5);      \u002F\u002F T = number\nidentity(\"hi\");   \u002F\u002F T = string",[],{"id":164,"html":165,"type":86},"b19","\u003Cp>\u003Ccode>double\u003C\u002Fcode>&#39;s parameter list says &quot;give me a number, I&#39;ll give you a number.&quot; \u003Ccode>identity\u003C\u002Fcode>&#39;s type parameter list says &quot;give me \u003Cem>some\u003C\u002Fem> type T, and I promise to give you back that same T&quot; — it&#39;s a contract about the \u003Cem>shape of the relationship\u003C\u002Fem>, not about one fixed type. TypeScript infers \u003Ccode>T\u003C\u002Fcode> from the argument you actually pass, the same way it infers the type of a variable from its initializer — you rarely write \u003Ccode>identity&lt;number&gt;(5)\u003C\u002Fcode> explicitly; the compiler figures out \u003Ccode>T = number\u003C\u002Fcode> on its own.\u003C\u002Fp>",{"id":167,"html":168,"type":86},"b20","\u003Cp>That&#39;s the whole idea. Everything below is this one substitution rule, applied to interfaces, constraints, defaults, and classes.\u003C\u002Fp>",{"id":170,"html":171,"text":171,"type":91,"level":31},"b21","Stage 1: your first generic function",{"id":173,"html":174,"type":86},"b22","\u003Cp>Fix the \u003Ccode>firstElement\u003C\u002Fcode> function from earlier with a type parameter instead of \u003Ccode>any\u003C\u002Fcode>:\u003C\u002Fp>",{"id":176,"code":177,"type":134,"language":135,"highlight":178},"b23","function firstElement\u003CT>(arr: T[]): T | undefined {\n  return arr[0];\n}\n\nconst n = firstElement([1, 2, 3]);    \u002F\u002F n: number | undefined\nconst s = firstElement([\"a\", \"b\"]);   \u002F\u002F s: string | undefined\nn?.toFixed(2);                        \u002F\u002F ✅ compiler knows n might be a number\ns?.toUpperCase();                     \u002F\u002F ✅ compiler knows s might be a string",[],{"id":180,"html":181,"type":182},"b24","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>T\u003C\u002Fcode> is inferred once, from the argument, and then reused for the return type. The function body never mentions \u003Ccode>number\u003C\u002Fcode> or \u003Ccode>string\u003C\u002Fcode> — it stays generic, but every call site gets a fully concrete, checked type back.\u003C\u002Fp>\n","quote",{"id":184,"html":185,"type":86},"b25","\u003C!-- playground:start -->",{"id":187,"html":188,"text":188,"type":91,"level":31},"b26","🎮 Try it yourself",{"id":190,"html":191,"type":86},"b27","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-20-weekly-typescript-generics\u002Fplayground\u002F\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":193,"html":194,"type":86},"b28","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":196,"html":197,"type":86},"b29","\u003C!-- playground:end -->",{"id":199,"html":200,"type":86},"b30","\u003Cp>The \u003Ccode>T | undefined\u003C\u002Fcode> return type matters too: an empty array is a valid \u003Ccode>T[]\u003C\u002Fcode>, so the function is honest that there might be nothing at index 0. This is a case where \u003Ccode>any\u003C\u002Fcode> would have silently hidden a real edge case that \u003Ccode>T | undefined\u003C\u002Fcode> forces you to handle.\u003C\u002Fp>",{"id":202,"html":203,"type":86},"b31","\u003Cp>Generic functions can also take more than the type parameter implies. A \u003Ccode>map\u003C\u002Fcode>-style helper needs a second, ordinary parameter — a callback — that also mentions \u003Ccode>T\u003C\u002Fcode>:\u003C\u002Fp>",{"id":205,"code":206,"type":134,"language":135,"highlight":207},"b32","function mapArray\u003CT, U>(arr: T[], fn: (item: T) => U): U[] {\n  return arr.map(fn);\n}\n\nconst lengths = mapArray([\"a\", \"bb\", \"ccc\"], (s) => s.length); \u002F\u002F lengths: number[]",[],{"id":209,"html":210,"type":86},"b33","\u003Cp>Here \u003Ccode>T\u003C\u002Fcode> is inferred from \u003Ccode>arr\u003C\u002Fcode> (\u003Ccode>string\u003C\u002Fcode>) and \u003Ccode>U\u003C\u002Fcode> is inferred from what \u003Ccode>fn\u003C\u002Fcode> returns (\u003Ccode>number\u003C\u002Fcode>) — two independent type parameters, each pinned down by a different argument.\u003C\u002Fp>",{"id":212,"html":213,"text":213,"type":91,"level":31},"b34","Stage 2: generic interfaces and types",{"id":215,"html":216,"type":86},"b35","\u003Cp>The same substitution rule applies to \u003Ccode>interface\u003C\u002Fcode> and \u003Ccode>type\u003C\u002Fcode>, not just functions. A generic interface is a shape with a type-level blank to fill in:\u003C\u002Fp>",{"id":218,"code":219,"type":134,"language":135,"highlight":220},"b36","interface Box\u003CT> {\n  value: T;\n}\n\nconst numberBox: Box\u003Cnumber> = { value: 42 };\nconst stringBox: Box\u003Cstring> = { value: \"hello\" };\n\u002F\u002F const bad: Box\u003Cnumber> = { value: \"hello\" }; \u002F\u002F ❌ string is not assignable to number",[],{"id":222,"html":223,"type":86},"b37","\u003Cp>\u003Ccode>Box&lt;T&gt;\u003C\u002Fcode> isn&#39;t a type by itself — it&#39;s a \u003Cem>template\u003C\u002Fem> for a type. \u003Ccode>Box&lt;number&gt;\u003C\u002Fcode> and \u003Ccode>Box&lt;string&gt;\u003C\u002Fcode> are the actual types, produced by substituting \u003Ccode>T\u003C\u002Fcode>. This is the exact same mechanism as \u003Ccode>Array&lt;T&gt;\u003C\u002Fcode> (the type behind \u003Ccode>T[]\u003C\u002Fcode>), \u003Ccode>Promise&lt;T&gt;\u003C\u002Fcode>, and \u003Ccode>Map&lt;K, V&gt;\u003C\u002Fcode> from the standard library — you&#39;ve been using generics since your first \u003Ccode>Promise&lt;void&gt;\u003C\u002Fcode>, you just hadn&#39;t named the mechanism yet.\u003C\u002Fp>",{"id":225,"html":226,"type":86},"b38","\u003Cp>Generic type aliases work identically and are common for API response shapes:\u003C\u002Fp>",{"id":228,"code":229,"type":134,"language":135,"highlight":230},"b39","type ApiResponse\u003CT> = {\n  data: T;\n  error: string | null;\n};\n\nasync function getUser(id: string): Promise\u003CApiResponse\u003CUser>> {\n  \u002F\u002F ...\n}",[],{"id":232,"html":233,"type":86},"b40","\u003Cp>Every endpoint reuses one \u003Ccode>ApiResponse&lt;T&gt;\u003C\u002Fcode> shape instead of a hand-written \u003Ccode>UserResponse\u003C\u002Fcode>, \u003Ccode>PostResponse\u003C\u002Fcode>, \u003Ccode>OrderResponse\u003C\u002Fcode> trio that all say the same thing with different names.\u003C\u002Fp>",{"id":235,"html":236,"text":236,"type":91,"level":31},"b41","Stage 3: constraining a type parameter with extends",{"id":238,"html":239,"type":86},"b42","\u003Cp>Unconstrained, \u003Ccode>T\u003C\u002Fcode> could be anything — which means you can&#39;t assume it has any properties at all:\u003C\u002Fp>",{"id":241,"code":242,"type":134,"language":135,"highlight":243},"b43","function getLength\u003CT>(item: T): number {\n  return item.length; \u002F\u002F ❌ Property 'length' does not exist on type 'T'\n}",[],{"id":245,"html":246,"type":86},"b44","\u003Cp>The compiler is right to reject this: nothing says \u003Ccode>T\u003C\u002Fcode> has a \u003Ccode>.length\u003C\u002Fcode>. The fix is a \u003Cstrong>constraint\u003C\u002Fstrong> — \u003Ccode>extends\u003C\u002Fcode> here doesn&#39;t mean &quot;inherits from,&quot; it means &quot;must be assignable to&quot;:\u003C\u002Fp>",{"id":248,"code":249,"type":134,"language":135,"highlight":250},"b45","interface HasLength {\n  length: number;\n}\n\nfunction getLength\u003CT extends HasLength>(item: T): number {\n  return item.length; \u002F\u002F ✅ every T that reaches this line is guaranteed to have `.length`\n}\n\ngetLength(\"hello\");        \u002F\u002F ✅ strings have .length\ngetLength([1, 2, 3]);      \u002F\u002F ✅ arrays have .length\ngetLength({ length: 10 }); \u002F\u002F ✅ any object shape with a length field\ngetLength(42);              \u002F\u002F ❌ number has no .length",[],{"id":252,"html":253,"type":182},"b46","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>T extends HasLength\u003C\u002Fcode> narrows \u003Cem>which types are legal\u003C\u002Fem>, not what \u003Ccode>T\u003C\u002Fcode> collapses to. \u003Ccode>T\u003C\u002Fcode> is still inferred per call — a \u003Ccode>string\u003C\u002Fcode>, an array, or a custom object — the constraint only guarantees the one property you need.\u003C\u002Fp>\n",{"id":255,"html":256,"type":86},"b47","\u003Cp>A very common constraint pattern uses \u003Ccode>keyof\u003C\u002Fcode> to guarantee a key actually exists on an object, which is what makes a type-safe \u003Ccode>getProperty\u003C\u002Fcode> helper possible:\u003C\u002Fp>",{"id":258,"code":259,"type":134,"language":135,"highlight":260},"b48","function getProperty\u003CT, K extends keyof T>(obj: T, key: K): T[K] {\n  return obj[key];\n}\n\nconst user = { name: \"Ada\", age: 36 };\ngetProperty(user, \"name\");   \u002F\u002F ✅ inferred as string\ngetProperty(user, \"email\");  \u002F\u002F ❌ \"email\" is not a key of { name: string; age: number }",[],{"id":262,"html":263,"type":86},"b49","\u003Cp>\u003Ccode>K extends keyof T\u003C\u002Fcode> reads as &quot;K must be one of T&#39;s actual property names.&quot; Once that holds, \u003Ccode>T[K]\u003C\u002Fcode> — an \u003Cem>indexed access type\u003C\u002Fem> — gives you the precise type of that property, so \u003Ccode>getProperty(user, &quot;age&quot;)\u003C\u002Fcode> returns \u003Ccode>number\u003C\u002Fcode>, not some generic \u003Ccode>unknown\u003C\u002Fcode>.\u003C\u002Fp>",{"id":265,"html":266,"text":266,"type":91,"level":31},"b50","Stage 4: default type parameters",{"id":268,"html":269,"type":86},"b51","\u003Cp>A type parameter can have a default, exactly like a function parameter can have a default value — used when the caller doesn&#39;t supply one:\u003C\u002Fp>",{"id":271,"code":272,"type":134,"language":135,"highlight":273},"b52","interface FetchOptions\u003CTResponse = unknown> {\n  url: string;\n  parse?: (raw: string) => TResponse;\n}\n\nconst raw: FetchOptions = { url: \"\u002Fping\" };                 \u002F\u002F TResponse defaults to unknown\nconst typed: FetchOptions\u003C{ ok: boolean }> = {               \u002F\u002F explicit, overrides the default\n  url: \"\u002Fhealth\",\n  parse: (raw) => JSON.parse(raw),\n};",[],{"id":275,"html":276,"type":86},"b53","\u003Cp>Defaults matter most on widely-used generic types where most callers don&#39;t care about every parameter — a state-management or component-library type might have four type parameters with sensible defaults so 90% of call sites only ever write the one that matters to them, and the rest fall back silently.\u003C\u002Fp>",{"id":278,"html":279,"text":279,"type":91,"level":31},"b54","Stage 5: multiple type parameters and generic classes",{"id":281,"html":282,"type":86},"b55","\u003Cp>Nothing restricts you to one \u003Ccode>T\u003C\u002Fcode>. Multiple type parameters are common wherever there&#39;s a key\u002Fvalue or input\u002Foutput relationship:\u003C\u002Fp>",{"id":284,"code":285,"type":134,"language":135,"highlight":286},"b56","function zip\u003CA, B>(a: A[], b: B[]): [A, B][] {\n  return a.map((item, i) => [item, b[i]]);\n}\n\nzip([1, 2], [\"a\", \"b\"]); \u002F\u002F [number, string][] — inferred as [[1, \"a\"], [2, \"b\"]]",[],{"id":288,"html":289,"type":86},"b57","\u003Cp>Classes take type parameters the same way functions do — on the class itself, available to every method and property inside:\u003C\u002Fp>",{"id":291,"code":292,"type":134,"language":135,"highlight":293},"b58","class Stack\u003CT> {\n  private items: T[] = [];\n\n  push(item: T): void {\n    this.items.push(item);\n  }\n\n  pop(): T | undefined {\n    return this.items.pop();\n  }\n\n  get size(): number {\n    return this.items.length;\n  }\n}\n\nconst numbers = new Stack\u003Cnumber>();\nnumbers.push(1);\nnumbers.push(2);\nnumbers.pop(); \u002F\u002F number | undefined\n\nconst strings = new Stack\u003Cstring>();\nstrings.push(\"a\");\n\u002F\u002F strings.push(1); \u002F\u002F ❌ number is not assignable to string",[],{"id":295,"html":296,"type":86},"b59","\u003Cp>\u003Ccode>T\u003C\u002Fcode> is fixed once, at \u003Ccode>new Stack&lt;number&gt;()\u003C\u002Fcode>, and every method on that instance uses that same \u003Ccode>T\u003C\u002Fcode> for the rest of its life — \u003Ccode>push\u003C\u002Fcode> and \u003Ccode>pop\u003C\u002Fcode> stay in sync automatically, without repeating the type anywhere else in the class body.\u003C\u002Fp>",{"id":298,"html":299,"text":299,"type":91,"level":31},"b60","Edge cases and gotchas",{"id":301,"type":97,"items":302,"ordered":18},"b61",[303,304,305,306,307],"\u003Cstrong>Type erasure at runtime.\u003C\u002Fstrong> Generics are a compile-time-only construct — they&#39;re erased entirely from the emitted JavaScript. You cannot check \u003Ccode>typeof T\u003C\u002Fcode> or branch on \u003Ccode>T\u003C\u002Fcode> at runtime; if you need runtime knowledge of the type, you must pass it explicitly as a value (a constructor, a discriminant field, or a plain string tag), because by the time your code runs, \u003Ccode>T\u003C\u002Fcode> no longer exists.","\u003Cstrong>Inference can fail on object literals with contextual typing.\u003C\u002Fstrong> \u003Ccode>identity({ name: &quot;Ada&quot; })\u003C\u002Fcode> infers \u003Ccode>T\u003C\u002Fcode> as \u003Ccode>{ name: string }\u003C\u002Fcode>, which is often what you want, but if you need a \u003Cem>wider\u003C\u002Fem> inferred literal type (e.g. keeping a string as a specific literal rather than widening to \u003Ccode>string\u003C\u002Fcode>), you&#39;ll need \u003Ccode>as const\u003C\u002Fcode> on the argument, not a change to the generic itself.","\u003Cstrong>Arrow functions with generics need care in \u003Ccode>.tsx\u003C\u002Fcode> files.\u003C\u002Fstrong> \u003Ccode>const identity = &lt;T&gt;(x: T) =&gt; x;\u003C\u002Fcode> can be parsed as a JSX opening tag inside a \u003Ccode>.tsx\u003C\u002Fcode> file. The common workaround is a trailing comma — \u003Ccode>&lt;T,&gt;(x: T) =&gt; x\u003C\u002Fcode> — or naming a constraint explicitly, \u003Ccode>&lt;T extends unknown&gt;(x: T) =&gt; x\u003C\u002Fcode>.","\u003Cstrong>Conditional types distribute over unions unless you stop them.\u003C\u002Fstrong> \u003Ccode>type ToArray&lt;T&gt; = T extends any ? T[] : never;\u003C\u002Fcode> applied to \u003Ccode>string | number\u003C\u002Fcode> produces \u003Ccode>string[] | number[]\u003C\u002Fcode>, not \u003Ccode>(string | number)[]\u003C\u002Fcode>, because a naked type parameter in a conditional type distributes across each union member individually. Wrapping both sides in a tuple (\u003Ccode>[T] extends [any] ? ...\u003C\u002Fcode>) disables that distribution when you don&#39;t want it.","\u003Cstrong>A constraint is a floor, not the resolved type.\u003C\u002Fstrong> \u003Ccode>function f&lt;T extends { id: string }&gt;(x: T)\u003C\u002Fcode> still infers the caller&#39;s \u003Cem>full\u003C\u002Fem> type for \u003Ccode>T\u003C\u002Fcode> (every extra property survives), not just \u003Ccode>{ id: string }\u003C\u002Fcode> — the constraint only restricts which types are legal, it doesn&#39;t narrow what gets stored in \u003Ccode>T\u003C\u002Fcode>.",{"id":309,"html":310,"text":310,"type":91,"level":31},"b62","Best practices: when (not) to reach for generics",{"id":312,"html":313,"type":86},"b63","\u003Cp>\u003Cstrong>Reach for a generic when\u003C\u002Fstrong> a function or type&#39;s inputs and outputs are \u003Cem>related\u003C\u002Fem> and that relationship needs to survive into the caller&#39;s code — a \u003Ccode>firstElement&lt;T&gt;\u003C\u002Fcode> that returns the same \u003Ccode>T\u003C\u002Fcode> it received, a \u003Ccode>Box&lt;T&gt;\u003C\u002Fcode> that wraps any payload, an \u003Ccode>ApiResponse&lt;T&gt;\u003C\u002Fcode> reused across every endpoint.\u003C\u002Fp>",{"id":315,"html":316,"type":86},"b64","\u003Cp>\u003Cstrong>Don&#39;t reach for a generic when\u003C\u002Fstrong> a union type says what you mean more simply. If a parameter only ever needs to be \u003Ccode>&quot;light&quot; | &quot;dark&quot;\u003C\u002Fcode>, write that union — a \u003Ccode>&lt;T extends &quot;light&quot; | &quot;dark&quot;&gt;\u003C\u002Fcode> generic adds ceremony without adding safety.\u003C\u002Fp>",{"id":318,"html":319,"type":86},"b65","\u003Cp>\u003Cstrong>Don&#39;t reach for a generic to avoid writing \u003Ccode>unknown\u003C\u002Fcode>.\u003C\u002Fstrong> If a value&#39;s type genuinely isn&#39;t knowable until you inspect it at runtime (parsed JSON, a \u003Ccode>catch\u003C\u002Fcode> block&#39;s error), the honest type is \u003Ccode>unknown\u003C\u002Fcode> plus a runtime check — not a type parameter with no way to constrain it, and not \u003Ccode>any\u003C\u002Fcode>, which is how you end up back at the \u003Ccode>firstElement\u003C\u002Fcode> bug from the top of this article. TypeScript&#39;s own \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fyour-typescript-config-objects-are-losing-type-information-satisfies-fixes-it-3hdh\">\u003Ccode>satisfies\u003C\u002Fcode> operator\u003C\u002Fa> solves a related but different problem — keeping a literal&#39;s narrow type while still validating it against a shape — worth knowing alongside generics, not instead of them.\u003C\u002Fp>",{"id":321,"html":322,"type":86},"b66","\u003Cp>\u003Cstrong>Don&#39;t over-parameterize.\u003C\u002Fstrong> A function with four type parameters, three of them defaulted and never varied at any call site in your codebase, is a sign the abstraction arrived before the second real use case did. Write the concrete version first; generalize when a second caller actually needs a different type.\u003C\u002Fp>",{"id":324,"html":325,"text":325,"type":91,"level":31},"b67","FAQ",{"id":327,"html":328,"text":328,"type":91,"level":329},"b68","What is a generic in TypeScript?",3,{"id":331,"html":332,"type":86},"b69","\u003Cp>A generic is a type parameter — a placeholder for a type, written in angle brackets like \u003Ccode>&lt;T&gt;\u003C\u002Fcode> — that lets a function, interface, type, or class work with any type while the compiler still tracks and checks that type through every use, instead of collapsing everything to \u003Ccode>any\u003C\u002Fcode>.\u003C\u002Fp>",{"id":334,"html":335,"text":336,"type":91,"level":329},"b70","Is \u003Ccode>T extends SomeType\u003C\u002Fcode> the same as class inheritance?","Is T extends SomeType the same as class inheritance?",{"id":338,"html":339,"type":86},"b71","\u003Cp>No. In a generic constraint, \u003Ccode>extends\u003C\u002Fcode> means &quot;must be assignable to&quot; or &quot;must have at least this shape&quot; — it restricts which types are legal for \u003Ccode>T\u003C\u002Fcode>, not a class hierarchy. \u003Ccode>T extends { id: string }\u003C\u002Fcode> accepts any type with an \u003Ccode>id: string\u003C\u002Fcode> property, not just subclasses of some \u003Ccode>{ id: string }\u003C\u002Fcode> class.\u003C\u002Fp>",{"id":341,"html":342,"text":342,"type":91,"level":329},"b72","Can I use a default value for a generic type parameter?",{"id":344,"html":345,"type":86},"b73","\u003Cp>Yes — \u003Ccode>interface Box&lt;T = unknown&gt;\u003C\u002Fcode> gives \u003Ccode>T\u003C\u002Fcode> a default the same way a function parameter can have one. Callers that don&#39;t specify a type argument get the default; callers that do (\u003Ccode>Box&lt;number&gt;\u003C\u002Fcode>) override it.\u003C\u002Fp>",{"id":347,"html":348,"text":349,"type":91,"level":329},"b74","Is \u003Ccode>any\u003C\u002Fcode> the same as a generic?","Is any the same as a generic?",{"id":351,"html":352,"type":86},"b75","\u003Cp>No, and this is the most common confusion. \u003Ccode>any\u003C\u002Fcode> disables type checking entirely for that value — TypeScript stops tracking it. A generic \u003Ccode>T\u003C\u002Fcode> keeps type checking fully active; it just defers \u003Cem>which\u003C\u002Fem> type until the call site, and enforces that the same \u003Ccode>T\u003C\u002Fcode> is used consistently everywhere it appears.\u003C\u002Fp>",{"id":354,"html":355,"text":355,"type":91,"level":329},"b76","Do generic types exist at runtime?",{"id":357,"html":358,"type":86},"b77","\u003Cp>No. TypeScript&#39;s type system, including all generics, is erased during compilation — there is no \u003Ccode>T\u003C\u002Fcode> in the emitted JavaScript, and you cannot inspect it with \u003Ccode>typeof\u003C\u002Fcode> or \u003Ccode>instanceof\u003C\u002Fcode> at runtime. Anything you need to know while the program is running must be represented as an actual runtime value, not just a type parameter.\u003C\u002Fp>",{"id":360,"html":361,"text":361,"type":91,"level":31},"b78","Cheat sheet",{"id":363,"head":364,"rows":368,"type":401},"b79",[365,366,367],"Task","Syntax","Notes",[369,373,377,381,385,389,393,397],[370,371,372],"Generic function","\u003Ccode>function f&lt;T&gt;(x: T): T\u003C\u002Fcode>","\u003Ccode>T\u003C\u002Fcode> inferred from the argument",[374,375,376],"Generic interface\u002Ftype","\u003Ccode>interface Box&lt;T&gt; { value: T }\u003C\u002Fcode>","Instantiate as \u003Ccode>Box&lt;number&gt;\u003C\u002Fcode>",[378,379,380],"Multiple type parameters","\u003Ccode>function zip&lt;A, B&gt;(a: A[], b: B[])\u003C\u002Fcode>","Each inferred independently",[382,383,384],"Constrain a type parameter","\u003Ccode>&lt;T extends HasLength&gt;\u003C\u002Fcode>","&quot;Must be assignable to,&quot; not inheritance",[386,387,388],"Require a real key of an object","\u003Ccode>&lt;T, K extends keyof T&gt;\u003C\u002Fcode>","Pairs with indexed access: \u003Ccode>T[K]\u003C\u002Fcode>",[390,391,392],"Default type parameter","\u003Ccode>&lt;T = unknown&gt;\u003C\u002Fcode>","Used when the caller omits a type argument",[394,395,396],"Generic class","\u003Ccode>class Stack&lt;T&gt; { push(item: T): void }\u003C\u002Fcode>","\u003Ccode>T\u003C\u002Fcode> fixed once, at \u003Ccode>new Stack&lt;number&gt;()\u003C\u002Fcode>",[398,399,400],"Explicit type argument (rare)","\u003Ccode>identity&lt;string&gt;(&quot;hi&quot;)\u003C\u002Fcode>","Usually unnecessary — inference handles it","table",{"id":403,"code":404,"type":134,"language":135,"highlight":405},"b80","\u002F\u002F The whole pattern, copy-paste ready\ninterface ApiResponse\u003CTData = unknown> {\n  data: TData;\n  error: string | null;\n}\n\nfunction firstMatching\u003CT, K extends keyof T>(\n  items: T[],\n  key: K,\n  value: T[K]\n): T | undefined {\n  return items.find((item) => item[key] === value);\n}\n\nclass Cache\u003CT> {\n  private store = new Map\u003Cstring, T>();\n  set(key: string, value: T): void { this.store.set(key, value); }\n  get(key: string): T | undefined { return this.store.get(key); }\n}\n\n\u002F\u002F usage — every T flows through, checked, with no `any` anywhere\nconst cache = new Cache\u003CApiResponse\u003C{ id: string }>>();",[],{"id":407,"html":408,"type":86},"b81","\u003C!-- quiz:start -->",{"id":410,"html":411,"text":411,"type":91,"level":31},"b82","🧠 Test yourself",{"id":413,"html":414,"type":86},"b83","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-20-weekly-typescript-generics\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":416,"html":417,"type":86},"b84","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":419,"html":420,"type":86},"b85","\u003C!-- quiz:end -->",{"id":422,"html":423,"text":423,"type":91,"level":31},"b86","Key takeaways",{"id":425,"type":97,"items":426,"ordered":18},"b87",[427,428,429,430,431],"A type parameter is a function argument for types — the compiler substitutes it at the call site and checks the relationship stays consistent, the same way it checks a value argument.","\u003Ccode>extends\u003C\u002Fcode> in a generic constraint means &quot;must be assignable to,&quot; not class inheritance — it narrows which types are legal without collapsing \u003Ccode>T\u003C\u002Fcode> to one fixed type.","Reach for a generic when an input and an output (or two inputs) are \u003Cem>related\u003C\u002Fem> and that relationship must survive into the caller&#39;s code; reach for a plain union or \u003Ccode>unknown\u003C\u002Fcode> when it doesn&#39;t.","Generics are erased at compile time — there&#39;s no \u003Ccode>T\u003C\u002Fcode> left at runtime, so runtime type decisions need an actual value, not a type parameter.","\u003Ccode>any\u003C\u002Fcode> and a generic solve opposite problems: \u003Ccode>any\u003C\u002Fcode> turns off checking, a generic keeps it on while deferring which type.",{"id":433,"html":434,"type":86},"b88","\u003Cp>You started this article maintaining four near-identical functions, one per type, or one function typed \u003Ccode>any\u003C\u002Fcode> that quietly let a string crash on \u003Ccode>.toUpperCase()\u003C\u002Fcode>. Now you have the one mechanism — a type parameter, substituted and checked at every call site — that replaces both: a single \u003Ccode>firstElement&lt;T&gt;\u003C\u002Fcode>, a single \u003Ccode>Cache&lt;T&gt;\u003C\u002Fcode>, a single \u003Ccode>ApiResponse&lt;T&gt;\u003C\u002Fcode>, each one exactly as safe as if you&#39;d hand-written it for every type you&#39;ll ever call it with. Where&#39;s the first place in your own codebase you&#39;ll delete a copy-pasted function and replace it with one generic? Tell me in the comments.\u003C\u002Fp>",{"id":436,"type":437},"b89","divider",{"id":439,"html":440,"type":86},"b90","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":442,"type":97,"items":443,"ordered":18},"b91",[444,445,446,447,448],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","You write a function that works on numbers. Then you need the same function for strings. You copy-paste it and change the type annotation. Then arrays. Then objects. Now you have four functions with identical bodies and different type signatures — or you gave up and typed everything `any`, and the compiler stopped helping you at the one place you needed it most: the boundary between what you pass in and what you get back.\n\n## What you'll learn\n\nBy the end of this guide you'll be able to:\n\n- Write generic functions that preserve the exact type of their input in their output, instead of widening it to `any` or `unknown`\n- Constrain a type parameter with `extends` so the compiler enforces a shape without collapsing to one concrete type\n- Give a type parameter a sensible default, the way you'd default a function argument\n- Read and write generic interfaces, types, and classes with more than one type parameter\n- Diagnose the most common generic inference failures instead of reaching for `any` to silence them\n\n**Who this is for:** you've written TypeScript with interfaces and function signatures, and you've seen `\u003CT>` in library code, but you want the model that makes it predictable instead of memorized syntax.\n\n## Contents\n\n- [Why generics exist](#why-generics-exist)\n- [The mental model](#the-mental-model-a-type-parameter-is-a-function-argument-for-types)\n- [Stage 1: your first generic function](#stage-1-your-first-generic-function)\n- [Stage 2: generic interfaces and types](#stage-2-generic-interfaces-and-types)\n- [Stage 3: constraining a type parameter with extends](#stage-3-constraining-a-type-parameter-with-extends)\n- [Stage 4: default type parameters](#stage-4-default-type-parameters)\n- [Stage 5: multiple type parameters and generic classes](#stage-5-multiple-type-parameters-and-generic-classes)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices](#best-practices-when-not-to-reach-for-generics)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n- [Key takeaways](#key-takeaways)\n\n## Why generics exist\n\nHere's the naive fix for \"I need this function to work on more than one type\" — widen the parameter to `any`:\n\n```ts\nfunction firstElement(arr: any): any {\n  return arr[0];\n}\n\nconst n = firstElement([1, 2, 3]);      \u002F\u002F typed as `any` — no autocomplete, no error checking\nconst s = firstElement([\"a\", \"b\"]);     \u002F\u002F also `any` — TypeScript has no idea it's a string\nn.toUpperCase();                        \u002F\u002F compiles fine, crashes at runtime: not a string\n```\n\nThe function works at runtime for both calls. But the moment you use the result, TypeScript has nothing to say — `any` erases the connection between what went in and what came out. You've traded a compile-time error for a runtime crash, which is the one trade TypeScript exists to prevent.\n\nThe alternative most people try next is copy-pasting one function per type:\n\n```ts\nfunction firstNumber(arr: number[]): number { return arr[0]; }\nfunction firstString(arr: string[]): string { return arr[0]; }\n\u002F\u002F ...one more function for every type you ever call this with\n```\n\nThis is type-safe, but it doesn't scale — you're maintaining N identical function bodies, and a bug fix means updating all of them. Generics solve exactly this: one function body, and a type that adapts to whatever you call it with, while the compiler still checks that the relationship holds.\n\n## The mental model: a type parameter is a function argument for types\n\n**The mental model:** a generic `\u003CT>` is a parameter, exactly like a function parameter — except instead of a value, you're passing in a *type*, and the compiler substitutes it everywhere `T` appears and checks that everything stays consistent.\n\nCompare the two side by side:\n\n```ts\n\u002F\u002F a regular function: `x` is a placeholder for a VALUE, filled in at the call site\nfunction double(x: number): number {\n  return x * 2;\n}\ndouble(5); \u002F\u002F x = 5\n\n\u002F\u002F a generic function: `T` is a placeholder for a TYPE, filled in at the call site\nfunction identity\u003CT>(x: T): T {\n  return x;\n}\nidentity(5);      \u002F\u002F T = number\nidentity(\"hi\");   \u002F\u002F T = string\n```\n\n`double`'s parameter list says \"give me a number, I'll give you a number.\" `identity`'s type parameter list says \"give me *some* type T, and I promise to give you back that same T\" — it's a contract about the *shape of the relationship*, not about one fixed type. TypeScript infers `T` from the argument you actually pass, the same way it infers the type of a variable from its initializer — you rarely write `identity\u003Cnumber>(5)` explicitly; the compiler figures out `T = number` on its own.\n\nThat's the whole idea. Everything below is this one substitution rule, applied to interfaces, constraints, defaults, and classes.\n\n## Stage 1: your first generic function\n\nFix the `firstElement` function from earlier with a type parameter instead of `any`:\n\n```ts\nfunction firstElement\u003CT>(arr: T[]): T | undefined {\n  return arr[0];\n}\n\nconst n = firstElement([1, 2, 3]);    \u002F\u002F n: number | undefined\nconst s = firstElement([\"a\", \"b\"]);   \u002F\u002F s: string | undefined\nn?.toFixed(2);                        \u002F\u002F ✅ compiler knows n might be a number\ns?.toUpperCase();                     \u002F\u002F ✅ compiler knows s might be a string\n```\n\n> **Key concept:** `T` is inferred once, from the argument, and then reused for the return type. The function body never mentions `number` or `string` — it stays generic, but every call site gets a fully concrete, checked type back.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-20-weekly-typescript-generics\u002Fplayground\u002F)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\nThe `T | undefined` return type matters too: an empty array is a valid `T[]`, so the function is honest that there might be nothing at index 0. This is a case where `any` would have silently hidden a real edge case that `T | undefined` forces you to handle.\n\nGeneric functions can also take more than the type parameter implies. A `map`-style helper needs a second, ordinary parameter — a callback — that also mentions `T`:\n\n```ts\nfunction mapArray\u003CT, U>(arr: T[], fn: (item: T) => U): U[] {\n  return arr.map(fn);\n}\n\nconst lengths = mapArray([\"a\", \"bb\", \"ccc\"], (s) => s.length); \u002F\u002F lengths: number[]\n```\n\nHere `T` is inferred from `arr` (`string`) and `U` is inferred from what `fn` returns (`number`) — two independent type parameters, each pinned down by a different argument.\n\n## Stage 2: generic interfaces and types\n\nThe same substitution rule applies to `interface` and `type`, not just functions. A generic interface is a shape with a type-level blank to fill in:\n\n```ts\ninterface Box\u003CT> {\n  value: T;\n}\n\nconst numberBox: Box\u003Cnumber> = { value: 42 };\nconst stringBox: Box\u003Cstring> = { value: \"hello\" };\n\u002F\u002F const bad: Box\u003Cnumber> = { value: \"hello\" }; \u002F\u002F ❌ string is not assignable to number\n```\n\n`Box\u003CT>` isn't a type by itself — it's a *template* for a type. `Box\u003Cnumber>` and `Box\u003Cstring>` are the actual types, produced by substituting `T`. This is the exact same mechanism as `Array\u003CT>` (the type behind `T[]`), `Promise\u003CT>`, and `Map\u003CK, V>` from the standard library — you've been using generics since your first `Promise\u003Cvoid>`, you just hadn't named the mechanism yet.\n\nGeneric type aliases work identically and are common for API response shapes:\n\n```ts\ntype ApiResponse\u003CT> = {\n  data: T;\n  error: string | null;\n};\n\nasync function getUser(id: string): Promise\u003CApiResponse\u003CUser>> {\n  \u002F\u002F ...\n}\n```\n\nEvery endpoint reuses one `ApiResponse\u003CT>` shape instead of a hand-written `UserResponse`, `PostResponse`, `OrderResponse` trio that all say the same thing with different names.\n\n## Stage 3: constraining a type parameter with extends\n\nUnconstrained, `T` could be anything — which means you can't assume it has any properties at all:\n\n```ts\nfunction getLength\u003CT>(item: T): number {\n  return item.length; \u002F\u002F ❌ Property 'length' does not exist on type 'T'\n}\n```\n\nThe compiler is right to reject this: nothing says `T` has a `.length`. The fix is a **constraint** — `extends` here doesn't mean \"inherits from,\" it means \"must be assignable to\":\n\n```ts\ninterface HasLength {\n  length: number;\n}\n\nfunction getLength\u003CT extends HasLength>(item: T): number {\n  return item.length; \u002F\u002F ✅ every T that reaches this line is guaranteed to have `.length`\n}\n\ngetLength(\"hello\");        \u002F\u002F ✅ strings have .length\ngetLength([1, 2, 3]);      \u002F\u002F ✅ arrays have .length\ngetLength({ length: 10 }); \u002F\u002F ✅ any object shape with a length field\ngetLength(42);              \u002F\u002F ❌ number has no .length\n```\n\n> **Key concept:** `T extends HasLength` narrows *which types are legal*, not what `T` collapses to. `T` is still inferred per call — a `string`, an array, or a custom object — the constraint only guarantees the one property you need.\n\nA very common constraint pattern uses `keyof` to guarantee a key actually exists on an object, which is what makes a type-safe `getProperty` helper possible:\n\n```ts\nfunction getProperty\u003CT, K extends keyof T>(obj: T, key: K): T[K] {\n  return obj[key];\n}\n\nconst user = { name: \"Ada\", age: 36 };\ngetProperty(user, \"name\");   \u002F\u002F ✅ inferred as string\ngetProperty(user, \"email\");  \u002F\u002F ❌ \"email\" is not a key of { name: string; age: number }\n```\n\n`K extends keyof T` reads as \"K must be one of T's actual property names.\" Once that holds, `T[K]` — an *indexed access type* — gives you the precise type of that property, so `getProperty(user, \"age\")` returns `number`, not some generic `unknown`.\n\n## Stage 4: default type parameters\n\nA type parameter can have a default, exactly like a function parameter can have a default value — used when the caller doesn't supply one:\n\n```ts\ninterface FetchOptions\u003CTResponse = unknown> {\n  url: string;\n  parse?: (raw: string) => TResponse;\n}\n\nconst raw: FetchOptions = { url: \"\u002Fping\" };                 \u002F\u002F TResponse defaults to unknown\nconst typed: FetchOptions\u003C{ ok: boolean }> = {               \u002F\u002F explicit, overrides the default\n  url: \"\u002Fhealth\",\n  parse: (raw) => JSON.parse(raw),\n};\n```\n\nDefaults matter most on widely-used generic types where most callers don't care about every parameter — a state-management or component-library type might have four type parameters with sensible defaults so 90% of call sites only ever write the one that matters to them, and the rest fall back silently.\n\n## Stage 5: multiple type parameters and generic classes\n\nNothing restricts you to one `T`. Multiple type parameters are common wherever there's a key\u002Fvalue or input\u002Foutput relationship:\n\n```ts\nfunction zip\u003CA, B>(a: A[], b: B[]): [A, B][] {\n  return a.map((item, i) => [item, b[i]]);\n}\n\nzip([1, 2], [\"a\", \"b\"]); \u002F\u002F [number, string][] — inferred as [[1, \"a\"], [2, \"b\"]]\n```\n\nClasses take type parameters the same way functions do — on the class itself, available to every method and property inside:\n\n```ts\nclass Stack\u003CT> {\n  private items: T[] = [];\n\n  push(item: T): void {\n    this.items.push(item);\n  }\n\n  pop(): T | undefined {\n    return this.items.pop();\n  }\n\n  get size(): number {\n    return this.items.length;\n  }\n}\n\nconst numbers = new Stack\u003Cnumber>();\nnumbers.push(1);\nnumbers.push(2);\nnumbers.pop(); \u002F\u002F number | undefined\n\nconst strings = new Stack\u003Cstring>();\nstrings.push(\"a\");\n\u002F\u002F strings.push(1); \u002F\u002F ❌ number is not assignable to string\n```\n\n`T` is fixed once, at `new Stack\u003Cnumber>()`, and every method on that instance uses that same `T` for the rest of its life — `push` and `pop` stay in sync automatically, without repeating the type anywhere else in the class body.\n\n## Edge cases and gotchas\n\n- **Type erasure at runtime.** Generics are a compile-time-only construct — they're erased entirely from the emitted JavaScript. You cannot check `typeof T` or branch on `T` at runtime; if you need runtime knowledge of the type, you must pass it explicitly as a value (a constructor, a discriminant field, or a plain string tag), because by the time your code runs, `T` no longer exists.\n- **Inference can fail on object literals with contextual typing.** `identity({ name: \"Ada\" })` infers `T` as `{ name: string }`, which is often what you want, but if you need a *wider* inferred literal type (e.g. keeping a string as a specific literal rather than widening to `string`), you'll need `as const` on the argument, not a change to the generic itself.\n- **Arrow functions with generics need care in `.tsx` files.** `const identity = \u003CT>(x: T) => x;` can be parsed as a JSX opening tag inside a `.tsx` file. The common workaround is a trailing comma — `\u003CT,>(x: T) => x` — or naming a constraint explicitly, `\u003CT extends unknown>(x: T) => x`.\n- **Conditional types distribute over unions unless you stop them.** `type ToArray\u003CT> = T extends any ? T[] : never;` applied to `string | number` produces `string[] | number[]`, not `(string | number)[]`, because a naked type parameter in a conditional type distributes across each union member individually. Wrapping both sides in a tuple (`[T] extends [any] ? ...`) disables that distribution when you don't want it.\n- **A constraint is a floor, not the resolved type.** `function f\u003CT extends { id: string }>(x: T)` still infers the caller's *full* type for `T` (every extra property survives), not just `{ id: string }` — the constraint only restricts which types are legal, it doesn't narrow what gets stored in `T`.\n\n## Best practices: when (not) to reach for generics\n\n**Reach for a generic when** a function or type's inputs and outputs are *related* and that relationship needs to survive into the caller's code — a `firstElement\u003CT>` that returns the same `T` it received, a `Box\u003CT>` that wraps any payload, an `ApiResponse\u003CT>` reused across every endpoint.\n\n**Don't reach for a generic when** a union type says what you mean more simply. If a parameter only ever needs to be `\"light\" | \"dark\"`, write that union — a `\u003CT extends \"light\" | \"dark\">` generic adds ceremony without adding safety.\n\n**Don't reach for a generic to avoid writing `unknown`.** If a value's type genuinely isn't knowable until you inspect it at runtime (parsed JSON, a `catch` block's error), the honest type is `unknown` plus a runtime check — not a type parameter with no way to constrain it, and not `any`, which is how you end up back at the `firstElement` bug from the top of this article. TypeScript's own [`satisfies` operator](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fyour-typescript-config-objects-are-losing-type-information-satisfies-fixes-it-3hdh) solves a related but different problem — keeping a literal's narrow type while still validating it against a shape — worth knowing alongside generics, not instead of them.\n\n**Don't over-parameterize.** A function with four type parameters, three of them defaulted and never varied at any call site in your codebase, is a sign the abstraction arrived before the second real use case did. Write the concrete version first; generalize when a second caller actually needs a different type.\n\n## FAQ\n\n### What is a generic in TypeScript?\n\nA generic is a type parameter — a placeholder for a type, written in angle brackets like `\u003CT>` — that lets a function, interface, type, or class work with any type while the compiler still tracks and checks that type through every use, instead of collapsing everything to `any`.\n\n### Is `T extends SomeType` the same as class inheritance?\n\nNo. In a generic constraint, `extends` means \"must be assignable to\" or \"must have at least this shape\" — it restricts which types are legal for `T`, not a class hierarchy. `T extends { id: string }` accepts any type with an `id: string` property, not just subclasses of some `{ id: string }` class.\n\n### Can I use a default value for a generic type parameter?\n\nYes — `interface Box\u003CT = unknown>` gives `T` a default the same way a function parameter can have one. Callers that don't specify a type argument get the default; callers that do (`Box\u003Cnumber>`) override it.\n\n### Is `any` the same as a generic?\n\nNo, and this is the most common confusion. `any` disables type checking entirely for that value — TypeScript stops tracking it. A generic `T` keeps type checking fully active; it just defers *which* type until the call site, and enforces that the same `T` is used consistently everywhere it appears.\n\n### Do generic types exist at runtime?\n\nNo. TypeScript's type system, including all generics, is erased during compilation — there is no `T` in the emitted JavaScript, and you cannot inspect it with `typeof` or `instanceof` at runtime. Anything you need to know while the program is running must be represented as an actual runtime value, not just a type parameter.\n\n## Cheat sheet\n\n| Task | Syntax | Notes |\n| --- | --- | --- |\n| Generic function | `function f\u003CT>(x: T): T` | `T` inferred from the argument |\n| Generic interface\u002Ftype | `interface Box\u003CT> { value: T }` | Instantiate as `Box\u003Cnumber>` |\n| Multiple type parameters | `function zip\u003CA, B>(a: A[], b: B[])` | Each inferred independently |\n| Constrain a type parameter | `\u003CT extends HasLength>` | \"Must be assignable to,\" not inheritance |\n| Require a real key of an object | `\u003CT, K extends keyof T>` | Pairs with indexed access: `T[K]` |\n| Default type parameter | `\u003CT = unknown>` | Used when the caller omits a type argument |\n| Generic class | `class Stack\u003CT> { push(item: T): void }` | `T` fixed once, at `new Stack\u003Cnumber>()` |\n| Explicit type argument (rare) | `identity\u003Cstring>(\"hi\")` | Usually unnecessary — inference handles it |\n\n```ts\n\u002F\u002F The whole pattern, copy-paste ready\ninterface ApiResponse\u003CTData = unknown> {\n  data: TData;\n  error: string | null;\n}\n\nfunction firstMatching\u003CT, K extends keyof T>(\n  items: T[],\n  key: K,\n  value: T[K]\n): T | undefined {\n  return items.find((item) => item[key] === value);\n}\n\nclass Cache\u003CT> {\n  private store = new Map\u003Cstring, T>();\n  set(key: string, value: T): void { this.store.set(key, value); }\n  get(key: string): T | undefined { return this.store.get(key); }\n}\n\n\u002F\u002F usage — every T flows through, checked, with no `any` anywhere\nconst cache = new Cache\u003CApiResponse\u003C{ id: string }>>();\n```\n\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-20-weekly-typescript-generics)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n## Key takeaways\n\n- A type parameter is a function argument for types — the compiler substitutes it at the call site and checks the relationship stays consistent, the same way it checks a value argument.\n- `extends` in a generic constraint means \"must be assignable to,\" not class inheritance — it narrows which types are legal without collapsing `T` to one fixed type.\n- Reach for a generic when an input and an output (or two inputs) are *related* and that relationship must survive into the caller's code; reach for a plain union or `unknown` when it doesn't.\n- Generics are erased at compile time — there's no `T` left at runtime, so runtime type decisions need an actual value, not a type parameter.\n- `any` and a generic solve opposite problems: `any` turns off checking, a generic keeps it on while deferring which type.\n\nYou started this article maintaining four near-identical functions, one per type, or one function typed `any` that quietly let a string crash on `.toUpperCase()`. Now you have the one mechanism — a type parameter, substituted and checked at every call site — that replaces both: a single `firstElement\u003CT>`, a single `Cache\u003CT>`, a single `ApiResponse\u003CT>`, each one exactly as safe as if you'd hand-written it for every type you'll ever call it with. Where's the first place in your own codebase you'll delete a copy-pasted function and replace it with one generic? Tell me in the comments.\n\n---\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":50,"canonical":451,"description":51},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-typescript-generics","019fe660-57eb-7258-b452-d93e631b12b5",{"id":454,"locked":18},"019fe660-5ece-75f7-97dc-27d2d22663f3",[456],{"id":33,"slug":34,"title":36,"_count":457},{"questions":39},[459],{"locale":13,"slug":34},{"id":33,"slug":34,"title":36,"_count":461,"questionCount":39},{"questions":39}]