Python for Analytics — Interview Questions¶
Questions covering Python fundamentals as they apply to data analytics roles. Focus: data types, functions, list/dict operations, file I/O, and practical problem-solving.
Core Language¶
[Beginner] What are Python's main data types and which are mutable?
Show answer
| Type | Mutable? | Example |
|---|---|---|
int |
No | 42 |
float |
No | 3.14 |
str |
No | "hello" |
bool |
No | True, False |
None |
— | None |
list |
Yes | [1, 2, 3] |
dict |
Yes | {"key": "value"} |
set |
Yes | {1, 2, 3} |
tuple |
No | (1, 2, 3) |
Why mutability matters for analytics: When you pass a list to a function and modify it inside the function, the original list changes too (pass by reference). This can cause hard-to-find bugs if you're not expecting it.
[Beginner] What is the difference between == and is in Python?
Show answer
==compares values — are they equal?iscompares identity — are they the same object in memory?
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True — same values
print(a is b) # False — different objects
print(a is c) # True — same object (c points to a)
For analytics: always use == to compare values. Use is only for None checks: if x is None (not if x == None).
[Beginner] What is a list comprehension? Convert this loop to a comprehension:
Show answer
List comprehensions are more Pythonic, often faster, and shorter. Use them when the transformation is simple. If the logic is complex (multiple conditions, nested logic), use a regular loop for readability.
[Mid-level] Explain the difference between append, extend, and + for lists.
Show answer
a = [1, 2, 3]
# append: adds one item (even if it's a list)
a.append([4, 5])
print(a) # [1, 2, 3, [4, 5]] ← list nested inside
a = [1, 2, 3]
# extend: adds all items from an iterable
a.extend([4, 5])
print(a) # [1, 2, 3, 4, 5] ← items unpacked
a = [1, 2, 3]
# + : creates a NEW list (doesn't modify a)
b = a + [4, 5]
print(a) # [1, 2, 3] ← unchanged
print(b) # [1, 2, 3, 4, 5]
For analytics: use extend when combining lists. Avoid + in a loop — each + creates a new list, making the loop O(n²). Use extend or collections.deque instead.
Functions¶
[Beginner] What happens when a function has no return statement?
Show answer
It implicitly returns None. This catches people when they write result = my_function() expecting a value but get None.
def print_total(data):
total = sum(data)
print(f"Total: {total}") # no return!
result = print_total([100, 200])
print(result) # None
Analytics best practice: functions that calculate something should return the result, not just print it. Functions that only print/log are fine without return. Keeping them separate makes testing easier.
[Mid-level] What is the difference between *args and **kwargs?
Show answer
*args— collects extra positional arguments as a tuple**kwargs— collects extra keyword arguments as a dict
def revenue_metrics(*months, currency="£", **filters):
"""
months: variable-length positional args (tuple)
currency: keyword arg with default
filters: any extra keyword args (dict)
"""
print(f"Months: {months}")
print(f"Currency: {currency}")
print(f"Filters: {filters}")
revenue_metrics("Jan", "Feb", "Mar", currency="$", region="India", segment="VIP")
# Months: ('Jan', 'Feb', 'Mar')
# Currency: $
# Filters: {'region': 'India', 'segment': 'VIP'}
Data Structures¶
[Mid-level] Why is in faster for a set than for a list?
Show answer
A list is stored as an array — checking value in list scans every element one by one: O(n) time.
A set uses a hash table — checking value in set computes a hash and jumps to the right bucket: O(1) average time.
# This matters at scale:
vip_ids_list = ["C001", "C003", ..., "C999"] # 10,000 items
vip_ids_set = {"C001", "C003", ..., "C999"} # same 10,000 items
# For 1M lookups:
"C500" in vip_ids_list # ~5,000 comparisons on average
"C500" in vip_ids_set # ~1 hash lookup
Practical rule: if you're checking membership repeatedly, convert your list to a set first.
[Mid-level] What does dict.get(key, default) do, and why is it safer than dict[key]?
Show answer
dict[key] raises KeyError if the key doesn't exist.
dict.get(key, default) returns default (or None if no default given) instead of raising an error.
segment_discounts = {"VIP": 0.15, "Regular": 0.05}
customer_segment = "New"
# WRONG — KeyError if segment not in dict
discount = segment_discounts[customer_segment]
# CORRECT — safe
discount = segment_discounts.get(customer_segment, 0) # 0 for unknown segments
In analytics, always use .get() when the key might not exist — especially when mapping codes to labels, looking up lookup tables, or processing data with missing categories.
Practical Analytics¶
[Mid-level] You have a CSV where the revenue column contains values like "£1,249.50" and "N/A". Write a function to clean this column and return floats, treating "N/A" as 0.
Show answer
def clean_revenue(value):
"""Parse revenue strings like '£1,249.50' or 'N/A' to float."""
if value is None or value.strip() in ("N/A", "", "-"):
return 0.0
# Remove currency symbols and commas
cleaned = value.strip().replace("£", "").replace("$", "").replace(",", "")
try:
return float(cleaned)
except ValueError:
return 0.0
# Test
test_values = ["£1,249.50", "N/A", "389.00", "", "$2,500.00", "unknown"]
for v in test_values:
print(f"{v!r:20} → {clean_revenue(v)}")
[Mid-level] Write a function that groups a list of dicts by a specified key and returns a dict of lists.
Show answer
def group_by(records, key):
"""Group a list of dicts by a given key field."""
from collections import defaultdict
result = defaultdict(list)
for record in records:
result[record[key]].append(record)
return dict(result)
# Usage
orders = [
{"id": "O1", "status": "completed", "total": 100},
{"id": "O2", "status": "pending", "total": 50},
{"id": "O3", "status": "completed", "total": 200},
]
grouped = group_by(orders, "status")
print(grouped["completed"])
# [{'id': 'O1', 'status': 'completed', 'total': 100},
# {'id': 'O3', 'status': 'completed', 'total': 200}]
[Senior] What is a generator in Python, and when would you use one instead of a list?
Show answer
A generator is a function that yields values one at a time instead of building the entire result in memory.
# List: builds all 1 million items in memory at once
def square_list(n):
return [x**2 for x in range(n)]
# Generator: produces one item at a time, uses O(1) memory
def square_gen(n):
for x in range(n):
yield x**2
# Usage is identical
for val in square_gen(1_000_000):
process(val) # only one value in memory at a time
For analytics: use generators when: - Processing a large CSV row by row (don't load 10GB into memory) - Building a pipeline where each step feeds the next - Computing running totals or statistics over a stream