Skip to content

Python Language Fundamentals#

Python is the default language for most FAANG-style coding interviews. Before patterns and algorithms, you need a precise mental model of how Python treats values, names, and types — interviewers probe this constantly in warm-up questions and follow-ups.

How to use this page

Read it once end-to-end, then revisit the Interview traps section before mock interviews. Continue with OperatorsControl FlowData Structures.

At a glance
Track Python Basics
Sections 13 major topics
Outline Use the right-hand TOC to jump

Topics: What makes Python different · How Python runs your code · Variables: names bound to objects · Core built-in types (scalars) · Dynamic typing and type hints · Mutability vs immutability · Identity (is) vs equality (==) · Truthiness and falsy values · … (+5 more)

  1. What makes Python different
  2. How Python runs your code
  3. Variables: names bound to objects
  4. Core built-in types (scalars)
  5. Dynamic typing and type hints
  6. Mutability vs immutability
  7. Identity (is) vs equality (==)
  8. Truthiness and falsy values
  9. Naming conventions (PEP 8)
  10. Comments and docstrings
  11. Indentation and code blocks
  12. Literals and basic expressions … and 1 more sections

What makes Python different#

Property What it means in practice
Interpreted Code runs via the Python interpreter; no separate compile step before execution (though bytecode is generated internally).
Dynamically typed Variable names are not tied to a fixed type — the object carries the type.
Strongly typed Python does not silently coerce incompatible types ("3" + 3 raises TypeError).
Indentation-based blocks Whitespace defines scope — not braces.
Everything is an object Integers, functions, classes, and modules are all objects with identity and type.

Python prioritizes readability and expressiveness. In interviews, that translates to writing clear, idiomatic code quickly — not fighting the language.

How Python runs your code#

When you execute a .py file or REPL input:

  1. Source code is parsed into an abstract syntax tree (AST).
  2. The AST is compiled to bytecode (.pyc cached in __pycache__/ when applicable).
  3. The Python Virtual Machine (PVM) executes bytecode instruction by instruction.
# hello.py
print("Hello, interview!")
python hello.py          # run a script
python -m pdb hello.py   # run with debugger
python -c "print(2+2)"   # one-liner

You rarely need bytecode details in interviews, but knowing that Python is not a "compile-to-machine-code" language explains startup cost and why imports matter for performance.

Variables: names bound to objects#

In Python, a variable is a label (name) attached to an object in memory — not a typed box that holds a value.

x = 10        # name x → int object 10
x = "ten"     # name x now → str object "ten" (the int may be garbage-collected)

Code & explanation

1
2
3
4
5
6
7
8
a = [1, 2, 3]
b = a           # b points to the SAME list object as a
b.append(4)
print(a)        # [1, 2, 3, 4] — both names see the mutation

c = a[:]        # c points to a NEW list (shallow copy)
c.append(99)
print(a)        # [1, 2, 3, 4] — unchanged
Explanation

  1. Assignment never copies an object by default — it binds another name to the same object.
  2. Mutating through one name affects all names referencing that object.
  3. To get an independent container, use slicing ([:]), .copy(), or copy.deepcopy() for nested structures.

This model is the root cause of most "unexpected mutation" bugs and a common interview discussion topic.

Core built-in types (scalars)#

Before collections (Data Structures) and Strings, know the scalar types:

Type Example Notes
int 42, -7, 0 Arbitrary precision — no fixed 32-bit overflow like C/Java.
float 3.14, 2.0, 1e-3 IEEE 754 double; beware precision issues.
bool True, False Subclass of int (True == 1, False == 0).
NoneType None Singleton meaning "no value" / "absent".
str "hello", 'world' Immutable sequence of Unicode code points.
type(42)          # <class 'int'>
type(3.0)         # <class 'float'>
type(True)        # <class 'bool'>
type(None)        # <class 'NoneType'>

# int does not overflow at 32 bits
big = 10 ** 100   # valid

Float precision trap#

