Sunday, October 19, 2025
HomeJavaScriptMy gross sales pitch for TypeScript

My gross sales pitch for TypeScript


Roughly, TypeScript is JavaScript plus kind data. The latter is eliminated earlier than TypeScript code is executed by JavaScript engines. Due to this fact, writing and deploying TypeScript is extra work. Is that added work value it? On this weblog submit, I’m going to argue that sure, it’s. Learn it if you’re skeptical about TypeScript however focused on giving it an opportunity.

Notation used on this weblog submit  

In TypeScript code, I’ll present the errors reported by TypeScript through feedback that begin with @ts-expect-error – e.g.:



const worth = 5 * '8';

That makes it simpler to mechanically check all of the supply code on this weblog submit. It’s additionally a built-in TypeScript characteristic that may be helpful (albeit not often).

TypeScript profit: auto-completion and detecting extra errors throughout enhancing  

Let’s have a look at examples of code the place TypeScript helps us – by auto-completing and by detecting errors. The primary instance is straightforward; later ones are extra refined.

Instance: typos, incorrect sorts, lacking arguments  

class Level {
  x: quantity;
  y: quantity;
  constructor(x: quantity, y = x) {
    this.x = x;
    this.y = y;
  }
}
const point1 = new Level(3, 8);


console.log(point1.z); 



point1.x.toUpperCase(); 

const point2 = new Level(3); 


const point3 = new Level(); 



const point4 = new Level(3, '8'); 

What is going on right here?

  • Line A: TypeScript is aware of the kind of point1 and it doesn’t have a property .z.

  • Line B: point1.x is a quantity and subsequently doesn’t have the string methodology .toUpperCase().

  • Line C: This invocation works as a result of the second argument of new Level() is non-obligatory.

  • Line D: At the least one argument should be supplied.

  • Line E: The second argument of new Level() should be a quantity.

In line A, we get auto-completion:

Instance: getting operate outcomes mistaken  

What number of points are you able to see within the following JavaScript code?

operate reverseString(str) {
  if (str.size === 0) {
    return str;
  }
  Array.from(str).reverse();
}

Let’s see what TypeScript tells us if we add kind annotations (line A):



operate reverseString(str: string): string { 
  if (str.size === 0) {
    return str;
  }
  Array.from(str).reverse(); 
}

TypeScript tells us:

  • On the finish, there isn’t any return assertion – which is true: We forgot to start out line B with return and subsequently implicitly return undefined after line B.
  • The implicitly returned undefinedis incompatible with the return kind string (line A).

If we repair this difficulty, TypeScript factors out one other error:

operate reverseString(str: string): string { 
  if (str.size === 0) {
    return str;
  }
  
  
  return Array.from(str).reverse(); 
}

In line B, we’re returning an Array whereas the return kind in line A says that we need to return a string. If we repair that difficulty too, TypeScript is lastly proud of our code:

operate reverseString(str: string): string {
  if (str.size === 0) {
    return str;
  }
  return Array.from(str).reverse().be a part of('');
}

Instance: working with non-obligatory properties  

In our subsequent instance, we work with names which might be outlined through objects. We outline the construction of these objects through the next TypeScrip kind:

kind NameDef = {
  title?: string, 
  nick?: string, 
};

In different phrases: NameDef objects have two properties whose values are strings. Each properties are non-obligatory – which is indicated through the query marks in line A and line B.

The next code incorporates an error and TypeScript warns us about it:

operate getName(nameDef: NameDef): string {
  
  
  return nameDef.nick ?? nameDef.title;
}

?? is the nullish coalescing operator that returns its left-hand facet – except it’s undefined or null. In that case, it returns its right-hand facet. For extra data, see “Exploring JavaScript”.

nameDef.title could also be lacking. In that case, the result’s undefined and never a string. If we repair that, TypeScript doesn’t report any extra errors:

operate getName(nameDef: NameDef): string {
  return nameDef.nick ?? nameDef.title ?? '(Nameless)';
}

