Skip to content

TypeScript – Naming Conventions

TypeScript naming conventions build directly on JavaScript conventions while adding new identifier categories — interfaces, type aliases, enums, generics, decorators, and type predicates. The goal is the same: a name should communicate what something is and what it does without requiring the reader to chase down its definition.

Enforcement

All naming rules are enforced via @typescript-eslint rules in eslint.config.ts. Violations fail CI. Configure your IDE to show lint errors inline — do not wait for CI to catch them.

Quick Reference

Identifier Convention Example
Variable camelCase orderTotal, isLoading
Function camelCase (verb) calculateTotal(), fetchUser()
Constant (module-level) UPPER_SNAKE_CASE MAX_RETRY_COUNT, API_BASE_URL
Class PascalCase (noun) OrderService, PaymentProcessor
Interface PascalCase (no I prefix) OrderRepository, UserProfile
Type alias PascalCase OrderStatus, ApiResponse<T>
Custom utility type PascalCase — describes transformation DeepReadonly<T>, Nullable<T>
Enum type PascalCase singular Direction, HttpMethod
Enum member PascalCase Direction.North, HttpMethod.Get
Generic parameter Single T or prefixed TEntity T, TData, TError
Type predicate camelCaseisX / assertIsX isOrder(), assertIsUser()
Decorator PascalCase @Injectable(), @Controller()
File kebab-case.ts order-service.ts, user.types.ts
Declaration file kebab-case.d.ts global.d.ts, env.d.ts
React component file PascalCase.tsx UserCard.tsx
Test file name.test.ts / name.spec.ts order-service.test.ts

Variables and Constants

// ✅ camelCase for variables
const orderTotal = calculateTotal(cart.items);
const isAuthenticated = validateToken(req.headers.authorization);
let currentPage = 1;

// ✅ UPPER_SNAKE_CASE for module-level fixed constants
const MAX_RETRY_COUNT = 3;
const DEFAULT_PAGE_SIZE = 20;
const API_BASE_URL = 'https://api.cygnusdynamics.com/v2';
const SESSION_TIMEOUT_MS = 1_800_000; // numeric separators for readability

// ✅ camelCase for function-scoped constants
function processOrder(order: Order): number {
  const taxRate = 0.20;
  return order.total * (1 + taxRate);
}

// ❌ Wrong case
const MaxRetryCount = 3;     // PascalCase is for types/classes
const max_retry_count = 3;   // snake_case not used in TypeScript

Functions and Methods

camelCase verbs. The name should describe the action clearly:

// ✅ Verb-first camelCase
function fetchOrderById(orderId: string): Promise<Order> {}
function calculateShippingCost(order: Order, destination: Address): number {}
function validateEmailAddress(email: string): boolean {}

// ✅ Boolean-returning functions — is / has / can / should prefix
function isOrderEligibleForDiscount(order: Order): boolean {}
function hasActiveSubscription(user: User): boolean {}
function canProcessRefund(order: Order, user: User): boolean {}

// ❌ PascalCase for a function — that is for classes
function OrderTotal(items: OrderItem[]): number {}

Async Functions

Do not append Async — the async keyword and Promise<T> return type make it explicit:

// ✅
async function fetchUserProfile(userId: string): Promise<UserProfile> {}
async function processPayment(order: Order, card: Card): Promise<PaymentResult> {}

// ❌ Redundant suffix
async function fetchUserProfileAsync(userId: string): Promise<UserProfile> {}

Type Predicate and Assertion Functions

Type guard and assertion functions follow a consistent naming pattern that signals their role:

// ✅ Type predicates — isX() returns a boolean with a type predicate annotation
//    Naming mirrors boolean variables: is / has / can prefix
function isOrder(value: unknown): value is Order {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    'status' in value
  );
}

function isPaymentError(err: unknown): err is PaymentError {
  return err instanceof Error && err.name === 'PaymentError';
}

// ✅ Assertion functions — assertIsX() throws if the type is wrong,
//    narrowing the type for all code that follows
function assertIsOrder(value: unknown): asserts value is Order {
  if (!isOrder(value)) {
    throw new TypeError(`Expected Order, received ${typeof value}`);
  }
}

