Basic Types#
TypeScript's type system starts with the same types you know from JavaScript, with additional types to make your code more expressive and safe.
Primitive Types#
String, Number, Boolean#
let username: string = "kushal101";
let age: number = 25;
let isActive: boolean = true;
// Type inference - TypeScript automatically detects the type
let email = "user@example.com"; // TypeScript knows this is a string
null and undefined#
let nullable: null = null;
let undefinedValue: undefined = undefined;
// Using with other types
let maybeString: string | null = null;
maybeString = "now it's a string";
any and unknown#
// ❌ Avoid 'any' - disables type checking
let anything: any = "string";
anything = 42;
anything.nonExistentMethod(); // No error - dangerous!
// ✅ Use 'unknown' - type-safe alternative
let something: unknown = "string";
something = 42;
// Must check type before using
if (typeof something === "string") {
console.log(something.toUpperCase()); // OK
}
void#
Used for functions that don't return a value:
function logMessage(message: string): void {
console.log(message);
// No return statement
}
never#
Represents values that never occur:
// Function that always throws
function throwError(message: string): never {
throw new Error(message);
}
// Function with infinite loop
function infiniteLoop(): never {
while (true) {
// ...
}
}
Arrays#
// Array of numbers
let numbers: number[] = [1, 2, 3, 4, 5];
// Alternative syntax
let strings: Array<string> = ["a", "b", "c"];
// Mixed types (not recommended)
let mixed: (string | number)[] = [1, "two", 3, "four"];
// Array methods are type-safe
numbers.push(6); // ✅ OK
// numbers.push("7"); // ❌ Error: Argument of type 'string' is not assignable to parameter of type 'number'
Tuples#
Fixed-length arrays with known types for each position:
// [x, y] coordinates
let point: [number, number] = [10, 20];
// User info: [id, name, isAdmin]
let user: [number, string, boolean] = [1, "Alice", true];
// Accessing tuple elements
console.log(user[0]); // 1 (type: number)
console.log(user[1]); // "Alice" (type: string)
// Destructuring tuples
let [id, name, isAdmin] = user;
// Optional tuple elements
let optionalTuple: [string, number?] = ["hello"];
optionalTuple = ["hello", 42];
Enums#
Define a set of named constants:
// Numeric enum (default)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
let move: Direction = Direction.Up;
console.log(move); // 0
// String enum
enum Status {
Pending = "PENDING",
Approved = "APPROVED",
Rejected = "REJECTED"
}
let orderStatus: Status = Status.Pending;
console.log(orderStatus); // "PENDING"
// Enum with custom values
enum HttpStatus {
OK = 200,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalServerError = 500
}
Object Types#
// Inline object type
let person: { name: string; age: number } = {
name: "John",
age: 30
};
// Optional properties
let user: {
id: number;
name: string;
email?: string; // Optional
} = {
id: 1,
name: "Alice"
// email is optional
};
// Readonly properties
let config: {
readonly apiKey: string;
timeout: number;
} = {
apiKey: "abc123",
timeout: 5000
};
// config.apiKey = "new-key"; // ❌ Error: Cannot assign to 'apiKey' because it is a read-only property
config.timeout = 10000; // ✅ OK
Union Types#
A value can be one of several types:
// String or number
let id: string | number;
id = "abc123"; // ✅ OK
id = 123; // ✅ OK
// id = true; // ❌ Error
// Function with union type parameter
function printId(id: string | number): void {
console.log(`ID: ${id}`);
}
printId(101); // ✅ OK
printId("abc123"); // ✅ OK
// Working with union types - type narrowing
function processValue(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase(); // TypeScript knows it's a string here
} else {
return value.toFixed(2); // TypeScript knows it's a number here
}
}
Intersection Types#
Combine multiple types:
type Person = {
name: string;
age: number;
};
type Employee = {
employeeId: number;
department: string;
};
// Combine both types
type Staff = Person & Employee;
let staff: Staff = {
name: "Bob",
age: 35,
employeeId: 1001,
department: "Engineering"
};
Type Aliases#
Create custom type names:
// Simple alias
type ID = string | number;
type UserID = string;
// Object type alias
type Point = {
x: number;
y: number;
};
// Function type alias
type GreetFunction = (name: string) => string;
const greet: GreetFunction = (name) => `Hello, ${name}!`;
// Complex type alias
type ApiResponse<T> = {
data: T;
error: string | null;
status: number;
};
Literal Types#
Exact values as types:
// String literals
let direction: "left" | "right" | "up" | "down";
direction = "left"; // ✅ OK
// direction = "forward"; // ❌ Error
// Number literals
let roll: 1 | 2 | 3 | 4 | 5 | 6;
roll = 4; // ✅ OK
// roll = 7; // ❌ Error
// Boolean literals (less common)
let success: true = true;
// success = false; // ❌ Error
// Common pattern: combining literals with types
type ButtonSize = "small" | "medium" | "large";
type ButtonVariant = "primary" | "secondary" | "danger";
interface ButtonProps {
size: ButtonSize;
variant: ButtonVariant;
label: string;
}
Type Assertions#
Tell TypeScript you know better about a type:
// Angle-bracket syntax
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;
// As syntax (preferred, especially in React)
let someValue2: unknown = "this is a string";
let strLength2: number = (someValue2 as string).length;
// Real-world example
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
// Non-null assertion operator
function processText(text: string | null | undefined) {
// You KNOW text is not null/undefined here
console.log(text!.toUpperCase());
}
Practical Examples#
Form Data#
type FormData = {
username: string;
password: string;
email?: string;
age: number;
termsAccepted: boolean;
};
function submitForm(data: FormData): void {
console.log("Submitting:", data);
}
submitForm({
username: "johndoe",
password: "secret123",
age: 25,
termsAccepted: true
});
API Response#
type User = {
id: number;
name: string;
email: string;
};
type ApiResponse = {
success: boolean;
data: User | null;
error: string | null;
};
async function fetchUser(id: number): Promise<ApiResponse> {
try {
const response = await fetch(`/api/users/${id}`);
const user: User = await response.json();
return { success: true, data: user, error: null };
} catch (err) {
return { success: false, data: null, error: String(err) };
}
}
Configuration Object#
type LogLevel = "debug" | "info" | "warn" | "error";
type AppConfig = {
readonly apiUrl: string;
readonly apiKey: string;
port: number;
logLevel: LogLevel;
features: {
enableAuth: boolean;
enableCache: boolean;
};
};
const config: AppConfig = {
apiUrl: "https://api.example.com",
apiKey: "secret-key",
port: 3000,
logLevel: "info",
features: {
enableAuth: true,
enableCache: false
}
};
Best Practices#
-
✅ Use type inference when possible
let name = "John"; // TypeScript infers string -
✅ Prefer
unknownoveranylet data: unknown; // Type-safe -
✅ Use literal types for fixed sets of values
type Status = "pending" | "approved" | "rejected"; -
✅ Use
readonlyfor immutable datatype Config = { readonly apiKey: string }; -
❌ Avoid excessive type assertions
// Only use when you're certain const element = document.getElementById("id") as HTMLInputElement;
Next Steps#
- Learn about Functions and how to type them properly
- Explore Interfaces & Types for complex data structures