Skip to main content

Variables & Types

Introduction​

Variables are containers that store values. Think of them as labeled boxes where you can keep different kinds of data – numbers, text, true/false values, and more.

The key difference between JavaScript and TypeScript is how they handle types . In JavaScript, you can put anything in any variable at any time. TypeScript, however, is statically typed , which means:

  1. You declare what type of data a variable will hold (or TypeScript figures it out for you)
  2. TypeScript checks your code before it runs to make sure you're using the right types
  3. This catches many common bugs during development, not in production

This might feel restrictive at first, but it's like having a helpful assistant who catches mistakes before they become problems!

Why Types Matter​

Let's see why types are so powerful:

JavaScript (without types):

let age = 25;
age = "twenty-five"; // JavaScript allows this
console.log(age + 5); // "twenty-five5" - probably not what you wanted!

TypeScript (with types):

let age: number = 25;
age = "twenty-five"; // ❌ Error: Type 'string' is not assignable to type 'number'
// TypeScript catches the mistake before you run the code!

Variable Declaration Keywords​

Before we dive into types, let's understand how to declare variables in TypeScript.

let - For Values That Change​

Use let when you need to reassign a variable:

let score: number = 0;
score = 10; // ✅ This works
score = 20; // ✅ This also works

Key properties of let:

  • Block-scoped : Only accessible within the {} block where it's defined
  • Can be reassigned : You can change the value later
  • Cannot be redeclared : You can't declare the same variable twice in the same scope
let count = 5;
let count = 10; // ❌ Error: Cannot redeclare block-scoped variable 'count'

if (true) {
let count = 10; // ✅ This is fine - different scope
console.log(count); // 10
}
console.log(count); // 5

const - For Values That Don't Change​

Use const when a value should never change:

const pi: number = 3.14159;
pi = 3.14; // ❌ Error: Cannot assign to 'pi' because it is a constant

Key properties of const:

  • Block-scoped : Same as let
  • Cannot be reassigned : The value is fixed after initialization
  • Must be initialized : You must give it a value when you declare it
const name: string; // ❌ Error: 'const' declarations must be initialized
const name: string = "Alice"; // ✅ Correct

Important Note about const with Objects and Arrays:

While you can't reassign a const variable, you can modify the contents of objects and arrays:

const person = { name: "Alice", age: 25 };
person.age = 26; // ✅ This works - we're modifying the object, not reassigning it
person = { name: "Bob", age: 30 }; // ❌ Error: Cannot assign to 'person'

const numbers = [1, 2, 3];
numbers.push(4); // ✅ This works - we're modifying the array
numbers = [5, 6, 7]; // ❌ Error: Cannot assign to 'numbers'

Avoiding var​

TypeScript (and modern JavaScript) has a third keyword: var. You should avoid using it. It has confusing scoping rules and can lead to bugs. Always use let or const instead.


Understanding Types​

Primitive Types​

TypeScript has seven primitive types. These are the basic building blocks for all data:

1. number - All Numbers​

TypeScript uses a single number type for all numeric values:

let age: number = 25; // Integer
let price: number = 19.99; // Decimal
let temperature: number = -5; // Negative
let hex: number = 0xFF; // Hexadecimal
let binary: number = 0b1010; // Binary
let octal: number = 0o744; // Octal