function assertIsDefined<T>(value: T | null | undefined): asserts value is T {
  if (value == null) {
    throw new Error('Expected defined value, received null or undefined');
  }
}

// Usage
const data: unknown = await fetchFromApi();

if (isOrder(data)) {
  console.log(data.status);  // TypeScript: data is Order
}

assertIsOrder(data);
console.log(data.status);    // TypeScript: data is Order after the assertion
Pattern Naming Return type
Type predicate isX(v), hasX(v) boolean with value is T
Assertion function assertIsX(v), assertDefined(v) void with asserts value is T

Interfaces

PascalCase. Do not prefix with I — the TypeScript team itself abandoned this .NET convention:

// ✅ PascalCase — no I prefix
interface OrderRepository {
  findById(id: string): Promise<Order | null>;
  save(order: Order): Promise<Order>;
  delete(id: string): Promise<void>;
}

interface PaginatedResponse<T> {
  items: T[];
  totalCount: number;
  page: number;
  hasNextPage: boolean;
}

// ❌ I prefix — outdated convention
interface IOrderRepository {}
interface IUserProfile {}

When to use interface vs type

Use interface Use type
Object shapes — models, DTOs, props Union types: 'pending' \| 'confirmed'
API contracts that may be extended Intersection types: User & Admin
Class contracts (implements) Tuple types: [string, number]
Declaration merging scenarios Computed/mapped/conditional types
React component props Function type aliases

Type Aliases

PascalCase, named after what they represent:

// ✅ Named after what they represent
type OrderId      = string;
type UserId       = string;
type ISO8601Date  = string;
type EmailAddress = string;

type OrderStatus = 'pending' | 'confirmed' | 'shipped' | 'delivered' | 'cancelled';

type ApiResponse<TData> =
  | { success: true;  data: TData }
  | { success: false; error: ApiError };

// ❌ Named after structure — not descriptive
type StringOrNumber = string | number;  // what does this represent in the domain?
type ObjWithId = { id: string };        // what kind of object?

Custom Utility Types

Custom utility types are PascalCase and named for the transformation they perform:

// ✅ Named after the transformation
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

type Nullable<T>       = T | null;
type Optional<T>       = T | undefined;
type MaybePromise<T>   = T | Promise<T>;

// "Make specific keys optional" — name describes the operation precisely
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

// Branded type utility — see Types page and Best Practices for full usage
type Brand<T, TBrand extends string> = T & { readonly _brand: TBrand };

// Usage — names communicate intent at every call site
type OrderId     = Brand<string, 'OrderId'>;
type UserId      = Brand<string, 'UserId'>;
type Cents       = Brand<number, 'Cents'>;

// ❌ Abbreviated or vague utility type names
type MkOpt<T, K extends keyof T> = ...;  // abbreviation
type Brnd<T, B> = ...;                   // abbreviation

Enums

PascalCase singular type name. Members are PascalCasenot UPPER_SNAKE_CASE:

// ✅ PascalCase members, string values
enum OrderStatus {
  Pending   = 'pending',
  Confirmed = 'confirmed',
  Shipped   = 'shipped',
  Delivered = 'delivered',
  Cancelled = 'cancelled',
}

enum HttpStatusCode {
  Ok                  = 200,
  Created             = 201,
  BadRequest          = 400,
  Unauthorized        = 401,
  NotFound            = 404,
  InternalServerError = 500,
}

// ❌ UPPER_SNAKE_CASE members — not TypeScript convention
enum OrderStatus {
  PENDING = 'pending',
}

// ❌ Numeric enum without explicit values — loses meaning in serialisation
enum OrderStatus {
  Pending,    // = 0 — meaningless in logs and API responses
}

as const over enums for simple string unions

For simple string value sets, as const objects are often preferred — plain JavaScript at runtime, tree-shakeable, no import needed for the type:

const OrderStatus = {
  Pending:   'pending',
  Confirmed: 'confirmed',
  Cancelled: 'cancelled',
} as const;

