I wrote an on why to use Typescript if you're still in doubt about whether to use it in your projects or not. In short, Typescript allows you to write maintainable code. Javascript works well for prototyping but becomes frustrating when you return to a project again. Suddenly, you see blocks of code where you can't understand what kind of data passes there. article In this article, I want to introduce you to Typescript while playing with code. Thus, you see it in action and don't overflow with an unnecessary theory. I recommend playing with the code you'll meet here inside the . Typescript Playground Imagine you want to count items, which have the field that contains the field set to , in an array. If an item doesn't have , we don't count it. meta countable true meta.countable function getAmount (arr: any[]) { return arr.filter(item => item.meta.countable === true).length; } Typescript array type with anything in there Why are you using ? It's not OK! Don't use in such cases. Read more about that later on in this article. We see the keyword near the argument, that's new to us! I guess you already know what it is. This way we tell TS that we’re expecting a parameter called of type. any any any arr arr any[] It literally means any Javascript type can be passed into the array. I.e. is an array and every item of it is of type . Using the type means you’ll get type checking. If you pass in an argument that’s not an array, you’ll end up with the errors below: arr any any[] // Argument of type 'string' is not assignable to parameter of type 'any[]' getAmount('string'); // Argument of type 'number' is not assignable to parameter of type 'any[]' getAmount(29); The compiler ensures you should pass exactly what you've pointed out as an argument for the function. What if you need to pass several types, for example, an array and a string? And if is a string, then return 0. A weird case, but imagine you work on a legacy system that uses this function in many places. arr getAmount arr function getAmount (arr: any[] | string) { if (typeof arr === 'string') { return 0; } return arr.filter(item => item.meta.countable === true).length; } getAmount('55'); // now it's possible to pass a string getAmount([{ meta: {countable: true} }]); means "or". Thus, can be an array containing values of any type ( ) or a string. Refer to for more everyday types in Typescript. | arr any[] this page The compiler is smart enough to even infer a return type of . getAmount // function getAmount(arr: any[] | string): number function getAmount (arr: any[] | string) { // because we always return a number // 0 or arr.length(filtered } Type inferring for a function that always returns a number Sometimes, Typescript can't infer a type because of ambiguity. Usually, it's a to explicitly indicate the return type of a function. good practice function getAmount(arr: any[] | string): number { // ... } function myFunction(arg: any): boolean {/* function body */} Now you know how to write functions and point arguments and return types! In most cases, that's all you need. All other code is still Javascript with more types. However, let's dive deeper and highlight more complicated cases and what things to avoid. Someone may pass anything in an array: function getAmount(arr: any[]): number { // ... } getAmount([5, "string", {}, () => {}]); // no error That's not what we expect. TS works well in this case, we specified , so what’s the problem? any[] Don't use if there's no real need for it. It's easier to pass than describing an advanced type, but that's what Typescript is for. Future-proof your application any any TypeScript Objects We may want to replace with and it would work if we pass objects there right? Correct, but and functions are also objects. They’re also not what we expect either. any[] object[] null , try to narrow the types. Don't use object[] interface Item { meta?: { countable?: boolean; } } function getAmount (arr: Item[]) { return arr.filter(item => item.meta?.countable === true).length; } getAmount([ {}, {meta: {countable: true}} ]); // 1 Now it works as expected. We specified a separate for a possible array element. Interfaces and types allow you to create your own types using basic Typescript types. Some examples: interface // is also called "type alias" type Hash = string; // interface are "object" types and allow us // to specify an object immediately interface Person { name: string; isOkay: boolean; }; // it's the same as using a type alias type Person = { name: string; isOkay: boolean; }; Types and Interfaces Let's start implementing a booking tickets service to dive deeper into these types and interfaces. We want to have the possibility to book a ticket for a person. type Person = { name: string; } type Ticket = { from: string; to: string; person: Person; } function bookTicket (from: string, to: string, person: Person): Ticket { // some procesing return { from, to, person, }; } bookTicket('Paris', 'Mars', {name: 'Joey'}); The code seems okay. However, we can book a ticket to Mars using the function, but we don't fly to Mars yet. What can we change in our code to reflect this? We could add validation for and fields inside the function, but we can simply do this with TypeScript instead. For example, we could list possible locations we're flying to and from. from to type AvailableLocation = 'Paris' | 'Moon' | 'London'; type Person = { name: string; } type Ticket = { from: AvailableLocation; to: AvailableLocation; person: Person; } function bookTicket (from: AvailableLocation, to: AvailableLocation, person: Person): Ticket { // some procesing return { from, to, person, }; } // Error: Argument of type '"Mars"' is not assignable to parameter of type 'AvailableLocation' bookTicket('Paris', 'Mars', {name: 'Joey'}); We narrowed the possible options for locations. Thus, eliminated cases when we can write code that calls the function with invalid locations like "Mars" or "Andromeda Galaxy". We listed multiple allowed options via "or" operator - . We might be using enums for this purpose too: Paris | Moon enum Locations { Paris, Moon, London, } type Ticket { from: Locations; to: Locations; person: Person; } bookTicket(Locations.Paris, Locations.Moon, {name: 'Joey'}); There are differences in using types and enums, I won't cover them this time, but you may refer to for more details. this page As you might notice, somewhere I used for an object type and then declared another one via . You may use whichever you prefer for such cases or choose based on your project code guidelines. For more information about the difference, . interface type read here Using to Type Objects Record Sometimes you have generic objects, where a key is always (and it's a string, if you want to use other values, use instead) and a value is always too. In this case, you may define its type as follows: string always Map string type SomeObject = { [key: string]: string; } const o: SomeObject = {key: 'string value'} There's another way to do the same using : Record<keyType, valueType> type SomeObject = Record<string, string>; // it means an object with string values, e.g. {who: "me"} It's something new here: , computed types to re-use the existing ones. Let's re-create the type: generics Record type Record<Key, Value> = { [key: Key]: Value; } Thus, if we want to create an object, we don't need to write such signatures every time. So, an object with number values is as simple as: const obj: Record<string, number> = {level: 40, count: 10}; We may need more complex types, for example, to represent the state of our API requests. Imagine you have a global state where you put all the API data. Thus, you know where to show a loader, when to remove it, and to show relevant data. type StateItem = { isLoading: boolean; response: Record<string, unknown> | null; }; type State = Record<string, StateItem>; const state: State = { getInvoices: { isLoading: false, response: null, }, }; Do you see the inconveniences here? We might narrow a type for keys: it's a string, but we want to be sure we put valid API request names there. The second thing is the I put for the (an object with values), yet it's still better than , because you should determine its type before any processing. state unknown response unknown any type APIRequest = 'getInvoices' | 'getUsers' | 'getActions'; type BaseResponse = {isOk: boolean}; type GetInvoicesResponse = BaseResponse & {data: string[]}; type GetUsersResponse = BaseResponse & {data: Record<string, string>[]}; type GetActionsResponse = BaseResponse & {data: string[]}; type StateItem = { isLoading: boolean; response?: GetInvoicesResponse | GetUsersResponse | GetActionsResponse; }; type State = Record<APIRequest, StateItem>; // Type is missing the following properties from type 'State': getUsers, getActions const state: State = { getInvoices: { isLoading: false, response: {isOk: false, data: ['item']}, }, }; Let's disassemble some pieces of the above: type is a list of possible requests names. Narrowing types are for the better. See the error comment near the const? Typescript requires you to specify all the requests. APIRequest state represents a default and basic response, we always know that we receive . Thus, we may prevent code duplication and re-use the type. BaseResponse {isOk: true | false} We made a type for every request possible. While it's better than it was before, we could do even better. The problem with these types is that is too generic: we may have . If there are more requests, there is more ambiguity. Let's employ generics to reduce duplicate code. response GetInvoicesResponse | GetUsersResponse | GetActionsResponse type BaseResponse = {isOk: boolean;}; type GetInvoicesResponse = BaseResponse & {data: string[]}; type GetUsersResponse = BaseResponse & {data: Record<string, string>[]}; type GetActionsResponse = BaseResponse & {data: string[]}; type StateItem<Response> = { isLoading: boolean; response?: Response; }; type State = { getInvoices: StateItem<GetInvoicesResponse>; getUsers: StateItem<GetUsersResponse>; getActions: StateItem<GetActionsResponse>; }; It's more readable and safe to specify every request separately, thus there's no need to check on every response type possible. state.getInvoices.response Don't use type. Prefer . You should check the type before doing any further operations with it. any unknown type Obj = Record<string, unknown>; const o: Obj = {a: 's'}; o.a.toString(); // Object is of type 'unknown' 2. Prefer over , which can be , any kind of object, a function. refers to a generic type. 3. Narrow types where possible. If it's a few strings you use often, probably they can be combined in one type(see the example about API requests state). Record<string, T> object null T type GoogleEmail = `${string}@gmail.com`; // yet it's still a string const email1: GoogleEmail = 'my@gmail.com'; // Type '"my@example.com"' is not assignable to type '`${string}@gmail.com`' const email2: GoogleEmail = 'my@example.com'; Something new here: template types. Any email is a string, but if you can narrow a type, then why not(it's an example, sometimes it's an overhead). Other Use Cases You May Encounter Generics in Functions You saw generics. It's a powerful way to re-use the code. The other examples include functions: type Response<T> = { isOk: boolean; statusCode: number; data: T; } async function callAPI<T> (route: string, method: string, body: unknown): Response<T> { // it's a pseudo-fetch, the real API differs const response = await fetch(route, method, body); // some manipulations with data return response; } So, the syntax is . You may use (or other names for a generic, or, a few of them) inside a function too. function <name>:<type> (args) {} T Specifying Types for Readability Imagine you work a lot with variables that are strings, but it's hard to understand which is what type exactly. For example, when dealing with OAuth tokens. type AccessToken = string; type IdToken = string; Both tokens are JWT strings, but sometimes it's useful to understand the context. function callProviderEndpoint (token: AccessToken) {} function decodeUserInfo (token: IdToken) {} So, the syntax is . You may use (or other names for a generic, or, a few of them) inside a function too. function <name>:<type> (args) {} T Type Assertions There are cases when you need to cast(transform for the compiler) a type to another one. For example, when a library method returns an object and you know it's not useful, you need a more narrow type. You may write as as it allows you to transform a type into a desired one(if it's possible). The easiest cast is for any types: the compiler doesn't know anything about a value, so it trusts you. There are cases when you'd want to cast something into for compatibility, but often it's laziness to to do so. const result = libResult Record<string, number> any Invest time into writing good(corresponding to reality) types. You may also do casts like follows: const response = <MyCorrectType>libResponse; // the same as const result = libResponse as MyCorrectType; Some General Questions One May Ask Should I Learn TypeScript? Definitely. I presume you're already familiar with JavaScript, which is simple and fast to prototype. Typescript adds type safety and readability. Your app's logic becomes more predictable. Read more about reasons to use TypeScript. How to Learn TypeScript? Read the documentation about the basic types, or this article. Practice the examples by yourself and go code! Install the environment(many frameworks have their already prepared Typescript ecosystem for you to install and use instantly) and make things happen. It's okay if you don't understand some things or you're tired of the compiler errors. It'll get easier. Summary I didn't cover all the things you need to know in this article. What I’ve listed above should be enough to spark your interest and learn the basics that cover most cases you'll encounter. Learn as you go. First Published here