Naming Conventions in TypeScript (Types, Interfaces, Variables)
Introduction
Naming is more than aesthetics: it’s a readability, maintainability, and collaboration tool. In TypeScript, a language that blends structural typing with JavaScript’s flexibility, consistent naming conventions reduce cognitive load, make intent obvious, and prevent subtle type errors. This guide helps intermediate developers adopt principled naming conventions for types, interfaces, variables, functions, enums, generics, and file-level exports. You’ll learn when to use PascalCase vs camelCase, how to name boolean flags, how to treat interfaces versus types, naming patterns for generics, and how to adapt conventions across the codebase.
By the end you will: have concrete rules you can apply across projects; see practical examples and refactors; understand interactions with compiler options and declaration files; and know how naming choices affect tooling, migration, and interoperability with plain JavaScript libraries. We’ll also cover edge cases—like naming overloads, discriminated unions, and global declaration files—and provide troubleshooting tips tied to common compiler errors and module-resolution concerns.
Throughout this article you’ll find code snippets, step-by-step refactors, and pointers to related topics like configuring tsconfig.json, generating declaration files, and using DefinitelyTyped definitions so your naming strategy plays nicely with the rest of your TypeScript toolchain.
Background & Context
TypeScript’s static typing gives you the power to express intent via types. Names are the primary carrier of intent: a well-chosen name communicates usage, lifetime, and constraints faster than comments. Naming conventions help when reading unfamiliar code, reviewing pull requests, or generating public types for library consumers.
Different communities and teams favor different conventions, but many patterns map naturally onto TypeScript’s constructs. For library authors, consistent public names are critical when publishing declaration files or relying on Using DefinitelyTyped for External Library Declarations. If you work in a mixed JS/TS environment, naming becomes part of interoperability: see our guide on Calling JavaScript from TypeScript and Vice Versa for tips on aligning names across both languages.
Good naming reduces errors such as "cannot find name" or missing properties, which often appear when declaration files are incomplete—see Fixing the "Cannot find name 'X'" Error in TypeScript and Troubleshooting Missing or Incorrect Declaration Files in TypeScript.
Key Takeaways
- Use PascalCase for exported types, interfaces, and classes; camelCase for variables, functions, and properties.
- Prefer descriptive names over cryptic abbreviations; use short generics (T, U) only in local contexts.
- Name boolean variables as predicates (isX, hasY) to clarify truthy checks.
- Use discriminated unions with a stable "kind" or "type" property for safe narrowing.
- Keep public API names stable across releases; avoid committing to implementation details.
- Leverage tooling and tsconfig options to maintain consistency across the codebase.
Prerequisites & Setup
This guide assumes you have a working TypeScript setup (Node.js and npm/yarn), and a basic understanding of TypeScript syntax, interfaces, types, and generics. A starter tsconfig.json with project-level type checking is recommended—see Introduction to tsconfig.json: Configuring Your Project to set that up.
If you integrate JavaScript files, enable JSDoc type checking or migrate JS with Enabling @ts-check in JSDoc for Type Checking JavaScript Files and consult Migrating a JavaScript Project to TypeScript (Step-by-Step) before large renames. For third-party libs, check for declarations via Using DefinitelyTyped for External Library Declarations or write a local .d.ts file following Writing a Simple Declaration File for a JS Module.
Main Tutorial Sections
1. PascalCase vs camelCase: The Basic Rules
Recommendation: Types, interfaces, enums, and classes use PascalCase (e.g., UserProfile, ColorMode). Variables, functions, and object properties use camelCase (e.g., userProfile, getColorMode). This aligns with common TypeScript and JavaScript ecosystems and aids quick identification of values vs types.
Example:
// Good
interface UserProfile { id: string; displayName: string }
type ApiResponse<T> = { data: T; status: number }
class AuthService { /* ... */ }
const currentUser: UserProfile = { id: 'a', displayName: 'Alex' }
function fetchUser(id: string): Promise<UserProfile> { /* ... */ }Why it matters: When scanning code, PascalCase signals a type; camelCase signals a runtime value. This is especially helpful in mixed JS/TS sources and when writing declaration files—see Declaration Files for Global Variables and Functions for naming patterns in .d.ts files.
2. Naming Interfaces vs Type Aliases
Historically some teams prefixed interfaces with I (e.g., IUser). Modern TypeScript favors plain PascalCase without prefixes. Reserve I prefixes only if your codebase has a legacy dependency on that pattern.
Example:
// Preferred
interface PaymentMethod { id: string; provider: string }
// Avoid unless mandated by style guide
interface IPaymentMethod { ... }When to use type vs interface: prefer interface for objects you expect to extend or implement; use type for unions, mapped types, and complex compositions. See patterns in Introduction to Declaration Files (.d.ts): Typing Existing JS to decide naming in declaration contexts.
3. Boolean Naming: Predicates Make Intent Clear
Booleans are easier to reason about when named as predicates. Use prefixes like is, has, should, or can.
Example:
let isAdmin = false
function hasPermission(user: UserProfile, action: string): boolean { /* ... */ }
const shouldRetry = (err: Error) => err.message.includes('timeout')Avoid generic names like flag, ok, or enabled unless scoped. enabled is acceptable when paired with context: featureXEnabled.
4. Naming Enums and Discriminated Unions
For enums, use PascalCase for the enum name and UPPER_SNAKE or PascalCase for members depending on team convention. Prefer string enums or union types for better readability and maintainability.
Examples:
// Union-based discriminant
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rect'; width: number; height: number }
// String enum
enum ColorMode { Light = 'light', Dark = 'dark' }Choose a discriminant property name like kind, type, or tag and use consistent values; this makes control flow narrowing predictable and readable.
5. Generics: Short Names vs Descriptive Names
For local, single-generic types, T, U, K, V are fine (e.g., PromiseUserT or ResponseData.
Example:
function mapProps<T, U>(items: T[], fn: (item: T) => U): U[] { return items.map(fn) }
// Descriptive in a library
type ApiResult<TData = unknown> = { data: TData; meta: Meta }Overly long generic names reduce clarity; balance brevity with expressiveness.
6. Naming Functions and Methods: Verbs First
Functions perform actions; name them with verbs or verb phrases (e.g., fetchUser, validateEmail, calculateTotal). For getters that return properties, getX is acceptable but prefer direct names for simple accessors.
Example:
function buildQuery(params: QueryParams): string { /* ... */ }
class Cache { get(key: string) { /* ... */ } }Avoid names like doStuff or handleData that reveal little about behavior.
7. File and Module Naming
Match exported names with file names when appropriate. For single-export modules, prefer the export name as the filename: UserService.ts exports UserService. For grouped exports, use folder-based organization with index.ts or descriptive names.
If you use path mapping (baseUrl and paths), maintain predictable file names—see Controlling Module Resolution with baseUrl and paths for examples and pitfalls.
Example:
src/services/UserService.ts // exports class UserService src/models/User.ts // exports type User
Consistency aids discoverability and simplifies refactors.
8. Public API Naming and Stability
When you publish a library or expose types to other teams, treat names as public contracts. Avoid leaking implementation details into names. Document expected behavior and keep backward-compatible renames via deprecation cycles.
If your library consumes JavaScript libraries, check for existing types in Using JavaScript Libraries in TypeScript Projects and align naming to the community conventions used there.
Example: Prefer createClient over clientFactoryInternal for a public scatter.
9. Working with Existing JavaScript: Declaration Naming
When writing or adjusting .d.ts files, choose names that make sense for consumers. If you generate types from JSDoc or convert JS to TS, use clear PascalCase for exported types and avoid accidental global declarations.
See Writing a Simple Declaration File for a JS Module and Declaration Files for Global Variables and Functions for patterns and examples.
Example snippet for a simple module declaration:
declare module 'my-lib' {
export interface Config { url: string }
export function connect(cfg: Config): Promise<void>
}10. Team Conventions and Tooling Integration
Establish rules in linters and style guides: ESLint + typescript-eslint can enforce naming rules (e.g., interface-name-prefix, camelcase). Add rules to your repo to catch deviations automatically.
Also consider continuous integration checks for exported API stability and add tests that assert naming-based invariants. For migration-heavy projects consult Migrating a JavaScript Project to TypeScript (Step-by-Step) for a strategy that minimizes naming drift during conversion.
Advanced Techniques
Naming interacts with advanced TypeScript features. For example, when you use mapped types, name the resulting type to convey transformation intent: ReadonlyProps<T> or PartialDeep<T>. For conditional types, pick names that express the transformation (NonNullableKeys<T>).
Use namespace-like groupings when related types are numerous: either put them in a module or use a prefix group (e.g., AuthToken, AuthResult). For library exports that must stay stable, consider an explicit index.ts re-export strategy to hide internal names.
Automate refactors with codemods when renaming widely used API names. When generating declaration files, ensure exported names match your public API—see Generating Source Maps with TypeScript (sourceMap option) for source mapping tips when refactoring and rebuilding.
Best Practices & Common Pitfalls
Dos:
- Do be consistent. Enforce with linters and code reviews.
- Do use predicates for booleans and verbs for functions.
- Do prefer PascalCase for types and camelCase for values.
- Do document public names and keep them stable.
Don'ts:
- Don’t use Hungarian notation or redundant prefixes like
strName. - Don’t name types after implementation details (e.g.,
ArrayWrapperForXwhenXListsuffices). - Don’t overuse single-letter generics in public APIs.
Troubleshooting:
- If the compiler complains about missing names, check modular scope and declarations—see Fixing the "Cannot find name 'X'" Error in TypeScript.
- If properties appear missing due to naming mismatches, check typing of external libs and consider adding or fixing .d.ts files—see Troubleshooting Missing or Incorrect Declaration Files in TypeScript and Using DefinitelyTyped for External Library Declarations.
Real-World Applications
Naming conventions reduce onboarding time and bugs in codebases of all sizes. In API development, clear request/response type names (e.g., CreateOrderRequest, OrderResponse) make client-server contracts unambiguous. In UI code, component props and state types (e.g., UserListProps, ModalState) improve traceability.
When integrating third-party JS modules, align your naming with upstream libs and use wrappers to adapt names without altering external code—see Using JavaScript Libraries in TypeScript Projects for practical guidance.
Conclusion & Next Steps
Adopt a naming convention that balances clarity, brevity, and consistency. Enforce it with tooling, document the rules in your CONTRIBUTING guide, and review public APIs carefully. Next steps: add ESLint rules for naming, review your exported .d.ts files, and run a codemod to align older code. Consult the linked guides above for configuration and declaration file workflows.
Enhanced FAQ
Q1: Should I prefix interfaces with "I" in TypeScript?
A1: Generally no. Modern TypeScript prefers plain PascalCase for interfaces (e.g., User or UserProfile). Avoid I prefixes unless your team has a legacy requirement. Using plain names reduces noise and aligns interfaces with type aliases.
Q2: When should I use type vs interface for naming?
A2: Use interface for object-like shapes you expect to extend, implement, or merge. Use type aliases for unions, intersections, mapped types, or when you need to name complex transforms. Choose names that describe intent: User (interface), UserUpdate (type union of optional props), UserRecord = Record<string, User>.
Q3: How should I name generic type parameters in public APIs?
A3: For local utilities, single-letter generics (T, U, K, V) are fine. For public APIs, prefer descriptive names when the type has domain meaning, e.g., ApiResponse<TData>, EntityId<T>. This improves generated docs and readability.
Q4: Do enums need a consistent member naming style?
A4: Yes. For string enums, PascalCase members (e.g., ColorMode.Light) or UPPER_SNAKE (e.g., ColorMode.LIGHT) are common. Pick one and apply it consistently. For discriminated unions, prefer string literal unions with a kind property for easier narrowing.
Q5: How do naming conventions affect declaration files (.d.ts)? A5: Public declaration files represent your API surface. Use stable, descriptive names and avoid leaking internal types. When authoring .d.ts files, see Introduction to Declaration Files (.d.ts): Typing Existing JS and Writing a Simple Declaration File for a JS Module for patterns that minimize consumer friction.
Q6: What tools help enforce naming conventions? A6: ESLint with typescript-eslint plugin can enforce naming rules (e.g., camelcase, interface-name-prefix). Pair it with Prettier for formatting. Add CI checks to prevent regressions. For API stability, consider tests or tools that diff exported types across releases.
Q7: How to approach renames in large codebases? A7: Use automated refactors (IDE or codemods) and keep changes behind feature flags if runtime compatibility matters. For libraries, deprecate old names first, provide shim exports for a release, and then remove. Use source maps and build tooling—refer to Generating Source Maps with TypeScript (sourceMap option) to help debug after refactors.
Q8: Should file names match the exported type name?
A8: It's a helpful convention for discoverability. For single default exports, matching the file name to the export (e.g., UserService.ts -> UserService) reduces friction. For grouped exports, group related types in a folder with an index.ts entrypoint.
Q9: How do naming conventions interact with JS interoperability? A9: When calling JS from TS, maintain consistent naming to avoid confusion. If a JS library uses snake_case, provide a camelCase wrapper in your TS layer, or document the naming difference. Check Calling JavaScript from TypeScript and Vice Versa for patterns.
Q10: What if I encounter a "property does not exist on type" error after renaming? A10: First verify the type definitions and property names are in sync. If the property is from an external library, check for updated declarations or add a .d.ts fix—see Property 'x' does not exist on type 'Y' Error: Diagnosis and Fixes and Troubleshooting Missing or Incorrect Declaration Files in TypeScript for debugging steps.
Further reading and related guides: consider reviewing Understanding strict Mode and Recommended Strictness Flags to see how strictness choices affect naming guarantees, and Using noImplicitAny to Avoid Untyped Variables to catch places where naming alone isn't enough to communicate type intent.
If you're integrating or consuming JavaScript libraries, our guide to Using JavaScript Libraries in TypeScript Projects and troubleshooting declaration issues above will help keep your naming strategy consistent across mixed-language boundaries.
