Python is one of the most widely used languages on the planet, and that didn't happen by accident. A specific set of design choices, clean syntax, dynamic typing, a giant standard library, free distribution, made it the default pick for scripting, data work, backends, automation, and AI. This lesson walks through those features one by one. Treat it as a tour.
Each branch is a separate design goal of the language or its runtime. Some are language-level (syntax, type system, paradigms). Some live in the CPython implementation (memory management, the interpreter). Others are really about distribution and tooling (the standard library, PyPI, the open-source license). Put them all together and you get what people mean when they say "Python".
Python's most obvious feature is what's missing from it: braces and semicolons. Blocks of code aren't wrapped in { and }, and statements don't end in ;. Instead, indentation defines structure. A line that's indented under an if belongs to that if. A line that drops back to the previous indentation level ends the block.
Here's a tiny example. Notice there are no braces, no semicolons, and the body of the loop is just the indented lines below it.
Why this matters: indentation is something you'd format anyway in a brace-based language to make the code readable. Python made it the actual syntax, which means the code you read on a screen and the code the interpreter sees are the same shape. There's no room for the indentation to lie about what's going on. The trade-off is that mixing tabs and spaces, or losing a level of indentation in a copy-paste, becomes a syntax error instead of a style nit.
Python is dynamically typed, which means you don't declare a type when you create a variable. The same name can be bound to an integer one moment and a string the next. The interpreter figures out the type at runtime by looking at the object the name refers to.
That flexibility is what makes Python so quick to prototype in. You don't write type signatures up front. The downside is that mistakes which would be caught at compile time in a statically typed language show up only when the bad line actually runs. Type hints with tools like mypy and pyright add optional static checking on top of Python, giving you the prototyping speed and the safety net.
Python is also strongly typed, which is a separate property and often gets confused with the first one. Strongly typed means Python won't silently coerce values of one type into another when you mix them in an expression. Adding "5" + 5 doesn't quietly turn the string into a number or the number into a string. It raises a TypeError.
Why this matters: you get the convenience of not declaring types, but you don't get the kind of silent string-to-number coercion that hides real bugs in some other languages. The price you pay is that explicit conversions (int(x), str(x), float(x)) are your job, not the interpreter's.
Python is interpreted from your point of view as a developer. You write code in a .py file, you run it, and it executes. There's no separate compile step in your workflow, no makefile, no build artifact you ship instead of the source. The same script you save is the script that runs.
Under the hood, CPython does compile your source to bytecode and then runs that bytecode on a virtual machine, but that's an implementation detail you don't have to think about most days.
Why this matters: the edit-run loop is fast. Change a line, hit run, see the result. That tight loop is a huge part of why Python feels productive, especially in data work, scripting, and exploratory programming where you're trying many small variations.
Python doesn't force you into one programming style. It supports three major paradigms, and most real Python code mixes them.
Procedural is the simplest: write a sequence of statements and functions that act on data. Most short scripts look like this. Read a file, transform some values, print a result.
Object-oriented is where you model things as classes that hold data and behavior together. A Product class with a name and price, a Cart class that holds products and computes totals, an Order class with a status. Everything in Python is actually an object under the hood (integers, strings, functions, even classes themselves), so OOP isn't a bolt-on.
Functional features include first-class functions, lambdas, map, filter, reduce, list comprehensions, and generators. You can write whole programs in a mostly functional style by transforming streams of data through small composable functions.
Why this matters: you pick the style that fits the problem instead of bending the problem to fit the language. A one-off data cleaning script can stay procedural. A large e-commerce backend benefits from objects. A pipeline that transforms records works well with comprehensions and generators. Python lets you mix all three in the same file without ceremony.
You never call malloc or free in Python. The interpreter handles memory for you. There are two pieces to that.
The first is reference counting. Every object tracks how many references currently point to it. When you assign a list to a name, the count goes up. When that name goes out of scope or gets reassigned, the count goes down. The moment the count hits zero, the object's memory is reclaimed right away. Reference counting is fast and predictable, but it can't handle one case on its own: cycles, where two or more objects reference each other in a loop.
That's where the second piece comes in: a cyclic garbage collector that periodically scans for unreachable cycles and breaks them. Together, reference counting and the cyclic collector reclaim everything you stop using, without you writing any cleanup code.
Why this matters: whole categories of bugs that plague C and C++ codebases (use-after-free, double-free, leaked memory) effectively don't exist in normal Python. You think about your domain (products, orders, carts) instead of tracking who owns which chunk of memory. weakref and __slots__ are available for the cases where you do want more control.
Python ships with a huge standard library. The phrase "batteries included" was coined to describe it, and the idea is that for most everyday tasks, you don't need to install anything extra. The right module is already there.
A small sample of what comes preinstalled:
| Module | What It Does |
|---|---|
os | Talk to the operating system: paths, environment variables, processes |
json | Read and write JSON, which is how most web APIs exchange data |
datetime | Dates, times, durations, formatting, parsing |
re | Regular expressions for pattern matching in strings |
collections | Extra container types like Counter, defaultdict, deque, OrderedDict |
csv | Read and write CSV files |
pathlib | Modern path handling that works the same on Windows, macOS, and Linux |
unittest | A built-in unit testing framework |
urllib | Open URLs and make basic HTTP requests without a third-party library |
That's a fraction of it. The standard library is the reason a fresh Python install can already parse a CSV of orders, hit a JSON API, format a timestamp, and write the result to a file without you adding a single dependency.
When the standard library doesn't have what you need, the Python Package Index does. PyPI hosts over 500,000 third-party packages, and pip, the package installer that ships with Python, is how you pull them into a project.
A small slice of what people install from PyPI on a typical day:
| Package | Used For |
|---|---|
requests | Friendly HTTP client for calling REST APIs |
numpy, pandas | Numerical and tabular data work |
flask, fastapi, django | Web frameworks for building backends |
pytest | Popular alternative to the built-in unittest |
sqlalchemy | Talking to relational databases from Python |
pydantic | Validating and parsing structured data with type hints |
Why this matters: most of the work of building a real application is glue, talking to databases, calling APIs, serving HTTP, validating input. PyPI means you almost never have to write that glue from scratch. You install the well-tested package and focus on what's specific to your problem.
The same .py file runs on Windows, macOS, and Linux as long as a compatible Python interpreter is installed. There's no recompilation step when you move a script from your laptop to a server. The interpreter is what's platform-specific, not your code.
That portability shows up in the standard library too. Modules like pathlib, os, and subprocess paper over the differences between operating systems so the same code reads files, joins paths, and spawns processes on all three. You'll still hit OS-specific edge cases now and then (line endings, file permissions, executable names), but the everyday surface is the same everywhere.
Why this matters: writing a deployment script, a data pipeline, or a backend service in Python means one codebase covers everyone on your team regardless of what they develop on, and the same code runs in production.
Python is free to download, free to use commercially, and the entire source of CPython (the reference implementation) is open. It's released under the Python Software Foundation License, a permissive license that lets you use Python in proprietary software without paying anyone or giving up your own source code.
The PSF, a non-profit, stewards the language. Development happens in public on GitHub. Language changes go through a public process called PEPs (Python Enhancement Proposals), which anyone can read and many can contribute to. There's no vendor that can decide to stop supporting Python or lock it behind a paywall.
Why this matters: long-lived software needs a stable foundation. A language owned by a single company can change direction or charge for what used to be free. Python's governance and license make it a safe bet for projects that need to run for years or decades.
Type python at a terminal and you get a prompt. That's the REPL, short for read-eval-print loop. You type an expression, it evaluates, and it prints the result. Then it waits for the next one.
The REPL is how you test ideas, inspect data, and learn the language without setting up a project. It also makes it cheap to experiment. If you're not sure what a method returns or how a function behaves, you don't have to read the docs and guess. You call it and see.
Why this matters: a tight feedback loop is one of the strongest tools you have when you're learning a language or debugging something tricky. Tools like IPython and Jupyter are evolved versions of the same idea, and they're the default working environment in data science for the same reason.
In Python, functions are objects. You can assign a function to a variable, pass it as an argument to another function, return it from a function, and store it in a list or dict. Nothing about a function is second-class compared to an int or a string.
apply_discount and discount now point to the same function object. Either name calls it. The same idea is what makes map, filter, sorted(key=...), decorators, and callbacks possible. You hand a function to something else and let it call you back.
Why this matters: a lot of Python's expressiveness comes from this property. Decorators wrap functions with other functions. Higher-order utilities like sorted(orders, key=lambda o: o.total) only work because the key argument can be a function. Callback-driven libraries lean on it constantly.
Python is extensible, meaning you can write performance-critical pieces in C or C++ and call them from Python. The CPython interpreter exposes a C API for exactly this. That's how numpy does fast array math, how cryptography wraps OpenSSL, and how many database drivers reach native libraries. From your Python code, those packages look like normal modules; underneath, they're C extensions.
It's also embeddable in the other direction: a C or C++ application can embed the CPython interpreter and expose its own objects to Python scripts. Game engines, 3D modeling tools (Blender), and scientific applications often do this to give power users a Python scripting layer on top of native code.
Why this matters: Python doesn't have to be the fastest language in the world for the slow parts to be a problem. The escape hatch is that you can drop into C for a hot loop and stay in Python for everything else. Most of the scientific Python stack is built this way: a thin, friendly Python surface over heavily optimized native code.
| Feature | What It Means | Why It Matters |
|---|---|---|
| Readable Syntax | Indentation-based blocks, no braces or semicolons | Code is consistent and visually clear by default |
| Dynamically Typed | Variables don't declare a type | Less boilerplate, faster prototyping |
| Strongly Typed | No silent coercion between unrelated types | Catches real bugs that languages with loose coercion hide |
| Interpreted | Run .py files directly, no build step in your workflow | Tight edit-run-debug loop |
| Multi-Paradigm | Procedural, object-oriented, and functional all supported | Pick the style that fits the problem, not the language |
| Automatic Memory Management | Reference counting plus a cyclic garbage collector | No manual free, no use-after-free, no double-free |
| Batteries Included | Large standard library covers everyday tasks | Many real programs need zero third-party dependencies |
| Huge PyPI Ecosystem | Over 500,000 packages installable with pip | The glue work is mostly already written |
| Portable | Same code runs on Windows, macOS, and Linux | One codebase for the whole team and production |
| Free and Open Source | PSF License, open development on GitHub | Safe foundation for long-lived software |
| Interactive REPL | Type code at a prompt, see results immediately | Cheap experimentation and debugging |
| First-Class Functions | Functions are values you can pass around | Enables decorators, callbacks, higher-order utilities |
| Embeddable and Extensible | Drop into C for hot paths, embed Python in native apps | Performance escape hatch when pure Python isn't fast enough |
7 quizzes