Instance: forgetting change instances  

Think about the next kind for colours:

kind Colour = 'crimson' | 'inexperienced' | 'blue';

In different phrases: a shade is both the string 'crimson' or the string 'inexperienced' or the string 'blue'. The next operate interprets such colours to CSS hexadecimal shade values:

operate getCssColor(shade: Colour): `#${string}` {
  change (shade) {
    case 'crimson':
      return '#FF0000';
    case 'inexperienced':
      
      
      return '00FF00'; 
    default:
      
      
      
      throw new UnexpectedValueError(shade); 
  }
}

In line A, we get an error as a result of we return a string that’s incompatible with the return kind `#${string}`: It doesn’t begin with a hash image.

The error in line C implies that we forgot a case (the worth 'blue'). To grasp the error message, we should know that TypeScript frequently adapts the kind of shade:

  • Earlier than the change assertion, its kind is 'crimson' | 'inexperienced' | 'blue'.
  • After we crossed off the instances 'crimson' and 'inexperienced', its kind is 'blue' in line B.

And that kind is incompatible with the particular kind by no means that the parameter of new UnexpectedValueError() has. That kind is used for variables at places that we by no means attain. For extra data see the 2ality submit “The underside kind by no means in TypeScript”.

After we repair each errors, our code seems to be like this:

operate getCssColor(shade: Colour): `#${string}` {
  change (shade) {
    case 'crimson':
      return '#FF0000';
    case 'inexperienced':
      return '#00FF00';
    case 'blue':
      return '#0000FF';
    default:
      throw new UnexpectedValueError(shade);
  }
}

That is what the error class UnexpectedValueError seems to be like:

class UnexpectedValueError extends Error {
  constructor(
    
    worth: by no means,
    
    
    
    message = `Surprising worth: ${{}.toString.name(worth)}`
  ) {
    tremendous(message)
  }
}

Lastly, TypeScript offers us auto-completion for the argument of getCssColor():

Instance: code handles some instances incorrectly  

The next kind describes content material through objects. Content material will be textual content, a picture or a video:

kind Content material =
  | {
    type: 'textual content',
    charCount: quantity,
  }
  | {
    type: 'picture',
    width: quantity,
    top: quantity,
  }
  | {
    type: 'video',
    width: quantity,
    top: quantity,
    runningTimeInSeconds: quantity,
  }
;

Within the following code, we use content material incorrectly:

operate getWidth(content material: Content material): quantity {
  
  
  return content material.width;
}

TypeScript warns us as a result of not all types of content material have the property .content material. Nonetheless, all of them do have the property .type – which we are able to use to repair the error:

operate getWidth(content material: Content material): quantity {
  if (content material.type === 'textual content') {
    return NaN;
  }
  return content material.width; 
}

Notice that TypeScript doesn’t complain in line A, as a result of we’ve got excluded textual content content material, which is the one content material that doesn’t have the property .width.

Kind annotations for operate parameters and outcomes are good documentation  

Think about the next JavaScript code:

operate filter(gadgets, callback) {
  
}

That doesn’t inform us a lot concerning the arguments anticipated by filter(). We additionally don’t know what it returns. In distinction, that is what the corresponding TypeScript code seems to be like:

operate filter(
  gadgets: Iterable<string>,
  callback: (merchandise: string, index: quantity) => boolean
): Iterable<string> {
  
}

This data tells us:

  • Argument gadgets is an iterable over strings.
  • The callback receives a string and an index and returns a boolean.
  • The results of filter() is one other iterable over strings.

Sure, the kind notation takes getting used to. However, as soon as we perceive it, we are able to shortly get a tough perceive of what filter() does. Extra shortly than by studying prose in English (which, admittedly, continues to be wanted to fill within the gaps left by the kind notation and the title of the operate).

I discover it simpler to know TypeScript code bases than JavaScript code bases as a result of, to me, TypeScript supplies an extra layer of documentation.

