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 | camelCase — isX / 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 PascalCase — not 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:
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