In TypeScript, what does `import type { User } from './models'` mean, and how does it differ from a plain `import { User } from './models'`?
answer
- about what survives compilation
- a promise, not an optimisation
- type positions only, never a value
- guarantees erasure; plain imports rely on elision
- inline form marks single specifiers
basics
~20 simport type declares the binding as types only: TypeScript always erases that statement from the emitted JavaScript, and the name cannot be used as a value. A plain import is erased only when the compiler decides nothing in the file uses it at runtime.
solid answer
~50 s`import type` is an explicit promise to the compiler that everything the statement brings in is used only in type positions. Two things follow. First, the statement is **guaranteed** to be erased — nothing is emitted for it, so it cannot even trigger the imported module's side effects. Second, the binding is usable only where a type is expected: writing `new User()` or passing `User` as a value is a compile error. A plain `import { User }` is different in kind: TypeScript's *import elision* will drop it from the output if it sees the name used only as a type, but that decision depends on whole-program information, and if you later use `User` as a value the import quietly reappears in the emitted JavaScript. There is also an inline form, `import { type User, createUser } from './models'`, which marks individual specifiers when one statement carries both types and values.
go deeper
Know that import type brings in only a type, never a runtime value, and that the whole statement vanishes from the compiled JavaScript. Be able to say what breaks if you then try to call or construct that binding.
Explain import elision as the implicit alternative, and why an explicit type keyword is decidable from a single file while elision needs whole-program information. Show the inline type specifier form and the matching export type syntax.
Show the runtime failure mode: a type-only import left unmarked survives a per-file transpiler and produces a module that requests an export nobody emitted. Be able to talk about relying on side-effect imports separately from type imports.
Own the convention. Decide whether the codebase writes type-only imports by hand and has the compiler enforce it, weighing a one-time mechanical migration against the class of build-tool-only runtime failures it retires for every future contributor.
## The problem this syntax solves TypeScript's central rule is that types are erased: the compiler emits JavaScript with the type layer removed. That creates an awkward case for imports. `import { User } from './models'` is ordinary JavaScript syntax that, at runtime, actually loads `./models` and binds one of its exports. But if `User` is an interface, there is no runtime export named `User` at all — the interface emitted nothing. Keeping that import in the output would produce a module that fails to link. TypeScript's historical answer is **import elision**: while emitting, the compiler looks at how each imported name is used and drops the ones that are only ever used in type positions, dropping the whole statement when nothing is left. Elision is convenient but implicit, and its correctness depends on information from other files. ## What the explicit form does ```ts import type { User } from "./models"; ``` This declares up front that the statement is type-only. Two guarantees follow. **It is always erased.** The emitted JavaScript contains no trace of the statement, so the module is never loaded because of it. If `./models` had a top-level side effect you were relying on, a type-only import will not run it — you would need a separate `import "./models";`. **The binding is a type, not a value.** Everything below is legal, because each use is a type position: ```ts let current: User; function save(u: User): Promise<void> { /* ... */ } interface Session { user: User } ``` And this is a compile error, because it uses the name where a runtime value is required: ```ts const u = new User(); // error: 'User' cannot be used as a value console.log(User); // error ``` That second guarantee is why the form is useful even for classes. A class produces *both* a type and a runtime value; importing it with `import type` deliberately takes only the type half, and the compiler will stop you the moment someone reaches for the value half. ## The inline modifier When one module gives you both types and values, you do not need two statements: ```ts import { type User, createUser } from "./models"; ``` `createUser` is a real value import and survives into the output; `type User` is dropped. There is a matching export form: ```ts export type { User }; export { type User, createUser } from "./models"; ``` ## Why interviewers care Elision requires the compiler to know what every imported name *is*, which requires reading the other file. Tools that compile one file at a time — esbuild, swc, Babel's TypeScript transform — never read the other file. They see `import { User } from './models'` and have no way to tell an interface from a function, so they must keep the import. If `User` was type-only, the emitted module now imports something that does not exist. Explicit `import type` removes the guesswork: the `type` keyword is *syntax*, visible in the single file being transpiled, so any tool can delete it correctly without cross-file knowledge. That is why compiler options exist to require it, and why modern codebases write it by hand rather than relying on elision. ## Practical guidance - Reach for `import type` (or the inline `type` modifier) whenever an import is genuinely type-only. It costs nothing and documents intent. - Do not use it for a module you import *for* its side effect; use a bare `import "./setup";` for that. - Remember the direction of the guarantee: `import type` guarantees erasure, whereas a plain import only *might* be erased. When in doubt, being explicit is always the safe side. - The same reasoning applies to exports: re-exporting a type through a plain `export { ... } from` is the mirror-image hazard, and `export type { ... } from` is the mirror-image fix.
- If a module needs to be loaded for its side effects and you only use a type from it, what do you write?Two statements: `import type { Handler } from "./plugin";` for the type, plus a bare `import "./plugin";` to keep the side effect. A type-only import emits nothing at all, so on its own it will never load the module and the side effect silently disappears.
- A class is both a type and a value. What breaks if you import it with `import type`?Only the value half. Annotations, `implements` clauses and `extends` in an interface still work, but `new Klass()`, `Klass.staticMethod()`, `instanceof Klass` and passing `Klass` as an argument all become compile errors, because the binding was declared type-only and is erased from the output.
- Why does the inline form `import { type A, b }` exist when `import type { A }` already does?So a single module's types and values can travel in one statement instead of two. The statement is kept in the emitted JavaScript for `b`, with the `type A` specifier removed. It also lets a tool that reads only this file decide correctly, since the marker is right there in the syntax.
saying these in an interview costs you the question
- Calls import type a style preference with no compile-time effect
- Says import type makes the import faster or smaller at runtime
- Expects a type-only import to still run the module's side effects
- Uses an import type binding in a new expression or instanceof check
- Assumes a plain import of an interface always disappears from the output