🎯 Why TypeScript in 2026?
TypeScript has become the de facto standard for large-scale JavaScript applications. In 2026, over 80% of new projects use TypeScript, and for good reason:
Benefits of TypeScript
- Catch errors early: Find bugs at compile-time, not runtime
- Better IDE support: Autocomplete, refactoring, navigation
- Self-documenting: Types serve as inline documentation
- Safer refactoring: Confidence when changing code
- Team collaboration: Clear contracts between modules
- Modern JavaScript: Use latest features with confidence
🚀 Getting Started
Installation
# Install TypeScript globally npm install -g typescript # Or in your project npm install --save-dev typescript # Initialize TypeScript config tsc --init # Compile TypeScript file tsc index.ts # Watch mode tsc --watch
tsconfig.json (Recommended Settings)
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"allowJs": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}📘 Basic Types
Primitive Types
// String
let name: string = "John"
let message: string = `Hello, ${name}`
// Number
let age: number = 30
let price: number = 99.99
let hex: number = 0xf00d
// Boolean
let isActive: boolean = true
let hasPermission: boolean = false
// Array
let numbers: number[] = [1, 2, 3]
let names: Array<string> = ["Alice", "Bob"]
// Tuple (fixed-length array with known types)
let person: [string, number] = ["John", 30]
// Enum
enum Color {
Red,
Green,
Blue
}
let color: Color = Color.Red
// Any (avoid when possible)
let anything: any = "hello"
anything = 42
// Unknown (safer than any)
let value: unknown = "hello"
if (typeof value === "string") {
console.log(value.toUpperCase())
}
// Void (no return value)
function log(message: string): void {
console.log(message)
}
// Null and Undefined
let n: null = null
let u: undefined = undefined
// Never (function never returns)
function throwError(message: string): never {
throw new Error(message)
}🏗️ Interfaces & Types
Interfaces
// Basic interface
interface User {
id: number
name: string
email: string
age?: number // Optional property
readonly createdAt: Date // Read-only
}
// Using interface
const user: User = {
id: 1,
name: "John",
email: "john@example.com",
createdAt: new Date()
}
// Interface with methods
interface Calculator {
add(a: number, b: number): number
subtract(a: number, b: number): number
}
// Extending interfaces
interface Admin extends User {
role: string
permissions: string[]
}
// Interface for functions
interface SearchFunc {
(query: string, limit: number): string[]
}
const search: SearchFunc = (query, limit) => {
return [] // Implementation
}Type Aliases
// Basic type alias
type ID = string | number
// Object type
type Product = {
id: ID
name: string
price: number
inStock: boolean
}
// Union types
type Status = "pending" | "approved" | "rejected"
type Result = Success | Error
// Intersection types
type Timestamped = {
createdAt: Date
updatedAt: Date
}
type User = {
name: string
email: string
}
type TimestampedUser = User & Timestamped
// Function type
type MathOperation = (a: number, b: number) => number
const add: MathOperation = (a, b) => a + bInterface vs Type: When to Use What?
Use Interface when:
- Defining object shapes
- You need declaration merging
- Working with classes
- Building public APIs
Use Type when:
- Creating union or intersection types
- Defining primitive aliases
- Using mapped or conditional types
- Creating tuple types
🎨 Advanced Types
Generics
// Generic function
function identity<T>(arg: T): T {
return arg
}
const num = identity<number>(42)
const str = identity<string>("hello")
// Generic interface
interface ApiResponse<T> {
data: T
status: number
message: string
}
type UserResponse = ApiResponse<User>
type ProductResponse = ApiResponse<Product[]>
// Generic class
class DataStore<T> {
private data: T[] = []
add(item: T): void {
this.data.push(item)
}
get(index: number): T | undefined {
return this.data[index]
}
getAll(): T[] {
return this.data
}
}
const userStore = new DataStore<User>()
const productStore = new DataStore<Product>()
// Generic constraints
interface HasId {
id: number
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id)
}Utility Types
interface User {
id: number
name: string
email: string
age: number
}
// Partial - all properties optional
type PartialUser = Partial<User>
// { id?: number; name?: string; email?: string; age?: number }
// Required - all properties required
type RequiredUser = Required<PartialUser>
// Readonly - all properties read-only
type ReadonlyUser = Readonly<User>
// Pick - select specific properties
type UserPreview = Pick<User, 'id' | 'name'>
// { id: number; name: string }
// Omit - exclude specific properties
type UserWithoutAge = Omit<User, 'age'>
// { id: number; name: string; email: string }
// Record - create object type with specific keys
type UserRoles = Record<string, User>
// { [key: string]: User }
// Exclude - exclude types from union
type Status = "active" | "inactive" | "pending"
type ActiveStatus = Exclude<Status, "inactive">
// "active" | "pending"
// Extract - extract types from union
type InactiveStatus = Extract<Status, "inactive" | "pending">
// "inactive" | "pending"
// NonNullable - remove null and undefined
type MaybeString = string | null | undefined
type DefiniteString = NonNullable<MaybeString>
// string
// ReturnType - get function return type
function getUser() {
return { id: 1, name: "John" }
}
type User = ReturnType<typeof getUser>
// Parameters - get function parameter types
function createUser(name: string, age: number) {}
type CreateUserParams = Parameters<typeof createUser>
// [string, number]Conditional Types
// Basic conditional type
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false
// Practical example
type ApiResponse<T> = T extends Error
? { success: false; error: T }
: { success: true; data: T }
// Infer keyword
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never
function getUser() {
return { id: 1, name: "John" }
}
type UserType = GetReturnType<typeof getUser>
// { id: number; name: string }🏛️ Classes & OOP
// Basic class
class User {
// Properties
private id: number
public name: string
protected email: string
readonly createdAt: Date
// Constructor
constructor(id: number, name: string, email: string) {
this.id = id
this.name = name
this.email = email
this.createdAt = new Date()
}
// Methods
public greet(): string {
return `Hello, I'm ${this.name}`
}
private validateEmail(): boolean {
return this.email.includes('@')
}
// Getter
get userId(): number {
return this.id
}
// Setter
set userName(name: string) {
if (name.length > 0) {
this.name = name
}
}
// Static method
static createGuest(): User {
return new User(0, "Guest", "guest@example.com")
}
}
// Inheritance
class Admin extends User {
private role: string
constructor(id: number, name: string, email: string, role: string) {
super(id, name, email)
this.role = role
}
public override greet(): string {
return `Hello, I'm Admin ${this.name}`
}
}
// Abstract class
abstract class Shape {
abstract area(): number
abstract perimeter(): number
describe(): string {
return `Area: ${this.area()}, Perimeter: ${this.perimeter()}`
}
}
class Circle extends Shape {
constructor(private radius: number) {
super()
}
area(): number {
return Math.PI * this.radius ** 2
}
perimeter(): number {
return 2 * Math.PI * this.radius
}
}
// Implementing interfaces
interface Printable {
print(): void
}
class Document implements Printable {
constructor(private content: string) {}
print(): void {
console.log(this.content)
}
}🎯 Real-World Patterns
API Response Handling
// Define API response types
interface ApiSuccess<T> {
success: true
data: T
message?: string
}
interface ApiError {
success: false
error: string
code: number
}
type ApiResponse<T> = ApiSuccess<T> | ApiError
// Type guard
function isApiSuccess<T>(response: ApiResponse<T>): response is ApiSuccess<T> {
return response.success === true
}
// Usage
async function fetchUser(id: number): Promise<ApiResponse<User>> {
try {
const response = await fetch(`/api/users/${id}`)
const data = await response.json()
return { success: true, data }
} catch (error) {
return {
success: false,
error: error.message,
code: 500
}
}
}
// Using the API
const response = await fetchUser(1)
if (isApiSuccess(response)) {
console.log(response.data.name) // TypeScript knows this is User
} else {
console.error(response.error) // TypeScript knows this is error
}Form Validation
// Form data type
interface LoginForm {
email: string
password: string
}
// Validation result
type ValidationResult<T> = {
[K in keyof T]?: string
}
// Validator function
function validateLoginForm(form: LoginForm): ValidationResult<LoginForm> {
const errors: ValidationResult<LoginForm> = {}
if (!form.email.includes('@')) {
errors.email = 'Invalid email address'
}
if (form.password.length < 8) {
errors.password = 'Password must be at least 8 characters'
}
return errors
}
// Usage
const form: LoginForm = {
email: 'test@example.com',
password: 'pass123'
}
const errors = validateLoginForm(form)
if (Object.keys(errors).length > 0) {
console.log('Validation failed:', errors)
}⚡ Best Practices
✅ Do:
- Enable
strictmode in tsconfig.json - Use
unknowninstead ofanywhen possible - Prefer interfaces for object shapes
- Use type guards for narrowing types
- Leverage utility types (Partial, Pick, Omit)
- Write descriptive type names
- Use const assertions for literal types
- Avoid type assertions unless necessary
❌ Don't:
- Use
anyeverywhere (defeats the purpose) - Ignore TypeScript errors with
@ts-ignore - Over-engineer types (keep them simple)
- Use type assertions to bypass errors
- Duplicate type definitions
- Mix interfaces and types inconsistently
- Forget to update types when code changes
🛠️ TypeScript with Popular Frameworks
React + TypeScript
import React, { useState } from 'react'
// Props interface
interface ButtonProps {
label: string
onClick: () => void
variant?: 'primary' | 'secondary'
disabled?: boolean
}
// Functional component
const Button: React.FC<ButtonProps> = ({
label,
onClick,
variant = 'primary',
disabled = false
}) => {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant}`}
>
{label}
</button>
)
}
// With state
interface User {
id: number
name: string
}
const UserList: React.FC = () => {
const [users, setUsers] = useState<User[]>([])
const [loading, setLoading] = useState<boolean>(false)
return <div>{/* ... */}</div>
}Node.js + Express
import express, { Request, Response, NextFunction } from 'express'
const app = express()
// Typed request/response
interface UserRequest extends Request {
body: {
name: string
email: string
}
}
app.post('/users', (req: UserRequest, res: Response) => {
const { name, email } = req.body
// TypeScript knows the types!
res.json({ success: true })
})
// Middleware with types
const authMiddleware = (
req: Request,
res: Response,
next: NextFunction
) => {
// Auth logic
next()
}🎯 Conclusion
TypeScript transforms JavaScript development by adding type safety without sacrificing flexibility. Start with basic types, gradually adopt interfaces and generics, and eventually master advanced patterns. The initial learning curve pays off with fewer bugs, better tooling, and more maintainable code.
Remember: TypeScript is a tool to help you, not restrict you. Use it to catch errors early, document your code, and build confidence in your applications. Happy typing!
🛠️ Format Your TypeScript Code
Keep your TypeScript code clean and consistent! Use our free Code Formatter to beautify TypeScript, JavaScript, HTML, CSS, and JSON instantly.
Try Code Formatter →