Types for JavaScript: JSDoc & TypeScript
Learn how JSDoc, checkJs, TypeScript, and declaration files catch JavaScript type mistakes before runtime while keeping validation honest.
- 01Add types to JavaScriptWrite types in JSDoc comments with tags such as
@param,@returns,@typedef,@property,@template, and@import. - 02Check code before it runsTurn on
// @ts-check,checkJs, and strict TypeScript, so mistakes show up in your editor before users see a crash. - 03Pick the right toolKnow when TypeScript,
.d.tsdeclaration files, and runtime checks each protect your code.
Find type mistakes before your users do
A type is the kind of a value: a number, a string, an object, a function, and so on. JavaScript checks types only while the code runs. That time is called runtime. A variable can hold a number now and a string later. This is flexible, but some mistakes stay hidden until runtime:
- a property name with a typo;
- a missing callback, which is a function you pass in so it can be called later;
- calling something that is not a function, which throws a
TypeError.
If the mistake sits on a path that rarely runs, a user may be the first person to find it.
Static type checking means a tool reads your code before it runs. "Static" means "without running". The tool is called a type checker. It works out the type of each value, parameter, property, and return value. When something doesn't fit, it shows a warning: a wavy line in your editor, or an error message in the terminal. It never runs your program.
This lesson builds on data types, checking types, and type coercion. At runtime, JavaScript still uses the tools you know: typeof, Array.isArray, truthy checks, and if checks. The new tool in this lesson is a checker that can warn you before the code runs.
A proofreader reads an article before it is printed. They mark a missing word, a misspelled name, or a sentence that makes no sense. They can't tell you whether the facts are true. A type checker does the same job for your code. It marks mistakes before anyone runs the code.
- In real life: The proofreader marks a missing word before printing
- In JavaScript: The checker marks a missing property before running
- In real life: The writer still checks that the facts are true
- In JavaScript: Your code still checks data that comes from outside
- In real life: A style guide gives everyone the same rules
- In JavaScript: JSDoc, TypeScript, and
.d.tsfiles give the checker its rules
const lesson = { title: "Types", onComplete: undefined,}; lesson.onComplete();Line 3 sets onComplete to undefined. Line 6 calls it anyway. JavaScript finds out only when it reaches line 6, so it throws a TypeError. A type checker could warn you about line 6 while you type.
A callback is optional when it may be missing. Your code must treat it that way: check that it is a function before you call it.
const lesson = { onComplete: undefined };
const finish = lesson.onComplete;
if (typeof finish === "function") finish();
console.log("Lesson saved");Line 2 reads the callback, which may be missing. Line 3 calls it only if it is a function. Here it is undefined, so the call is skipped. Line 4 prints Lesson saved and nothing throws. This check also works for data that comes from outside your code.
A checker finds some mistakes early. It can't prove that your code does the right thing, for example that the lesson was really saved. Tests and runtime checks answer those questions.
The table compares three tools. Each row gives the tool, what it is good at, and what it can't do.
| Tool | What does it catch? | What can't it do? |
|---|---|---|
JSDoc + checkJs | You keep .js files and add comments. Good for adding types bit by bit, and for libraries that don't want a build step. | The comments can get long. Complex types are harder to read than in TypeScript. |
| TypeScript files | You write .ts or .tsx files with type syntax. You get inference, strict mode, unions, generics, and strong editor help. | The types are removed before the code runs. You still need a tool that compiles or strips them. |
| Runtime checks | Real JavaScript checks on data from APIs, forms, storage, and URLs. | They protect real data, but they don't warn you about your own calls while you type. |
JSDoc: types written in comments
COMMENTS AS TYPESJSDoc is a way to write types inside comments. You keep your normal .js files. You add special comments that start with /**. Editors that use TypeScript can read them.
JavaScript skips comments when it runs, so these comments change nothing at runtime. To make the checker read them, put // @ts-check at the top of a file, or turn on checkJs for the whole project. The next section shows how.
// @ts-check
/** @param {number} price @returns {number} */
function withTax(price) {
return price + 2;
}
console.log(withTax(3));Line 2 tells the checker two things: price is a number, and the function returns a number. Line 4 adds a fee of 2, so the call on line 6 prints 5. The comment doesn't change how the code runs. If you call withTax("3"), JavaScript still joins the text and prints 32, unless your code checks the input.
Each row of the next table is one JSDoc tag: what you write, what it does, and an example.
| What you write | What it does | Example |
|---|---|---|
@param {Type} name | Gives a function parameter a type. | @param {number} price |
@returns {Type} | Gives the value a function returns a type. | @returns {string} |
@type {Type} | Gives a variable, value, or property a type. | /** @type {CourseCard} */ |
@typedef + @property | Names an object shape, so you can use it again. | title, lessons, and an optional status |
[name] or [name=default] | Marks a parameter or property as optional, with a default value. | @param {number} [limit=10] |
A | B | A union: the value can be type A or type B. | string | number |
@template T | Makes a generic: T stands for whatever type the caller passes. | first<T>(items): T | undefined |
@import {Type} from "./file" | Brings in a type name for comments only. Needs TypeScript 5.5 or newer. | No code is loaded at runtime. |
Name an object shape, with optional and either-or types
The next example uses a few new tags and words:
@typedefgives a name to an object shape. A shape is the list of properties an object has, with their types. Here the name isCourseCard.@propertyadds one property to that shape.- Square brackets, like
[status="draft"], mark a property as optional. The part after=is its default value. string | numberis a union: the value can be a string or a number.
// @ts-check/** * @typedef {object} CourseCard * @property {string} title * @property {number} lessons * @property {"draft" | "published"} [status="draft"] */ /** * @param {CourseCard} course * @param {string | number} [prefix="Course"] * @returns {string} */function summarize(course, prefix = "Course") { const label = typeof prefix === "number" ? "#" + prefix : prefix; return label + ": " + course.title + " (" + course.lessons + ")";} console.log(summarize({ title: "Types", lessons: 4, status: "published" }, 7));Read it line by line:
- Lines 3 to 5 name the shape
CourseCard: a stringtitleand a numberlessons. - Line 6 adds an optional
status. It can only be"draft"or"published". - Line 10 says
coursemust have theCourseCardshape. - Line 11 says
prefixis optional, and it can be a string or a number. - Line 15 uses
typeofto find out which one it got. A number such as7becomes"#7". - Line 19 passes
7, so it prints#7: Types (4).
A union only lists what a value might be. Your code still has to check which one it is, as line 15 does.
Keep the item type with @template
A generic function works with many types and remembers which one it got. @template T makes a stand-in name, T, that means "whatever type the caller passes". Here, T is the type of the array's items. The function returns an item of that same type.
first function in JSDoc// @ts-check/** * @template T * @param {T[]} items * @returns {T | undefined} */function first(items) { return items[0];} const firstName = first(["Ada", "Lin"]);console.log(firstName);Line 4 says items is an array of T. Line 11 passes an array of strings, so here T is string. The first item is Ada, so line 12 prints Ada.
Line 5 says the result is T | undefined. So the checker treats firstName as string | undefined. Why undefined? Because an array can be empty:
// @ts-check
/** @template T @param {T[]} items @returns {T | undefined} */
function first(items) {
return items[0];
}
console.log(first([]) ?? "No items");Line 6 calls first([]). Line 4 returns items[0], which is undefined for an empty array. The ?? operator gives a fallback value when the left side is undefined or null, so line 6 prints No items. The generic keeps the item type. It doesn't promise that an item exists.
Import a type without importing code
Sometimes a type is written in another file. TypeScript 5.5 added the JSDoc @import tag for this. It brings in a type name for your comments only. It loads no code at runtime.
An older way also works: write {import("./types").Course} inside the comment. @import is easier to read when several comments use the same type.
This example needs a second file, types.d.ts, where the type is written. The editor on this page can't check that file, so this block is for reading only.
// @ts-check/** @import {CourseFromTypes} from "./types" */ /** @type {CourseFromTypes} */const importedCard = { title: "JSDoc", lessons: 2 }; console.log(importedCard.title);Line 2 imports the type name CourseFromTypes. Line 4 uses it as the type of importedCard on line 5. Line 7 prints JSDoc.
You don't have to write every type. The checker can often work out a type by itself from the values. This is called inference. The panel below shows the types the real checker worked out for some of the examples in this lesson.
firstName on line 11string | undefinedimportedCard on line 5CourseFromTypesEach type here comes from the real TypeScript checker, which this lesson's test runs. None of them is a guess.
Turn on checkJs
REAL DIAGNOSTICSJSDoc comments help only when the checker is running. There are two ways to turn it on:
- For one file: put
// @ts-checkon the first line. - For the whole project: set
allowJsandcheckJstotruein ajsconfig.jsonortsconfig.jsonfile.
Also set strict to true. It turns on extra checks for values that may be missing, such as undefined and null. It also makes the unknown type, which you'll meet later, much more useful.
Now the checker can catch withTax("3") before it prints 32. The JSDoc says price is a number, but that call gives it a string.
A warning in your editor is easy to miss. So also run tsc --noEmit in continuous integration (CI): the automatic checks that run on every change before it ships. tsc is the TypeScript checker, and --noEmit means "check only, don't write any files".
Avoid // @ts-nocheck. It turns off checking for the whole file. Use it only for a short time, while you fix the file.
{ "compilerOptions": { "allowJs": true, "checkJs": true, "strict": true, "noEmit": true }, "include": ["src/**/*.js"]}Line 3 includes JavaScript files. Line 4 reports errors in them. Line 5 turns on the stricter checks.
An error message from the checker is called a diagnostic. Each one has a code such as TS2722, a line number, and a message. Pick an example below to see its real diagnostic.
// @ts-check/** * @typedef {{ title: string, onComplete?: () => void }} Lesson */ /** @param {Lesson} lesson */function finishLesson(lesson) { lesson.onComplete();} Cannot invoke an object which is possibly 'undefined'.
Pick an example to see what the checker reports for it. The page shows saved results. This lesson's test runs the real TypeScript 5.9 checker and makes sure each code, line, and message matches.
The big change is when you find the mistake. Without a checker, the missing callback throws only at runtime. With a checker, TS2722 shows up in your editor before you open the browser. With a named object shape, the checker also catches a property name that looks right but doesn't exist, such as lessonCount.
TypeScript at a glance
TYPES ARE REMOVEDTypeScript is JavaScript plus a way to write types in the code itself, plus a checker. Its files end in .ts. You write a type after a colon. This is called an annotation:
const price: number = 3gives a variable a type.function greet(user: User): stringgives a parameter a type, and says the function returns a string.
You don't need to annotate everything. TypeScript works out many types from the values, as the checker did for JSDoc. There are two ways to give a type a name:
- A
typealias can name any type: an object shape, or a union such asstring | number. - An
interfacenames only an object shape. You can extend it later.
Many teams use both, and pick whichever reads more clearly.
type User = { name: string };
function greet(user: User): string {
return "Hello, " + user.name;
}
console.log(greet({ name: "Asha" }));Run this in a TypeScript project, not in this page's editor. Line 1 names the shape User: an object with a string name. Line 2 says greet takes a User and returns a string. Line 3 reads the name. Line 5 prints Hello, Asha.
Writing interface User { name: string } would name the same shape. But a union such as string | number needs a type alias.
The next example uses a union of three object shapes. Each shape has a status property with a different fixed value, so status tells the shapes apart. Narrowing means the checker learns more about a value from an if check. Inside the if, it knows which shape you have.
type ApiState = | { status: "loading" } | { status: "ready"; user: { name: string } } | { status: "error"; message: string }; function label(state: ApiState) { if (state.status === "ready") { const readyState = state; return readyState.user.name.toUpperCase(); } if ("message" in state) { return state.message; } return "Loading";} const result = label({ status: "ready", user: { name: "Ada" } });console.log(result);- Lines 1 to 4 say an
ApiStateis loading, ready, or error. - Line 7 checks
state.status === "ready". Inside thisif, the checker knows it has the ready shape, souserexists. - Line 11 checks
"message" in state. Theinoperator asks: "does this object have a property with this name?" Only the error shape hasmessage, so inside thisifthe checker knows it has the error shape. - Line 17 passes a ready object, so line 18 prints
ADA.
In the panel in the JSDoc section, press Add the TypeScript example. It shows the types the checker found for readyState and result. This lesson's test gets them from the real TypeScript checker, so they are not guesses.
A generic function remembers a type. For example, the type of the items that go in is the type of the item that comes out.
unknown vs anyunknown makes you check a value before you use it. any turns off checking for that value.
Turns on extra checks for missing values, such as undefined and null, and for values with no type at all.
Here is unknown in action. Line 2 reads payload.name without checking what payload is first, so the checker reports an error.
unknown must be checked before useTypeScriptfunction readName(payload: unknown) { return payload.name;} Here is a very common TypeScript error. Line 1 says lessons must be a number. Line 2 gives it the string "four", so the checker reports TS2322.
type Course = { title: string; lessons: number };const course: Course = { title: "Types", lessons: "four" }; A type assertion, written with as, tells the checker "trust me, this is a number". It doesn't change the value. In the next example the value stays a string at runtime.
as doesn't turn a string into a numberTypeScriptconst price = "3" as unknown as number;
console.log(price + 1);In a TypeScript project, line 1 claims "3" is a number, but it stays the string "3". Line 2 joins text, so it prints 31, not 4. To really turn text into a number, use Number(price) and check that the result is a real number.
TypeScript adds no checks at runtime. Browsers run only JavaScript. The editor on this site can run TypeScript examples because it removes the types first. Removing the types is called type stripping.
Node 22.18+ and Node 23.6+ also strip types by default. But:
- Node doesn't check the types.
- Node doesn't read the
pathssetting intsconfig. - Some TypeScript features turn into real JavaScript code, so they can't just be removed. Examples are enums, parameter properties, import aliases, and namespaces that hold code. These need the
--experimental-transform-typesflag or a full TypeScript tool. - Node doesn't run
.tsxfiles, and it doesn't strip types in packages insidenode_modules.
So stripping is not a type check. Still run tsc --noEmit on its own.
For example, Node 22.18+ can run the next .ts file directly. It removes : number from line 1. Then line 2 prints 4. Run it in Node, not in this page's editor.
const price: number = 3;
console.log(price + 1);: number before it runs the file. The type is gone, so it can't check anything at runtime.There is also a plan to add type syntax to JavaScript itself. TC39 is the group that designs JavaScript. Its Type Annotations proposal is at Stage 1, the earliest stage. The idea is like type stripping: JavaScript engines would skip type annotations as if they were comments, and separate tools would check them.
It would not support every TypeScript feature; enums, for example, are left out. It is only a proposal. You can't use it in browsers today.
Narrowing and real data at runtime
STEP THROUGHThe checker narrows types by reading your code. A runtime check, also called a validator, looks at the real data while the code runs. You need both where data comes into your code from outside:
- responses from
fetch; - text you read with
JSON.parse; - values saved in
localStorage; - URLs and form fields;
- messages from another window.
An API's documentation may promise a field, and the real data may still be missing it. So check the real value before you show a price:
const data = JSON.parse('{"title":"Tea"}');
const isOrder = typeof data === "object" && data !== null &&
typeof data.title === "string" && typeof data.price === "number";
console.log(isOrder ? data.price : "Invalid order");Line 1 reads data that has no price. Lines 2 and 3 check three things: data is an object, title is a string, and price is a number. The price is missing, so isOrder is false. Line 4 prints Invalid order.
Checking only that data is an object would not be enough. The price would still be missing when you show it.
price, so the check gives false. The code prints Invalid order instead of showing a missing price.Now step through a bigger validator. Pick good or broken data, and watch which branch runs.
Step through a real runtime check. Types help while you write code; this check protects the place where outside data comes in.
script
return typeof value === "object" && value !== null && typeof value.title === "string" && Array.isArray(value.lessons) && value.lessons.every((lesson) => typeof lesson === "string");} const payload = JSON.parse('{"title":"Types for JS","lessons":["JSDoc","TypeScript"]}');if (isCourse(payload)) { console.log(payload.title.toUpperCase());} else { console.log("Invalid course data");}The guard is plain JavaScript. In TypeScript you can give it a special return type, value is CoursePayload. This is called a type predicate. It tells the checker: "if this function returns true, the value has the CoursePayload shape". So the same if protects users at runtime and teaches the checker.
How can a checker find a mistake without running the code? The toy checker below shows the idea. It reads the parameters a function expects and the arguments a call passes, then compares them.
A tiny checker shows the idea: it reads facts from the code and reports problems without running the function it checks.
script
name: "enroll", params: [ { name: "student", type: "string" }, { name: "lessons", type: "number" }, ],}; function checkCall(signature, args) { const errors = []; if (args.length !== signature.params.length) { errors.push(`Expected ${signature.params.length} arguments, got ${args.length}.`); } signature.params.forEach((param, index) => { if (index < args.length && typeof args[index] !== param.type) { errors.push(`${param.name} should be ${param.type}, got ${typeof args[index]}.`); } }); return errors;} const report = checkCall(signature, ["Ada", "4"]);console.log(report.join("\\n") || "Call matches the signature.");The toy checker is tiny on purpose. It only counts arguments and compares typeof results. Real TypeScript does much more:
- it reads every file in the project;
- it reads
.d.tsfiles; - it follows imports;
- it follows
ifchecks and early returns; - it compares complex types.
The main idea is the same: it reports a bad call without running the function.
Declaration files: types for JavaScript that already exists
.D.TSA declaration file ends in .d.ts. It describes types, but it holds no working code. TypeScript uses declaration files to understand:
- JavaScript packages;
- browser APIs, such as
document; - Node APIs;
- global variables;
- older scripts.
A declaration file only describes. If it is out of date, it can say something exists when it doesn't:
const widget = {};
console.log(typeof widget.mount);Line 1 makes an empty widget. Line 2 prints undefined, even if a .d.ts file says mount exists. Keep declaration files in step with the package you installed. Test the real code too; don't trust a description that may be old.
.d.ts file only describes mount(). The real widget is empty, so typeof widget.mount prints undefined.Here is a small declaration file for an older widget module. The word declare means "this exists at runtime; I'm only describing it".
.d.ts file for an older widget moduleTypeScriptdeclare module "legacy-widget" { export type WidgetOptions = { theme?: "light" | "dark"; onClose?: () => void; }; export function mount(target: Element, options?: WidgetOptions): void;}Line 1 describes a module named legacy-widget. Line 2 names the shape of its options. Line 7 describes the mount function. None of these lines make anything at runtime.
Each row of the next table is one word you'll meet around declaration files: the word, what it means, and where you see it.
| Word | What it means | Where you see it |
|---|---|---|
.d.ts | A file that describes types for JavaScript that lives somewhere else. | Next to a package's code, or in a separate @types/name package. |
declare | Says a value, module, or global exists at runtime. This file only describes it. | declare module "legacy-widget" { ... } |
types field | Tells TypeScript where a package's main declaration file is. | "types": "./dist/index.d.ts" in package.json |
exports field | Can give a declaration file for each path a package exports. | "types": "./dist/index.d.ts" inside one export entry |
| DefinitelyTyped | A community project that publishes declaration files as @types/* packages. | npm i -D @types/node for Node's globals. |
| Declaration files from JSDoc | TypeScript can read checked .js files and write .d.ts files. | Turn on allowJs, checkJs, declaration, and emitDeclarationOnly. |
A package can tell TypeScript where its declaration files are in two ways:
- a top-level
typesfield in itspackage.json; - a
typespath inside each entry of itsexportsfield.
Some packages include no declaration files. Then a community project called DefinitelyTyped may publish them as a separate package whose name starts with @types/.
Make declaration files from JSDoc
TypeScript can also read JSDoc-typed JavaScript and write .d.ts files for you. Then people who use your JavaScript library get types too. This lesson's test does exactly that with the lesson's JSDoc code. It checks that the result matches the file shown below.
.d.ts files from checked JavaScriptJSON{ "compilerOptions": { "allowJs": true, "checkJs": true, "declaration": true, "emitDeclarationOnly": true, "outDir": "types" }, "include": ["src/**/*.js"]}Line 4 checks the JavaScript files. Line 5 turns on declaration files. Line 6 writes only declaration files, with no JavaScript. Line 7 puts them in a folder named types.
.d.ts file TypeScript wrote from the JSDocTypeScript/** * @typedef {object} CourseSummary * @property {string} title * @property {number} lessons *//** * @param {CourseSummary} course * @returns {string} */export function formatCourse(course: CourseSummary): string;export type CourseSummary = { title: string; lessons: number;}; Line 10 is the function's type: it takes a CourseSummary and returns a string. Lines 11 to 14 turn the JSDoc @typedef into a TypeScript type. The JSDoc comments are kept above them.
In real projects: pick the right tool
SORT ITReal teams rarely use types alone. Each tool has its own job:
- Types check calls and object shapes.
- Runtime checks protect data you don't control.
- A linter, a tool that checks code style and common slips, keeps the code tidy.
- Tests check that the program does the right thing.
Types are great at finding operations that can never work. Tests are great at showing that important features still work.
const data = JSON.parse('{"price":"3"}');
console.log(typeof data.price);Line 1 reads data where the price is in quotes, so it is a string. Line 2 prints string, not number. Writing a type such as { price: number } for this data, or using as, can't change the data.
So check or convert the data where it enters your code. After that check, normal TypeScript types keep the rest of the app consistent.
A gentle way to add types to a JavaScript project:
- Start with
// @ts-checkin one risky JavaScript file. - Add JSDoc where the checker can't work out a type, especially for function parameters.
- Move to
.tsfiles when type syntax is easier to read than comments. - Keep runtime checks for API data and user input, even after you move to TypeScript.
`course.lenght` when `course` has the type `{ length: number }``JSON.parse(response).lessons` from an API you don't control`button.addEventListener("click", 42)`- A user submits an empty password
- A local variable is created but never used
- A list is sorted smallest first, but the product needs largest first
Sort each situation by the tool that catches it first and reliably. Some bugs need tests or a linter, not types.
Common misconceptions
- “JSDoc always checks my JavaScript.” JSDoc is only comments until
// @ts-checkorcheckJstells TypeScript to read it. - “TypeScript checks API data for me.” Types are removed before the code runs. Data from outside still needs a runtime check.
- “
anyis a quick fix.” Sometimes you need it where outside data comes in, but it also hides mistakes. Useunknownuntil you have checked the value. - “A
.d.tsfile holds the module's code.” It only describes a module. The real code must exist at runtime. - “If Node can run my TypeScript, my types are checked.” Node only removes the types. It doesn't check them.
The next table compares three kinds of tools. Each row asks the same question about all three.
| Question | Static types | Runtime checks | Linting and tests |
|---|---|---|---|
| When does it run? | Before the code runs: in the editor, in tsc, or in CI. | While the program runs. | Linting runs before the code runs. Tests run chosen parts of the program. |
| What is it best at? | Calls, property names, missing values, and operations that can never work. | Data you don't control: from APIs, users, storage, and URLs. | Code style, accessibility rules, behavior, and features that broke again. |
| What is the warning sign? | Hiding errors with any, or JSDoc that is out of date. | Expecting typed code to check unknown data. | Testing only the normal path, or following lint rules without understanding them. |
Practice exercises
5 EXERCISESRead the code. Type exactly what the last line prints.
// @ts-check
/**
* @typedef {object} CourseCard
* @property {string} title
* @property {number} lessons
* @property {"draft" | "published"} [status="draft"]
*/
/**
* @param {CourseCard} course
* @param {string | number} [prefix="Course"]
* @returns {string}
*/
function summarize(course, prefix = "Course") {
const label = typeof prefix === "number" ? "#" + prefix : prefix;
return label + ": " + course.title + " (" + course.lessons + ")";
}
console.log(summarize({ title: "Types", lessons: 4, status: "published" }, 7));The call passes a valid course and the number 7. Line 15 turns 7 into #7, so the code prints #7: Types (4).
Type the diagnostic code that the checker reports for the unsafe call.
// @ts-check
/**
* @typedef {{ title: string, onComplete?: () => void }} Lesson
*/
/** @param {Lesson} lesson */
function finishLesson(lesson) {
lesson.onComplete();
}
TypeScript reports TS2722, because the code calls the optional callback without checking that it exists.
first print?Type the name that this JSDoc generic example prints.
// @ts-check
/**
* @template T
* @param {T[]} items
* @returns {T | undefined}
*/
function first(items) {
return items[0];
}
const firstName = first(["Ada", "Lin"]);
console.log(firstName);The function returns items[0], so it prints Ada. The checker still calls the type string | undefined, because an array can be empty.
Predict what this JavaScript version of the ready branch prints.
const state = { status: "ready", user: { name: "Ada" } };
function label(state) {
if (state.status === "ready") {
return state.user.name.toUpperCase();
}
if ("message" in state) {
return state.message;
}
return "Loading";
}
console.log(label(state));The ready branch returns state.user.name.toUpperCase(), so it prints ADA.
A JavaScript package doesn't include its own types. Which package name prefix usually gives you declaration files written by the community?
npm i -D @types/nodeCommunity declaration packages use the @types prefix. The DefinitelyTyped project looks after them.
Check your understanding
8 QUESTIONSQuestion 1 of 8What does a static type checker do for JavaScript?
Choose an answer to see the explanation.
Question 2 of 8Which real diagnostic does the checker report for this JavaScript?
Read the code, then predictJavaScript// @ts-check /** * @typedef {{ title: string, onComplete?: () => void }} Lesson */ /** @param {Lesson} lesson */ function finishLesson(lesson) { lesson.onComplete(); }Choose an answer to see the explanation.
Question 3 of 8What does the JSDoc object-shape example print?
Read the code, then predict// @ts-check /** * @typedef {object} CourseCard * @property {string} title * @property {number} lessons * @property {"draft" | "published"} [status="draft"] */ /** * @param {CourseCard} course * @param {string | number} [prefix="Course"] * @returns {string} */ function summarize(course, prefix = "Course") { const label = typeof prefix === "number" ? "#" + prefix : prefix; return label + ": " + course.title + " (" + course.lessons + ")"; } console.log(summarize({ title: "Types", lessons: 4, status: "published" }, 7));Choose an answer to see the explanation.
Question 4 of 8Which settings turn on checking for every JavaScript file in a project?
Choose an answer to see the explanation.
Question 5 of 8What does this JavaScript version of the narrowing example print?
Read the code, then predictconst state = { status: "ready", user: { name: "Ada" } }; function label(state) { if (state.status === "ready") { return state.user.name.toUpperCase(); } if ("message" in state) { return state.message; } return "Loading"; } console.log(label(state));Choose an answer to see the explanation.
Question 6 of 8Why use
unknowninstead ofanyfor data from outside your code?Choose an answer to see the explanation.
Question 7 of 8What is true about Node's built-in TypeScript type stripping?
Choose an answer to see the explanation.
Question 8 of 8What does a declaration file do?
Choose an answer to see the explanation.
Key takeaways
- Static type checking reads code before it runs. It turns many runtime crashes into warnings in your editor.
- JSDoc plus
checkJsadds types to.jsfiles without changing how they run. - TypeScript adds annotations, inference, unions, narrowing, generics, and strict checks. Then the types are removed before the code runs.
unknownmakes you check outside data before you use it.anyturns checking off for that value.- Declaration files describe JavaScript that exists somewhere else. Runtime checks still protect data you don't control.
Remember the one-liner.
Use types to catch code mistakes early, and use runtime checks to make sure outside data is safe.
Up next: Runtimes, the places where JavaScript runs.