My Type of Coding: TypeScript is for Humans

When I started writing JavaScript, I loved it. I often compared it to painting, as opposed to a language like Java where it felt like I was building with blocks. These analogies represents spaces where patterns emerge. and what shape they will take. In the painting world of JavaScript the patterns are innumerable. Almost any design pattern you like is possible, and you can make values essentially do whatever you please. This creates a problem though, and the problem is the "you" in those statements. You are not alone! The design patterns you like may be obscure to someone else, and the tricks you do with values may not be amusing to someone else who has to figure them out. And so the beauty of the painting is in the mind of the coder who created it, and the mess is for everyone else. What Patterns are in the Painting "The Sacrament of the Last Supper" by Salvador Dali contains a myriad of patterns. Geometry, Perspective, Symbolism, Realism, Symmetry...but underneath it all what does it mean? In order to understand the genesis of these patterns, we would need the thousand words the picture is worth to tell us what the artist was thinking during the creation of a masterpiece. Coding, however, is not painting. There is no "last" supper, it's an everlasting supper. We continue to add fruit to the table then use the painting as a recipe for dinner. So where do I add the fruit? Should it be an apple or a pear? Should I add the fruit at all? If I add a fruit should I remove a disciple? These are the questions we have to answer when we code. Now we come to TypeScript TypeScript is the language we use to describe patterns in our JavaScript. We're still painting, but while we're painting we're forced to explain the patterns in our mind to those who must carry the patterns in the code into the future. The patterns it elucidates are not obscure, ideally they should be what you were thinking anyway if you were just writing JavaScript. type Frame = { src: URL } type Element = { children?: Element[] } type FrameElement = Frame & Element We can describe the patterns in the same way as we're thinking about them as well. In the example above we have the concept of a Frame, which contains a src: URL property, and an Element, which can recursively have children?: Element[]. This explains something particular about how we were thinking about the code. That there are separate ways to look at the same object, and maybe separate objects that answer similar requirements. Like a pear in the painting having both a real and symbolic meaning, or an apple and a pear both being fruit. type NotFound = 404 type OK = 200 type Err = 500 type StatusCode = NotFound | OK | Err // Or enum StatusCode { NotFound = 400, OK = 200, Err = 500, } Sometimes we're presented with a choice, two ways to explain the same thing. Here we have a representation of HTTP status codes in an application. The first way of representing the code exists only in TypeLand®️. The second creates an enum, which is both a type and an object at runtime. Which you choose depends on the requirements but also how you view the pattern you're trying to create, and how you see that pattern being used in the future as well. As in all coding, there's a preference for simpler answers. const observeSymbol = Symbol('observe') type Unsubscribe = () => void type Observer = (value: T) => void type Subscribe = (observer: Observer) => Unsubscribe type Observable = T & { [observeSymbol]: Subscribe } type ObservableMap = { [K in keyof T]: T[K] extends () => void ? never : Observable } That said, sometimes things get complicated. Remember that TypeScript is essentially two programming languages in one. The one that rules TypeLand®️ and the one that runs the runtime. The type side has its own variables (aliases), functions (generics), logic (conditional types), comprehensions (mapped types), ect. We can make the complexity simpler just like we do in JavaScript or any programming language however. In the example above, I could have just written out the ObservableMap type without the intermediate types. But, by factoring them out, as we would factor out references and functions, makes the code more readable. It also makes the patterns reusable. For example I might find myself wanting to subscribe to each key on an ObservableMap, in which case I might later add this: type ObserverMap { [K in keyof T]: T[K] extends () => void ? never : Observer } const ObserveObject = (map: ObservableMap, obs: ObserverMap): Unsubscribe => { return Object.keys(map).reduce((acc: Unsubscribe, k: keyof ObservableMap) => { if (typeof map[k] !== 'function') { const unsubscribe = map[k][observableSymbol](obs[k]) return () => { acc() unsubscribe() } } }, () => {}) } Just like smaller more general functions in JavaScript, these smaller more general types o

Apr 3, 2025 - 15:18
 0
My Type of Coding: TypeScript is for Humans

When I started writing JavaScript, I loved it. I often compared it to painting, as opposed to a language like Java where it felt like I was building with blocks. These analogies represents spaces where patterns emerge. and what shape they will take.

In the painting world of JavaScript the patterns are innumerable. Almost any design pattern you like is possible, and you can make values essentially do whatever you please. This creates a problem though, and the problem is the "you" in those statements.

You are not alone! The design patterns you like may be obscure to someone else, and the tricks you do with values may not be amusing to someone else who has to figure them out. And so the beauty of the painting is in the mind of the coder who created it, and the mess is for everyone else.

