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 →