Last-Minute Python Revision Sheet

Review core memory models, GIL restrictions, decorators, generators, collections, gotchas, and Data Engineering best practices.

15-Min Quick Read
1. Core Syntax, Unpacking & Slicing
2-Min Read
Why It Matters: Choosing the right data structure is critical for performance. For example, checking membership (in) on a list takes O(N) linear time, while on a set or dict it takes O(1) constant time, which dramatically impacts the speed of data deduplication pipelines.

Essential Syntax Examples

python
# Unpacking with asterisk operator
first, *middle, last = [10, 20, 30, 40]

# Slicing: [start:stop:step] (Step -1 reverses)
rev_str = "DataPlay"[::-1]

# Zip multiple streams
keys = ["name", "role"]
vals = ["Alex", "admin"]
user = dict(zip(keys, vals))

Standard Collections Time Complexity

CollectionLookupMembershipAppend/AddDeletePopIteration
ListO(1)O(N)O(1)O(N)O(1) / O(N)O(N)
TupleO(1)O(N)N/AN/AN/AO(N)
SetN/AO(1)O(1)O(1)O(1)O(N)
DictO(1)O(1)O(1)O(1)O(1)O(N)
DequeO(N)O(N)O(1)O(N)O(1)O(N)

Key Takeaways: Always prefer sets/dicts over lists for membership checking (in) to query in O(1) average time instead of O(N) linear time.

Remember: Using list.pop(0) inside queues is a major anti-pattern. This shifts all items in memory, causing O(N) complexity; always use collections.deque.popleft() instead which runs in O(1) time.

2. OOP Protocols & Decorators
3-Min Read
Why It Matters: Object-Oriented Programming (OOP) and Decorators allow you to write clean, reusable, and maintainable data pipelines. Custom dunder protocols let your custom classes act like native Python collections, while decorators provide a clean, non-intrusive way to inject logging, caching, or access controls.

Class Decorator & Slots Syntax

python
class User:
    # Pre-allocates attribute storage, saving RAM
    __slots__ = ("name", "_role")
    
    def __init__(self, name, role):
        self.name = name
        self._role = role
        
    @property
    def role(self):
        return self._role

Custom Decorator Closure Wrapper

python
import functools