What Patterns are in the Painting

A photograph of the painting

"The Sacrament of the Last Supper" by Salvador Dali contains a myriad of patterns. Geometry, Perspective, Symbolism, Realism, Symmetry...but underneath it all what does it mean? In order to understand the genesis of these patterns, we would need the thousand words the picture is worth to tell us what the artist was thinking during the creation of a masterpiece.

Coding, however, is not painting. There is no "last" supper, it's an everlasting supper. We continue to add fruit to the table then use the painting as a recipe for dinner. So where do I add the fruit? Should it be an apple or a pear? Should I add the fruit at all? If I add a fruit should I remove a disciple? These are the questions we have to answer when we code.

Now we come to TypeScript

TypeScript is the language we use to describe patterns in our JavaScript. We're still painting, but while we're painting we're forced to explain the patterns in our mind to those who must carry the patterns in the code into the future. The patterns it elucidates are not obscure, ideally they should be what you were thinking anyway if you were just writing JavaScript.

type Frame = {
    src: URL
}

type Element = {
    children?: Element[]
}

type FrameElement = Frame & Element

We can describe the patterns in the same way as we're thinking about them as well. In the example above we have the concept of a Frame, which contains a src: URL property, and an Element, which can recursively have children?: Element[]. This explains something particular about how we were thinking about the code. That there are separate ways to look at the same object, and maybe separate objects that answer similar requirements. Like a pear in the painting having both a real and symbolic meaning, or an apple and a pear both being fruit.

type NotFound = 404
type OK = 200
type Err = 500

type StatusCode = NotFound | OK | Err

// Or

enum StatusCode {
  NotFound = 400,
  OK = 200,
  Err = 500,
}

Sometimes we're presented with a choice, two ways to explain the same thing. Here we have a representation of HTTP status codes in an application. The first way of representing the code exists only in TypeLand®️. The second creates an enum, which is both a type and an object at runtime. Which you choose depends on the requirements but also how you view the pattern you're trying to create, and how you see that pattern being used in the future as well. As in all coding, there's a preference for simpler answers.

const observeSymbol = Symbol('observe')

type Unsubscribe = () => void
type Observer<T> = (value: T) => void
type Subscribe<T> = (observer: Observer<T>) => Unsubscribe

type Observable<T> = T & {
  [observeSymbol]: Subscribe<T>
}

type ObservableMap<T> = {
    [K in keyof T]: T[K] extends () => void ? never : Observable<T[K]>
}

That said, sometimes things get complicated. Remember that TypeScript is essentially two programming languages in one. The one that rules TypeLand®️ and the one that runs the runtime. The type side has its own variables (aliases), functions (generics), logic (conditional types), comprehensions (mapped types), ect. We can make the complexity simpler just like we do in JavaScript or any programming language however.

In the example above, I could have just written out the ObservableMap type without the intermediate types. But, by factoring them out, as we would factor out references and functions, makes the code more readable. It also makes the patterns reusable. For example I might find myself wanting to subscribe to each key on an ObservableMap, in which case I might later add this:

type ObserverMap<T> {
    [K in keyof T]: T[K] extends () => void ? never : Observer<U>
}

const ObserveObject = <T>(map: ObservableMap<T>, obs: ObserverMap<T>): Unsubscribe => {
  return Object.keys(map).reduce((acc: Unsubscribe, k: keyof ObservableMap<T>) => {
      if (typeof map[k] !== 'function') {
          const unsubscribe = map[k][observableSymbol](obs[k])
          return () => {
              acc()
              unsubscribe()
          }
      }
  }, () => {})
}

Just like smaller more general functions in JavaScript, these smaller more general types or patterns in TypeScript allow us to reuse ideas to create new ones. Like Dali borrows patterns from da Vinci. While the above may seem complex (it is), in the end it's merely describing what we are actually doing in JavaScript. The real complexity is not documenting the patterns, and forcing everyone who looks at the code to re-invent them to figure out how to extend them.

What we didn't cover

We covered the human reason for TypeScript, as a form of documentation for how we as developers think of the JavaScript we write. What we didn't cover are the automation implications of this translation. Now not only can humans read the painting, but we've structured it in a way that machines can understand it as well. The ideas that we expressed in the original work can be checked for consistency, and this leads to a more reliable codebase. There are certain refactors as well that can be performed automatically, which is an added bonus.

Conclusion

Painting is an art, and so is coding — whether in JavaScript, TypeScript, or __________. However you choose to write your JavaScript, consider the person in the future how has to figure out what exists only in your mind as you write it. Unlike a painting, the mystery is not the point of the art, the function is. Leave the mystery to Dali, and write code that is understandable, verifiable, and extensible.