Unlike some languages (like Java or C#), TypeScript doesn't have separate types for integers and decimals.

2. string - Text​

Strings represent text and can be created with single quotes, double quotes, or backticks:

let firstName: string = "Alice";
let lastName: string = 'Smith';
let greeting: string = `Hello, ${firstName}!`; // Template literal

Template literals (backticks) allow you to embed expressions:

let name: string = "Alice";
let age: number = 25;
let message: string = `${name} is ${age} years old`;
console.log(message); // "Alice is 25 years old"

3. boolean - True or False​

Booleans have only two possible values:

let isStudent: boolean = true;
let isGraduated: boolean = false;
let hasPermission: boolean = age >= 18;

4. null - Intentionally Empty​

Use null to explicitly indicate "no value":

let selectedItem: string | null = null;
// Later, when an item is selected:
selectedItem = "Item 1";

5. undefined - Not Yet Assigned​

When a variable is declared but not initialized, it's undefined:

let result: number | undefined;
console.log(result); // undefined
result = 42;
console.log(result); // 42

null vs undefined:

  • null: You explicitly set it to "nothing"
  • undefined: It hasn't been set yet

6. symbol - Unique Identifiers​

Symbols create unique identifiers, often used as object keys:

let id1: symbol = Symbol("id");
let id2: symbol = Symbol("id");
console.log(id1 === id2); // false - each symbol is unique!

We won't use symbols much in this course, but they're useful for advanced scenarios.

7. bigint - Very Large Integers​

For integers larger than the number type can handle:

let big: bigint = 9007199254740991n; // Note the 'n' suffix
let huge: bigint = BigInt("99999999999999999999");

You'll rarely need bigint unless you're working with very large numbers (cryptography, scientific computing, etc.).


Type Annotations vs Type Inference​

One of TypeScript's best features is that you don't always need to write types explicitly!

Type Annotations (Explicit)​

You explicitly tell TypeScript what type a variable is:

let age: number = 25;
let name: string = "Alice";
let isStudent: boolean = true;

Type Inference (Automatic)​

TypeScript is smart enough to figure out types from the values you assign:

let age = 25; // TypeScript infers: number
let name = "Alice"; // TypeScript infers: string
let isStudent = true; // TypeScript infers: boolean

When to use which?

  • Use inference when the type is obvious from the value
  • Use annotations when:
    • Declaring a variable without immediately assigning it
    • You want to be extra clear about the type
    • The type is complex
// Inference is fine here
let count = 0;

// Annotation is better here
let userInput: string | number; // Could be either type
let data: { name: string; age: number }; // Complex type

Pro tip: In VS Code, hover over any variable to see its inferred type!


Advanced Type Concepts​

Union Types - Multiple Possible Types​

Sometimes a variable can hold more than one type of value:

let id: string | number;

id = "ABC123"; // ✅ String is allowed
console.log(id);

id = 456; // ✅ Number is also allowed
console.log(id);

id = true; // ❌ Error: boolean is not allowed

Union types are incredibly useful for flexibility while maintaining type safety.

Real-world example:

function printId(id: string | number) {
console.log(`Your ID is: ${id}`);
}

printId("ABC123"); // ✅
printId(456); // ✅

Literal Types - Exact Values​

You can specify that a variable must be a specific value:

let direction: "north" | "south" | "east" | "west";

direction = "north"; // ✅
direction = "south"; // ✅
direction = "up"; // ❌ Error: '"up"' is not assignable

This is great for ensuring only valid options are used:

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

function makeRequest(url: string, method: HttpMethod) {
// method can only be one of the four allowed values
}

makeRequest("/api/users", "GET"); // ✅
makeRequest("/api/users", "PATCH"); // ❌ Error

Type Aliases - Reusable Types​

When you have complex types, you can give them names:

// Instead of writing this every time:
let user1: { name: string; age: number; email: string };
let user2: { name: string; age: number; email: string };

// Define it once:
type User = {
name: string;
age: number;
email: string;
};

// Use it everywhere:
let user1: User = { name: "Alice", age: 25, email: "alice@email.com" };
let user2: User = { name: "Bob", age: 30, email: "bob@email.com" };

Complex Types​

Arrays​

Arrays hold multiple values of the same type:

// Two ways to declare array types:
let numbers: number[] = [1, 2, 3, 4, 5];
let names: Array<string> = ["Alice", "Bob", "Charlie"];

// TypeScript prevents type mismatches:
numbers.push(6); // ✅
numbers.push("six"); // ❌ Error: Argument of type 'string' is not assignable

Arrays with union types:

let mixedArray: (string | number)[] = ["Alice", 25, "Bob", 30];

Tuples - Fixed-Length Arrays with Specific Types​

Tuples are arrays where the type of each position is known:

let person: [string, number, boolean] = ["Alice", 25, true];

console.log(person[0]); // "Alice" - TypeScript knows this is a string
console.log(person[1]); // 25 - TypeScript knows this is a number

// Order matters!
person = [25, "Alice", true]; // ❌ Error: Types in wrong positions

Tuples are useful for returning multiple values:

function getUser(): [string, number] {
return ["Alice", 25];
}

let [userName, userAge] = getUser();

Objects​

Objects hold key-value pairs:

let student: { name: string; grade: string; age: number } = {
name: "Alice",
grade: "A",
age: 20
};

// TypeScript checks all properties:
let invalid = {
name: "Bob",
age: 22
// ❌ Error: Missing property 'grade'
};

Optional properties:

let user: { name: string; age: number; email?: string } = {
name: "Alice",
age: 25
// email is optional, so this is fine
};

user.email = "alice@email.com"; // Can add it later

Enums - Named Constants​

Enums let you define a set of named constants:

enum DayOfWeek {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}

let today: DayOfWeek = DayOfWeek.Monday;
console.log(today); // 0 (enums are numbered starting from 0)

String enums:

enum Direction {
North = "NORTH",
South = "SOUTH",
East = "EAST",
West = "WEST"
}

let heading: Direction = Direction.North;
console.log(heading); // "NORTH"

Special Types: any and unknown​

The any Type - Escape Hatch​

any disables type checking for a variable:

let anything: any = "hello";
anything = 42;
anything = true;
anything.toUpperCase(); // No error, but dangerous!

When to use any:

  • When migrating JavaScript code to TypeScript
  • When working with third-party libraries without types
  • As a last resort when types are too complex

Warning: Use any sparingly! It defeats the purpose of TypeScript.

The unknown Type - Safer Alternative​

unknown is like any, but safer. You must check the type before using it:

let something: unknown = "hello";

// Can't use it directly:
something.toUpperCase(); // ❌ Error: Object is of type 'unknown'

// Must check the type first:
if (typeof something === "string") {
console.log(something.toUpperCase()); // ✅ Now TypeScript knows it's a string
}

Prefer unknown over any when you're unsure about the type.


The typeof Operator​

TypeScript inherits the typeof operator from JavaScript:

let name: string = "Alice";
let age: number = 25;

console.log(typeof name); // "string"
console.log(typeof age); // "number"
console.log(typeof true); // "boolean"
console.log(typeof null); // "object" (JavaScript quirk!)

This is useful for runtime type checks, especially with unknown types.


Concepts​

Quick Reference​

// Declaring variables
let mutable: number = 10;
const immutable: string = "constant";

// Primitive types
let num: number = 42;
let str: string = "Hello";
let bool: boolean = true;
let n: null = null;
let u: undefined = undefined;

// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"];

// Tuples
let tuple: [string, number] = ["Alice", 25];

// Union types
let id: string | number = "ABC123";

// Literal types
let direction: "left" | "right" = "left";

// Type aliases
type Point = { x: number; y: number };

// Objects
let point: Point = { x: 10, y: 20 };

// Enums
enum Color { Red, Green, Blue }
let c: Color = Color.Green;

Examples​

// Type inference
let inferredNumber = 42; // number
let inferredString = "hello"; // string
let inferredBoolean = true; // boolean

// Explicit types
let explicitNumber: number = 42;
let explicitString: string = "hello";

// Union types in action
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(2));
}
}