This extra documentation additionally helps when working in groups as a result of it’s clearer how code is for use and TypeScript usually warns us if we’re doing one thing mistaken.

Each time I migrate JavaScript code to TypeScript, I’m noticing an fascinating phenomenon: With the intention to discover the suitable sorts for the parameters of a operate or methodology, I’ve to verify the place it’s invoked. That implies that static sorts give me data domestically that I in any other case need to search for elsewhere.

TypeScript profit: higher refactoring  

Refactorings are automated code transformations that many built-in improvement environments provide.

Renaming strategies is an instance of a refactoring. Doing so in plain JavaScript will be tough as a result of the identical title may confer with totally different strategies. TypeScript has extra data on how strategies and kinds are related, which makes renaming strategies safer there.

Utilizing TypeScript has turn into simpler  

We now usually don’t want an additional construct step in comparison with JavaScript:

  • On server facet JavaScript platforms akin to Node.js, Deno and Bun, we are able to run TypeScript immediately – with out compiling it.
  • Most bundlers akin to Vite have built-in help for TypeScript.

Extra excellent news:

  • Compiling TypeScript to JavaScript has turn into extra environment friendly – due to a method known as kind stripping which merely removes the kind a part of TypeScript syntax and makes no different transformations (extra data).

Creating packages has additionally improved:

  • npm: Non-library packages will be printed in TypeScript. Library packages should include JavaScript plus declaration recordsdata (with kind data). Producing the latter has additionally improved – due to a method known as remoted declarations.
  • JSR (JavaScript Registry) is a substitute for npm the place packages will be uploaded as TypeScript. It helps a wide range of platforms. For Node.js, it mechanically generates JavaScript recordsdata and declaration recordsdata.

Alas, kind checking continues to be comparatively sluggish and should be carried out through the TypeScript compiler tsc.

The downsides of utilizing TypeScript  

  • It’s an added layer on prime of JavaScript: extra complexity, extra issues to be taught, and so on.
  • npm packages can solely be used if they’ve static kind definitions. Today, most packages both include kind definitions or there are kind definitions out there for them on DefinitelyTyped. Nonetheless, particularly the latter can sometimes be barely mistaken, which results in points that you simply don’t have with out static typing.
  • Configuring TypeScript through tsconfig.json additionally provides a little bit of complexity and means that there’s a lot of variation w.r.t. how TypeScript code bases are type-checked. There are two mitigating components:
    • For my very own tasks, I’m now utilizing a maximally strict tsconfig.json – which eradicated my doubts about what my tsconfig.json ought to seem like.
    • Kind stripping (see earlier part) has clarified the function of tsconfig.json for me: With them, it solely configures how kind checking works. Producing JavaScript will be carried out with out tsconfig.json.

TypeScript FAQ  

Is TypeScript code heavyweight?  

TypeScript code can be heavyweight. Nevertheless it doesn’t need to be. For instance, as a consequence of kind inference, we are able to usually get away with comparatively few kind annotations:

operate setDifference<T>(set1: Set<T>, set2: Set<T>): Set<T> {
  const end result = new Set<T>();
  for (const elem of set1) {
    if (!set2.has(elem)) {
      end result.add(elem);
    }
  }
  return end result;
}

The one non-JavaScript syntax on this code is <T>: Its first prevalence setDifference<T> implies that the operate setDifference() has a kind parameter – a parameter on the kind stage. All later occurrences of <T> confer with that parameter. They imply:

  • The parameters set1 and set2 are Units whose components have the identical kind T.
  • The end result can also be a Set. Its components have the identical kind as these of set1 and set2.

Notice that we usually don’t have to supply the kind parameter <T> – TypeScript can extract it mechanically from the kinds of the parameters:

assert.deepEqual(
  setDifference(new Set(['a', 'b']), new Set(['b'])),
  new Set(['a']),
);
assert.deepEqual(
  setDifference(new Set(['a', 'b']), new Set(['a', 'b'])),
  new Set(),
);

