6 min read
CS Fundamentals Index
Tier 1 -- Foundations
Object-Oriented Programming
Tier 2 -- Core Concepts
Tier 3 -- Debugging & Tradeoffs
CS Fundamentals Index
Tier 1 -- Foundations
Object-Oriented Programming
Tier 2 -- Core Concepts
Tier 3 -- Debugging & Tradeoffs
Modules & Imports
1. What Is It?
A is a file (or package) of related code you can pull into another file by name. Imports are the mechanism for doing that pulling. Without modules, every program is one giant source file, nothing is reusable, and naming collisions are inevitable.
The concept solves three problems at once: reuse (one math library, used by everyone), namespacing (your parse and mine don't collide), and (exposing a deliberate API while hiding the rest).
Two backend engineers at the same company each write a utility function called parse() in their respective services. When both functions are eventually needed in a shared codebase, there is no naming conflict. Which property of the module system is responsible for preventing this collision?
2. How It Works
At its core, importing does two things:
- Find and load the — locate the file on disk, compile/parse it, execute any top-level code.
- Bind names into the importing so you can refer to the 's contents.
# Python — file math_utils.py PI = 3.14159 def area(r): return PI * r * r
# Python — in another file import math_utils # load, bind 'math_utils' print(math_utils.area(2)) from math_utils import area, PI # bind specific names from math_utils import area as compute_area # rename import math_utils as mu # whole-module alias
// Java — file: src/geom/Circle.java package geom; public class Circle { public static double area(double r) { return Math.PI * r * r; } }
// Java — elsewhere import geom.Circle; // make Circle visible by short name double a = Circle.area(2); import static geom.Circle.area; // bring the static method itself into scope double b = area(2);
// C++ — header file: circle.h #pragma once double area(double r); // circle.cpp #include "circle.h" double area(double r) { return 3.14159 * r * r; } // main.cpp #include "circle.h" int main() { return area(2); }
#include is not import
C++'s #include is a preprocessor directive that pastes the header file's text into the including file. The compiler then compiles the combined result. This is why you need include guards (#pragma once or #ifndef ... #define ...) — without them, a header included twice defines everything twice.
Modern C++ (modules introduced in C++20; import std; standardized in C++23) provides real modules that behave more like Python/Java — but #include remains standard in most codebases today.
Python import mechanics
When Python sees import foo:
- Check
sys.modules— already loaded? Return the cached module. - Search
sys.path(a list of directories) forfoo.pyorfoo/package. - Execute the module's top-level code once; cache in
sys.modules. - Bind
fooin the current namespace.
Top-level code runs at import time, not just when you call a function inside. Print statements, side effects, network calls — they all fire the first time anyone imports the module.
A backend developer writes a Python module that establishes a database connection at the top level of the file (outside any function). Another module imports it twice in the same process. How many times will the database connection be established?
3. What You Actually Need to Know
- Import once, used everywhere. Python caches modules in
sys.modules. A secondimport foois a dict lookup, not a re-execution. This is also why editing a isn't seen by a running interpreter — you need to restart or useimportlib.reload. - Avoid
from import *. It dumps every public name into your , causing collisions and making it impossible to trace where a name came from. Prefer explicit imports. - Circular imports happen when
a.pyimportsb.pyandb.pyimportsa.py. Python partially loads one, hits the second, and you get a half-initialized module with some names missing. The fix is usually restructuring (move shared code into a third module) or deferring the import to inside a function. - Package vs. module. A module is a single file. A package is a directory containing an
__init__.py(Python) or a namespace (Javapackage, C++namespace). Packages let you group related modules and nest them. - Java's package = directory. Package
com.example.geommust live incom/example/geom/. The compiler enforces this. - C++ header/implementation split. Headers declare what exists (signatures, definitions).
.cppfiles implement them. Headers are for everyone who uses your code; implementations get compiled separately and linked. - Debugging clues:
ModuleNotFoundError/ImportError(Python) → wrong path, missing__init__.py, or name collision with a local file.ClassNotFoundException(Java runtime) → wasn't on the classpath.- C++ linker error
undefined reference→ you declared something in a header but never compiled/linked its implementation. - Surprising side effects at startup → module's top-level code runs on import; move heavy work into functions.
Your Python web application imports a configuration module at startup. A teammate edits that module to update a database URL, but the running server still uses the old URL. What is the most likely reason?
4. Language Differences
| Aspect | Python | Java | C++ |
|---|---|---|---|
| Unit | Module (file) or package (directory) | Class (usually one per file) in a package | Header + translation unit; C++20 modules |
| Import syntax | import, from ... import | import ...; (at file top) | #include "..." or import ...; (C++20) |
| Loaded at runtime or compile time? | Runtime | Compile + runtime (class loader) | Preprocessing (for #include); compile/link |
| Can partially import? | Yes (from m import foo) | Yes (import pkg.Foo) | No — #include pastes the whole file |
| Name pollution risk | from m import * | import pkg.*; | Every symbol in the header |
| Circular import behavior | Partially-loaded module | Compile error or runtime NoClassDefFoundError | Linker / duplicate-definition errors; needs forward declarations |
Python's "execute at import" model is the most surprising of the three — a isn't just declarations; it's a script that runs the first time it's imported.
A backend developer is building a shared utilities library and notices that after switching from Python to C++, adding a new helper function to a shared header file causes a significant increase in compile times across many unrelated modules. Which fundamental difference in how C++ handles #include versus Python's import best explains this problem?
5. Tradeoffs & Decisions
- Explicit imports vs. star imports. Explicit is almost always better — grep-ability, no collisions, IDE autocomplete. Star imports are OK for a small, well-known utility or inside a REPL.
- Aliasing.
import numpy as npis a convention that saves typing and makes code portable across projects. Aliasing is good when the full name is long or clashes. - Import location: top of file vs. inside function. Top-of-file is standard — it's clear what the file depends on. Deferred (inside-function) imports are justified only for resolving circular imports or avoiding expensive loads at startup.
- Package boundaries as API contracts. What's exported vs. internal matters. Python has
__all__and leading underscores as conventions. Java haspublic/package-private/private. C++ separates.h(public) from.cpp(internal) and uses anonymous namespaces for file-local functions. - Dependency depth. A that transitively imports thousands of lines takes longer to load and is a maintenance liability. Keep imports shallow where possible.
If you see a circular import, it usually means two modules should be one, or a third module should hold their shared code. You'd choose deferred imports only when restructuring isn't possible.
A backend service imports a heavy analytics library at the top of its main module, but that library is only used in one rarely-called admin endpoint. Startup time is noticeably slow, and the team can't easily restructure the code. What is the most justified approach?
6. Interview Cheat Sheet
- A is a reusable unit of code (file or package); imports bind names from that into your current .
- Python imports execute the module's top-level code once and cache the result in
sys.modules— subsequent imports are cheap. - C++
#includepastes text; it's a preprocessor operation, not a true import. Hence header guards and the header/.cppsplit. - Circular imports happen when two modules need each other at load time; fix by restructuring or deferring.
- Avoid
from m import *— it pollutes the namespace and hides the source of names. - Package boundaries are your API contract — be deliberate about what's public vs. internal.
Follow-ups:
- "What does
importactually do in Python?" — Locate the module onsys.path, execute its top-level code once, cache insys.modules, and bind the module name in the caller's namespace. - "Why do C++ headers need include guards?" —
#includepastes the header text; without guards, including a header transitively twice would redefine everything and cause a compile error. - "How do circular imports break?" — The first module is partially loaded when the second tries to use it, so some names it needs don't yet exist.
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.