// Type alias
type User = {
name: string;
age: number;
email?: string;
};

let user: User = {
name: "Alice",
age: 25
};

// Tuple
let rgb: [number, number, number] = [255, 0, 0];

// Enum
enum Status {
Pending = "PENDING",
Approved = "APPROVED",
Rejected = "REJECTED"
}

let currentStatus: Status = Status.Pending;

Challenges​

1. Variable Declaration with Types​

  • Declare typed variables for your firstName, lastName, age, and isGraduate.
  • Print each variable along with its type using typeof.
💡 Show Answer
let firstName: string = "Alice";
let lastName: string = "Smith";
let age: number = 25;
let isGraduate: boolean = true;

console.log(firstName, typeof firstName); // Alice string
console.log(lastName, typeof lastName); // Smith string
console.log(age, typeof age); // 25 number
console.log(isGraduate, typeof isGraduate); // true boolean


2. Let vs Const​

  • Create a let variable called score and reassign it multiple times.
  • Create a const variable called maxScore and try to reassign it.
  • What error do you get?
💡 Show Answer
let score: number = 0;
score = 50; // ✅ Works
score = 100; // ✅ Works
console.log(score); // 100

const maxScore: number = 100;
// maxScore = 200; // ❌ Error: Cannot assign to 'maxScore' because it is a constant

// However, with objects:
const player = { name: "Alice", points: 0 };
player.points = 50; // ✅ This works! We're modifying the object, not reassigning
console.log(player); // { name: 'Alice', points: 50 }

3. Type Inference Practice​

  • Declare three variables without type annotations: one with a number, one with a string, and one with a boolean.
  • Hover over them in VS Code (or use console.log(typeof variable)) to verify TypeScript inferred the correct types.
  • Try assigning a different type to one of them and observe the error.
💡 Show Answer
let salary = 50000; // TypeScript infers: number
let city = "London"; // TypeScript infers: string
let active = true; // TypeScript infers: boolean

console.log(typeof salary); // number
console.log(typeof city); // string
console.log(typeof active); // boolean

// Try to break it:
// salary = "fifty thousand"; // ❌ Error: Type 'string' is not assignable to type 'number'


4. Union Types in Action​

  • Create a variable userId that can be either a string or a number.
  • Assign a string value, log it, then assign a number value and log it.
  • Try assigning a boolean and observe the error.
💡 Show Answer
let userId: string | number;

userId = "ABC123";
console.log(`User ID: ${userId}, Type: ${typeof userId}`); // User ID: ABC123, Type: string

userId = 456;
console.log(`User ID: ${userId}, Type: ${typeof userId}`); // User ID: 456, Type: number

// userId = true; // ❌ Error: Type 'boolean' is not assignable to type 'string | number'

5. Literal Types for Direction​

  • Create a variable direction that can only be one of four values: "north", "south", "east", or "west".
  • Try assigning valid values and one invalid value.
