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).

Quick check

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?

Choose one answer

2. How It Works

At its core, importing does two things:

  1. Find and load the — locate the file on disk, compile/parse it, execute any top-level code.
  2. 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:

  1. Check sys.modules — already loaded? Return the cached module.
  2. Search sys.path (a list of directories) for foo.py or foo/ package.
  3. Add the new module to sys.modules, then execute its top-level code once.
  4. Bind foo in 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
  1. The first import runs the top-level code in math utils, then binds the module name.
  2. The module name lets this file call area with a radius of two.
  3. An explicit import binds area and PI directly in the current scope.
  4. The alias calc refers to the same function under a new name.
  5. The alias mu refers to the cached module, so its top-level code does not run again.
Quick check

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?

Choose one answer

3. What You Actually Need to Know

  • Import once, used everywhere. Python caches modules in sys.modules. A second import foo is 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 use importlib.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.py imports b.py and b.py imports a.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 (Java package, C++ namespace). Packages let you group related modules and nest them.
  • Java's package = directory. Package com.example.geom must live in com/example/geom/. The loader and build tools rely on this layout (javac itself does not reject a source file in another directory).
  • C++ header/implementation split. Headers declare what exists (signatures, definitions). .cpp files 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.
Quick check

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?

Choose one answer

4. Language Differences

AspectPythonJavaC++
UnitModule (file) or package (directory)Class (usually one per file) in a packageHeader + translation unit; C++20 modules
Import syntaximport, from ... importimport ...; (at file top)#include "..." or import ...; (C++20)
Loaded at runtime or compile time?RuntimeCompile + 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 riskfrom m import *import pkg.*;Every symbol in the header
Circular import behaviorPartially-loaded moduleAllowed; circular static initializers can see default (uninitialized) valuesLinker / 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
  1. Python uses modules and packages, Java uses classes in packages, and C++ separates headers from source files.
  2. Python runs imports at runtime, Java checks imports at compile time and loads classes at runtime, and C++ includes run before compilation.
  3. Python and Java can select names, while a C++ include pastes the whole header text.
Quick check

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?

Choose one answer

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 np is 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 has public/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
  1. In Python, keep imports at the top by default so readers can see what the file depends on.
  2. An import inside a function can defer an expensive load until that function needs it.
  3. For a circular import, restructure the modules first, and defer the import only when restructuring is not possible.
Quick check

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?

Choose one answer

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++ #include pastes text; it's a preprocessor operation, not a true import. Hence header guards and the header/.cpp split.
  • 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 import actually do in Python?" — Locate the module on sys.path, add it to sys.modules, execute its top-level code once, and bind the module name in the caller's namespace.
  • "Why do C++ headers need include guards?" — #include pastes 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.

Reading

On this page

My notes

Sign in to keep highlights and notes.

Glossary history

Click dotted jargon to save explanations here.