This is gonna be an unpopular opinion but as someone who learned to program in loosely typed languages, I have never seen the TS appeal.

TS feels like something that was created to lure programmers who couldn’t wrap their heads around JS loose nature. Almost like it was created to convince Java and C# developers to use JS.

It have never felt about it like something that would make my code better or more organized. It definitely slows me down with very little benefits in return. (Again, can’t stress enough that this is a personal opinion based on personal use cases)

I appreciate that it forces me to be more intentional about organizing my code but I also get that when I use frameworks.

Of course I also appreciate when I can identify that an error is coming from a type mismatch, but as I said I learned to program in loosely typed languages first, so I never created that concept of defining types in my head. I’m always extra-aware of types mismatches in my JS code and is usually the first thing I check when something goes wrong. But I have never felt the need to have a way to explicitly define the types that I’m working with. I honestly fail to see the benefit when a large amount of code that you have to interact with (libraries and such) was written in classic JS.

Once again. Probably unpopular opinion, so I don’t mind the downvotes.

You don't have to write as many tests when you use static typing, because your method contracts are solid.

You can refactor your entire codebase with a few operations and don't have to worry about anything breaking.

Is your timestamp in seconds? Millis? Is it a duration? Does it have a time zone?

Have you ever written a method that returns more than one type of thing? Or had polymorphic inputs?

Where are your dynamically dispatched calls even used, and how do you know you modified them all?

One of the most common operations in building new features is plumbing new state through the system. Types are there to help you in such perilous times.

Types are refreshing and reassuring.

You don't have to write as many tests when you use static typing, because your method contracts are solid.

People often say this, and I don't get it. What JS tests are you writing that become unnecessary in TypeScript? I've used a fair amount of TypeScript and plain JS, and end up with similar amounts of tests for each. With JS, I almost never want to verify only that a value is of a specific type; I want to look at its contents, which means I'd need to write the same test in TypeScript.

Let's say you have a method that takes a thing:

    function doSomething(thing) { ... }
How many different possible representations of a 'thing' do you have? A json object? A class object with behaviors? A database id? Some sort of natural key like a SKU? A URL? Is it a metric or imperial thing?

You need integration-level tests around every method call to ensure that caller and callee agree what kind of 'thing' representation to use. Type systems can eliminate this class of bug entirely.

I never throw inputs of random types to my functions. Actually that could be a good idea, some fuzzying at the public API level could catch some bugs and attacks. But not at unit test level. If a function expects an integer argument I test it with integer values. That in Node, Ruby, Python and Elixir. I never saw anybody doing something different. Well, if we wanted to enforce types we could use any static typed language out there.

The inputs shouldn’t be “random”, but the idea is that you should provide an input distribution that’s roughly representative of your expected distribution of real-world values (+ some perturbation to find edge cases).

As for frameworks in the languages you listed...

Ruby and Python: https://github.com/HypothesisWorks/hypothesis

Elixir and Erlang: https://github.com/proper-testing/proper

Node and JS: https://jsverify.github.io/

As for real world use-cases, imagine you’re writing a program that accepts timestamps as input and has to implement branching, requirements-defined business logic based off of them. When you’re writing your unit tests you can use the requirements to select timestamps that are “known good” and “known bad”, but it’s hard to explore this state space on your own.

Same thing goes for handling unexpected inputs to certain functions. You probably don’t want to check _every_ type of input for _every_ dynamic function, but it might make sense to make sure that certain “entry points” to your program fail in the expected manner when they get poorly typed input.