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:
- You declare what type of data a variable will hold (or TypeScript figures it out for you)
- TypeScript checks your code before it runs to make sure you're using the right types
- 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, andisGraduate. - 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
letvariable calledscoreand reassign it multiple times. - Create a
constvariable calledmaxScoreand 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
userIdthat can be either astringor anumber. - Assign a string value, log it, then assign a number value and log it.
- Try assigning a
booleanand 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
directionthat 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
Productwith properties:name(string),price(number), andinStock(boolean). - Create two
Productobjects using this type. - Try creating an invalid
Productmissing 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
DayOfWeekwith all seven days. - Create a variable
todayand 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
anyand assign different types to it. - Create a variable with type
unknownand try to use it without type checking (observe the error). - Fix the
unknownexample 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
constby default, only useletwhen you need to reassign - Let TypeScript infer types when they're obvious
- Use union types when a value can be multiple types
- Avoid
any– useunknowninstead when you're unsure - Create type aliases for complex or repeated types