learn
TypeScript

Modules and declaration files

import type, .d.ts bridges, @types and the declare keyword.

Your typed code constantly meets untyped JavaScript: libraries, legacy files, globals. Declaration files (.d.ts) describe those neighbors so the checker can verify your side of the conversation. Interviewers ask "where do the types for lodash come from?" — this page is that answer.

Real-life situation

You install a small slug helper written in plain JavaScript. No annotations, no checking — every call is a guess. A declaration file bridges the gap: it promises slugify(text: string): string without touching the library, and suddenly wrong calls fail in your editor. When a library ships no types, the community DefinitelyTyped collection (@types/... packages) usually has them.

Visual

Declaration file bridge
BrowserAPIRealtimeCacheDatabaseShareddurableephemeral

Try it

Describe values you did not write

Checked with TypeScript 6.0.3, strict mode

typescriptTab moves to the next control
function slugify(text: string): string {
return text.trim().toLowerCase().split(" ").join("-");
}
const shopVersion = "2.1.0";
type Slugify = typeof slugify;
function productSlug(name: string, make: Slugify): string {
const clean = name.trim().toLowerCase();
return make(clean);
}
function versionLabel(prefix: string, version: string): string {
return prefix + " v" + version;
}
const make: Slugify = slugify;
console.log(productSlug("  Red Chair ", make));
console.log(versionLabel("shop", shopVersion));
console.log(make("Tea Pot"));
const code: number = shopVersion;
console.log("code " + code);

Compiler not loaded. Check or Run loads it here (about 9 MB) — nothing downloads until you do.

Runner helpers

sleep(ms) waits; fakeFetch(url) returns deterministic sample data. TypeScript is stripped before running; imports and exports are not available in the sandbox.

Try changing the code annotation to string — clean. typeof borrows a value's type without repeating it, which is exactly what a .d.ts file contains: declare function slugify(text: string): string; — a promise with no body. import type brings in only the type meaning, so bundlers can drop the import entirely (Node's own type stripping needs that keyword to erase imports correctly).

The static version of the bridge, build-checked below, shows the typed side: the same promises a .d.ts file would hold, with bodies so the page can verify them. The real .d.ts would contain only the declare lines (shown as inline code above, never executed).

function slugify(text: string): string {
  return text.trim().toLowerCase().split(" ").join("-");
}
const shopVersion = "2.1.0";
const slug: string = slugify("Red Chair");
const tag: string = shopVersion;
console.log(slug + " " + tag);

For JavaScript-side module mechanics, see modules in the JavaScript topic.

Will it compile? — decide first, then reveal.

check.ts
const limit = 10;
type Limit = typeof limit;
const next: Limit = 10;
console.log(next);

Interview questions

How do import and export work in TypeScript?basic

Exactly as in modern JavaScript: you export functions, classes, and types from one file and import them where needed. Each file with top-level import or export is a module with its own scope. You can export values and types side by side, and importers pull only what they use. Keep exports intentional — a small public surface makes refactors and API reviews far easier than re-exporting everything.

Interviewers also ask

  • What makes a file a module?
What does import type do, and why use it?intermediate

import type { User } brings in only the static type, guaranteeing no runtime dependency on the source module. The import is fully erased at emit, which avoids circular runtime imports and lets bundlers skip the module. Use it whenever you need just the shape — props, payloads, return types. It also documents intent: readers see immediately that the import cannot affect runtime behavior.

Interviewers also ask

  • How does import type help with circular imports?
How do you share a type across files?basic

You export it from one module with export interface User and import it with import type { User } wherever it is needed. One canonical definition keeps every consumer in sync — changing the interface updates all usages at the next check. Place widely shared shapes in a nearby types.ts or next to the domain they describe. Avoid global ambient types for app code, since explicit imports trace where each name comes from.

Interviewers also ask

  • Where should you put widely shared types?
What is a .d.ts declaration file?intermediate

A file containing only types — no runtime code — that describes the shapes of JavaScript for the compiler. Libraries ship them so your editor and tsc understand the API without seeing the implementation. You can also write local .d.ts files to type untyped JavaScript you depend on. They are never executed, only read during checking, which is why they contain declare statements rather than real logic.

Interviewers also ask

  • Are declaration files ever executed?
What is DefinitelyTyped and when do you need @types packages?intermediate

DefinitelyTyped is the community repository of declaration packages for libraries that do not bundle their own types. When an import errors with a missing-declarations message, you install the matching @types package as a dev dependency — for example @types/lodash for lodash. Modern well-maintained libraries usually ship types already, so check the package itself first. Remove the @types package once the library bundles its own.

Interviewers also ask

  • How do you know if a library already ships its own types?
How do you declare a missing module with declare module?intermediate

You write a short ambient declaration like declare module 'x' { const v: string; export default v } in a .d.ts file to give the compiler a shape for an untyped import. It unblocks the build without editing the library. Keep the declaration minimal and accurate, and prefer contributing real types upstream or installing @types when available. Treat local declares as temporary shims with a note about what should replace them.

Interviewers also ask

  • Why prefer @types over a hand-written declare module?

Key takeaways

  • .d.ts files promise shapes for plain JavaScript — no rewrite needed.
  • typeof borrows types; import type erases fully at emit.
  • Missing library types? Look for @types/... first, declare second.

References

On this page