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.
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:
- Tier 1: Fundamentals, Types & String Sanitization: Truthiness, defensive type coercion, palindrome validation, phone formatting, and f-string alignment.
- Tier 2: Relational Data Structures: Sliding window slicing, dictionary inversion, set overlap metrics, tuple unpacking, and frequency counting.
- Tier 3: Control Flow, Functional Python & Generators: List comprehension flattening, variable arguments (
*args/**kwargs), lambda sorting, memory-efficient generators, and recursive hierarchy traversal. - 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.
- 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:
| Feature / Criteria |
|---|
Part 1: How to Practice Python for Analytics
To maximize retention while working through these questions, follow three core habits:
- 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.
- Avoid Premature Library Importation: Master built-in Python primitives (
dict,set,list,zip,enumerate) before reaching for external libraries. Interviewers often prohibitimport pandasin Stage 1 screening rounds to test your algorithmic problem-solving fundamentals. - 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:
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.0Step-by-Step Logic Breakdown:
- Checking
val is Noneguards againstNoneTypeerrors 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:
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:
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], anddigits[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:
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.38Step-by-Step Logic Breakdown:
- Standard Python
floatuses 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:
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:
<20specifies left alignment within a 20-character wide field.>10,dspecifies right alignment within a 10-character field formatted as an integer with thousands separator commas.>14,.2fformats 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:
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 rollingStep-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:
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 (alicewould be overwritten bycharlie). defaultdict(list)initializes an empty list for any newly encountered department key.- Appending
keytoinverted[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:
- Retained Users: Active in both months.
- Churned Users: Active in January but not in February.
- New Users: Active in February but not in January.
- Retention Rate: Percentage of January users who remained active in February.
Python Solution Code:
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:
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 <= 299leverages 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:
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 CourseTier 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:
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,
forclauses 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 >= thresholdfilters 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:
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 conversionsStep-by-Step Logic Breakdown:
*transformerscollects positional arguments into a tuple of callables.**optionscollects arbitrary keyword configuration flags into a dictionary.- The pipeline loops through each transformer function iteratively, then applies global sanitization options like
drop_noneand 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:
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 akeyparameter 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:
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
yieldtransforms a regular function into a generator. - Instead of returning a populated list, execution pauses after each
yieldstatement 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:
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
valueis not adict, the function assignsflat_dict[new_key] = value. - Recursive step: when
valueis adict, 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:
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.DictReaderautomatically 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:
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
Exceptioncreates 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:
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.0Step-by-Step Logic Breakdown:
- Chaining standard bracket notation
d["data"]["order"]crashes withKeyErrorif 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:
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_attemptsloop catches exceptions and multipliesdelay *= backoff_factorbefore 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:
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.Formatterstandardizes 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:
- The customer's state is either
'CA'or'NY'. - The transaction
total_amountis strictly greater than $150.00. - The
statusis'completed'.
Python Solution Code:
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:
- Drop rows where the unique
customer_idis null (since records without an identifier cannot be joined). - Impute missing numeric
salaryvalues with the median salary of the customer's specificdepartment.
Python Solution Code:
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(...)replacesNaNvalues 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:
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"andcolumns="product_category"establish the 2D matrix axes.fill_value=0.0ensures regions with zero sales in a given category display 0.0 instead ofNaN.margins=Truecalculates 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:
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=Truegenerates a special column_mergewith 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:
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 apd.Seriesobject 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:
- 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. - 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." - Prefer Dictionary Lookups over Nested Scans: Checking
x in my_listtakes $O(N)$ time, whereas checkingx in my_dicttakes $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)$. - Use Meaningful Variable Names: In production, avoid
d,temp, orfoo. Use domain-specific names likeactive_user_cohort,unsettled_transactions, ornormalized_phone. - 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:
- Free Python Course: Interactive lessons covering Python data structures and algorithms.
- Python Essentials Course: Deep dive into functions, object-oriented programming, and file handling.
- Python Programs for Practice: 30+ practical script exercises with step-by-step walkthroughs.
- Free Data Analyst Career Course: Complete end-to-end curriculum bridging SQL, Python, and business analytics.
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 CourseFrequently 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.

Written by
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.
Related Articles
Python for Data Analysis: The Complete Workflow Playbook (2026)
Master python data analysis with this complete playbook: pandas wrangling, exploratory data analysis, statistical cohorts, and production data pipelines.
Python Tutorial: The Complete Guide for Data Analysts (2026)
Master Python programming with this comprehensive python tutorial for data analysts: variables, data structures, control flow, functions, NumPy, Pandas, and real-world projects.
SQL COUNT Function: COUNT(*), COUNT(1) & COUNT(DISTINCT) Guide
Master the SQL COUNT function with examples of COUNT(*), COUNT(1), COUNT(DISTINCT), NULL handling, and conditional counting techniques.