Skip to content

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.

def bad_filter(data):
    data.remove(data[0])   # modifies the ORIGINAL list!
    return data

revenues = [42000, 38500, 51200]
bad_filter(revenues)
print(revenues)   # [38500, 51200] — original modified!

# Fix: work on a copy
def safe_filter(data):
    copy = data.copy()
    copy.remove(copy[0])
    return copy

[Beginner] What is the difference between == and is in Python?

Show answer
  • == compares values — are they equal?
  • is compares 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:

result = []
for x in data:
    if x > 0:
        result.append(x * 2)

Show answer
result = [x * 2 for x in data if x > 0]

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

# Memory-efficient CSV processing
def read_large_csv(filepath):
    import csv
    with open(filepath, "r", newline="", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            yield row   # one row at a time

total = sum(float(row["revenue"]) for row in read_large_csv("huge.csv"))

← Practice Exercises · Next: NumPy and Pandas →