Q210: Declaration Files, Module Typing and Third Party Types
What Interviewers Want To Evaluate
Interviewers want to know whether you can work with real TypeScript ecosystems, not only ideal code.
They check declaration files, ambient types, module declarations, @types packages, typed assets, third-party SDK typing, module augmentation, and how to avoid unsafe global type changes.
Short Interview Answer
Declaration files describe JavaScript or external modules to TypeScript without emitting runtime code. I use them for untyped packages, assets, environment variables, and controlled module augmentation. I prefer official package types first, then @types packages, then narrow local declarations. I avoid broad declare module "*" patterns because they hide real typing problems.
Detailed Interview Answer
TypeScript needs type information.
When code is written in TypeScript, that information usually comes from source files.
When code comes from JavaScript, assets, or external packages, declaration files can describe it.
Declaration files usually end with:
.d.ts
They contain types only.
They do not run at runtime.
Third Party Package Types
Many packages ship their own types.
{
"types": "./dist/index.d.ts"
}
When types are bundled, TypeScript can read them automatically.
For older JavaScript packages, types may live in DefinitelyTyped.
npm install -D @types/package-name
Prefer official types when available.
Use @types when the package does not ship types.
Local Module Declaration
Sometimes a package has no types.
Create a local declaration.
declare module "legacy-chart" {
export type ChartOptions = {
container: HTMLElement;
data: Array<{ label: string; value: number }>;
};
export function renderChart(options: ChartOptions): void;
}
This is better than:
declare module "legacy-chart";
The broad version turns imports into any.
That should be temporary at most.
Asset Declarations
Frontend projects often import non-code assets.
declare module "*.mdx" {
const content: React.ComponentType;
export default content;
}
declare module "*.svg" {
const src: string;
export default src;
}
The exact declaration depends on the bundler and framework.
If SVGs are transformed into React components, the type should reflect that.
Environment Variables
Environment variables are strings at runtime.
You can type known keys.
declare namespace NodeJS {
interface ProcessEnv {
NEXT_PUBLIC_SITE_URL: string;
ANALYTICS_WRITE_KEY?: string;
}
}
But this does not guarantee values exist at runtime.
For required environment variables, validate during startup or inside lazy configuration.
function requireEnv(name: string) {
const value = process.env[name];
if (!value) {
throw new Error(`Missing environment variable: ${name}`);
}
return value;
}
Types improve autocomplete.
Validation protects runtime.
Global Declarations
Use globals carefully.
declare global {
interface Window {
analytics?: {
track(event: string, payload: Record<string, unknown>): void;
};
}
}
export {};
The export {} makes the declaration file a module, which keeps global augmentation explicit.
Global types are convenient but can become invisible coupling.
Module Augmentation
Module augmentation extends an existing module's types.
Example:
declare module "some-auth-library" {
interface Session {
userId: string;
role: "admin" | "member";
}
}
This can be useful when a library intentionally supports extension.
It is risky when used to patch around misunderstood types.
Read the library's typing model before augmenting.
Type Roots And Include
TypeScript only sees declaration files included by configuration.
Important config areas:
{
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts"],
"compilerOptions": {
"types": ["node"]
}
}
If you set types, TypeScript limits included global packages to that list.
That can accidentally remove test or framework globals.
skipLibCheck
Many projects use:
{
"compilerOptions": {
"skipLibCheck": true
}
}
This skips type checking declaration files from dependencies.
It can improve build speed and reduce dependency noise.
Trade-off:
faster typecheck
fewer third-party type conflicts
less confidence in dependency declaration correctness
For applications, it is often acceptable.
For libraries, be more careful.
Typing Untyped SDKs
When an SDK is untyped, avoid declaring everything as any.
Start with the surface you use.
declare module "analytics-sdk" {
export function init(key: string): void;
export function track(
event: string,
payload?: Record<string, string | number | boolean>,
): void;
}
This creates a useful boundary without typing the whole package.
Expand it as usage grows.
Runtime Reality
Declaration files can lie.
This compiles:
declare module "broken-sdk" {
export function loadUser(id: string): Promise<{ id: string; name: string }>;
}
But the runtime package may return a different shape.
For important external data, still validate or normalize.
Types describe expectations.
They do not control third-party runtime behavior.
Common Mistakes
Common mistakes include:
using broad declare module patterns permanently
typing environment variables but not validating them
augmenting global types casually
forgetting declaration files must be included by tsconfig
trusting third-party declarations blindly
using skipLibCheck without understanding the trade-off
The dangerous habit is using declaration files to silence errors instead of modeling the integration.
Senior Trade-Offs
For third-party types, I prefer this order:
official bundled types
maintained @types package
narrow local declaration for used surface
temporary unknown boundary with runtime validation
last-resort any with tracked cleanup
This keeps delivery moving while preserving safety.
Interview Framing
In an interview, say:
Declaration files are type descriptions for code or assets TypeScript cannot infer directly. I keep them narrow, close to the integration, and honest about runtime validation. I avoid broad declarations because they remove the compiler's value.
Revision Notes
.d.tsfiles provide types without runtime code.- Prefer official package types, then
@types, then local declarations. - Broad untyped module declarations create
anyislands. - Environment variable types do not prove runtime presence.
- Module augmentation should be deliberate.
- Declaration files must be included by TypeScript configuration.
Follow-Up Questions
- What is a declaration file?
- When would you install an
@typespackage? - Why is
declare module "package";risky? - How would you type environment variables safely?
- What is module augmentation?
How I Would Answer This In A Real Interview
I would say declaration files are how TypeScript understands JavaScript packages, assets, globals, and extensions that do not come from normal TypeScript source. I would first look for official types or an @types package. If those do not exist, I would write a narrow local declaration for only the surface we use. I would avoid broad declarations and still validate runtime data from external systems.