{"id":2485,"date":"2024-06-04T08:40:52","date_gmt":"2024-06-04T14:40:52","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=2485"},"modified":"2024-06-04T08:40:53","modified_gmt":"2024-06-04T14:40:53","slug":"testing-types-in-typescript","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/testing-types-in-typescript\/","title":{"rendered":"Testing Types in TypeScript"},"content":{"rendered":"\n<p class=\"has-small-font-size\">Say that 10 times fast.<\/p>\n\n\n\n<p>As your TypeScript usage gets more advanced, it can be extremely helpful to have utilities around that test and verify your types. Like unit testing, but without needing to set up Jest, deal with mocking, etc. In this post, we&#8217;ll introduce this idea. Then we&#8217;ll dive deeply into one particular testing utility that&#8217;s surprisingly difficult to create: a type that checks whether two types are the same.<\/p>\n\n\n\n<p>This post will cover some advanced corners of TypeScript you&#8217;re unlikely to need for regular application code. If you&#8217;re not a huge fan of TS, please understand that you probably won&#8217;t need the things in this post for your everyday work, which is fine. But if you&#8217;re writing or maintaining TypeScript libraries, or even library-like code in your team&#8217;s app, the things we discuss here might come in handy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Type helpers<\/h2>\n\n\n\n<p>Consider this&nbsp;<code>Expect<\/code>&nbsp;helper:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Expect&lt;T <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-literal\">true<\/span>&gt; = T;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This type demands you pass&nbsp;<code>true<\/code>&nbsp;into it. This seems silly, but stay with me.<\/p>\n\n\n\n<p>Now imagine, for some reason, you have a helper for figuring out whether a type is some kind of array:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> IsArray&lt;T&gt; = T <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-built_in\">Array<\/span>&lt;<span class=\"hljs-built_in\">any<\/span>&gt; ? <span class=\"hljs-literal\">true<\/span> : <span class=\"hljs-literal\">false<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You&#8217;d like to verify that <code>IsArray<\/code> works properly. You could type:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> X = IsArray&lt;<span class=\"hljs-built_in\">number<\/span>&#91;]&gt;;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then mouse over the&nbsp;<code>X<\/code>&nbsp;and verify that it&#8217;s true, which it is. But we don&#8217;t settle for ad hoc testing like that with normal code, so why would we with our advanced types?<\/p>\n\n\n\n<p>Why don&#8217;t we write this instead:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> X = Expect&lt;IsArray&lt;<span class=\"hljs-built_in\">number<\/span>&#91;]&gt;&gt;;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If we had messed up our&nbsp;<code>IsArray<\/code>&nbsp;type, the line above would error out, which we can see by passing the wrong thing into it:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Y = Expect&lt;IsArray&lt;<span class=\"hljs-built_in\">number<\/span>&gt;&gt;;\n<span class=\"hljs-comment\">\/\/ error: Type 'false' does not satisfy the constraint 'true'.<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Better yet, let&#8217;s create another helper:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Not&lt;T <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-literal\">false<\/span>&gt; = <span class=\"hljs-literal\">true<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>and now we can actually test the negative of our IsArray helper<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Y = Expect&lt;Not&lt;IsArray&lt;<span class=\"hljs-built_in\">number<\/span>&gt;&gt;&gt;;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Except these fake type names like X and Y will get annoying very quickly, so let&#8217;s do this instead<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ ts-ignore just to ignore the unused warning - everything inside of Tests will still type check<\/span>\n<span class=\"hljs-comment\">\/\/ @ts-ignore<\/span>\n<span class=\"hljs-keyword\">type<\/span> Tests = &#91;\n  Expect&lt;IsArray&lt;<span class=\"hljs-built_in\">number<\/span>&#91;]&gt;&gt;,\n  Expect&lt;Not&lt;IsArray&lt;<span class=\"hljs-built_in\">number<\/span>&gt;&gt;&gt;\n];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>So far so good. We can put these tests for our types right in our application files if we want, or move them to a separate file; the types are all erased when we ship either way, so don&#8217;t worry about bundle size.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Getting serious<\/h2>\n\n\n\n<p>Our&nbsp;<code>IsArray<\/code>&nbsp;type was trivial, as were our tests. In real life, we&#8217;ll be writing types that do more interesting things, usually taking in one or more types, and creating something new. And to test those sorts of things, we&#8217;ll need to be able to verify that two types are identical.<\/p>\n\n\n\n<p>For example, say you want to write a type that takes in a generic, and if that generic is a function, returns the parameters of that function, else returns never. Pretend the&nbsp;<code>Parameters<\/code>&nbsp;type is not built into TypeScript, and imagine you write this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> ParametersOf&lt;T&gt; = T <span class=\"hljs-keyword\">extends<\/span> (...args: infer U) =&gt; <span class=\"hljs-built_in\">any<\/span> ? U : never;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Which we&#8217;d test like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-comment\">\/\/ ts-ignore just to ignore the unused warning - everything inside of Tests will still type check<\/span>\n<span class=\"hljs-comment\">\/\/ @ts-ignore<\/span>\n<span class=\"hljs-keyword\">type<\/span> Tests = &#91;\n  Expect&lt;TypesMatch&lt;ParametersOf&lt;<span class=\"hljs-function\">(<span class=\"hljs-params\"><span class=\"hljs-params\">a<\/span>: <span class=\"hljs-params\">string<\/span><\/span>) =&gt;<\/span> <span class=\"hljs-built_in\">void<\/span>&gt;, &#91;<span class=\"hljs-built_in\">string<\/span>]&gt;&gt;,\n  Expect&lt;TypesMatch&lt;ParametersOf&lt;<span class=\"hljs-built_in\">string<\/span>&gt;, never&gt;&gt;\n];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Great. But how do you write&nbsp;<code>TypesMatch<\/code>?<\/p>\n\n\n\n<p>That&#8217;s the subject of the entire rest of this post. Buckle up!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Type Equality<\/h2>\n\n\n\n<p>Checking type equality is surprisingly hard in TypeScript, and the obvious solution will fail for baffling reasons unless you understand conditional types. Let&#8217;s tackle that before moving on.<\/p>\n\n\n\n<p>We&#8217;ll start with the most obvious, potential solution:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> TypesMatch&lt;T, U&gt; = T <span class=\"hljs-keyword\">extends<\/span> U ? <span class=\"hljs-literal\">true<\/span> : <span class=\"hljs-literal\">false<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can think of&nbsp;<code>T extends U<\/code>&nbsp;in the same way as with object-oriented inheritance: is T the same as, or a sub-type of U. And instead of (just) object-oriented hierarchies, remember that you can have literal types in TypeScript.&nbsp;<code>type Foo = \"foo\"<\/code>&nbsp;is a perfectly valid type in TypeScript. It&#8217;s the type that represents all strings that match&nbsp;<code>\"foo\"<\/code>. Similarly,&nbsp;<code>type Foo = \"foo\" | \"bar\";<\/code>&nbsp;is the type representing all strings that match either&nbsp;<code>\"foo\"<\/code>, or&nbsp;<code>\"bar\"<\/code>. And literal types, and unions of literal types like that can both be thought of as sub-types of string, for these purposes.<\/p>\n\n\n\n<p>Another (more common way) to think about this is that&nbsp;<code>T extends U<\/code>&nbsp;is true if T&nbsp;<em>can be assigned to<\/em>&nbsp;U, which makes sense; if T is the same, or a sub-type of U, then a variable of type T can be&nbsp;<em>assigned to<\/em>&nbsp;a variable of type U.<\/p>\n\n\n\n<p>The obvious test works:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Tests = &#91;Expect&lt;TypesMatch&lt;<span class=\"hljs-built_in\">string<\/span>, <span class=\"hljs-built_in\">string<\/span>&gt;&gt;];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>So far, so good. And:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Tests = &#91;Expect&lt;Not&lt;TypesMatch&lt;<span class=\"hljs-built_in\">string<\/span>, <span class=\"hljs-string\">\"foo\"<\/span>&gt;&gt;&gt;];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This also works, since a variable of type&nbsp;<code>string<\/code>&nbsp;cannot be assigned to a variable of type&nbsp;<code>\"foo\"<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">let<\/span> foo: <span class=\"hljs-string\">\"foo\"<\/span> = <span class=\"hljs-string\">\"foo\"<\/span>;\n<span class=\"hljs-keyword\">let<\/span> str: <span class=\"hljs-built_in\">string<\/span> = <span class=\"hljs-string\">\"blah blah blah\"<\/span>;\n\nfoo = str; <span class=\"hljs-comment\">\/\/ Error<\/span>\n<span class=\"hljs-comment\">\/\/ Type 'string' is not assignable to type '\"foo\"'.<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>But we hit problems with:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Tests = &#91;Expect&lt;Not&lt;TypesMatch&lt;<span class=\"hljs-string\">\"foo\"<\/span>, <span class=\"hljs-built_in\">string<\/span>&gt;&gt;&gt;];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This fails. The string literal type&nbsp;<code>\"foo\"<\/code>&nbsp;is assignable to variables of type string.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Just test them both ways<\/h3>\n\n\n\n<p>I know what you&#8217;re thinking: just test it from both directions!<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> TypesMatch&lt;T, U&gt; = T <span class=\"hljs-keyword\">extends<\/span> U\n  ? U <span class=\"hljs-keyword\">extends<\/span> T\n    ? <span class=\"hljs-literal\">true<\/span>\n    : <span class=\"hljs-literal\">false<\/span>\n  : <span class=\"hljs-literal\">false<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This solves both of our problems from above. Now both of these tests pass.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Tests = &#91;\n  Expect&lt;Not&lt;TypesMatch&lt;<span class=\"hljs-built_in\">string<\/span>, <span class=\"hljs-string\">\"foo\"<\/span>&gt;&gt;&gt;,\n  Expect&lt;Not&lt;TypesMatch&lt;<span class=\"hljs-string\">\"foo\"<\/span>, <span class=\"hljs-built_in\">string<\/span>&gt;&gt;&gt;,\n];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Let&#8217;s try union types:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Tests = &#91;\n  Expect&lt;TypesMatch&lt;<span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span>, <span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span>&gt;&gt;,\n  Expect&lt;Not&lt;TypesMatch&lt;<span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span>, <span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span> | <span class=\"hljs-built_in\">boolean<\/span>&gt;&gt;&gt;\n];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Both<\/strong>&nbsp;of these fail with:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Type &#8216;boolean&#8217; does not satisfy the constraint &#8216;true&#8217;<\/p>\n<\/blockquote>\n\n\n\n<p>Identical union types fail to match as identical, and different union types fail to match as different. What in the world is happening.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conditional types over unions<\/h2>\n\n\n\n<p>So why did this not work with union types?<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> TypesMatch&lt;T, U&gt; = T <span class=\"hljs-keyword\">extends<\/span> U\n  ? U <span class=\"hljs-keyword\">extends<\/span> T\n    ? <span class=\"hljs-literal\">true<\/span>\n    : <span class=\"hljs-literal\">false<\/span>\n  : <span class=\"hljs-literal\">false<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Let&#8217;s back up and try to simplify. Let&#8217;s imagine some simple (useless) types. Imagine a square and circle:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Square = {\n  length: <span class=\"hljs-built_in\">number<\/span>;\n};\n<span class=\"hljs-keyword\">type<\/span> Circle = {\n  radius: <span class=\"hljs-built_in\">number<\/span>;\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now imagine a type that takes a generic in, and returns a description. If we pass in a Square, it returns the string literal type&nbsp;<code>\"4 Sides\"<\/code>. If we pass in a circle, it returns the string literal&nbsp;<code>\"Round\"<\/code>. And if we pass in anything else, it returns the string literal type&nbsp;<code>\"Dunno\"<\/code>. This is very silly but just go with it.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Description&lt;T&gt; = T <span class=\"hljs-keyword\">extends<\/span> Square\n  ? <span class=\"hljs-string\">\"4 Sides\"<\/span>\n  : T <span class=\"hljs-keyword\">extends<\/span> Circle\n  ? <span class=\"hljs-string\">\"Round\"<\/span>\n  : <span class=\"hljs-string\">\"Dunno\"<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now imagine a function to use this type:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getDescription<\/span>&lt;<span class=\"hljs-title\">T<\/span>&gt;(<span class=\"hljs-params\">obj: T<\/span>): <span class=\"hljs-title\">Description<\/span>&lt;<span class=\"hljs-title\">T<\/span>&gt; <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">null<\/span> <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-built_in\">any<\/span>;\n}\n<span class=\"hljs-keyword\">const<\/span> s: Square = { length: <span class=\"hljs-number\">1<\/span> };\n<span class=\"hljs-keyword\">const<\/span> c: Circle = { radius: <span class=\"hljs-number\">1<\/span> };\n\n<span class=\"hljs-keyword\">const<\/span> sDescription = getDescription(s);\n<span class=\"hljs-keyword\">const<\/span> cDescription = getDescription(c);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>sDescription<\/code>&nbsp;is of type&nbsp;<code>\"4 Sides\"<\/code>&nbsp;and&nbsp;<code>cDescription<\/code>&nbsp;is of type&nbsp;<code>\"Round\"<\/code>. Nothing surprising. Now let&#8217;s consider a union type.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">const<\/span> either: Circle | Square = {} <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-built_in\">any<\/span>;\n<span class=\"hljs-keyword\">const<\/span> eitherDescription = getDescription(either);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The type\u00a0<code>Circle | Square<\/code>\u00a0does not extend\u00a0<code>Square<\/code>\u00a0(a variable of type\u00a0<code>Circle | Square<\/code>\u00a0cannot be assigned to a variable of type\u00a0<code>Square<\/code>) nor does it extend Circle. So we might naively expect\u00a0<code>eitherDescription<\/code>\u00a0to be\u00a0<code>\"Dunno\"<\/code>. But intuitively this feels wrong.\u00a0<code>either<\/code>\u00a0is a Circle or a Square, so the description should be either\u00a0<code>\"4 Sides\"<\/code>\u00a0or\u00a0<code>\"Round\"<\/code>.<\/p>\n\n\n\n<p>And that&#8217;s exactly what it is:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"152\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img1.jpg?resize=800%2C152&#038;ssl=1\" alt=\"Union Type\" class=\"wp-image-2488\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img1.jpg?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img1.jpg?resize=300%2C57&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img1.jpg?resize=768%2C146&amp;ssl=1 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Distributing union types<\/h2>\n\n\n\n<p>When we have a generic type argument that&#8217;s also a type union, pushed across an&nbsp;<code>extends<\/code>&nbsp;check in a conditional type, the union itself is split up with each member of the union substituted into that check. TypeScript then takes every result, and unions them together.&nbsp;<em>That<\/em>&nbsp;union is the result of that&nbsp;<code>extends<\/code>&nbsp;operation.<\/p>\n\n\n\n<p>Any&nbsp;<code>never<\/code>&#8216;s are removed, as are any duplicates.<\/p>\n\n\n\n<p>So for the above, we start with our type:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"996\" height=\"314\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2a.jpg?resize=996%2C314&#038;ssl=1\" alt=\"\" class=\"wp-image-2490\" style=\"width:506px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2a.jpg?w=996&amp;ssl=1 996w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2a.jpg?resize=300%2C95&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2a.jpg?resize=768%2C242&amp;ssl=1 768w\" sizes=\"auto, (max-width: 996px) 100vw, 996px\" \/><\/figure>\n\n\n\n<p>We substitute the union type that we passed in for T<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"234\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2b.jpg?resize=1024%2C234&#038;ssl=1\" alt=\"\" class=\"wp-image-2491\" style=\"width:685px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2b.jpg?resize=1024%2C234&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2b.jpg?resize=300%2C69&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2b.jpg?resize=768%2C176&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2b.jpg?w=1364&amp;ssl=1 1364w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Once our conditional type hits the extends keyword, if we passed in a union type, TypeScript will distribute over the union; it&#8217;ll run that ternary for each type in our union, and then union all of those results together. Square is first:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"220\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2c.jpg?resize=1024%2C220&#038;ssl=1\" alt=\"\" class=\"wp-image-2493\" style=\"width:686px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2c.jpg?resize=1024%2C220&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2c.jpg?resize=300%2C64&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2c.jpg?resize=768%2C165&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2c.jpg?w=1498&amp;ssl=1 1498w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p><code>Square<\/code>&nbsp;extends&nbsp;<code>Square<\/code>&nbsp;so&nbsp;<code>\"4 Sides\"<\/code>&nbsp;is the result. Then repeat with Circle:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"227\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2d.jpg?resize=1024%2C227&#038;ssl=1\" alt=\"\" class=\"wp-image-2494\" style=\"width:678px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2d.jpg?resize=1024%2C227&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2d.jpg?resize=300%2C67&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2d.jpg?resize=768%2C170&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img2d.jpg?w=1496&amp;ssl=1 1496w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p><code>Circle<\/code>&nbsp;extends&nbsp;<code>Circle<\/code>&nbsp;so&nbsp;<code>\"Round\"<\/code>&nbsp;is the result of the second iteration. The two are union&#8217;d together, resulting in the&nbsp;<code>\"4 Sides\" | \"Round\"<\/code>&nbsp;that we just saw.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Playing on Hard Mode<\/h2>\n\n\n\n<p>Let&#8217;s take a fresh look at this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> TypesMatch&lt;T, U&gt; = T <span class=\"hljs-keyword\">extends<\/span> U\n  ? U <span class=\"hljs-keyword\">extends<\/span> T\n    ? <span class=\"hljs-literal\">true<\/span>\n    : <span class=\"hljs-literal\">false<\/span>\n  : <span class=\"hljs-literal\">false<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here&#8217;s what happens with:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\">TypesMatch&lt;<span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span>, <span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span>&gt;;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We start:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"698\" height=\"250\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3a.jpg?resize=698%2C250&#038;ssl=1\" alt=\"\" class=\"wp-image-2495\" style=\"width:459px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3a.jpg?w=698&amp;ssl=1 698w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3a.jpg?resize=300%2C107&amp;ssl=1 300w\" sizes=\"auto, (max-width: 698px) 100vw, 698px\" \/><\/figure>\n\n\n\n<p>Then substitute&nbsp;<code>string | number<\/code>&nbsp;in for T and U. Evaluation starts, and immediately gets to our first&nbsp;<code>extends<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"210\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3b1.jpg?resize=1024%2C210&#038;ssl=1\" alt=\"\" class=\"wp-image-2496\" style=\"width:743px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3b1.jpg?resize=1024%2C210&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3b1.jpg?resize=300%2C61&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3b1.jpg?resize=768%2C157&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3b1.jpg?w=1182&amp;ssl=1 1182w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>T and U are both unions, but only the type&nbsp;<em>before<\/em>&nbsp;the&nbsp;<code>extends<\/code>&nbsp;is distributed. Let&#8217;s substitute&nbsp;<code>string | number<\/code>&nbsp;for U:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"193\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3b2.jpg?resize=1024%2C193&#038;ssl=1\" alt=\"\" class=\"wp-image-2497\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3b2.jpg?resize=1024%2C193&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3b2.jpg?resize=300%2C56&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3b2.jpg?resize=768%2C144&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3b2.jpg?w=1436&amp;ssl=1 1436w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Now we&#8217;re ready to process that first&nbsp;<code>extends<\/code>. We&#8217;ll need to break up the T union, and run it for every type in that union.&nbsp;<code>string<\/code>&nbsp;is first:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"164\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3c.jpg?resize=1024%2C164&#038;ssl=1\" alt=\"\" class=\"wp-image-2498\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3c.jpg?resize=1024%2C164&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3c.jpg?resize=300%2C48&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3c.jpg?resize=768%2C123&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3c.jpg?w=1508&amp;ssl=1 1508w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p><code>string<\/code>&nbsp;does extends&nbsp;<code>string | number<\/code>&nbsp;so we hit the true branch, and are immediately greeted by a second&nbsp;<code>extends<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"172\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3d.jpg?resize=1024%2C172&#038;ssl=1\" alt=\"\" class=\"wp-image-2499\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3d.jpg?resize=1024%2C172&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3d.jpg?resize=300%2C50&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3d.jpg?resize=768%2C129&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3d.jpg?resize=1536%2C258&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3d.jpg?w=1550&amp;ssl=1 1550w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Think of it like a nested loop. We&#8217;ll process this inner&nbsp;<code>extends<\/code>&nbsp;in the same way. We&#8217;ll substitute each type in the&nbsp;<code>U<\/code>&nbsp;union, starting with&nbsp;<code>string<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"168\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3e.jpg?resize=1024%2C168&#038;ssl=1\" alt=\"\" class=\"wp-image-2500\" style=\"width:859px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3e.jpg?resize=1024%2C168&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3e.jpg?resize=300%2C49&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3e.jpg?resize=768%2C126&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3e.jpg?resize=1536%2C252&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3e.jpg?w=1538&amp;ssl=1 1538w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>and of course&nbsp;<code>string extends string<\/code>&nbsp;is true. Our first result is&nbsp;<code>true<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"158\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3f1.jpg?resize=1024%2C158&#038;ssl=1\" alt=\"\" class=\"wp-image-2501\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3f1.jpg?resize=1024%2C158&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3f1.jpg?resize=300%2C46&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3f1.jpg?resize=768%2C119&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3f1.jpg?w=1526&amp;ssl=1 1526w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Now let&#8217;s continue processing our inner loop. U will next be&nbsp;<code>number<\/code>.&nbsp;<code>number extends string<\/code>&nbsp;is of course false, which is the second result of our type.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"163\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3f2.jpg?resize=1024%2C163&#038;ssl=1\" alt=\"\" class=\"wp-image-2502\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3f2.jpg?resize=1024%2C163&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3f2.jpg?resize=300%2C48&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3f2.jpg?resize=768%2C122&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3f2.jpg?w=1520&amp;ssl=1 1520w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>So far we have&nbsp;<code>true | false<\/code>. Now our outer loop continues.&nbsp;<code>T<\/code>&nbsp;moves on to become the second member of its union,&nbsp;<code>number<\/code>.&nbsp;<code>number extends string | number<\/code>&nbsp;is true:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"180\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3h.jpg?resize=1024%2C180&#038;ssl=1\" alt=\"\" class=\"wp-image-2503\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3h.jpg?resize=1024%2C180&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3h.jpg?resize=300%2C53&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3h.jpg?resize=768%2C135&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3h.jpg?w=1526&amp;ssl=1 1526w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>so we again hit that first branch:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"169\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3i.jpg?resize=1024%2C169&#038;ssl=1\" alt=\"\" class=\"wp-image-2504\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3i.jpg?resize=1024%2C169&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3i.jpg?resize=300%2C50&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3i.jpg?resize=768%2C127&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3i.jpg?resize=1536%2C254&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3i.jpg?w=1538&amp;ssl=1 1538w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>The inner loop starts all over again, with&nbsp;<code>U<\/code>&nbsp;first becoming&nbsp;<code>string<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"168\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3j.jpg?resize=1024%2C168&#038;ssl=1\" alt=\"\" class=\"wp-image-2505\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3j.jpg?resize=1024%2C168&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3j.jpg?resize=300%2C49&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3j.jpg?resize=768%2C126&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3j.jpg?w=1520&amp;ssl=1 1520w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p><code>string<\/code>&nbsp;extends number is false, so our overall result is&nbsp;<code>true | false | false<\/code>.&nbsp;<code>U<\/code>&nbsp;then becomes&nbsp;<code>number<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"160\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3k.jpg?resize=1024%2C160&#038;ssl=1\" alt=\"\" class=\"wp-image-2506\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3k.jpg?resize=1024%2C160&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3k.jpg?resize=300%2C47&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3k.jpg?resize=768%2C120&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3k.jpg?resize=1536%2C239&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img3k.jpg?w=1540&amp;ssl=1 1540w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>which yields&nbsp;<code>true<\/code>.<\/p>\n\n\n\n<p>The whole thing produced&nbsp;<code>true | false | false | true<\/code>. TypeScript removes the duplicates, leaving us with&nbsp;<code>true | false<\/code>.<\/p>\n\n\n\n<p>Do you know what a simpler name for the type&nbsp;<code>true | false<\/code>&nbsp;is? It&#8217;s&nbsp;<code>boolean<\/code>. This type produced&nbsp;<code>boolean<\/code>, which is why we got the error:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Type &#8216;boolean&#8217; does not satisfy the constraint &#8216;true&#8217;<\/p>\n<\/blockquote>\n\n\n\n<p>We were expecting a literal type of&nbsp;<code>true<\/code>&nbsp;but got a union of true and false, which reduced to boolean.<\/p>\n\n\n\n<p>It&#8217;s the same idea with:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> Result = TypesMatch&lt;<span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span>, <span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span> | <span class=\"hljs-built_in\">boolean<\/span>&gt;;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We won&#8217;t go through all the permutations. Suffice it to say we&#8217;ll get some mix of true and false, which will reduce back to&nbsp;<code>boolean<\/code>&nbsp;again.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">So how to fix it?<\/h2>\n\n\n\n<p>We need to stop the union types from distributing. The distributing happens only with a raw generic type in a conditional type expression. We can turn it off by turning that type into another type, with the same assignability rules. A tuple type does that perfectly:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> TypesMatch&lt;T, U&gt; = &#91;T] <span class=\"hljs-keyword\">extends<\/span> &#91;U]\n  ? &#91;U] <span class=\"hljs-keyword\">extends<\/span> &#91;T]\n    ? <span class=\"hljs-literal\">true<\/span>\n    : <span class=\"hljs-literal\">false<\/span>\n  : <span class=\"hljs-literal\">false<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Instead of asking if&nbsp;<code>T extends U<\/code>&nbsp;I ask if&nbsp;<code>[T] extends [U]<\/code>.&nbsp;<code>[T]<\/code>&nbsp;is a tuple type with one member, T. Think of it with non-generic types.&nbsp;<code>[string]<\/code>&nbsp;is essentially an array with a single element (and no more!) that&#8217;s a string.<\/p>\n\n\n\n<p>All our normal rules apply.&nbsp;<code>[\"foo\"] extends [string]<\/code>&nbsp;is true, while&nbsp;<code>[string] extends [\"foo\"]<\/code>&nbsp;is false. You can assign a tuple with a single&nbsp;<code>\"foo\"<\/code>&nbsp;string literal to a tuple with a single string, for the same reason that you can assign a&nbsp;<code>\"foo\"<\/code>&nbsp;string literal to a variable of type&nbsp;<code>string<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">One last fix<\/h2>\n\n\n\n<p>We&#8217;re pretty much done, don&#8217;t worry.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> TypesMatch&lt;T, U&gt; = &#91;T] <span class=\"hljs-keyword\">extends<\/span> &#91;U]\n  ? &#91;U] <span class=\"hljs-keyword\">extends<\/span> &#91;T]\n    ? <span class=\"hljs-literal\">true<\/span>\n    : <span class=\"hljs-literal\">false<\/span>\n  : <span class=\"hljs-literal\">false<\/span>;\n\n<span class=\"hljs-keyword\">type<\/span> Tests = &#91;\n  Expect&lt;Not&lt;TypesMatch&lt;<span class=\"hljs-built_in\">string<\/span>, <span class=\"hljs-string\">\"foo\"<\/span>&gt;&gt;&gt;,\n  Expect&lt;TypesMatch&lt;<span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span>, <span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span>&gt;&gt;,\n  Expect&lt;Not&lt;TypesMatch&lt;<span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span>, <span class=\"hljs-built_in\">string<\/span> | <span class=\"hljs-built_in\">number<\/span> | object&gt;&gt;&gt;\n];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This mostly works, but there&#8217;s one rub: optional properties. This test currently fails<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\">Expect&lt;{ a: <span class=\"hljs-built_in\">number<\/span> }, { a: <span class=\"hljs-built_in\">number<\/span>; b?: <span class=\"hljs-built_in\">string<\/span> }&gt;;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Unfortunately both of those types are assignable to each other, which makes sense. The&nbsp;<code>b<\/code>&nbsp;in the second type is optional.&nbsp;<code>{ a: number; b?: string }<\/code>&nbsp;can in fact be assigned to a variable expecting&nbsp;<code>{ a: number }<\/code>, since the structure of&nbsp;<code>{ a: number }<\/code>&nbsp;is satisfied. TypeScript is happy to ignore the extra properties. We&#8217;re really close though. This only happens if either type has optional properties which are absent from the other type.<\/p>\n\n\n\n<p>This already fails:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\">Expect&lt;{ a: <span class=\"hljs-built_in\">number<\/span>; b?: <span class=\"hljs-built_in\">number<\/span> }, { a: <span class=\"hljs-built_in\">number<\/span>; b?: <span class=\"hljs-built_in\">string<\/span> }&gt;;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Those types are not at all compatible. So why don&#8217;t we just keep what we already have and verify that all of the property keys are the same. We&#8217;ll rename a smidge and wind up with this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> ShapesMatch&lt;T, U&gt; = &#91;T] <span class=\"hljs-keyword\">extends<\/span> &#91;U]\n  ? &#91;U] <span class=\"hljs-keyword\">extends<\/span> &#91;T]\n    ? <span class=\"hljs-literal\">true<\/span>\n    : <span class=\"hljs-literal\">false<\/span>\n  : <span class=\"hljs-literal\">false<\/span>;\n\n<span class=\"hljs-keyword\">type<\/span> TypesMatch&lt;T, U&gt; = ShapesMatch&lt;T, U&gt; <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-literal\">true<\/span>\n  ? ShapesMatch&lt;keyof T, keyof U&gt; <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-literal\">true<\/span>\n    ? <span class=\"hljs-literal\">true<\/span>\n    : <span class=\"hljs-literal\">false<\/span>\n  : <span class=\"hljs-literal\">false<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>What was&nbsp;<code>TypesMatch<\/code>&nbsp;is now&nbsp;<code>ShapesMatch<\/code> and our real&nbsp;<code>TypesMatch<\/code>&nbsp;calls that, then calls it again on the types&#8217; keys. Don&#8217;t be scared of&nbsp;<code>keyof T<\/code>&nbsp;\u2014 that&#8217;s safe. This will work on primitive types, function types, etc. So long as the types are the same, the result will be the same. If the types are object types, they&#8217;ll match up those object properties.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping up<\/h2>\n\n\n\n<p>Conditional types can be incredibly helpful when you&#8217;re building advanced types for testing or otherwise. But they also come with some behaviors that can be surprising to the uninitiated. Hopefully this post made some of that clearer.<a href=\"https:\/\/github.com\/arackaf\/my-blog\/blob\/arackis\/types-match\/blog\/types-match\/index.md#type-helpers\"><\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/arackaf\/my-blog\/blob\/arackis\/types-match\/blog\/types-match\/index.md#testing-your-types-in-typescript\"><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post explores advanced TypeScript utilities for testing and verifying type correctness, akin to unit testing without extra setup.<\/p>\n","protected":false},"author":21,"featured_media":2511,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[184],"class_list":["post-2485","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-typescript"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/pexels-photo-938165.jpeg?fit=1880%2C1191&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2485","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=2485"}],"version-history":[{"count":12,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2485\/revisions"}],"predecessor-version":[{"id":2549,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2485\/revisions\/2549"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/2511"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=2485"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=2485"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=2485"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}