# TypeScript Deep Dive: Mastering Types and Functions

Welcome to Level 4 of our TypeScript adventure! In this lesson, we're going to explore the core of what makes TypeScript so powerful: its type system and how it interacts with functions. Buckle up, because we're about to level up our TypeScript skills!

## Prerequisites

Before we dive in, make sure you're comfortable with JavaScript basics. If you need to set up your development environment, check out the first blog for the tools you'll need to install.

## Types in TypeScript: The Building Blocks of Safety

TypeScript's type system is what sets it apart from JavaScript. Let's explore the various types you'll encounter:

### Primitive Types

These are the basic building blocks:

```typescript
const name: string = "Steve"; 
const health: number = 50;
const hardMode: boolean = false;
```

* string: For text
    
* number: For any numeric value
    
* boolean: For true/false values
    

### Arrays

Arrays in TypeScript can be typed too:

```typescript
const names: string[] = ["Steve", "Bob"]; 
const health: number[] = [50, 100];
const hardMode: boolean[] = [false, true];
```

Using the same example above for `primitive types` we now have the same types but have assigned them as an `Array` with a list of values.

* names is now an array of type `string`
    
* health is an array of type `number`
    
* hardMode is now an array of type `boolean`.
    

### The 'Any' Type

The any type is TypeScript's escape hatch. It allows any value, but use it sparingly:

```typescript
let name: any;
name = { first: "Steve", second: "The-Pirate" };
name = "Steve";
name = ["Steve"];
name = null;
```

Using `any` allows us to change the `value` and `type` of the variable `name` without TypeScript producing any `type-checking` errors. Ofcourse, this is not best practice to re-assign the value of `name` with different types but I just wanted to show you that we could when using the `any` type.

\*Note: I would avoid using `any` where possible. Please create your own custom types if a standard type is not sufficient.

### Implicit Any and How to Avoid It

TypeScript will infer `any` when it can't determine a type:

```typescript
function throwDice(sides) {
  // 'sides' is implicitly 'any' as we haven't declared a type
}
```

If we have this in our TypeScript file, the variable `sides` will probably have a underline which if you hover over it in an IDE, it will give the following message:

`(parameter) sides: any`

We are not explicitly setting a type for `sides`. It should probably be a `number`, but TypeScript doesn’t have enough information to infer that. So its best guess is to use the type `any`.

`Implicit any` types are a common mistake when coming from JavaScript, so these should be used in very rare occasions.

Pro tip: Use `"noImplicitAny": true` in your `tsconfig.json` to catch these by default!

### The 'Unknown' Type: A Safer Alternative to 'Any'

`unknown` is like `any`, but safer:

```typescript
let name: unknown;
name.first = "Steve";
}
```

This will give us an error `Object is of type 'unknown'`.

This is happening because `unknown` is considered to be any possible type. It's different from `any` because you can't access any properties on it without narrowing it down first.

So to fix the example above we would have to narrow it down using the `typeof` command.

```javascript
let name: unknown;
if (typeof name === "object" && name !== null && "first" in name) {
  name.first = "Steve";
}
```

If you didn't want to use the type `unknown` you would either have to create an `interface` or you can use an `Anonymous` type.

We will go into further detail on `interfaces` further on in this course.

### Anonymous Types: Quick and Dirty Object Types

TypeScript can infer object types on the fly:

```typescript
let name = { first: "Steve" };
console.log(name.first);
```

TypeScript infers the type of `name` based on the structure of the object literal. In this case, it infers the type `{ first: string }`, which is an `object` type with a property `first` of type `string`.

We can access the `first` property of `name` using `name.first` without any type annotations or explicit type definitions.

By leveraging `type inference`, TypeScript can automatically determine the type of the object based on its structure. This allows you to write more concise code without explicitly specifying the type.

### Undefined and Optional Properties

Make properties optional with a union type:

```typescript
let name = { first: "Steve" as string | undefined };

name = {};
console.log(name.first);
```

In this case, we use a `type assertion` as `string | undefined` to explicitly specify that the `first` property can be either a `string` or `undefined`. This allows `name` to be assigned an object with or without the `first` property.

If you ran the code above, the first `console.log` would return `Steve` and the second `console.log` would return `undefined` and they would both be valid.

I do not want to go into too much detail of `undefined` types and I would recommend **explicitly** defining types in your code.Functions in TypeScript: Leveling Up Your Code

TypeScript adds some powerful features to JavaScript functions. Let's explore:

### Optional Parameters

Make parameters optional with `?`:

```typescript
function createCharacter(name?: string, health?: number) {
  return {
    name: name ?? 'Steve',
    health: health ?? 100
  }
}
const player1 = createCharacter();
const player2 = createCharacter("Bob", 50);
```

In the example above, for variable `player1` we call `createCharacter()` without any options and thanks to the `optional parameters` we set as part of the function, this will default to the name `Steve` and health `100`.

For variable `player2` we pass in values for the `optional parameters` which will then assign these values to the `player2` variable. Which is currently set to `Bob` and `50`.

`Optional Parameters` is not something that is only available in TypeScript. This can also be done in JavaScript, however with TypeScript we do get the benefit of `Type-Checking`. So if we decided to pass in a parameter that was of a different type, we would get an error from TypeScript before runtime.

### Default Parameter Values

TypeScript can infer types from default values:

```typescript
function createCharacter(name = 'Steve', health = 100) {
  return { name, health }
}

const player1 = createCharacter();
const player2 = createCharacter("Bob", 50);
```

In this example, we have put the default values as part of the functions parameters. So when we call the `createCharacter()` function for `player1`, this will automatically assign the values `Steve` and `100`.

For `player2`, we still pass in values for both `name` and `health` and thanks to the way default parameter values work, we would re-assign those values to what we have passed into the function.

### Function Overloading: The Advanced Technique

```typescript
Function overloading allows multiple function signatures:
function createCharacter(): Character;
function createCharacter(health: number): Character;
function createCharacter(health: number, name: string): Character; 
function createCharacter(health?: number, name?: string): Character {
  return { 
    health: health ?? 100,
    name: name ?? 'Steve' 
  };
}
```

The function declarations are `function overload signatures`. They define different ways the `createCharacter` function can be called with additional parameters, which is sometimes known as `overloading`.

However, we have one function that has an `implementation` of the `createCharacter` function. It has optional parameters `health` and `name` with default values of `100` and `Steve` respectively.

The function creates a character object with the provided or default values and returns it.

The return type of this function implementation is `Character`, which is compatible with all the overload signatures.

The `player1` variable will be of type `Character` with default values for health `100` and name `Steve`.

The `player2` variable will be of type `Character` with the provided health value `10` passed into the function and the default value for name `Steve`.

The `player3` variable will be of type `Character` with the provided health `50` and name `Bob` passed into the function.

**Function overloading** allows you to define multiple ways to call a function with different parameters added.

The TypeScript compiler uses the overload signatures to determine the appropriate types based on the arguments passed when the function is called.

The actual implementation of the function should be compatible with all the overload signatures.

Overloading provides flexibility in how a function can be called while maintaining type safety.

It allows you to define different parameter combinations and their corresponding return types, making the function more versatile and expressive.Type Annotations: Explicitly Defining Your Types

### Type Annotations

TypeScript allows you to explicitly state types for:

### Variables

```typescript
const health: number; 
const name: string; 
const hardMode: boolean; 
```

To specify the type of a variable, we must add the `type` after the variable declaration with a `:`, like we have already done many times in this lesson:

### Function Parameters

```typescript
function craftWeapon(materials: string[], skillLevel: number) {  
  // Function body
}
```

We can then call the function with the correct types as follows:

```typescript
craftWeapon(["Iron", "Mithril"], 75);
```

If we did not pass the correct type into the function, TypeScript would help us realize we are using an argument with the wrong type with an error message.

`Argument of type ‘string’ is not assignable to parameter of type 'number'`

This would be the error if we passed a `string` into the `skillLevel` parameter instead of a `number`.

### Return Values

```typescript
function getHealth(): number {  
  return 50;
}
let health = getHealth();
console.log(typeof(health));
console.log(health);
```

Here, `: number` indicates that the `getHealth` function will return a value of type `number`. It's a `type annotation` that explicitly defines the expected return type of the function.

Using the `typeof` operator, this will tell us the type of the `getHealth()` function. This should return the type of `number`.

If we then console log `health` we should get the result `50`.

## Wrapping Up

Congratulations! You've just completed a deep dive into TypeScript's type system and function features. You've learned about:

* Various types in TypeScript
    
* How to use these types with variables and functions
    
* Advanced features like function overloading
    

Remember, TypeScript's power comes from its ability to catch errors at compile-time. The more specific you are with your types, the more TypeScript can help you write bug-free code.

In the next level, we'll explore even more advanced TypeScript features. Keep coding, keep learning, and get ready for the next challenge!

Happy Coding!