type OrderStatus = typeof OrderStatus[keyof typeof OrderStatus];
// type OrderStatus = 'pending' | 'confirmed' | 'cancelled'

Generic Type Parameters

Single-letter T for simple generics. Descriptive TPrefix names when the role matters:

// ✅ Single T for simple, obvious generics
function identity<T>(value: T): T { return value; }
function first<T>(array: T[]): T | undefined { return array[0]; }

// ✅ Descriptive when multiple parameters or constraints are present
interface Repository<TEntity, TId = string> {
  findById(id: TId): Promise<TEntity | null>;
  save(entity: TEntity): Promise<TEntity>;
}

function convert<TSource, TTarget>(
  source: TSource,
  converter: (s: TSource) => TTarget,
): TTarget {
  return converter(source);
}

// ✅ Semantic names for domain-specific generics
type ApiResult<TData, TError = ApiError> = Promise<
  | { success: true;  data: TData }
  | { success: false; error: TError }
>;

// ❌ Single letters when context demands clarity
function merge<A, B>(a: A, b: B): A & B {}  // use TLeft, TRight or TFirst, TSecond

Classes

PascalCase nouns. TypeScript access modifiers replace the _ underscore convention:

// ✅ Access modifiers — no underscore prefix
class OrderService {
  private readonly repository: OrderRepository;
  private readonly logger: Logger;

  constructor(repository: OrderRepository, logger: Logger) {
    this.repository = repository;
    this.logger = logger;
  }

  async getOrderById(orderId: string): Promise<Order> {}
  async createOrder(request: CreateOrderRequest): Promise<Order> {}
}

// ❌ Underscore prefix — a JavaScript workaround, not needed in TypeScript
class OrderService {
  private _repository: OrderRepository;  // _ is redundant — private does the job
}

Decorators

Decorators use PascalCase. Class decorators are typically nouns; method and parameter decorators are typically verb phrases or adjectives:

// ✅ Class decorators — PascalCase nouns
@Injectable()
@Controller('/orders')
@Entity('orders')
class OrderController {}

// ✅ Method and parameter decorators — PascalCase
class OrderService {
  @Transactional()
  @Cacheable({ ttl: 300 })
  async getOrder(@Param('id') id: string): Promise<Order> {}

  @Post('/')
  @UseGuards(AuthGuard)
  async createOrder(@Body() body: CreateOrderDto): Promise<Order> {}
}

// ✅ Custom decorator factory — same PascalCase rule
function RateLimit(options: RateLimitOptions): MethodDecorator {
  return (_target, _key, descriptor) => {
    // implementation
    return descriptor;
  };
}

// ❌ camelCase decorators
@injectable()          // wrong
@controller('/orders') // wrong

File Naming

✅ Correct
order-service.ts            # service
order.repository.ts         # repository
create-order.dto.ts         # Data Transfer Object
order.types.ts              # type definitions for the order domain
order.schema.ts             # Zod schema
auth.middleware.ts           # middleware
UserCard.tsx                # React component — PascalCase.tsx
useOrderData.ts             # React hook
order-service.test.ts       # unit test
order.integration.test.ts   # integration test
index.ts                    # barrel export

✅ Declaration files — always kebab-case.d.ts
global.d.ts                 # global type augmentations
env.d.ts                    # environment variable types
express.d.ts                # module augmentation for express

❌ Incorrect
OrderService.ts             # PascalCase for non-component files
order_service.ts            # snake_case
orderService.ts             # camelCase

Declaration file conventions

Declaration files augment module types or add global declarations. Name them after what they augment:

// env.d.ts — typed environment variables, validated against your .env
declare namespace NodeJS {
  interface ProcessEnv {
    readonly NODE_ENV:           'development' | 'test' | 'production';
    readonly DATABASE_URL:       string;
    readonly STRIPE_SECRET_KEY:  string;
    readonly PORT?:              string;
  }
}

// express.d.ts — extend the Express Request type with app-level additions
declare global {
  namespace Express {
    interface Request {
      user?:     AuthenticatedUser;
      requestId: string;
    }
  }
}

export {};  // ensures this is treated as a module, not a script