0.1 + 0.2 == 0.3          # False
round(0.1 + 0.2, 10)      # 0.3 — compare with tolerance in production
from decimal import Decimal
Decimal("0.1") + Decimal("0.2") == Decimal("0.3")  # True

For money or exact decimal math, use decimal.Decimal or integer cents — not raw floats.

Dynamic typing and type hints#

Types live on objects, not variable names:

def process(value):
    return value * 2

process(5)       # 10
process("hi")  # "hihi"

Type hints (PEP 484) document intent and enable static analysis — they do not enforce types at runtime:

def process(value: int) -> int:
    return value * 2

process("hi")    # still runs; mypy/pyright would flag it

Use hints in interview code when they clarify contracts — especially for function signatures and collection contents (list[int], dict[str, int]). Deep coverage: Function Annotations and Type Hints.

Mutability vs immutability#

Immutable Mutable
int, float, bool, str, tuple, frozenset, bytes list, dict, set, bytearray, most user-defined objects

Why interviewers care:

  • Immutable objects can be dict keys and set elements (if hashable).
  • Passing a mutable object to a function lets the callee modify caller state unless you copy.
  • tuple containing a mutable list is hashable as a whole only if all elements are hashable — the list inside can still mutate.
key = ([1, 2],)       # tuple wrapping a list — hashable
cache = {key: "value"}
key[0].append(3)        # mutates the list inside the tuple
# key is now ([1, 2, 3],) — still the same tuple object, but contents changed

See Data Structures for when to pick each collection type.

Identity (is) vs equality (==)#

Operator Compares Use when
== Values (calls __eq__) "Do these have the same content?"
is Identity (same object in memory) "Are these the exact same object?" — mainly None, True, False
a = [1, 2, 3]
b = [1, 2, 3]
c = a

a == b    # True  — same contents
a is b    # False — different list objects
a is c    # True  — c is an alias for a

x = None
if x is None:       # preferred idiom
    print("absent")

Interview trap

Never use is to compare strings or integers (except small cached ints like -5 to 256 in CPython). Always use == for value comparison.

# CPython interns small ints — do NOT rely on this in interviews
a = 256
b = 256
a is b    # may be True or False depending on context — use ==

s1 = "hello"
s2 = "hello"
s1 is s2  # unpredictable — use ==

id(obj) returns the object's identity (memory address in CPython). Two objects with the same id are the same object.

Truthiness and falsy values#

Python evaluates values in boolean context (if, while, and, or, not):

Falsy: None, False, 0, 0.0, "", [], {}, set(), (), range(0)

Everything else is truthy.

def is_valid(name: str, items: list) -> bool:
    if not name:              # empty string → falsy
        return False
    if not items:             # empty list → falsy
        return False
    return True

# Short-circuit evaluation
result = default or compute_expensive()   # compute only if default is falsy
value = found if found is not None else fallback

and / or return the actual operand that determined the result, not strictly True/False:

0 or 42       # 42
"" or "hi"    # "hi"
1 and 2 and 3 # 3
0 and 3       # 0 (short-circuits)

Naming conventions (PEP 8)#

Consistent naming signals professionalism in live coding and take-home submissions:

Style Use for Example
snake_case Variables, functions, methods, modules user_count, find_max()
PascalCase Classes, exceptions BinarySearchTree, ValueError
SCREAMING_SNAKE_CASE Module-level constants MAX_RETRIES = 3
_leading_underscore "Internal" / non-public by convention _helper()
__double_leading Name mangling in classes (avoid in basics) __private
MAX_PAGE_SIZE = 100

class UserService:
    def __init__(self, db):
        self._db = db          # convention: internal attribute

    def get_active_users(self):
        return self._db.query("SELECT ...")

Rules of thumb:

  • Be descriptive over terse: left_index beats l (except standard loop i, j).
  • Avoid shadowing builtins: never name a variable list, dict, id, type, sum.
  • One statement per line; avoid semicolons except in one-liners on a whiteboard.

Comments and docstrings#

