Imports and Modules

Every example in this level fit on one screen. Real projects are thousands of files, and the module system is what keeps that from being chaos: each file is a module that keeps its internals to itself and exports the handful of things it wants to share; every other file imports what it needs by name. This is the last building block of Level 1, and it completes the picture of how real codebases hang together.

Think of a workshop wall of labelled toolboxes. Each toolbox is a file: the measuring tools in one, the cutting tools in another. When a job needs a tape measure, you fetch it from the right box by name, use it, and put it back. You do not tip all the boxes onto the floor. An import is precisely that fetch: from this box, give me this tool.

Exports are the doors

A module decides what it shows. The functions and values it exports are its public face; everything else, helper functions, internal counters, scratch variables, is private machinery that no other file can name. This is scope, from the previous lesson, promoted one level up: file scope for the internals, an explicit doorway for the exports.

That doorway is a contract. Callers depend only on the exports, so a module is free to gut and rewrite its private internals at any time, and no importer notices as long as the exports keep behaving. The same isolation argument that justified functions justifies modules at project scale.

Imports name what they need

On the consuming side, an import brings a module's exports into the current file, either under the module's name (utils.double, keeping the origin visible at every call site) or picked out individually. Import lines cluster at the top of a file, and experienced readers exploit that: the import list is a one-glance inventory of what a file talks to, network, files, cryptography, before reading a single line of logic.

Two panes in the switcher deserve a second look. Go makes visibility part of the name itself: Double with a capital D is exported, double with a lowercase d is private, no keyword needed. And Lua's module is nothing but a returned table, the key-value structure from earlier in this level doing one more job.

Modules also solve name collision at scale. Two modules can each export a function called parse, and importers keep them straight because each arrives inside its own namespace: json.parse versus config.parse. Without namespaces, every name in a project would compete in one global arena, which is exactly the disease you watched in the scope lesson's one-scope playground, at a thousand times the size.

What shipping does to module structure

Here is the part that matters for the rest of this course. Module structure is a development-time luxury, and shipping often flattens it. JavaScript bundlers famously merge hundreds of files into one for the browser. Scripts for embedded platforms commonly ship as a single file with every helper pasted in. Compiled languages fold modules into one binary, keeping just enough linking information to work.

But flattening is not erasure. The seams survive: a bundled JavaScript file still contains each module as a recognisable unit inside the bundler's wrapper, and a binary still carries an import table naming every external library function it calls, because the operating system needs that list to wire the program up at load time. A reader can recover a surprising amount of architecture from seams alone, which file called into which, what third-party code is present, where the cryptography lives.

That last fact cuts deep in reverse engineering. Calls that cross into the operating system or a standard library cannot be renamed away, because the other side of the door is not yours to change: a call to a system networking function must still name that function, whatever you did to the rest of the file. Those unrenamable edges become fixed landmarks in otherwise obfuscated code, and Level 5 has an entire lesson on reading a program through its imports alone. The honest statement of the limit: obfuscation controls the interior of your code, but every doorway to code you do not own keeps its public name.

Modules split programs across files, exports draw the public/private line, imports name exactly what crosses it, and namespaces keep a thousand files from fighting over one name. Shipping may flatten the structure, but the seams and the doorways to external code survive, and both sides of the protection game read them. That completes Level 1: you now hold every building block real programs are made of.

Frequently asked questions

What is the difference between a module, a package, and a library?

A module is usually one file with exports. A package is a group of modules distributed together, and a library is any code you pull in rather than write, often arriving as a package. The boundary mechanics, public doorway and private interior, are the same at every size.

I finished Level 1. What should I take into Level 3?

Three things: names are for humans and the machine never needs them, structure like scope and module boundaries is a map that tools on both sides must build, and everything you write is text until something runs it. Level 3 goes below that text: tokens, syntax trees, and bytecode, the forms your code actually takes on its way to running.

Keep learning

  • Scope: Where Names Live
  • Programming Fundamentals, One Idea in Seven Languages
  • From Text to Tokens: Lexing
  • Imports and API Calls Tell the Story
  • JavaScript Obfuscator
  • Go Obfuscator
  • Python Obfuscator

All lessons