JavaScript, by its nature, is a dynamically typed language. This means that any variable can hold any number of data types including string, number, arrays, objects, functions, etc. The type of a JavaScript variable is determined by the assignment. This means that when we assign a value to a variable, the JavaScript runtime interpreter determines the type of that variable.
However, the runtime can also reassign the types of a variable depending on how it is being used, or on how it is interacting with other variables. It may assign a number to string for example.
Given this flexibility, there are so many ways in which things can go wrong.

It is up to the programmer to ensure that the code is used as intended and to verify all the cases in which it can go wrong. A check like the following would work.

Great! This will now tell whoever is using the method to enter only numbers as params and throw a runtime error when wrong params are fed to it.
We could use parseInt to convert everything to number but that still doesn't work for cases where the params are objects, arrays, undefined or null. The list goes on.
It is very hard to write safe JavaScript that will always function as intended on anything more than a hobby project.
TypeScript is different. Once we declare a variable, we can’t change the type. This helps ensure that the code we wrote will behave as expected. Let’s look at a TypeScript version of the above example.

This program will not compile any more, because we have specified that only type of number is allowed as params for the function. If we look at this in our IDE, we will see these errors as they occur just like we would in any other strongly typed language.

Let us explore these types
Basic types
- boolean
- string
- number
- Array
- bigint
- symbol
- any (opt-out of type check)
- void (doesn’t return)
- null (has no value)
- undefined (value not given)
- never (function execution never reaches the last line)
- object
- unknown (has some value, we don’t know what it is)
Check out details of all the types here. unknown was added in TS 3.0.
Where and how do we use types
This list is not exhaustive. These are some points that I personally have found some use for.
- Declaring variables

- Declaring functions

- Complex types with Duck typing (looks like a duck, walks like a duck, then it probably is a duck)

- Array types

- Skip type checking for special cases (use type any).
This should be used as a temporary solution for receiving data from outside the program like a database or a server request param and converted to an appropriate type after checks before being used inside the application.

- Typecasting
This doesn’t actually change the type, it just tells the compiler how to look at a variable.

- Union type (mix of many types) with a type alias

- Union type in functions
This requires a type guard in some cases.

- Tuples
Array with defined length and types of each member.

- Optional elements
Use ? to tell the compiler if something is optional. This can also be done in functions if we set a default value to a param.

- Enums as type
This is a bit problematic when we’re using numbers

- Interfaces as types

I mentioned earlier in #6 as unknown as Type is useful in some cases.

Imagine that TestService controls all the database models. We know the functionality we want out of it. If we want to finish building the use-case logic first before deciding on the final database implementation, we can try the approach above.
Interfaces don’t exist in JavaScript. When TypeScript is compiled, all of them will cease to exist. They can only be used within TypeScript for planning and type checking. The above program can be compiled if we remove the implements ITestService in TestService but it will not actually work since functionalityA and functionalityBdon't exist inside TestService.
I use this purely when I’m planning a new feature and I want to push off some part of the implementation for later.
- Classes as types
I’ve mentioned some hacks around the type system. TypeScript is compiled into JavaScript, forcing something to be of type
anywill make it behave as it would have in JavaScript. This is not advisable unless a use-case specifically requires it.

Let’s try messing around with inheritance for a bit. We’ll create a subclass of A called B and cast it as A.

We can limit the properties that are only available in child classes by typecasting them as the parent. There’s a caveat. Casting a variable is just a way of telling the compiler to look at it in that particular way. It doesn’t change the underlying object that is getting cast.
To illustrate, let’s create a subclass of A called C. It will have a public property d that will have a default value. When we create an object of A and cast it to C, it behaves very unexpectedly.
Also, if we try to cast an object of A to any class that has a different set of properties we get an error.
Typecasting can also be used as a hack to create instances of abstract classes.

- Generics
Generics are a way of writing code that will deal with any type of object but still maintain object type integrity. This is a huge topic. I’ll write about it separately. I’m giving only some basic examples here.

And the generic that we will probably use the most.

To use Promise<string> like this, we need to set "target": "es6", in tsconfig.json.
Medium doesn’t have syntax highlighting and there’s a lot of code in this one. So I’m using screenshots instead of gists.
Further reading: