Before you begin
Why this matters
Every program of more than one file is split into modules, and names travel between them. Knowing where a name belongs lets you predict which function a call runs, and read the compiler’s complaint when two names collide.
What this helps you reason about
- Predict which of two same-named functions a call runs.
- Write a qualified name or an import for a name from another module.
- Recognize a top-level name that clashes with a global name, and know why a module avoids it.
A daily health card shows two goals: 8000 steps and 5 portions of fruit. Both parts of the program want a function called goal. This lesson shows how a namespace or a module lets both keep that name, and how other code says which goal it means.
Already know this? The short version
- Each file with an import or export is a module: its top-level names belong to the file. A file with neither is a script, and its top-level names are global, so they can clash with browser globals such as name and status.
- Inside one file, namespace N { export … } groups names; N.x is the qualified name, and the namespace is an ordinary object at run time.
- import { goal } from "./steps" brings in a short name; import * as Steps from "./steps" keeps the qualifier. Inside a namespace or function, the nearest declaration of a name wins.
- Today, a TypeScript program uses one module per file; namespaces are an older way to group names.
Words in this lesson (6)
- namespace
- A named block, namespace Steps { … }, that groups declarations. Code outside it uses a declaration marked export as Steps.goal.
- qualified name
- A name written with the module or namespace it comes from, such as Steps.goal. It says which goal is meant.
- module
- A file that contains at least one import or export. Its top-level names belong to that file, and other files reach them only through an import.
- import
- A line that makes names from another module usable in this file, such as import { goal } from "./steps".
- name clash
- Two different declarations with the same name in one scope. The compiler reports it, or one name hides the other.
- script
- A file with no import and no export. Its top-level names share one global scope with every other script and with the browser’s built-in names, such as status.
Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.
01 / Modules
What does a module do with a name?
A module is a part of a program with its own space for names. Two modules can each declare goal, and the two goals never meet.
In TypeScript, every file with an import or an export is a module. The programs in this lesson are one file each, so they use a namespace instead: a named block that groups declarations. namespace Steps { … } declares a namespace called Steps. Inside it, export marks a declaration that code outside the namespace may use. Which names a module lets other code use is the subject of a later lesson, Exports, visibility, and encapsulation.
Outside the namespace, write Steps.goal: the namespace name, a dot, and the short name. That is a qualified name. It says which goal you mean.
namespace Steps {
export function goal(thousands: number): string {
return thousands * 1000 + " steps";
}
}
namespace Fruit {
export function goal(portions: number): string {
return portions + " portions of fruit";
}
}
console.log(Steps.goal(8)); // 8000 steps
console.log(Fruit.goal(5)); // 5 portions of fruit A module or namespace gives its names their own space; a qualified name says which one you mean.
Coming from Python: A Python file is always a module. A TypeScript file is a module only when it has an import or an export; otherwise its top-level names are global.
Precisely: a namespace is an object
The compiler turns a namespace into a variable that holds an object, and runs the block once to fill it. Each exported declaration becomes a property, which is why the stepper shows Steps as {goal: function goal(thousands)}. Two namespace blocks with the same name are merged into one object.
Experiment
Call two functions that share one name
Each namespace has its own goal. Steps.goal and Fruit.goal are qualified names, so each call says which goal it means. Outside both namespaces, the short name goal is not declared at all.
Why it matters Two modules can use the same short name without any clash. Outside a module, write the module’s name in front of the short name.
Predict both printed lines. Then change Fruit.goal(5) to goal(5) and predict again.
namespace Steps {
export function goal(thousands: number): string {
return thousands * 1000 + " steps";
}
}
namespace Fruit {
export function goal(portions: number): string {
return portions + " portions of fruit";
}
}
console.log(Steps.goal(8));
console.log(Fruit.goal(5));
What to look for
At the first stop, Steps and Fruit show undefined. While each namespace block runs, the frame list shows a second <top level> frame above the first: the block runs once, like a function. After it, Steps shows {goal: function goal(thousands)}, and later Fruit shows {goal: function goal(portions)}: two objects, each with its own goal. Step into each call: the frame is named goal, first with thousands = 8, then with portions = 5. The stepper sometimes stops on the same line twice; that is normal for this debugger. The run prints 8000 steps, then 5 portions of fruit. After the edit, nothing runs, and the compiler reports Line 14: Cannot find name 'goal'.
Expected output
8000 steps
5 portions of fruit02 / Qualified names
How do you use a name from another module?
There are two ways to use a name from another module. A qualified name, Steps.goal, names the module every time. An import brings the short name into your file, so you can write goal alone.
Between files, the import line says where the name comes from: import { goal } from "./steps" brings in goal from the file steps.ts. import * as Steps from "./steps" brings in the whole module under the name Steps, so the code still writes Steps.goal.
Inside a namespace, its own names need no qualifier. Code in Fruit that writes goal means Fruit’s goal, because that declaration is nearest. This is the scope rule from Scope and shadowing: the nearest declaration of a name wins.
// In a file next to steps.ts:
import { goal } from "./steps";
import * as Steps from "./steps";
console.log(goal(8)); // the short name
console.log(Steps.goal(8)); // the qualified name A qualified name always says where a name comes from; a short name means the nearest declaration or import.
Coming from Java: A TypeScript import names a file path, "./steps", not a package name. The path is relative to the importing file.
Precisely: aliases inside one file
Inside one file, import goal = Steps.goal; gives a namespace member a short name. It is TypeScript syntax, not JavaScript, and it is rare in newer code.
03 / Clashes
What happens when two names clash?
A name clash happens when two declarations with the same name meet in one scope. In a module, a clash is a compile error: declaring goal twice at the top of one file fails.
A script clashes more often. A file with no import and no export is a script, and its top-level names share one global scope with every other script and with the browser’s built-in names. The browser already has globals such as name and status. So const status = … at the top of a script fails with Cannot redeclare block-scoped variable 'status'. The same line in a module is fine, because status then belongs to the file.
export {}; exports nothing, but it makes a file a module. Real program files usually have an import or an export anyway.
export {};
const status = "8000 steps reached"; // belongs to this file
console.log(status); A file with an import or an export keeps its names to itself; a script shares them with every other script.
Precisely: where the global names come from
The browser globals come from the DOM type library, lib.dom.d.ts, which the stepper’s compiler includes. In Node.js without DOM types, status is not a global, but two scripts compiled together still share their top-level names.
Experiment
Make a file its own module
export {} exports nothing, but it makes the file a module, and status then belongs to this file. Without it, the file is a script: its top-level names share one global scope with the browser’s built-in names, which the checker knows about. The browser already has a global status.
Why it matters A file with no import and no export shares its top-level names with every script and with the browser. Real TypeScript files almost always have an import or an export, so each one is its own module.
Predict whether this program compiles. Then delete the line export {}; and predict again.
export {};
const status = "8000 steps reached";
console.log(status);
What to look for
At the first stop, status shows not initialized yet; one stop later it shows "8000 steps reached". The run prints 8000 steps reached. After the edit, nothing runs, and the compiler reports Line 1: Cannot redeclare block-scoped variable 'status'.
Expected output
8000 steps reached04 / Files
How do modules map to files and folders?
In a real TypeScript program, each file is one module. The file name is the module’s name, and an import names the file with a path. A module that other files may use exports the names they need.
namespace is an older way to group names, from before JavaScript had modules. New code uses files. You still meet namespaces in older code and in type declaration files.
// steps.ts
export function goal(thousands: number): string {
return thousands * 1000 + " steps";
}
// card.ts
import { goal } from "./steps";
console.log(goal(8)); One file, one module: the file path is how other files name it.
Coming from C#: A C# namespace spans many files and has nothing to do with file names. In TypeScript, the file itself is the module.
Precisely: modules at run time
The stepper compiles to CommonJS, where each module becomes a function that Node.js runs once, the first time it is required. Under ES modules, Node.js and browsers load each file once and share it between every file that imports it.
05 / Try it
Can you explain a fresh example?
Decide, for each use of the short name, which definition is nearest. Which names a module lets other code use is the subject of a later lesson, Exports, visibility, and encapsulation.
namespace Steps {
export const goal = 8000;
}
namespace Fruit {
export const goal = 5;
export function card(): string {
return goal + " portions, " + Steps.goal + " steps";
}
}
const goal = 2;
console.log(Fruit.card(), goal);
What is printed?
Reveal the reasoning
5 portions, 8000 steps 2. Inside Fruit, the short name goal means Fruit’s own goal, the nearest declaration. Steps.goal is qualified, so it means the goal in Steps. Outside both namespaces, goal is the top-level constant 2.
Practice tickets
- MODN-101 · Growth chart heights come out 1 cm short → Free
- MODN-102 · The allergy screen warns about substances the patient is not allergic to → Sign in · Pro
- MODN-103 · Adults with normal readings are told their pressure is high → Sign in · Pro
Keep exploring
Exports, visibility, and encapsulation →