💡 Show Answer
let direction: "north" | "south" | "east" | "west";

direction = "north"; // ✅
console.log(direction);

direction = "east"; // ✅
console.log(direction);

// direction = "up"; // ❌ Error: Type '"up"' is not assignable to type '"north" | "south" | "east" | "west"'


6. Array Types​

  • Create an array of numbers called scores.
  • Create an array of strings called names.
  • Try pushing a wrong type to each array and observe the errors.
💡 Show Answer
let scores: number[] = [85, 90, 78, 92];
let names: string[] = ["Alice", "Bob", "Charlie"];

scores.push(95); // ✅
console.log(scores);

names.push("David"); // ✅
console.log(names);

// scores.push("ninety"); // ❌ Error: Argument of type 'string' is not assignable
// names.push(100); // ❌ Error: Argument of type 'number' is not assignable

// Alternative syntax:
let grades: Array<string> = ["A", "B", "C"];
grades.push("D"); // ✅

7. Object Types and Type Aliases​

  • Create a type alias called Product with properties: name (string), price (number), and inStock (boolean).
  • Create two Product objects using this type.
  • Try creating an invalid Product missing a required property.
💡 Show Answer
type Product = {
name: string;
price: number;
inStock: boolean;
};

let laptop: Product = {
name: "Dell XPS 13",
price: 999.99,
inStock: true
};

let phone: Product = {
name: "iPhone 14",
price: 799.99,
inStock: false
};

console.log(laptop);
console.log(phone);

// Invalid product:
/*
let invalidProduct: Product = {
name: "Tablet",
price: 399.99
// ❌ Error: Property 'inStock' is missing
};
*/


8. Tuple Types for Coordinates​

  • Create a tuple type that represents a coordinate with [x, y] where both are numbers.
  • Create a few coordinate tuples.
  • Try creating a tuple with the wrong order or wrong types.
💡 Show Answer
let point1: [number, number] = [10, 20];
let point2: [number, number] = [-5, 15];
let origin: [number, number] = [0, 0];

console.log(`Point 1: (${point1[0]}, ${point1[1]})`);
console.log(`Point 2: (${point2[0]}, ${point2[1]})`);

// Wrong order:
// let bad1: [number, number] = ["10", 20]; // ❌ Error: Type 'string' not assignable

// You can also create named tuples:
type Coordinate = [x: number, y: number];
let point3: Coordinate = [30, 40];

9. Enums for Days of the Week​

  • Create an enum called DayOfWeek with all seven days.
  • Create a variable today and set it to a day from the enum.
  • Print the enum value (number) and the enum name (string).
💡 Show Answer
enum DayOfWeek {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}

let today: DayOfWeek = DayOfWeek.Wednesday;
console.log(today); // 2 (index in enum)
console.log(DayOfWeek[today]); // "Wednesday"

// String enum version:
enum DayName {
Monday = "MON",
Tuesday = "TUE",
Wednesday = "WED",
Thursday = "THU",
Friday = "FRI",
Saturday = "SAT",
Sunday = "SUN"
}

let shortDay: DayName = DayName.Friday;
console.log(shortDay); // "FRI"


10. Working with any and unknown​

  • Create a variable with type any and assign different types to it.
  • Create a variable with type unknown and try to use it without type checking (observe the error).
  • Fix the unknown example by adding proper type checking.
💡 Show Answer
// Using 'any' (not recommended but sometimes necessary)
let anything: any = "hello";
anything = 42;
anything = true;
anything.toUpperCase(); // No error, but risky!
console.log(anything);

// Using 'unknown' (safer approach)
let something: unknown = "hello world";

// This won't work:
// console.log(something.toUpperCase()); // ❌ Error: Object is of type 'unknown'

// Proper way with type guard:
if (typeof something === "string") {
console.log(something.toUpperCase()); // ✅ HELLO WORLD
}

// Another example:
something = 42;
if (typeof something === "number") {
console.log(something.toFixed(2)); // ✅ 42.00
}

Summary​

In this chapter, you learned:

✅ How to declare variables with let and const

✅ The seven primitive types in TypeScript

✅ The difference between type annotations and type inference

✅ How to use union types and literal types

✅ Working with arrays, tuples, and objects

✅ Creating reusable types with type aliases

✅ Using enums for named constants

✅ The difference between any and unknown

These are the building blocks of TypeScript! In the next chapter, we'll learn about Mathematical Operators and how TypeScript keeps mathematical operations type-safe.

Pro tips for success:

  • Use const by default, only use let when you need to reassign
  • Let TypeScript infer types when they're obvious
  • Use union types when a value can be multiple types
  • Avoid any – use unknown instead when you're unsure
  • Create type aliases for complex or repeated types