Use import type for type-only imports
Use the import type syntax for imports that are only needed as TypeScript types, ensuring they are fully erased at compile time with zero runtime cost.
- import type is erased entirely at compile time — no runtime module evaluation
- Prevents accidentally importing a value when only a type is needed
- Reduces circular dependency issues caused by type-only relationships
- verbatimModuleSyntax in TypeScript 5 enforces this automatically
Rule Details
TypeScript's import type (opens in new tab) syntax explicitly marks an import as type-only, guaranteeing that the compiler erases it completely from the emitted JavaScript with no module evaluation.
Code Examples
// ❌ Regular import used only for a type — module is evaluated at runtime
import { User } from './user' // './user' module code runs
import { ApiResponse } from './api' // './api' module code runs
function formatUser(user: User): string {
return user.name
}
async function handleResponse(res: ApiResponse<User>): Promise<User> {
return res.data
}// ✅ import type — both imports are completely erased from the output JS
import type { User } from './user'
import type { ApiResponse } from './api'
function formatUser(user: User): string {
return user.name
}
async function handleResponse(res: ApiResponse<User>): Promise<User> {
return res.data
}The compiled output for the type-only version contains no reference to ./user or ./api. No module evaluation, no bundle inclusion.
Why It Matters
Regular imports cause the JavaScript runtime to evaluate and execute the imported module even when only a TypeScript type from that module is used. This can increase bundle size, introduce unintended side effects, and create circular dependency issues that are otherwise avoidable. Using import type makes the erased-at-runtime nature of type imports explicit and enables bundlers and the TypeScript compiler to handle them optimally.
Mixed Imports — Types and Values Together
When you need both a type and a runtime value from the same module, you can either use a single import with inline type modifiers, or separate the imports:
// Option A — inline type modifier (TypeScript 4.5+)
import { createUser, type User, type CreateUserOptions } from './user'
// ^^^^ type ^^^^ type — erased at compile time
// ^^^^^^^^^^^ value — kept in runtime output
// Option B — two separate imports
import { createUser } from './user' // runtime value
import type { User, CreateUserOptions } from './user' // types only
// Both are correct — choose based on team preferenceverbatimModuleSyntax — Automatic Enforcement
TypeScript 5.0 introduced verbatimModuleSyntax, which enforces that type-only imports use import type. The compiler will error if you write a regular import that is only used as a type.
// tsconfig.json
{
"compilerOptions": {
"verbatimModuleSyntax": true,
// verbatimModuleSyntax replaces the older importsNotUsedAsValues and
// preserveValueImports options — do not set those alongside it
"module": "ESNext",
"moduleResolution": "bundler"
}
}With verbatimModuleSyntax (opens in new tab) enabled, this becomes a compiler error:
// ❌ TypeScript error: This import is never used as a value and must use 'import type'
import { User } from './user'
function greet(user: User): string {
return `Hello, ${user.name}`
}ESLint Rule
Add the consistent-type-imports rule (opens in new tab) to enforce import type via your linter, independently of TypeScript version:
// eslint.config.js (flat config)
{
"rules": {
"@typescript-eslint/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}
]
}
}The rule is auto-fixable — running eslint --fix converts all violating imports automatically.
Re-exporting Types
Type-only exports follow the same pattern:
// types.ts
export type { User } from './user'
export type { ApiResponse, ApiError } from './api'
// Both are erased from the compiled output of types.ts
// Consumers of types.ts still get the full typesA TypeScript class declares both a type (the instance shape) and a runtime value (the constructor). If you use import type { MyClass } and then write new MyClass(), you will get a compile error because the constructor was erased. Use a regular import whenever you call new, access static members, or use the class as a value at runtime.
Type Imports in Declaration Files
In .d.ts files, all imports are already type-only by nature, but using import type is still considered good practice as it makes intent explicit:
// declarations.d.ts
import type { EventEmitter } from 'events'
declare class MyEmitter extends EventEmitter {
on(event: 'data', listener: (chunk: Buffer) => void): this
}Verification
The TypeScript 3.8 type-only import notes (opens in new tab) and the consistent-type-imports rule (opens in new tab) are the fastest way to verify both compiler and linter behavior agree on the same import boundary.
- Run
pnpm exec tsc --noEmitand confirm zero errors. If usingverbatimModuleSyntax: true, any type-only import withoutimport typewill surface as a compiler error. - Run the ESLint
consistent-type-importsrule across thesrc/directory and confirm zero violations (the rule is auto-fixable with--fix). - Inspect the compiled JavaScript output for a module that uses only type imports — confirm the imported module paths do not appear in the output file.
- Search for
import {in TypeScript files and verify each imported identifier is actually used as a runtime value, not only in type positions.
Use with AI
Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.
Check
Verify implementation
Review the import statements in this TypeScript file and identify any imports where only types or interfaces are used. These should use import type instead of a regular import.
Fix
Auto-fix issues
Convert the identified regular imports to import type where the imported binding is only used as a TypeScript type annotation and not as a runtime value.
Explain
Learn more
Explain the difference between import and import type in TypeScript, why type-only imports should be declared explicitly, and how verbatimModuleSyntax enforces this at the compiler level.
Review
Code review
Inspect all import statements in this file. For each import, check whether the imported name appears only in type positions (annotations, generics, implements clauses) versus runtime positions (function calls, new, typeof). Flag any regular import that is used only as a type.