TypeScript Style Guide

Writing clean and consistent TypeScript code

📐 What is a Style Guide?

A TypeScript style guide provides conventions for writing clean, readable, and maintainable code. Following consistent patterns helps teams collaborate effectively and reduces bugs in your applications.


// Good: Clear naming and structure
interface UserProfile {
  firstName: string
  lastName: string
  email: string
}
                                    

Key Principles

📝

Naming

Use clear, descriptive names

// Good
const userName = 'John'
// Bad
const un = 'John'
🎨

Formatting

Consistent code formatting

// Use 2 or 4 spaces
function greet() {
  return 'Hello'
}
🏗️

Structure

Organize code logically

// Imports first
import { User } from './types'
// Then code
💬

Comments

Document complex logic

// Calculate tax
const tax = 
  price * 0.15

🔹 Naming Conventions

Follow these naming patterns for consistency:

// Variables and functions: camelCase
let userName = 'John'
function calculateTotal() { }

// Classes and Interfaces: PascalCase
class UserAccount { }
interface UserProfile { }

// Constants: UPPER_SNAKE_CASE
const MAX_RETRY_COUNT = 3
const API_BASE_URL = 'https://api.example.com'

// Private properties: prefix with underscore
class User {
  private _password: string
  public username: string
}

// Type aliases: PascalCase
type UserId = string | number

// Enums: PascalCase for name and members
enum UserRole {
  Admin = 'ADMIN',
  User = 'USER',
  Guest = 'GUEST'
}

✓ Good Naming Examples:

getUserById()
isAuthenticated
totalAmount
UserProfile

🔹 Type Annotations

When and how to use type annotations:

// ✓ Good: Explicit types for function parameters
function greet(name: string, age: number): string {
  return `Hello ${name}, you are ${age} years old`
}

// ✓ Good: Type inference for simple assignments
const count = 5  // TypeScript infers number
const message = 'Hello'  // TypeScript infers string

// ✓ Good: Explicit types for complex objects
const user: { name: string; age: number } = {
  name: 'John',
  age: 30
}

// ✗ Bad: Unnecessary type annotations
const num: number = 5  // Redundant, inference works
const str: string = 'hello'  // Redundant

// ✓ Good: Use interfaces for reusable types
interface User {
  name: string
  age: number
}

const user: User = { name: 'John', age: 30 }

🔹 Interface vs Type

Choose the right tool for the job:

// ✓ Use interface for object shapes
interface User {
  id: number
  name: string
  email: string
}

// ✓ Use type for unions and primitives
type Status = 'active' | 'inactive' | 'pending'
type ID = string | number

// ✓ Interface can be extended
interface Admin extends User {
  role: string
  permissions: string[]
}

// ✓ Type can use unions and intersections
type UserWithRole = User & { role: string }

// ✓ Use interface for public APIs
export interface ApiResponse {
  data: any
  status: number
  message: string
}

// ✓ Use type for internal utilities
type Nullable<T> = T | null

🔹 Code Organization

Structure your TypeScript files properly:

// ✓ Good: Organized file structure

// 1. Imports (external first, then internal)
import React from 'react'
import { useState } from 'react'
import { User } from './types'
import { api } from './api'

// 2. Type definitions
interface Props {
  title: string
  count: number
}

// 3. Constants
const MAX_ITEMS = 100

// 4. Helper functions
function formatDate(date: Date): string {
  return date.toISOString()
}

// 5. Main component/class
export default function MyComponent({ title, count }: Props) {
  const [items, setItems] = useState<string[]>([])
  
  return (
    <div>
      <h1>{title}</h1>
      <p>Count: {count}</p>
    </div>
  )
}

🔹 Function Best Practices

Write clean, type-safe functions:

// ✓ Good: Explicit return types
function calculateTotal(price: number, tax: number): number {
  return price + (price * tax)
}

// ✓ Good: Use arrow functions for callbacks
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map((n) => n * 2)

// ✓ Good: Optional parameters at the end
function createUser(name: string, age: number, email?: string): User {
  return { name, age, email }
}

// ✓ Good: Use default parameters
function greet(name: string, greeting: string = 'Hello'): string {
  return `${greeting}, ${name}!`
}

// ✓ Good: Destructure parameters
function displayUser({ name, age }: User): void {
  console.log(`${name} is ${age} years old`)
}

// ✗ Bad: Too many parameters (use object instead)
function createOrder(id, name, price, quantity, discount, tax) { }

// ✓ Good: Use object parameter
interface OrderParams {
  id: number
  name: string
  price: number
  quantity: number
  discount?: number
  tax?: number
}

function createOrder(params: OrderParams) { }

🔹 Comments and Documentation

Document your code effectively:

// ✓ Good: JSDoc comments for functions
/**
 * Calculates the total price including tax
 * @param price - The base price
 * @param taxRate - Tax rate as decimal (e.g., 0.15 for 15%)
 * @returns The total price with tax
 */
function calculateTotal(price: number, taxRate: number): number {
  return price + (price * taxRate)
}

// ✓ Good: Explain complex logic
function processData(data: string[]): number {
  // Filter out empty strings and convert to numbers
  const numbers = data
    .filter(item => item.trim() !== '')
    .map(item => parseInt(item, 10))
  
  // Calculate sum using reduce
  return numbers.reduce((sum, num) => sum + num, 0)
}

// ✗ Bad: Obvious comments
const count = 5  // Set count to 5

// ✓ Good: Explain why, not what
const TIMEOUT = 5000  // API timeout increased to handle slow connections

🧠 Test Your Knowledge

What naming convention should you use for classes?