Lesson 33 of 80
import type isn't a style preference — under isolatedModules it's the only way to guarantee a type-only import doesn't leave a phantom runtime import behind.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
tsc normally has the whole program in view when it compiles: it can see that an imported name is only ever used as a type and safely erase that import from the output entirely. But a lot of real build pipelines — including this project's, which builds on Next.js and compiles through a single-file transpiler under the hood — don't work that way. They transpile one file at a time, with no cross-file type information, so they genuinely cannot tell whether an imported name is a type (safe to erase) or a value (must stay in the output).
import type and export type exist to close that gap. They tell the transpiler directly, at the syntax level, "this import has zero runtime existence — erase it unconditionally," without needing to understand TypeScript's type system at all.
// user-types.ts — this file has no runtime exports at all
export interface User {
id: string;
name: string;
}
// profile.tsx
import type { User } from "./user-types";
function renderProfile(user: User) {
return `${user.name} (${user.id})`;
}
User is an interface — it's erased completely at compile time, there is no User object at runtime. A single-file transpiler processing profile.tsx in isolation can't know that, though — all it sees is import { User } from "./user-types", which looks exactly like a normal value import. Marking it import type removes any ambiguity: the transpiler erases the whole statement without needing to check whether user-types.ts even exports a runtime value called User.
You can also mark individual specifiers inline, mixing a type and a value import in one statement:
import { type User, createUser } from "./user";
import type protects against isn't hypothetical. Under isolatedModules — required by single-file transpilers like the one Next.js's build uses — a value-style import of a type-only name can leave a real import statement in the compiled output, pointing at a module that exports nothing at runtime. That's a module-not-found or "X is not a function" error in production, not a type error.import { type Foo, bar } from "./mod" is valid syntax, and it's often the right choice — it marks just Foo as type-only inline, in the same statement as a normal value import of bar, rather than requiring two separate import lines.export type { User } from "./types" gives the same guarantee in reverse. A plain export { User } from "./types" re-export can leave runtime code behind under the same isolated-compilation conditions; export type guarantees it doesn't.verbatimModuleSyntax is stricter than isolatedModules alone. isolatedModules just requires the file to be safely compilable on its own; verbatimModuleSyntax additionally makes the type keyword mandatory wherever it's needed, and preserves your import syntax exactly as written into the output instead of letting TypeScript silently decide what to elide.This connects directly to the module resolution strategies covered elsewhere in this track — both exist because a bundler or single-file transpiler processing one file at a time doesn't have the full-program view that tsc itself has, so TypeScript needs syntax-level signals instead of relying on inference. It's also a concrete illustration of the interfaces-vs-types distinction: interfaces are purely compile-time constructs, which is exactly the property import type exists to protect at the module boundary.