Lesson 75 of 80
TypeScript doesn't know what `fetch` or `Array.prototype.at` are by magic — it reads them from declaration files chosen by the `lib` setting. Get that setting wrong and real, working browser APIs show up as compile errors.
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
TypeScript ships with a pile of .d.ts files describing the global APIs available in different JavaScript environments and language versions — lib.dom.d.ts for window, document, fetch; lib.es2022.array.d.ts for Array.prototype.at; lib.es5.d.ts for the baseline. None of that comes from your code — it comes from the lib compiler option in tsconfig.json, which is a list of which of these declaration files to load.
If lib isn't set explicitly, TypeScript infers a default from target — and that default can easily miss what you actually need. A common failure mode: a Node.js backend project sets target: "ES2020" and never touches lib, so TypeScript defaults to lib: ["ES2020"] with no DOM — and then fetch(url) or document fails to compile with "cannot find name," even though the runtime genuinely has fetch (modern Node ships it globally). The API works. TypeScript just was never told about it.
// tsconfig.json — before
{
"compilerOptions": {
"target": "ES2020"
// no "lib" set — TypeScript defaults to matching ES2020 only, no DOM
}
}
async function getUser(id: string) {
const res = await fetch(`/api/users/${id}`); // ❌ Cannot find name 'fetch'
return res.json();
}
// tsconfig.json — after
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"] // explicit: the language level, plus browser/fetch globals
}
}
async function getUser(id: string) {
const res = await fetch(`/api/users/${id}`); // ✅ fetch is now a known global
return res.json();
}
const nums = [1, 2, 3];
nums.at(-1); // ✅ Array.prototype.at needs ES2022 (or later) in lib
Each entry in lib is additive — ["ES2022", "DOM"] gives you both the ES2022 language/standard-library surface (which is where Array.prototype.at, Object.hasOwn, and similar newer methods live) and the DOM/browser globals, together.
lib is about type declarations, not runtime polyfills. Adding "DOM" to lib doesn't make fetch exist in an environment that genuinely lacks it — it only tells the compiler "assume this global exists." Get the environment wrong (e.g. targeting a browser API in code that actually runs in an old Node runtime) and you get a type-checks-fine, crashes-at-runtime bug, which is worse than a compile error.lib, not none. It's tempting to assume backend code has no use for lib tuning since there's no DOM — but Node adds new globals (fetch, structuredClone, Array.prototype.group) on its own schedule, and the lib entry needs to match the Node version actually running the code, not just "whatever the default target implies."@types packages. lib covers built-in JS/DOM globals shipped with TypeScript itself; @types/node, @types/react, and friends are separate declaration packages for third-party or runtime-specific APIs. Missing fetch types could be a lib problem; missing process.env types is a @types/node problem — different fixes for a similarly-shaped error.Everything in lib.dom.d.ts and its siblings is written using the same ambient-declaration mechanics as the .d.ts files lesson — declare statements with no implementation, just shapes. The lib option is really just "which pre-written ambient declaration files should be globally in scope for this project," the exact same category of tool you'd reach for to describe your own untyped JS module, just applied at the scale of an entire language/platform surface instead of one library.