๐Ÿ”ค Basic Types & Type System

TypeScript Fundamentals โ€” Basic Types & Type System
TypeScript Fundamentals
Course 1 ยท Chapter 2 ยท Basic Types & Type System

๐Ÿ”ค Basic Types & Type System

TypeScript's power comes from its type system. In this chapter, we'll explore the primitive types, learn how type annotations work, and understand how TypeScript infers types when you don't explicitly declare them.

The Primitive Types

TypeScript includes seven primitive types from JavaScript, plus two special types:

string

Text values. Can use single quotes, double quotes, or backticks.

const name: string = "Alice"; const greeting: string = 'Hello'; const message: string = `Hi, ${name}`;

number

All numeric values: integers, floats, Infinity, NaN.

const count: number = 42; const price: number = 19.99; const infinity: number = Infinity;

boolean

Only two values: true or false.

const isActive: boolean = true; const hasError: boolean = false;

null & undefined

Special values representing absence. Usually used with unions.

const x: null = null; const y: undefined = undefined;

bigint

Numbers larger than Number.MAX_SAFE_INTEGER (rare).

const huge: bigint = 9007199254740992n;

symbol

Unique identifiers (advanced; rarely used in beginner code).

const id: symbol = Symbol("id");

๐Ÿ“ Type Annotations

A type annotation explicitly tells TypeScript what type a variable should be. Use a colon followed by the type name.
const age: number = 25; const name: string = "Bob"; const isStudent: boolean = true; // โŒ TypeScript error! number is not assignable to string const city: string = 42;

Annotations on Function Parameters & Returns

You can annotate parameters and specify what a function returns:

function multiply(x: number, y: number): number { return x * y; } // โœ… Works multiply(3, 4); // Returns 12 // โŒ Error! Argument of type 'string' is not assignable to parameter of type 'number' multiply("3", 4);

๐Ÿง  Type Inference

You don't always need to write type annotations. TypeScript can often infer the type based on the value you assign.
โœ… No annotation needed:
const count = 42; // TypeScript knows count is a number const name = "Alice"; // TypeScript knows name is a string const active = true; // TypeScript knows active is a boolean

However, inference has limits. Sometimes you need explicit annotations:

โŒ Inference isn't smart enough here:
const data = null; // TypeScript infers type 'null', might not be what you want function process(value) { // No annotation, value's type is unclear return value.toUpperCase(); // Error: TypeScript doesn't know what value is }

Best Practice: When to Annotate

Situation Recommendation
Clear from assignment (e.g., const x = 5) Skip annotation, let inference work
Function parameters Always annotate
Function return values Highly recommended (documents intent)
Complex/unclear types Always annotate for clarity
Variable initialized to null or undefined Always annotate (inference too vague)

๐ŸŽญ Special Types: any & unknown

The any Type

any is an "escape hatch" that disables type checking. Use it sparingly!
let data: any = "hello"; data = 42; // โœ… Fine data = true; // โœ… Fine data.toUpperCase(); // โœ… Fine (no error, even though data might be a number!)

Why avoid `any`? It defeats the entire purpose of TypeScript. You lose type safety and IDE support.

The unknown Type

unknown is the safer, stricter cousin of any. It's type-safe because it forces you to check what the value actually is before using it.

let data: unknown = "hello"; data = 42; // โœ… Fine // โŒ Error! Object is of type 'unknown' data.toUpperCase(); // โœ… But this is fine โ€” we checked first! if (typeof data === "string") { data.toUpperCase(); // Now TypeScript knows data is a string }

any vs unknown

Use unknown instead of any. Always. unknown requires you to check the type before using the value, which keeps your code safe.

๐Ÿ” Type Narrowing (Preview)

As we saw with `unknown`, TypeScript can narrow types based on your code. This happens with:

  • typeof checks: typeof x === "string"
  • Instanceof checks: x instanceof Date
  • Truthiness checks: if (x) { ... }
  • Equality checks: if (x === null) { ... }

We'll dive deep into type narrowing in a later chapter, but understanding it now will help you write safer TypeScript.

๐Ÿ’ป Coding Challenges

Challenge 1: Type Annotation Errors

Fix the type errors in this code by adding correct type annotations:

const age = "25"; const score = 98.5; function greet(person) { return "Hello, " + person; }

Goal: Add type annotations so each variable/parameter has the correct type.

โ†’ Solution

Challenge 2: Inference vs Annotation

Which of these variables need explicit type annotations, and which can rely on inference?

const x = 42; const y = null; function process(value) { ... } const isReady = true;

Goal: Identify which ones need annotations and explain why.

โ†’ Solution

Challenge 3: any vs unknown

Write a function that safely handles unknown input. Check if it's a number, and if so, double it. Otherwise, return 0.

function doubleIfNumber(value: unknown): number { // Your code here }

Goal: Demonstrate type narrowing with `unknown`.

โ†’ Solution

๐Ÿ’ก Real-World Tip: Be Explicit with Functions

While you can let TypeScript infer types for variables, always be explicit with function parameters and return types. This makes your code self-documenting and easier for others (and future you!) to understand. A 10-second type annotation saves 10 minutes of confusion later.

๐ŸŽฏ What's Next

Now that you understand primitives and type annotations, we'll explore how to work with collections: objects, arrays, and tuples. You'll learn how to describe the shape of complex data structures.