def logger(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("Calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

Key Takeaways: __slots__ reduces memory footprint by preventing the creation of __dict__ for each object instance. It is critical for memory optimization when instantiating millions of small records in memory.

Remember: Decorators wrap functions using closures. Always decorate the wrapper function with @functools.wraps(func) to preserve original function metadata (docstring, function name).

3. Memory Model & GIL Concurrency
3-Min Read
Why It Matters:Python's Global Interpreter Lock (GIL) prevents CPU-bound tasks from running in parallel on multi-core systems when using standard threads. Understanding when to use thread-based vs process-based concurrency is critical for designing high-throughput data processing services.

GIL & Concurrency Reference

  • Global Interpreter Lock (GIL): A CPython mutex lock that ensures only one thread executes Python bytecode at a time, making memory management (reference counting) inherently thread-safe.
  • Threads (I/O-Bound Work): Best for network calls, database queries, and file reading. Standard threads yield/release the GIL during blocking operations, enabling concurrency without CPU overhead.
  • Multiprocessing (CPU-Bound Work): Best for CPU-intensive computing (e.g., massive calculations, JSON parsing). Bypasses the GIL entirely by allocating independent memory heaps and interpreters across cores.

Memory Management Details

  • Reference Counting: CPython tracks references to each heap allocation. Objects are immediately deallocated in O(1) time once their reference count falls to zero.
  • Generational GC: Scans the heap periodically to detect circular references (e.g. parent-child cyclic locks) that reference counting alone cannot clean.

Key Takeaways: Multi-threading in CPython does not achieve true parallelism for CPU-heavy tasks. Use the multiprocessing module to bypass the GIL and utilize multiple cores.

Remember: Reference counting runs in O(1) time and is deterministic. Generational GC sweeps are non-deterministic and scan objects in generational cycles.

4. Common Gotchas & Red Flags
3-Min Read
Why It Matters: Python features subtle behavior quirks (like mutable defaults and reference multipliers) that can cause silent data corruption or memory leaks in production. Reviewing these gotchas ensures your code behaves predictably.
1. == vs is

== checks value equality (calls __eq__), whereas is checks object identity (same memory address). Small integers (-5 to 256) are cached, but larger integers or identical lists will fail is.

2. Mutable Default Arguments

Default args are evaluated once at definition time. def append(val, lst=[]) shares the same list across calls. Use lst=None and initialize inside the body.

3. Shallow Copy vs Deepcopy

copy() duplicates the outer structure but shares nested object references. Modifying a nested list in a shallow copy changes the original. Use copy.deepcopy() instead.

4. List Multiplication ([[0]*3]*3)

Multiplying a nested list copies references to the same inner list. Modifying one cell updates all rows. Use list comprehensions: [[0]*3 for _ in range(3)].

5. Late Binding in Lambdas

Closures bind variables by reference, not value. Lambdas created inside loops look up variables at call time, yielding duplicate values. Bind at definition: lambda x, i=i: i * x.

6. Modifying a List During Iteration

Adding or removing items while iterating shifts indices, causing elements to be skipped or looped twice. Iterate over a copy (for x in lst[:]) or use list comprehensions.

Key Takeaways: Avoid mutable types as function defaults or multiplier targets to prevent shared memory references.

Remember: Never mutate the dimensions of a list/collection while iterating directly over it; filter with comprehensions or iterate over a copy.

5. Data Engineering Notes (Production Python)
3-Min Read
Why It Matters: Data pipelines often process files that exceed local memory limits. Using generators and chunked iteration enables O(1) memory efficiency for parsing millions of JSON or CSV records.

Streaming & Chunking Files

python
# Stream large log files line-by-line
def stream_log_lines(file_path):
    with open(file_path, "r", encoding="utf-8") as f:
        for line in f:
            yield line.strip()

# Chunked processing in Pandas
import pandas as pd
for chunk in pd.read_csv("large.csv", chunksize=10000):
    process(chunk)
Generators & Iterators

Generators use yield to suspend execution and return data lazily. This keeps memory consumption constant (O(1)) regardless of the dataset size, unlike list-based structures.

JSON & CSV Handlers

Prefer native streaming solutions (csv.reader, json.JSONDecoder) or chunked buffers over loading entire document trees into memory.

Pandas/NumPy in ETL

NumPy vectors and Pandas DataFrames perform vector calculations via highly optimized compiled C binaries. Avoid looping through rows (.iterrows()) and use vector operations instead.

Key Takeaways: Avoid loading whole files or records using read() or to_list() in production data pipelines. Prefer generators or chunked streaming.

Remember: Pandas loops are slow. Vector operations run up to 100x faster than iteration by leveraging compiled contiguous memory operations.

6. Core Python Interview Scenario Checklist

Select a common Python backend developer scenario to view senior response keypoints:

Scenario:

"Our microservice works fine locally but crashes with an OOM error when processing a 10GB file. What is the cause?"

Underlying Issue:

Eager list loading inside functions reads the entire file into memory instead of lazy generators streaming.

Expected Answer:
  • Explain that loading whole files (e.g., .read() or .readlines()) allocates O(N) memory proportional to file size.
  • Show how to stream lines memory-safely using a generator with 'yield'.
  • Demonstrate that lazy evaluation consumes O(1) auxiliary space complexity.
Common Mistake:

Reading the entire file into a list or memory buffer at once.

Key Concept:

Generators and lazy evaluation stream data on-demand, preventing memory bloating.

7. Last-Minute Quick Revision Reminders
Why It Matters: A quick reference list helps reinforce core principles instantly before an interview or production release.
  • List vs Tuple: Lists are mutable; tuples are immutable.
  • == vs is: == compares values; is compares object identity (memory addresses).
  • Generators save memory: Generators stream values using yield inside an O(1) memory bounds limit.
  • deepcopy() copies nested objects: copy.deepcopy() recursively duplicates nested data structures, preventing reference sharing.
  • Use dict.get() to avoid KeyError: dict.get(key, default) safely handles missing keys instead of throwing errors.
  • Prefer enumerate() over manual indexing: enumerate() yields index-value pairs cleanly without tracking variables.
  • Use context managers (with) for file handling: with open(...) guarantees file descriptors close automatically on exit or exception.
  • Threads vs Multiprocessing: Threads are best for I/O waits; multiprocessing is best for CPU-bound work (bypassing the GIL).