# Single-line comment — explain *why*, not *what* the syntax already shows

def binary_search(nums: list[int], target: int) -> int:
    """Return index of target in sorted nums, or -1 if absent.

    Args:
        nums: Sorted list of integers.
        target: Value to locate.

    Returns:
        Zero-based index of target, or -1.
    """
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if nums[mid] == target:
            return mid
        if nums[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1
  • # comments — for humans reading the code.
  • Docstrings — first string literal in a module, class, or function; accessible via help() and .__doc__.
  • In interviews, a one-line docstring on non-trivial functions is enough; skip boilerplate for trivial helpers.

Indentation and code blocks#

Python uses 4 spaces per indentation level (never mix tabs and spaces). A colon (:) starts a new block:

def classify(score: int) -> str:
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    else:
        return "C"

for i in range(3):
    print(i)

while stack:
    node = stack.pop()
    visit(node)

Blocks end when indentation returns to the previous level — there are no {}. This is non-negotiable in interviews: misaligned indentation is a syntax error.

Multi-line statements use parentheses, brackets, or backslashes — prefer implicit line joining inside ():

total = (
    base_price
    + tax
    + shipping
)

Full branching and looping patterns: Control Flow.

Literals and basic expressions#

# Numbers
decimal = 42
binary = 0b101010
hexadecimal = 0x2A
floating = 3.14
scientific = 1.5e6

# Strings (see Strings.md for formatting, slicing, methods)
single = 'hello'
double = "hello"
multi = """Line one
Line two"""

# Collections (see Data Structures.md)
nums = [1, 2, 3]
coords = (10, 20)
config = {"debug": True, "retries": 3}
tags = {"python", "faang"}

Operator precedence and behavior (//, %, **, bitwise, comparisons): Operators.

The if __name__ == "__main__" pattern#

Every Python file is a module. When executed directly, its __name__ is "__main__"; when imported, __name__ is the module path.

def solve():
    print("running solver")

if __name__ == "__main__":
    solve()

This keeps reusable functions importable without side effects. Environment setup (venv, pip, running tests): The Python Environment.

Interview traps (quick reference)#

Trap What goes wrong Safe approach
Shared mutable default def f(x=[]): — list reused across calls Use None and create inside: if x is None: x = []
Shallow vs deep copy Nested list mutated through copy copy.deepcopy() when needed
is vs == Wrong identity checks on values == for values; is for None
Float equality 0.1 + 0.2 != 0.3 Compare with epsilon or use Decimal
Truthiness on 0 if count: skips zero if count is not None: or if count > 0: when zero is valid
Shadowing builtins list = [1,2,3] breaks list() Never reuse builtin names

Code & explanation — mutable default argument

# BUG: same list object reused every call
def add_item_bad(item, bucket=[]):
    bucket.append(item)
    return bucket

print(add_item_bad("a"))  # ['a']
print(add_item_bad("b"))  # ['a', 'b']  ← surprise

# FIX
def add_item(item, bucket=None):
    if bucket is None:
        bucket = []
    bucket.append(item)
    return bucket
Explanation

  1. Default arguments are evaluated once at function definition time.
  2. A mutable default is shared across all calls that omit the argument.
  3. The None sentinel pattern creates a fresh object per call.

Details on functions, scope, and closures: Functions and Code Reuse and Closures.

Mental model checklist#

Before moving on, you should be able to answer:

  1. What happens in memory when you write b = a for a list?
  2. Why is None checked with is instead of ==?
  3. Which types can be dictionary keys?
  4. What values are falsy in Python?
  5. Does type(x) change when you reassign x to a different kind of object?

If any of these are shaky, reread the relevant section above.

What's next#

Topic Page
Arithmetic, comparison, logical operators Operators
if / for / while / match Control Flow
Lists, tuples, dicts, sets Data Structures
Slicing, formatting, string methods Strings
Functions, parameters, scope Functions and Code Reuse
try / except / raising errors Basic Error Handling
Interview algorithms & patterns Programming