Tutorial

Python Practice Questions: 25 Real Problems & Solutions

Practice 25 real Python problems for data analytics. Solve exercises on data types, control flow, functions, lambdas, file I/O, error handling, and pandas.

Anuj SainiSep 12, 202634 min read

Writing Python for data analytics requires a different mindset than general software engineering. In web development, you often process individual user requests one at a time. In data analytics and business intelligence, you manipulate millions of transactional records simultaneously, clean messy unstructured strings, extract data from nested JSON APIs, and optimize memory-intensive pipelines.

Many aspiring analysts fall into the tutorial trap: watching video lectures passively without writing code independently. When faced with a live technical screening or a take-home data challenge, they struggle with basic dictionary operations, off-by-one list indexing, or unhandled null values.

This practice guide provides 25 production-grade Python practice questions structured across five progressive learning tiers:

  1. Tier 1: Fundamentals, Types & String Sanitization: Truthiness, defensive type coercion, palindrome validation, phone formatting, and f-string alignment.
  2. Tier 2: Relational Data Structures: Sliding window slicing, dictionary inversion, set overlap metrics, tuple unpacking, and frequency counting.
  3. Tier 3: Control Flow, Functional Python & Generators: List comprehension flattening, variable arguments (*args/**kwargs), lambda sorting, memory-efficient generators, and recursive hierarchy traversal.
  4. Tier 4: File Handling, Exceptions & API Parsing: Context-managed CSV ingestion, custom domain validation exceptions, safe JSON traversal, exponential backoff retries, and structured file logging.
  5. Tier 5: Pandas & Tabular Data Manipulation: Boolean masking, missing value imputation, pivot tables vs groupby, merge indicators, and vectorization benchmarking.

For foundational practice, explore our hands-on Python Programs for Practice or jump into our structured Free Data Analyst Course. Every lesson and practice environment on Topfolio is 100% free to learn, with an optional ₹99 verified certificate to showcase on your LinkedIn profile.


Python Problem-Solving Progression Hierarchy

To succeed in technical interviews, structure your preparation along this progressive hierarchy:

+-----------------------------------------------------------------------------------+
|                        PYTHON ANALYTICS MASTERY LADDER                            |
+-----------------------------------------------------------------------------------+

   [ Tier 5: Vectorized Analytics & Pandas ]
   +-- DataFrame filtering, boolean indexing, NaN imputation, pivot tables, merges
   |
   [ Tier 4: Production Systems, I/O & Exceptions ]
   +-- Safe CSV parsing, JSON deserialization, custom exceptions, logging, retries
   |
   [ Tier 3: Functional Abstractions & Control Flow ]
   +-- List comprehensions, *args/**kwargs, lambda sorting, generators, recursion
   |
   [ Tier 2: Relational Data Structures ]
   +-- Sliding windows, dictionary inversion, set operations, tuple unpacking
   |
   [ Tier 1: Fundamentals, Types & String Sanitization ]
   +-- Truthiness, type coercion, regex cleaning, f-strings, precision math

Python Data Transformation Paradigms Compared

Before diving into the questions, understand the performance and readability trade-offs between Python's primary iteration paradigms:

Python Execution Paradigms for Data Analytics
Feature / Criteria

Part 1: How to Practice Python for Analytics

To maximize retention while working through these questions, follow three core habits:

  1. Write Code in an Interactive REPL or Jupyter Notebook: Do not just read the answers. Type each script into an interactive Python shell, IPython, or Topfolio's Free Python Course. Modify inputs and observe where code breaks.
  2. Avoid Premature Library Importation: Master built-in Python primitives (dict, set, list, zip, enumerate) before reaching for external libraries. Interviewers often prohibit import pandas in Stage 1 screening rounds to test your algorithmic problem-solving fundamentals.
  3. Adopt a Defensive Data Cleaning Mindset: Production data is dirty. Strings contain trailing whitespace, numeric columns contain dollar signs or commas, and timestamps arrive in inconsistent timezones. Always handle missing values, nulls, and unexpected data types defensively.

Part 2: 25 Progressive Python Practice Questions


Tier 1: Fundamentals, Types & String Sanitization


Question 1: Truthiness & Defensive Type Coercion

Problem Statement: In data pipelines, raw user inputs arrive as unstructured strings, numbers, or None. Write a function clean_numeric_value(val, default=0.0) that converts input into a clean float. If the input is None, an empty string, or cannot be parsed as a float (e.g. "N/A" or "$12.50"), it must safely return the default float without crashing.

Python Solution Code:

python
def clean_numeric_value(val, default: float = 0.0) -> float:
    """
    Defensively coerces raw inputs into float values, stripping currency
    symbols and whitespace. Returns default value on invalid inputs.
    """
    if val is None:
        return default
    
    # If already an integer or float, return as float directly
    if isinstance(val, (int, float)):
        return float(val)
    
    # Clean string representation
    cleaned = str(val).strip().replace("$", "").replace(",", "")
    if not cleaned:
        return default
    
    try:
        return float(cleaned)
    except (ValueError, TypeError):
        return default
 
# Test Cases
print(clean_numeric_value("$1,450.50"))  # Expected: 1450.5
print(clean_numeric_value(None))          # Expected: 0.0
print(clean_numeric_value("  "))          # Expected: 0.0
print(clean_numeric_value("invalid", -1.0)) # Expected: -1.0
print(clean_numeric_value(42))            # Expected: 42.0

Step-by-Step Logic Breakdown:

  • Checking val is None guards against NoneType errors before string manipulation.
  • isinstance(val, (int, float)) short-circuits numeric primitives for maximum performance.
  • Common formatting artifacts like dollar signs ($) and thousands separators (,) are stripped before parsing.
  • A targeted try...except (ValueError, TypeError) block intercepts unparseable strings, returning the fallback default rather than halting the pipeline.

Expected Output:

1450.5
0.0
0.0
-1.0
42.0

Question 2: Case-Insensitive Alphanumeric Palindrome Checker

Problem Statement: Write a function is_clean_palindrome(text) that determines whether a given text is a palindrome, considering only alphanumeric characters and ignoring case and punctuation.

Python Solution Code:

python
def is_clean_palindrome(text: str) -> bool:
    """
    Validates if a string is a palindrome ignoring non-alphanumeric characters and casing.
    """
    if not isinstance(text, str):
        return False
    
    # Filter only alphanumeric characters and convert to lowercase
    cleaned = [char.lower() for char in text if char.isalnum()]
    
    # Compare sequence to its exact reverse using two-pointer or slice
    return cleaned == cleaned[::-1]
 
# Test Cases
print(is_clean_palindrome("A man, a plan, a canal: Panama!")) # True
print(is_clean_palindrome("Was it a car or a cat I saw?"))     # True
print(is_clean_palindrome("Topfolio Analytics"))              # False
print(is_clean_palindrome(""))                                # True (empty is valid)

Step-by-Step Logic Breakdown:

  • char.isalnum() checks if each character is either an ASCII letter or numeric digit, ignoring spaces, hyphens, and commas.
  • char.lower() ensures case-insensitivity.
  • cleaned[::-1] creates a reversed copy using Python's slice syntax. If the forward list equals the reversed list, the input string is a valid palindrome.

Expected Output:

True
True
False
True

Question 3: Dirty Phone Number Normalizer

Problem Statement: Customer contact records arrive in erratic formats: "+1 (555) 234-5678", "555.234.5678", "5552345678", or "1-555-234-5678". Write a function normalize_us_phone(raw_phone) that extracts the core 10 digits and formats them as standard (XXX) XXX-XXXX. If the string does not contain 10 valid digits (or 11 digits starting with country code 1), return None.

Python Solution Code:

python
import re
from typing import Optional
 
def normalize_us_phone(raw_phone: str) -> Optional[str]:
    """
    Extracts digits from raw input and formats standard US phone numbers.
    """
    if not raw_phone:
        return None
    
    # Extract only numeric digits using regex
    digits = re.sub(r"\D", "", str(raw_phone))
    
    # Handle optional leading US country code '1'
    if len(digits) == 11 and digits.startswith("1"):
        digits = digits[1:]
        
    if len(digits) != 10:
        return None
        
    area_code, prefix, line = digits[:3], digits[3:6], digits[6:]
    return f"({area_code}) {prefix}-{line}"
 
# Test Cases
print(normalize_us_phone("+1 (555) 234-5678")) # (555) 234-5678
print(normalize_us_phone("555.234.5678"))      # (555) 234-5678
print(normalize_us_phone("5552345678"))        # (555) 234-5678
print(normalize_us_phone("123-45"))            # None (invalid length)

Step-by-Step Logic Breakdown:

  • re.sub(r"\D", "", raw_phone) replaces every non-digit character (\D) with an empty string, isolating only numbers.
  • If the extracted digit sequence has length 11 and begins with 1, the country code is stripped.
  • Slicing digits[:3], digits[3:6], and digits[6:] separates the area code, exchange code, and subscriber number, formatted cleanly using an f-string.

Expected Output:

(555) 234-5678
(555) 234-5678
(555) 234-5678
None

Question 4: Floating Point Precision & Financial Currency Math

Problem Statement: In standard binary floating point arithmetic, 0.1 + 0.2 equals 0.30000000000000004. Write a function calculate_invoice_balance(items, tax_rate) using Python's decimal module to compute exact total costs with accurate 2-decimal rounding.

Python Solution Code:

python
from decimal import Decimal, ROUND_HALF_UP
 
def calculate_invoice_balance(items: list[tuple[str, str]], tax_rate: str) -> Decimal:
    """
    Calculates line-item invoice sum with exact tax rounding using Decimal.
    items: list of (item_name, price_as_string)
    tax_rate: tax percentage as string (e.g. '0.0825' for 8.25%)
    """
    subtotal = Decimal("0.00")
    for _, price_str in items:
        subtotal += Decimal(price_str)
        
    tax = subtotal * Decimal(tax_rate)
    total = subtotal + tax
    
    # Quantize to exactly 2 decimal places using standard commercial rounding
    cents = Decimal("0.01")
    return total.quantize(cents, rounding=ROUND_HALF_UP)
 
# Test Cases
invoice_items = [
    ("Database Server License", "1299.99"),
    ("Cloud Storage 1TB", "14.20"),
    ("Support Retainer", "499.50")
]
tax_pct = "0.0825" # 8.25%
 
print(calculate_invoice_balance(invoice_items, tax_pct)) # Expected: 1963.38

Step-by-Step Logic Breakdown:

  • Standard Python float uses IEEE 754 floating-point representation, which cannot represent certain decimal fractions precisely.
  • Constructing Decimal("string") from strings preserves exact base-10 fractional values.
  • .quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) enforces standard financial rounding where 0.5 rounds up to the nearest cent.

Expected Output:

1963.38

Question 5: Dynamic Metric Tabulation with f-Strings

Problem Statement: Given a list of department dictionaries containing names, employee headcounts, and total annual budget, print an aligned text report with column headers, left-aligned department names, right-aligned headcounts, and currency-formatted budgets with thousands separators.

Python Solution Code:

python
def print_department_summary(departments: list[dict]):
    """
    Prints a formatted summary table using advanced f-string specification.
    """
    print(f"{'Department':<20} | {'Headcount':>10} | {'Annual Budget':>15}")
    print("-" * 52)
    
    total_budget = 0
    total_headcount = 0
    
    for dept in departments:
        name = dept["name"]
        headcount = dept["headcount"]
        budget = dept["budget"]
        
        total_headcount += headcount
        total_budget += budget
        
        # Format: 20 chars left-aligned, 10 chars right-aligned, 15 chars right-aligned with commas
        print(f"{name:<20} | {headcount:>10,d} | ${budget:>14,.2f}")
        
    print("-" * 52)
    print(f"{'TOTAL':<20} | {total_headcount:>10,d} | ${total_budget:>14,.2f}")
 
# Test Case Data
depts = [
    {"name": "Data Analytics", "headcount": 14, "budget": 1420500.00},
    {"name": "Engineering", "headcount": 48, "budget": 5890000.50},
    {"name": "Marketing", "headcount": 8, "budget": 750200.25}
]
 
print_department_summary(depts)

Step-by-Step Logic Breakdown:

  • <20 specifies left alignment within a 20-character wide field.
  • >10,d specifies right alignment within a 10-character field formatted as an integer with thousands separator commas.
  • >14,.2f formats floating-point values to exactly two decimal places with thousands separators.

Expected Output:

Department           |  Headcount |   Annual Budget
----------------------------------------------------
Data Analytics       |         14 | $  1,420,500.00
Engineering          |         48 | $  5,890,000.50
Marketing            |          8 | $    750,200.25
----------------------------------------------------
TOTAL                |         70 | $  8,060,700.75

Tier 2: Relational Data Structures


Question 6: Sliding Window Moving Average Calculator

Problem Statement: Write a function calculate_moving_averages(data, window_size) that computes a rolling moving average over a list of numeric values using a sliding window of length k. If len(data) < window_size, return an empty list.

Python Solution Code:

python
def calculate_moving_averages(data: list[float], window_size: int) -> list[float]:
    """
    Computes rolling moving average using list slicing.
    """
    if window_size <= 0 or len(data) < window_size:
        return []
    
    averages = []
    # Slide across valid starting indices
    for i in range(len(data) - window_size + 1):
        window = data[i : i + window_size]
        avg = sum(window) / window_size
        averages.append(round(avg, 2))
        
    return averages
 
# Test Cases
daily_sales = [100.0, 120.0, 110.0, 150.0, 180.0, 200.0]
print(calculate_moving_averages(daily_sales, 3)) # 3-day rolling

Step-by-Step Logic Breakdown:

  • The range range(len(data) - window_size + 1) prevents index out-of-bounds exceptions.
  • data[i : i + window_size] slices the exact sub-list corresponding to the active window.
  • The arithmetic mean of the slice is calculated and rounded to 2 decimal places.

Expected Output:

[110.0, 126.67, 146.67, 176.67]

Question 7: Bidirectional Dictionary Inversion

Problem Statement: Write a function invert_dictionary(mapping) that inverts a dictionary of {user_id: department} into a grouped dictionary {department: [user_ids]} without overwriting colliding keys.

Python Solution Code:

python
from collections import defaultdict
 
def invert_dictionary(mapping: dict[str, str]) -> dict[str, list[str]]:
    """
    Inverts a dictionary, grouping multiple keys that share the same value.
    """
    inverted = defaultdict(list)
    for key, value in mapping.items():
        inverted[value].append(key)
        
    # Return as standard dict sorted for deterministic output
    return dict(sorted(inverted.items()))
 
# Test Cases
user_depts = {
    "alice": "Analytics",
    "bob": "Engineering",
    "charlie": "Analytics",
    "david": "Marketing",
    "elena": "Engineering"
}
 
print(invert_dictionary(user_depts))

Step-by-Step Logic Breakdown:

  • A naive {v: k for k, v in d.items()} dictionary comprehension overwrites duplicate values (alice would be overwritten by charlie).
  • defaultdict(list) initializes an empty list for any newly encountered department key.
  • Appending key to inverted[value] aggregates all users belonging to that department.

Expected Output:

{'Analytics': ['alice', 'charlie'], 'Engineering': ['bob', 'elena'], 'Marketing': ['david']}

Question 8: Audience Retention & Churn Analysis via Sets

Problem Statement: Product analytics tracks monthly active users. Given two sets of customer IDs representing active users in january_users and february_users, calculate:

  1. Retained Users: Active in both months.
  2. Churned Users: Active in January but not in February.
  3. New Users: Active in February but not in January.
  4. Retention Rate: Percentage of January users who remained active in February.

Python Solution Code:

python
def analyze_monthly_cohorts(january: set, february: set) -> dict:
    """
    Analyzes user retention using set operations.
    """
    retained = january.intersection(february)   # january & february
    churned = january.difference(february)       # january - february
    new_users = february.difference(january)     # february - january
    
    retention_rate = (len(retained) / len(january) * 100.0) if january else 0.0
    
    return {
        "retained_count": len(retained),
        "churned_count": len(churned),
        "new_users_count": len(new_users),
        "retention_rate_pct": round(retention_rate, 2)
    }
 
# Test Cases
jan_cohort = {"usr_101", "usr_102", "usr_103", "usr_104", "usr_105"}
feb_cohort = {"usr_103", "usr_104", "usr_105", "usr_106", "usr_107"}
 
metrics = analyze_monthly_cohorts(jan_cohort, feb_cohort)
for metric, val in metrics.items():
    print(f"{metric}: {val}")

Step-by-Step Logic Breakdown:

  • set.intersection() identifies elements present in both sets (retained cohort).
  • set.difference() isolates elements exclusive to one set (churned users who did not return, or newly acquired accounts).
  • Mathematical set operations in Python run with average $O(\min(\text(s), \text(t)))$ time complexity, far faster than nested loop iterations.

Expected Output:

retained_count: 3
churned_count: 2
new_users_count: 2
retention_rate_pct: 60.0

Question 9: Structured Server Log Unpacking

Problem Statement: Web server logs arrive as raw tuples: ("2024-04-10T12:00:01Z", "POST", "/api/v1/checkout", 201, 142.5). Write a function filter_slow_api_calls(logs, max_latency_ms) that uses tuple unpacking to find all successful endpoints (status_code between 200 and 299) where latency exceeded max_latency_ms.

Python Solution Code:

python
def filter_slow_api_calls(logs: list[tuple], max_latency_ms: float) -> list[dict]:
    """
    Unpacks log tuples and filters requests by status code and latency threshold.
    """
    slow_requests = []
    
    for timestamp, method, endpoint, status, latency in logs:
        if 200 <= status <= 299 and latency > max_latency_ms:
            slow_requests.append({
                "timestamp": timestamp,
                "endpoint": f"{method} {endpoint}",
                "status": status,
                "latency_ms": latency
            })
            
    return slow_requests
 
# Test Cases
server_logs = [
    ("2024-04-10T10:00:01Z", "GET", "/api/v1/products", 200, 45.2),
    ("2024-04-10T10:00:03Z", "POST", "/api/v1/checkout", 201, 310.8),
    ("2024-04-10T10:00:05Z", "GET", "/api/v1/users/me", 500, 450.0),
    ("2024-04-10T10:00:08Z", "GET", "/api/v1/search", 200, 220.4)
]
 
print(filter_slow_api_calls(server_logs, 200.0))

Step-by-Step Logic Breakdown:

  • The loop header for timestamp, method, endpoint, status, latency in logs: unpacks all 5 tuple elements directly into named variables.
  • 200 <= status <= 299 leverages Python's chained comparison syntax.
  • Failed requests (e.g. status 500) are excluded even if their latency is high, isolating slow but successful endpoints.

Expected Output:

[{'timestamp': '2024-04-10T10:00:03Z', 'endpoint': 'POST /api/v1/checkout', 'status': 201, 'latency_ms': 310.8}, {'timestamp': '2024-04-10T10:00:08Z', 'endpoint': 'GET /api/v1/search', 'status': 200, 'latency_ms': 220.4}]

Question 10: Word Frequency Counter & Top-K Extraction

Problem Statement: Implement a function get_top_k_words(text, k=3) that takes a paragraph of text, normalizes it to lowercase, removes punctuation, and returns the top k most frequently occurring words alongside their counts.

Python Solution Code:

python
import re
from collections import Counter
 
def get_top_k_words(text: str, k: int = 3) -> list[tuple[str, int]]:
    """
    Tokenizes text, strips punctuation, and extracts top K word frequencies.
    """
    if not text:
        return []
    
    # Extract lowercase words using regex word boundaries
    words = re.findall(r"\b\w+\b", text.lower())
    
    # Count occurrences using collections.Counter
    counts = Counter(words)
    
    # Return top K most common items
    return counts.most_common(k)
 
# Test Cases
paragraph = """
Data analysts use SQL and Python. Python is great for data cleaning,
while SQL is great for database aggregation. Data teams need both Python and SQL.
"""
 
print(get_top_k_words(paragraph, 3))

Step-by-Step Logic Breakdown:

  • re.findall(r"\b\w+\b", text.lower()) extracts word tokens while discarding commas, periods, and line breaks.
  • Counter(words) builds a hash map in $O(N)$ linear time.
  • .most_common(k) uses a heap under the hood to return the top $k$ items in $O(N \log k)$ time, far more efficient than sorting the entire dictionary.

Expected Output:

[('data', 3), ('python', 3), ('sql', 3)]

Practice Python Coding in Your Live Browser Sandbox

Run Python code with automated test feedback on Topfolio. Free to learn, optional ₹99 verified certificate.

Start Free Python Course

Tier 3: Control Flow, Functional Python & Generators


Question 11: 2D Matrix Flattening & Threshold Filtering

Problem Statement: Given a nested list of quarterly revenue figures per store location, write a single list comprehension that flattens the matrix into a single 1D list and excludes any revenue figure strictly below $100,000.

Python Solution Code:

python
def flatten_and_filter_sales(store_matrix: list[list[float]], threshold: float) -> list[float]:
    """
    Flattens a 2D matrix into 1D and filters values >= threshold using comprehension.
    """
    return [
        revenue
        for store in store_matrix
        for revenue in store
        if revenue >= threshold
    ]
 
# Test Cases
quarterly_sales_matrix = [
    [120000.0, 95000.0, 140000.0, 180000.0],  # Store 1
    [85000.0, 78000.0, 90000.0, 88000.0],     # Store 2 (Underperforming)
    [210000.0, 240000.0, 195000.0, 280000.0]  # Store 3
]
 
print(flatten_and_filter_sales(quarterly_sales_matrix, 100000.0))

Step-by-Step Logic Breakdown:

  • In nested list comprehensions, for clauses appear in the exact same order as traditional nested loops: outer loop first (for store in store_matrix), inner loop second (for revenue in store).
  • The trailing if revenue >= threshold filters values before they are emitted into the result list.

Expected Output:

[120000.0, 140000.0, 180000.0, 210000.0, 240000.0, 195000.0, 280000.0]

Question 12: Flexible Data Pipeline Function with *args and **kwargs

Problem Statement: Write a general-purpose data pipeline runner transform_dataset(data, *transformers, **options) that sequentially applies an arbitrary number of transformation functions (*transformers) to an input list, while allowing optional keyword flags (drop_none=True, round_digits=2).

Python Solution Code:

python
from typing import Callable
 
def transform_dataset(data: list, *transformers: Callable, **options) -> list:
    """
    Applies a chain of transformation functions to a dataset with optional configs.
    """
    result = list(data)
    
    # Apply each transformation function in sequence
    for fn in transformers:
        result = [fn(x) for x in result]
        
    # Apply optional keyword configurations
    if options.get("drop_none", False):
        result = [x for x in result if x is not None]
        
    round_digits = options.get("round_digits")
    if round_digits is not None:
        result = [round(x, round_digits) if isinstance(x, (int, float)) else x for x in result]
        
    return result
 
# Sample Transformer Functions
def to_celsius(f_temp):
    return (f_temp - 32) * 5 / 9 if f_temp is not None else None
 
def add_sensor_bias(temp):
    return temp + 0.45 if temp is not None else None
 
# Test Case
raw_temperatures = [72.5, 68.0, None, 85.2, 90.0]
 
clean_temps = transform_dataset(
    raw_temperatures,
    to_celsius,
    add_sensor_bias,
    drop_none=True,
    round_digits=1
)
 
print(clean_temps) # Expected Celsius conversions

Step-by-Step Logic Breakdown:

  • *transformers collects positional arguments into a tuple of callables.
  • **options collects arbitrary keyword configuration flags into a dictionary.
  • The pipeline loops through each transformer function iteratively, then applies global sanitization options like drop_none and numeric rounding.

Expected Output:

[23.0, 20.5, 30.0, 32.7]

Question 13: Multi-Key Custom Sorting with Lambda Expressions

Problem Statement: Given a list of employee dictionaries, sort the employees first by department name in alphabetical order, and then by annual salary from highest to lowest within each department.

Python Solution Code:

python
def sort_employees_by_dept_and_salary(employees: list[dict]) -> list[dict]:
    """
    Sorts employees by department ASC, then salary DESC using a lambda tuple key.
    """
    # Key returns a tuple: (department_string, -salary_float)
    return sorted(
        employees,
        key=lambda emp: (emp["department"], -emp["salary"])
    )
 
# Test Cases
staff = [
    {"name": "Alice", "department": "Engineering", "salary": 115000},
    {"name": "Bob", "department": "Analytics", "salary": 92000},
    {"name": "Charlie", "department": "Engineering", "salary": 140000},
    {"name": "David", "department": "Analytics", "salary": 105000},
    {"name": "Elena", "department": "Marketing", "salary": 88000}
]
 
sorted_staff = sort_employees_by_dept_and_salary(staff)
for s in sorted_staff:
    print(f"{s['department']:<12} | ${s['salary']:>7} | {s['name']}")

Step-by-Step Logic Breakdown:

  • Python's sorted() function accepts a key parameter returning a tuple for multi-column sorting.
  • To achieve ascending order on one key and descending order on another numeric key, negate the numeric value (-emp["salary"]).
  • Python's Timsort algorithm evaluates the first element of the tuple (department), breaking ties using the second negated element (-salary).

Expected Output:

Analytics    | $ 105000 | David
Analytics    | $  92000 | Bob
Engineering  | $ 140000 | Charlie
Engineering  | $ 115000 | Alice
Marketing    | $  88000 | Elena

Question 14: Memory-Efficient Large Log Streamer (Generators & Yield)

Problem Statement: Reading an entire 5GB server access log into a list with file.readlines() causes an OutOfMemoryError. Write a generator function stream_error_logs(file_path) that streams log lines one-by-one, yielding only lines containing HTTP status errors (status codes 400 through 599).

Python Solution Code:

python
from typing import Generator
import io
 
def stream_error_logs(log_stream: io.StringIO) -> Generator[str, None, None]:
    """
    Yields error log lines lazily without loading the entire stream into memory.
    """
    for line in log_stream:
        # Check if line contains common HTTP client/server error patterns
        # Example format: '2024-04-10 12:00:00 [404] /api/v1/missing'
        parts = line.strip().split()
        for token in parts:
            if token.startswith("[") and token.endswith("]") and token[1:-1].isdigit():
                status_code = int(token[1:-1])
                if 400 <= status_code <= 599:
                    yield line.strip()
                    break
 
# Simulated Log File Stream
mock_logs = io.StringIO("""
2024-04-10 10:00:01 [200] /api/v1/health
2024-04-10 10:00:05 [404] /api/v1/users/9999
2024-04-10 10:00:10 [200] /api/v1/products
2024-04-10 10:00:12 [500] /api/v1/payment/checkout
""")
 
# Consumer consumes one line at a time
for error_line in stream_error_logs(mock_logs):
    print(f"ALERT: {error_line}")

Step-by-Step Logic Breakdown:

  • Using yield transforms a regular function into a generator.
  • Instead of returning a populated list, execution pauses after each yield statement and resumes only when the consumer requests the next item.
  • The memory footprint remains $O(1)$ constant, regardless of whether the log file contains 10 rows or 100 million rows.

Expected Output:

ALERT: 2024-04-10 10:00:05 [404] /api/v1/users/9999
ALERT: 2024-04-10 10:00:12 [500] /api/v1/payment/checkout

Question 15: Nested Category Tree Flattener (Recursion)

Problem Statement: Product taxonomy trees often arrive as nested dictionaries: {"Electronics": {"Audio": {"Headphones": 45, "Speakers": 12}, "Displays": 30}}. Write a recursive function flatten_taxonomy(tree, parent_key="", sep=" > ") that produces a flat dictionary mapping complete hierarchical category paths to leaf values.

Python Solution Code:

python
def flatten_taxonomy(tree: dict, parent_key: str = "", sep: str = " > ") -> dict[str, int]:
    """
    Recursively flattens nested dictionary paths into breadcrumb keys.
    """
    flat_dict = {}
    
    for key, value in tree.items():
        # Construct path
        new_key = f"{parent_key}{sep}{key}" if parent_key else key
        
        if isinstance(value, dict):
            # Recurse deeper into subtree
            flat_dict.update(flatten_taxonomy(value, new_key, sep=sep))
        else:
            # Base case: leaf node
            flat_dict[new_key] = value
            
    return flat_dict
 
# Test Cases
catalog_tree = {
    "Electronics": {
        "Audio": {
            "Headphones": 45,
            "Speakers": 12
        },
        "Displays": 30
    },
    "Home Office": {
        "Desks": 18
    }
}
 
flattened = flatten_taxonomy(catalog_tree)
for path, count in flattened.items():
    print(f"{path}: {count} items")

Step-by-Step Logic Breakdown:

  • Base case: when value is not a dict, the function assigns flat_dict[new_key] = value.
  • Recursive step: when value is a dict, the function calls itself recursively with the updated breadcrumb path (parent_key > child_key).
  • flat_dict.update(...) merges results from deeper subtrees back to the caller.

Expected Output:

Electronics > Audio > Headphones: 45 items
Electronics > Audio > Speakers: 12 items
Electronics > Displays: 30 items
Home Office > Desks: 18 items

Tier 4: File Handling, Exceptions & API Parsing


Question 16: Safe CSV Ingestion with Context Managers

Problem Statement: Write a function parse_csv_safely(file_content) that uses csv.DictReader inside a context manager to parse order records. If a line has missing columns or unparseable numeric values, it should record the row number in an error log list rather than crashing.

Python Solution Code:

python
import csv
import io
 
def parse_csv_safely(csv_text: str) -> tuple[list[dict], list[dict]]:
    """
    Parses CSV text safely, returning (valid_records, rejected_records).
    """
    valid_rows = []
    rejected_rows = []
    
    # Use StringIO as mock file buffer
    file_obj = io.StringIO(csv_text.strip())
    reader = csv.DictReader(file_obj)
    
    for row_idx, row in enumerate(reader, start=2): # Start at 2 accounting for header
        try:
            order_id = int(row["order_id"])
            customer = row["customer_name"].strip()
            amount = float(row["total_amount"])
            
            if not customer:
                raise ValueError("Customer name cannot be blank")
                
            valid_rows.append({
                "order_id": order_id,
                "customer": customer,
                "total_amount": amount
            })
        except (ValueError, KeyError, TypeError) as err:
            rejected_rows.append({
                "row_number": row_idx,
                "raw_data": row,
                "error": str(err)
            })
            
    return valid_rows, rejected_rows
 
# Test Case
sample_csv = """order_id,customer_name,total_amount
1001,Elena Rostova,240.50
1002,,120.00
1003,Marcus Vance,invalid_price
1004,Chloe Bennett,89.99
"""
 
valids, rejects = parse_csv_safely(sample_csv)
print(f"Successfully Parsed ({len(valids)}):", valids)
print(f"Rejected Rows ({len(rejects)}):", rejects)

Step-by-Step Logic Breakdown:

  • csv.DictReader automatically uses the first CSV line as dictionary keys.
  • Wrapping row ingestion in try...except (ValueError, KeyError, TypeError) prevents bad rows from terminating data ingestion.
  • enumerate(reader, start=2) accurately tracks spreadsheet row numbers for error reporting.

Expected Output:

Successfully Parsed (2): [{'order_id': 1001, 'customer': 'Elena Rostova', 'total_amount': 240.5}, {'order_id': 1004, 'customer': 'Chloe Bennett', 'total_amount': 89.99}]
Rejected Rows (2): [{'row_number': 3, 'raw_data': {'order_id': '1002', 'customer_name': '', 'total_amount': '120.00'}, 'error': 'Customer name cannot be blank'}, {'row_number': 4, 'raw_data': {'order_id': '1003', 'customer_name': 'Marcus Vance', 'total_amount': 'invalid_price'}, 'error': "could not convert string to float: 'invalid_price'"}]

Question 17: Custom Validation Exception Raising

Problem Statement: In automated data pipelines, you must stop downstream reporting when critical business invariants are violated. Create a custom exception DataQualityValidationError and write a validation function validate_transaction_batch(transactions) that raises this error with a descriptive message if any transaction has a negative amount or future transaction timestamp.

Python Solution Code:

python
from datetime import datetime, timezone
 
class DataQualityValidationError(Exception):
    """Raised when incoming transactional data fails business validation checks."""
    pass
 
def validate_transaction_batch(transactions: list[dict]):
    """
    Validates transactional records against business integrity rules.
    """
    now = datetime.now(timezone.utc)
    
    for txn in transactions:
        txn_id = txn.get("id")
        amount = txn.get("amount", 0)
        txn_time = txn.get("timestamp")
        
        if amount < 0:
            raise DataQualityValidationError(
                f"Validation Failure: Transaction {txn_id} has negative amount ({amount})."
            )
            
        if txn_time and txn_time > now:
            raise DataQualityValidationError(
                f"Validation Failure: Transaction {txn_id} has future timestamp ({txn_time})."
            )
            
    return True
 
# Test Cases
clean_batch = [
    {"id": "T1", "amount": 100.0, "timestamp": datetime(2024, 1, 1, tzinfo=timezone.utc)},
    {"id": "T2", "amount": 50.0, "timestamp": datetime(2024, 1, 2, tzinfo=timezone.utc)}
]
print("Clean Batch Validated:", validate_transaction_batch(clean_batch))
 
dirty_batch = [
    {"id": "T3", "amount": -15.0, "timestamp": datetime(2024, 1, 1, tzinfo=timezone.utc)}
]
try:
    validate_transaction_batch(dirty_batch)
except DataQualityValidationError as ex:
    print(f"Caught Expected Exception: {ex}")

Step-by-Step Logic Breakdown:

  • Inheriting from Exception creates a distinct, catchable error type specific to your data validation layer.
  • Raising targeted exceptions allows caller scripts to isolate business rule violations from standard runtime syntax crashes.

Expected Output:

Clean Batch Validated: True
Caught Expected Exception: Validation Failure: Transaction T3 has negative amount (-15.0).

Question 18: Safe Nested JSON Traversal with Key Fallbacks

Problem Statement: REST APIs frequently return deeply nested JSON objects where intermediate keys may be missing or null: payload["data"]["user"]["profile"]["address"]["zip"]. Write a helper function safe_nested_get(dictionary, key_path, default=None) that traverses a period-delimited path safely without throwing KeyError or TypeError.

Python Solution Code:

python
def safe_nested_get(data: dict, path: str, default=None):
    """
    Traverses a nested dictionary using a dot-separated key path.
    """
    if not isinstance(data, dict):
        return default
        
    keys = path.split(".")
    current = data
    
    for key in keys:
        if isinstance(current, dict) and key in current:
            current = current[key]
        else:
            return default
            
    return current if current is not None else default
 
# Test Cases
api_payload = {
    "status": "success",
    "data": {
        "user": {
            "id": 402,
            "profile": {
                "name": "Sarah Connor",
                "address": {
                    "city": "Los Angeles",
                    "zip_code": "90001"
                }
            }
        }
    }
}
 
print(safe_nested_get(api_payload, "data.user.profile.address.zip_code")) # 90001
print(safe_nested_get(api_payload, "data.user.profile.phone", "N/A"))     # N/A
print(safe_nested_get(api_payload, "data.order.items.0.price", 0.0))      # 0.0

Step-by-Step Logic Breakdown:

  • Chaining standard bracket notation d["data"]["order"] crashes with KeyError if any key in the chain is omitted.
  • Iterating across path.split(".") checks that each level is a dictionary before accessing children.
  • If any node in the path is missing or non-dict, it cleanly short-circuits and returns the specified default fallback.

Expected Output:

90001
N/A
0.0

Question 19: Exponential Backoff & Retry Decorator

Problem Statement: Network requests to external data APIs fail intermittently due to rate limits or temporary downtime. Write a Python function decorator @retry(max_attempts=3, backoff_factor=1.5) that automatically retries a failing function, sleeping with exponential delay between attempts.

Python Solution Code:

python
import time
from functools import wraps
 
def retry(max_attempts: int = 3, backoff_factor: float = 1.5):
    """
    Decorator that retries a function with exponential backoff upon exception.
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            delay = 0.1  # Initial sleep time in seconds
            
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as err:
                    attempts += 1
                    if attempts >= max_attempts:
                        raise err
                    time.sleep(delay)
                    delay *= backoff_factor
        return wrapper
    return decorator
 
# Test Case: Function that fails twice before succeeding
call_count = 0
 
@retry(max_attempts=3, backoff_factor=2.0)
def fetch_external_analytics_data():
    global call_count
    call_count += 1
    if call_count < 3:
        raise ConnectionResetError(f"Simulated network timeout (Attempt {call_count})")
    return {"status": "200 OK", "records_synced": 450}
 
result = fetch_external_analytics_data()
print("Success after retries:", result)

Step-by-Step Logic Breakdown:

  • Decorator closures accept configuration parameters (max_attempts, backoff_factor) and wrap the target function.
  • functools.wraps(func) preserves original function metadata (name, docstrings).
  • The while attempts < max_attempts loop catches exceptions and multiplies delay *= backoff_factor before the next iteration. If all attempts fail, the final exception is re-raised.

Expected Output:

Success after retries: {'status': '200 OK', 'records_synced': 450}

Question 20: Structured Event Logging to File

Problem Statement: In production analytics jobs, using print() statements is bad practice because output cannot be easily filtered, routed, or inspected. Configure Python's standard logging library to write timestamped log messages with severity levels (INFO, WARNING, ERROR) to a string buffer or file.

Python Solution Code:

python
import logging
import io
 
def setup_pipeline_logger() -> tuple[logging.Logger, io.StringIO]:
    """
    Configures a structured pipeline logger writing to an in-memory stream.
    """
    log_stream = io.StringIO()
    logger = logging.getLogger("ETLPipeline")
    logger.setLevel(logging.INFO)
    
    # Prevent duplicate handlers if re-executed
    if not logger.handlers:
        handler = logging.StreamHandler(log_stream)
        formatter = logging.Formatter(
            fmt="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S"
        )
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        
    return logger, log_stream
 
# Test Run
logger, stream = setup_pipeline_logger()
logger.info("Extract step initiated: querying PostgreSQL warehouse.")
logger.warning("Query latency elevated: took 4.2s to fetch 50,000 records.")
logger.error("Load failure: duplicate primary key detected on customer_id 8942.")
 
print(stream.getvalue().strip())

Step-by-Step Logic Breakdown:

  • logging.getLogger("ETLPipeline") creates a namespaced logger.
  • logging.Formatter standardizes the log entry structure: ISO timestamp, severity level ([levelname]), logger namespace, and the message.
  • Stream handlers direct log output to console streams, files, or cloud monitoring aggregators.

Expected Output:

2024-04-10 12:00:00 [INFO] ETLPipeline - Extract step initiated: querying PostgreSQL warehouse.
2024-04-10 12:00:00 [WARNING] ETLPipeline - Query latency elevated: took 4.2s to fetch 50,000 records.
2024-04-10 12:00:00 [ERROR] ETLPipeline - Load failure: duplicate primary key detected on customer_id 8942.

Tier 5: Pandas & Tabular Data Manipulation


Question 21: Boolean Mask Filtering & Complex Indexing

Problem Statement: Given an e-commerce transactions DataFrame, filter for rows where:

  1. The customer's state is either 'CA' or 'NY'.
  2. The transaction total_amount is strictly greater than $150.00.
  3. The status is 'completed'.

Python Solution Code:

python
import pandas as pd
 
def filter_target_transactions(df: pd.DataFrame) -> pd.DataFrame:
    """
    Applies multi-condition boolean masking to an orders DataFrame.
    """
    # Enforce boolean conditions with parentheses and bitwise operators
    mask = (
        df["state"].isin(["CA", "NY"]) &
        (df["total_amount"] > 150.0) &
        (df["status"] == "completed")
    )
    
    return df[mask].sort_values(by="total_amount", ascending=False)
 
# Test DataFrame
orders_df = pd.DataFrame({
    "order_id": [101, 102, 103, 104, 105],
    "customer": ["Elena", "Marcus", "Chloe", "David", "Arthur"],
    "state": ["CA", "TX", "NY", "CA", "NY"],
    "total_amount": [240.50, 420.00, 180.00, 95.00, 310.00],
    "status": ["completed", "completed", "completed", "completed", "refunded"]
})
 
print(filter_target_transactions(orders_df))

Step-by-Step Logic Breakdown:

  • In pandas, standard Python logical operators (and, or, not) cannot be used because they evaluate the truthiness of the entire array. You must use bitwise operators: & (AND), | (OR), and ~ (NOT).
  • Each sub-condition must be wrapped in explicit parentheses (...) due to operator precedence rules (& has higher precedence than >).
  • .isin(["CA", "NY"]) provides an efficient set membership test across columnar values.

Expected Output:

   order_id customer state  total_amount     status
0       101    Elena    CA         240.5  completed
2       103    Chloe    NY         180.0  completed

Question 22: Missing Data Strategy (Imputation vs Dropping)

Problem Statement: Customer demographic tables often have missing values across multiple columns. Given a customer DataFrame, perform a two-step cleaning strategy:

  1. Drop rows where the unique customer_id is null (since records without an identifier cannot be joined).
  2. Impute missing numeric salary values with the median salary of the customer's specific department.

Python Solution Code:

python
import pandas as pd
 
def clean_missing_demographics(df: pd.DataFrame) -> pd.DataFrame:
    """
    Cleans demographic DataFrame using targeted drop and group-median imputation.
    """
    # Step 1: Drop records missing primary key
    df_clean = df.dropna(subset=["customer_id"]).copy()
    
    # Step 2: Impute salary with department-specific median
    dept_medians = df_clean.groupby("department")["salary"].transform("median")
    df_clean["salary"] = df_clean["salary"].fillna(dept_medians)
    
    return df_clean
 
# Test DataFrame
data = {
    "customer_id": [101, 102, None, 104, 105, 106],
    "department": ["Eng", "Eng", "Analytics", "Analytics", "Analytics", "Eng"],
    "salary": [120000.0, None, 95000.0, None, 90000.0, 140000.0]
}
df_raw = pd.DataFrame(data)
 
print(clean_missing_demographics(df_raw))

Step-by-Step Logic Breakdown:

  • df.dropna(subset=["customer_id"]) drops only records missing the mandatory primary key, preserving rows that have other missing attributes.
  • .groupby("department")["salary"].transform("median") computes the median for each group and broadcasts the result to match the original DataFrame length.
  • .fillna(...) replaces NaN values with their respective department median (e.g. Eng median = 130,000; Analytics median = 90,000).

Expected Output:

   customer_id department    salary
0        101.0        Eng  120000.0
1        102.0        Eng  130000.0
3        104.0  Analytics   90000.0
4        105.0  Analytics   90000.0
5        106.0        Eng  140000.0

Question 23: Pivot Table vs GroupBy Aggregation

Problem Statement: Given a retail sales DataFrame with columns region, product_category, and revenue, generate a pivot table displaying total revenue per region (as rows) and product category (as columns), including row and column totals (margins=True), with missing cells filled with 0.0.

Python Solution Code:

python
import pandas as pd
 
def build_sales_pivot_table(df: pd.DataFrame) -> pd.DataFrame:
    """
    Constructs a cross-tabulated sales revenue matrix with row and column totals.
    """
    pivot = pd.pivot_table(
        df,
        values="revenue",
        index="region",
        columns="product_category",
        aggfunc="sum",
        fill_value=0.0,
        margins=True,
        margins_name="Total Revenue"
    )
    return pivot.round(2)
 
# Test DataFrame
sales_data = pd.DataFrame({
    "region": ["North", "North", "South", "South", "East", "North"],
    "product_category": ["Audio", "Displays", "Audio", "Furniture", "Displays", "Audio"],
    "revenue": [450.0, 890.0, 310.0, 750.0, 620.0, 280.0]
})
 
print(build_sales_pivot_table(sales_data))

Step-by-Step Logic Breakdown:

  • values="revenue" defines the numeric metric to aggregate.
  • index="region" and columns="product_category" establish the 2D matrix axes.
  • fill_value=0.0 ensures regions with zero sales in a given category display 0.0 instead of NaN.
  • margins=True calculates grand totals across both rows and columns.

Expected Output:

product_category   Audio  Displays  Furniture  Total Revenue
region                                                      
East                 0.0     620.0        0.0          620.0
North              730.0     890.0        0.0         1620.0
South              310.0       0.0      750.0         1060.0
Total Revenue     1040.0    1510.0      750.0         3300.0

Question 24: Relational DataFrame Merging with Indicator

Problem Statement: You have two DataFrames: customers_df and orders_df. Perform a full outer join on customer_id and use the indicator=True parameter to audit which customers have never ordered and which orders reference nonexistent customers.

Python Solution Code:

python
import pandas as pd
 
def audit_customer_order_relationship(
    customers: pd.DataFrame, 
    orders: pd.DataFrame
) -> pd.DataFrame:
    """
    Performs full outer merge with indicator flag to classify relationship status.
    """
    merged = pd.merge(
        customers,
        orders,
        on="customer_id",
        how="outer",
        indicator=True
    )
    
    # Map the default indicator values to descriptive business terms
    label_map = {
        "both": "Verified Customer Order",
        "left_only": "Customer with Zero Orders",
        "right_only": "Orphaned Order (Missing Customer)"
    }
    merged["audit_status"] = merged["_merge"].map(label_map)
    
    return merged[["customer_id", "customer_name", "order_id", "audit_status"]]
 
# Test DataFrames
customers_data = pd.DataFrame({
    "customer_id": [1, 2, 3],
    "customer_name": ["Alice", "Bob", "Charlie"]
})
 
orders_data = pd.DataFrame({
    "order_id": [501, 502, 503],
    "customer_id": [1, 2, 99]  # Customer 99 does not exist in customers_df
})
 
print(audit_customer_order_relationship(customers_data, orders_data))

Step-by-Step Logic Breakdown:

  • how="outer" preserves all rows from both DataFrames.
  • indicator=True generates a special column _merge with values 'both', 'left_only', or 'right_only'.
  • Mapping these values to descriptive business terms enables data governance teams to detect broken foreign key references.

Expected Output:

   customer_id customer_name  order_id                      audit_status
0          1.0         Alice     501.0           Verified Customer Order
1          2.0           Bob     502.0           Verified Customer Order
2          3.0       Charlie       NaN         Customer with Zero Orders
3         99.0           NaN     503.0  Orphaned Order (Missing Customer)

Question 25: Row-Wise Apply vs Vectorization Benchmarking

Problem Statement: Assign a customer tier label based on spend: 'VIP' if spend is strictly greater than $1,000, 'Regular' if between $200 and $1,000, and 'Low' otherwise. Implement this using both .apply() and vectorized numpy.select(), explaining the performance disparity.

Python Solution Code:

python
import pandas as pd
import numpy as np
import time
 
def assign_tiers_apply(df: pd.DataFrame) -> pd.Series:
    """Row-by-row iteration using .apply (Slow)."""
    def label_tier(row):
        if row["spend"] > 1000:
            return "VIP"
        elif row["spend"] >= 200:
            return "Regular"
        else:
            return "Low"
    return df.apply(label_tier, axis=1)
 
def assign_tiers_vectorized(df: pd.DataFrame) -> pd.Series:
    """Vectorized conditions using np.select (Fast)."""
    conditions = [
        df["spend"] > 1000,
        df["spend"] >= 200
    ]
    choices = ["VIP", "Regular"]
    return pd.Series(np.select(conditions, choices, default="Low"), index=df.index)
 
# Benchmark on 100,000 records
sample_size = 100000
test_df = pd.DataFrame({"spend": np.random.uniform(10, 2000, size=sample_size)})
 
# Benchmark .apply()
start_apply = time.perf_counter()
res_apply = assign_tiers_apply(test_df)
time_apply = time.perf_counter() - start_apply
 
# Benchmark Vectorization
start_vec = time.perf_counter()
res_vec = assign_tiers_vectorized(test_df)
time_vec = time.perf_counter() - start_vec
 
print(f".apply() Execution Time:       {time_apply:.4f} seconds")
print(f"Vectorized Execution Time:     {time_vec:.4f} seconds")
print(f"Speedup Factor:                {time_apply / time_vec:.1f}x faster")
print("Results Identical:", (res_apply == res_vec).all())

Step-by-Step Logic Breakdown:

  • .apply(axis=1) boxes each DataFrame row into a pd.Series object and calls the Python callback function sequentially in Python bytecode (causing massive overhead).
  • np.select(conditions, choices, default="Low") passes contiguous memory pointers directly to compiled C arrays, eliminating Python interpreter overhead.
  • On 100,000 rows, vectorization is typically 40x to 80x faster than row-wise .apply().

Expected Output:

.apply() Execution Time:       1.8420 seconds
Vectorized Execution Time:     0.0245 seconds
Speedup Factor:                75.2x faster
Results Identical: True

5 Python Interview Best Practices for Data Analysts

When writing Python in live coding interviews, follow these senior engineering principles:

  1. Ask About Data Scale Upfront: Always ask how many rows the dataset contains. If the interviewer says 10 million rows, do not use list.append() or row-wise .apply(); explain that you will use generator expressions or vectorized NumPy operations.
  2. Defend Against Missing Data: State assumptions explicitly: "I'm assuming some customer records will have null values, so I'm using .get() with a default fallback."
  3. Prefer Dictionary Lookups over Nested Scans: Checking x in my_list takes $O(N)$ time, whereas checking x in my_dict takes $O(1)$ constant time. In nested loops, converting a lookup table to a dictionary or set turns an $O(N^2)$ algorithm into $O(N)$.
  4. Use Meaningful Variable Names: In production, avoid d, temp, or foo. Use domain-specific names like active_user_cohort, unsettled_transactions, or normalized_phone.
  5. Write Clean Docstrings and Type Hints: Using def calculate_churn(users: set[str]) -> float: signals to hiring managers that you write maintainable, self-documenting production code.

Master Python for Analytics with Topfolio

True programming proficiency requires typing code, handling runtime exceptions, and inspecting data structures in a live execution environment.

Continue your learning journey with Topfolio:

All interactive learning tracks on Topfolio are 100% free to learn. You can also earn an optional ₹99 verified certificate to showcase your validated Python abilities to prospective employers.

Practice Python Coding in Your Live Browser Sandbox

Run Python code with automated test feedback on Topfolio. Free to learn, optional ₹99 verified certificate.

Start Free Python Course

Frequently Asked Questions

How should a beginner start practicing Python for data analytics?

Beginners should focus on core data structures (lists, dictionaries, sets) and control flow before moving to libraries. Once comfortable writing custom functions and list comprehensions, transition to pandas and NumPy for tabular data manipulation.

Why is vectorization preferred over loops in Python data analysis?

Python for-loops execute in interpreted bytecode with dynamic type checking on each iteration. Vectorized operations in pandas and NumPy run in pre-compiled C code across contiguous memory blocks, executing 50x to 100x faster on large datasets.

What is the difference between a list comprehension and a generator expression?

A list comprehension builds the entire list in memory immediately, which is ideal for smaller datasets requiring indexing. A generator expression produces items lazily on demand using an iterator, keeping memory usage constant even when processing millions of rows.

How do you handle missing values in pandas without distorting metrics?

Never replace missing values with zero arbitrarily. For numeric columns, impute using the median within categorical sub-groups to reduce outlier skew, or use forward-fill for time-series data. Drop rows only when critical foreign keys or identifiers are missing.

What are the most common Python coding mistakes in technical interviews?

Common mistakes include mutating lists while iterating over them, using mutable default arguments like def fn(data=[]), writing nested loops where dictionary lookups provide O(1) time complexity, and failing to handle exceptions defensively with try/except blocks.

Where can I practice Python coding exercises with live browser verification?

Topfolio provides an interactive browser-based Python coding environment with automated unit tests and instant grading. All exercises are 100% free to learn, with an optional verified certificate available for ₹99.

Anuj Saini

Written by

Anuj SainiFounder & Lead Instructor

Founder at Topfolio with 6+ years in data & analytics across JPMC, Ultrahuman, and high-growth startups. Sat on hiring panels, reviewed 500+ resumes, and writes practical SQL & data guides.