With regards to utilizing setDifference(), the TypeScript code shouldn’t be totally different from JavaScript code on this case.

Is TypeScript attempting to show JavaScript into C# or Java?  

Over time, the character of TypeScript has developed.

TypeScript 0.8 was launched in October 2012 when JavaScript had remained stagnant for a very long time. Due to this fact, TypeScript added options that its group felt JavaScript was lacking – e.g. lessons, modules and enums.

Since then, JavaScript has gained many new options. TypeScript now tracks what JavaScript supplies and doesn’t introduce new language-level options anymore – for instance:

  • In 2012, TypeScript had its personal means of doing modules. Now it helps ECMAScript modules and CommonJS.

  • In 2012, TypeScript had lessons that had been transpiled to features. Since ECMAScript 6 got here out in 2015, TypeScript has supported the built-in lessons.

  • In 2015, TypeScript launched its personal taste of decorators, to be able to help Angular. In 2022, ECMAScript decorators reached stage 3 and TypeScript has supported them since. For extra data, see part “The historical past of decorators” within the 2ality submit on ECMAScript decorators.

  • If the kind checking choice erasableSyntaxOnly is lively, TypeScript solely helps JavaScript’s language options – e.g. we aren’t allowed to make use of enums. This selection allows kind stripping and is in style amongst TypeScript programmers. Thus it seems to be like sooner or later, most TypeScript will actually be pure JavaScript plus kind data.

  • TypeScript will solely get higher enums or sample matching if and when JavaScript will get them.

TypeScript is greater than OOP  

A typical false impression is that TypeScript solely helps a class-heavy OOP type; it helps many practical programming patterns simply as effectively – e.g. discriminated unions that are a (barely much less elegant) model of algebraic knowledge sorts:

kind Content material =
  | {
    type: 'textual content',
    charCount: quantity,
  }
  | {
    type: 'picture',
    width: quantity,
    top: quantity,
  }
  | {
    type: 'video',
    width: quantity,
    top: quantity,
    runningTimeInSeconds: quantity,
  }
;

In Haskell, this knowledge kind would seem like this (with out labels, for simplicity’s sake):

knowledge Content material =
  Textual content Int
  | Picture Int Int
  | Video Int Int Int

Extra data: “TypeScript for practical programmers” within the TypeScript Handbook.

Superior utilization of sorts appears very sophisticated. Do I actually need to be taught that?  

Regular use of TypeScript nearly all the time entails comparatively easy sorts. For libraries, sophisticated sorts will be helpful however then they’re sophisticated to put in writing and never sophisticated to make use of. My common suggestion is to make sorts so simple as doable and subsequently simpler to know and preserve. If sorts for code are too sophisticated then it’s usually doable to simplify them – e.g. by altering the code and utilizing two features as a substitute of 1 or by not capturing each final element with them.

One key perception for making sense of superior sorts, is that they’re largely like a brand new programming language on the kind stage and often describe how enter sorts are remodeled into output sorts. In some ways, they’re just like JavaScript. There are:

  • Variables (kind variables)
  • Capabilities with parameters (generic sorts with kind parameters)
  • Conditional expressions C ? T : F (conditional sorts)
  • Loops over objects (mapped sorts)
  • And many others.

For extra data on this matter, see “An outline of computing with sorts” in “Tackling TypeScript”.

Are sophisticated sorts value it?  

Typically they’re – for instance, as an experiment, I wrote a easy SQL API that provides you plenty of kind completions and warnings throughout enhancing (should you make typos and so on). Notice that writing that API concerned some work; utilizing it’s easy.

How lengthy does it take to be taught TypeScript?  

I consider that you would be able to be taught the fundamentals of TypeScript inside a day and be productive the subsequent day. There may be nonetheless extra to be taught after that, however you are able to do so whereas already utilizing it.

I’ve written a chapter that teaches you these fundamentals. In case you are new to TypeScript, I’d love to listen to from you: Is my assumption right? Had been you prepared to start out utilizing (easy) TypeScript after studying it?

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments