TypeScript Data Types
Introduction
TypeScript, an extension of JavaScript, introduces a robust typing system that helps catch errors during development rather than at runtime. Understanding TypeScript's data types is fundamental to leveraging its full potential as they form the foundation for type checking - one of TypeScript's core benefits.
In this guide, we'll explore various data types in TypeScript, from basic primitives to more complex types. We'll see how these types can make your code more reliable, self-documenting, and maintainable.
Why Types Matter
Before diving into TypeScript's type system, let's understand why types are important:
- Error Detection: Types help catch errors during development
- Code Documentation: Types serve as built-in documentation
- IDE Support: Better autocompletion and intellisense
- Refactoring: Makes code changes safer and easier
Basic Types
Boolean
The most basic data type is the boolean, which can have either true
or false
values.
let isCompleted: boolean = false;
isCompleted = true; // Valid
// isCompleted = "yes"; // Error: Type 'string' is not assignable to type 'boolean'
Number
All numbers in TypeScript are floating-point values. These can be integers, decimals, hexadecimals, binary, and octal literals.
let decimal: number = 10;
let hex: number = 0xf00d; // hexadecimal
let binary: number = 0b1010; // binary
let octal: number = 0o744; // octal
String
Text data is represented using the string type. You can use single quotes ('
), double quotes ("
) or template strings (``) to define strings.
let firstName: string = "John";
let lastName: string = 'Doe';
let greeting: string = `Hello, my name is ${firstName} ${lastName}`;
console.log(greeting); // Output: Hello, my name is John Doe
Array
TypeScript offers two ways to define arrays:
// Using square brackets
let numbers: number[] = [1, 2, 3, 4, 5];
// Using generic Array type
let fruits: Array<string> = ["apple", "banana", "cherry"];
// Mixed array (using union type, which we'll cover later)
let mixed: (number | string)[] = [1, "two", 3, "four"];
Tuple
Tuples let you express an array with a fixed number of elements whose types are known.
// Define a tuple type
let person: [string, number] = ["Alice", 30];
console.log(person[0]); // Output: Alice
console.log(person[1]); // Output: 30
// This would be an error:
// person[2] = "developer"; // Error: Tuple type '[string, number]' has no element at index '2'
Enum
Enums allow us to define a set of named constants.
enum Direction {
Up,
Down,
Left,
Right
}
let playerDirection: Direction = Direction.Up;
console.log(playerDirection); // Output: 0
// You can also set custom values
enum HttpStatus {
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401
}
console.log(HttpStatus.OK); // Output: 200
Any
The any
type allows you to opt-out of type checking. It's useful when you're working with dynamic content or migrating from JavaScript to TypeScript.
let dynamicValue: any = 4;
dynamicValue = "a string"; // No error
dynamicValue = true; // No error
// Be careful with any as it loses type safety!
Void
void
represents the absence of a type, commonly used as the return type of functions that don't return a value.
function logMessage(message: string): void {
console.log(message);
// No return statement
}
let unusable: void = undefined; // You can only assign undefined or null (if strictNullChecks is not enabled)
Null and Undefined
In TypeScript, both null
and undefined
have their own types.
let u: undefined = undefined;
let n: null = null;
// By default, null and undefined are subtypes of all other types
// However, when using --strictNullChecks flag, they can only be assigned to themselves or any
Never
The never
type represents values that never occur. It's typically used for functions that always throw exceptions or never return.
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
Object
The object
type represents non-primitive types.
let user: object = {
name: "Alice",
age: 30
};
// More specific object type (recommended)
let typedUser: { name: string; age: number } = {
name: "Bob",
age: 25
};
console.log(typedUser.name); // Output: Bob
Special Types in TypeScript
Union Types
Union types allow you to specify that a value can be one of several types.
let id: string | number;
id = 101; // Valid
id = "A-101"; // Also valid
// id = true; // Error: Type 'boolean' is not assignable to type 'string | number'
Intersection Types
Intersection types combine multiple types into one.
type Person = {
name: string;
};
type Employee = {
employeeId: number;
department: string;
};
type EmployeeDetails = Person & Employee;
const employee: EmployeeDetails = {
name: "John Doe",
employeeId: 12345,
department: "Engineering"
};
Type Aliases
Type aliases create new names for types. They're similar to interfaces but can name primitives, unions, tuples, and other types.
type Age = number;
type Point = [number, number];
type UserID = string | number;
let myAge: Age = 30;
let coordinate: Point = [10, 20];
let userId: UserID = "user-123";
Literal Types
Literal types allow you to specify exact values a variable can have.
type Direction = "North" | "East" | "South" | "West";
let currentDirection: Direction = "North";
// Only these values are allowed:
currentDirection = "East"; // Valid
// currentDirection = "Northeast"; // Error: Type '"Northeast"' is not assignable to type 'Direction'
// Number literals work too
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 6;
Type Assertions
Type assertions are a way to tell the compiler "trust me, I know what I'm doing." It's like type casting in other languages but has no runtime impact.
// Using angle-bracket syntax (not valid in JSX)
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// Using the 'as' syntax (works in JSX)
let otherValue: any = "hello world";
let otherLength: number = (otherValue as string).length;
Type Inference
TypeScript can infer types when you don't explicitly specify them.
// TypeScript infers 'message' to be of type 'string'
let message = "Hello, TypeScript!";
// message = 42; // Error: Type 'number' is not assignable to type 'string'
Practical Examples
Form Validation
// Define the structure of a form
interface RegistrationForm {
username: string;
email: string;
password: string;
age: number;
newsletter?: boolean; // Optional field
}
function validateForm(form: RegistrationForm): boolean {
// Email validation with a simple regex
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(form.email)) {
console.error("Invalid email format");
return false;
}
// Password validation
if (form.password.length < 8) {
console.error("Password must be at least 8 characters");
return false;
}
// Age validation
if (form.age < 18 || form.age > 120) {
console.error("Age must be between 18 and 120");
return false;
}
return true;
}
// Example usage
const newUser: RegistrationForm = {
username: "dev_user",
email: "[email protected]",
password: "secure123",
age: 28,
newsletter: true
};
if (validateForm(newUser)) {
console.log("Form is valid, processing submission...");
// Process the form...
}
API Response Handling
// Define the API response structure
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
// Mock API function
function fetchUserData(userId: number): Promise<User> {
return new Promise((resolve, reject) => {
// Simulate API call
setTimeout(() => {
if (userId > 0) {
const user: User = {
id: userId,
name: "Jane Smith",
email: "[email protected]",
isActive: true
};
resolve(user);
} else {
reject(new Error("Invalid user ID"));
}
}, 500);
});
}
// Using the API
async function displayUserInfo(userId: number): Promise<void> {
try {
const user = await fetchUserData(userId);
// With type information, we get autocompletion for user properties
console.log(`User: ${user.name} (${user.email})`);
if (user.isActive) {
console.log("This user is currently active");
}
} catch (error) {
console.error("Failed to fetch user:", error);
}
}
displayUserInfo(123);
Type Safety with Functions
// Function with parameter and return type annotations
function calculateTax(income: number, taxRate: number): number {
return income * taxRate;
}
// Function with optional parameters
function createGreeting(name: string, title?: string): string {
if (title) {
return `Hello, ${title} ${name}!`;
}
return `Hello, ${name}!`;
}
console.log(createGreeting("Alice", "Ms.")); // Output: Hello, Ms. Alice!
console.log(createGreeting("Bob")); // Output: Hello, Bob!
// Function with default parameters
function buildUrl(path: string, domain: string = "example.com"): string {
return `https://${domain}/${path}`;
}
console.log(buildUrl("products")); // Output: https://example.com/products
console.log(buildUrl("blog", "mysite.com")); // Output: https://mysite.com/blog
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // Output: 15
Type Guard Functions
Type guards help TypeScript narrow down the type of a variable within a conditional block.
function isString(value: any): value is string {
return typeof value === 'string';
}
function processValue(value: string | number) {
if (isString(value)) {
// TypeScript knows value is a string in this block
console.log(value.toUpperCase());
} else {
// TypeScript knows value is a number in this block
console.log(value.toFixed(2));
}
}
processValue("hello"); // Output: HELLO
processValue(42); // Output: 42.00
Summary
TypeScript's type system offers a rich set of tools to make your code more robust and maintainable:
-
Basic types (boolean, number, string, array, tuple, enum, any, void, null, undefined, never, object) form the foundation.
-
Advanced types (unions, intersections, type aliases, literals) provide flexibility and precision.
-
Type assertions let you override the inferred type when needed.
-
Type inference reduces the need for explicit type annotations.
Understanding these concepts will help you write more reliable code with fewer bugs, better documentation, and improved developer experience.
Exercises
To reinforce your understanding, try these exercises:
-
Create a function that accepts an array of mixed types (numbers and strings) and returns the sum of all numbers and the concatenation of all strings.
-
Define a type for a task management application that includes task types (bug, feature, documentation) with different properties for each type.
-
Implement a simple shopping cart with TypeScript types for products, cart items, and checkout functionality.
Additional Resources
- TypeScript Official Handbook
- TypeScript Playground - Experiment with TypeScript code online
- TypeScript Deep Dive - A comprehensive free book
Remember, mastering TypeScript types takes practice. Start simple and gradually incorporate more advanced typing features as you become comfortable with the basics.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)