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 — 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);#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. - Add the new module to
sys.modules, then execute its top-level code once. - 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.
Load the module, then bind names
The first import runs math_utils once and binds its name. Later imports reuse the cached module and bind new names.
The first import runs the top-level code in math utils, then binds the module name.
Text version
- The first import runs the top-level code in math utils, then binds the module name.
- The module name lets this file call area with a radius of two.
- An explicit import binds area and PI directly in the current scope.
- The alias calc refers to the same function under a new name.
- The alias mu refers to the cached module, so its top-level code does not run again.
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 loader and build tools rely on this layout (javacitself does not reject a source file in another directory). - 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) → class 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 | Allowed; circular static initializers can see default (uninitialized) values | 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.
Import mechanisms differ across languages
Python loads modules at runtime, Java imports resolve names at compile time, and C++ includes paste header text.
Python uses modules and packages, Java uses classes in packages, and C++ separates headers from source files.
Text version
- Python uses modules and packages, Java uses classes in packages, and C++ separates headers from source files.
- Python runs imports at runtime, Java checks imports at compile time and loads classes at runtime, and C++ includes run before compilation.
- Python and Java can select names, while a C++ include pastes the whole header text.
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.
Choose where a Python import belongs
In Python, keep imports at the top by default; defer expensive loads or use a deferred import when circular dependencies cannot be restructured.
In Python, keep imports at the top by default so readers can see what the file depends on.
Text version
- In Python, keep imports at the top by default so readers can see what the file depends on.
- An import inside a function can defer an expensive load until that function needs it.
- For a circular import, restructure the modules first, and defer the import only when restructuring is not 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, add it tosys.modules, execute its top-level code once, 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.