Code-First Revision Guide

DS / ML Interview

APIs · Patterns · Snippets · Common mistakes

01

Python

python

Core Data Structures

TypeKey opsComplexity
listappend, pop, insert, index, sortappend O(1), insert O(n)
tupleimmutable; hashable → dict key / set elemO(1) index
setadd, discard, |, &, -, ^O(1) lookup
dictget, setdefault, items, keys, values, updateO(1) avg
strsplit, join, strip, replace, find, format, f""immutable
TRAPlist.sort() in-place; sorted() returns new list. Pass key= not comparator.

collections module

from collections import Counter, defaultdict, deque, OrderedDict

# Counter — frequency map
c = Counter("abracadabra")
c.most_common(3)          # [('a',5),('b',2),('r',2)]
c1 + c2; c1 - c2         # arithmetic on counters

# defaultdict — no KeyError
dd = defaultdict(list)
dd['k'].append(1)       # auto-creates []
dd2 = defaultdict(int)   # dd2['x'] += 1

# deque — O(1) both ends
dq = deque(maxlen=3)
dq.appendleft(0); dq.popleft()
dq.append(4); dq.pop()

heapq (min-heap)

import heapq
h = []; heapq.heappush(h, 3)
smallest = heapq.heappop(h)
heapq.heapify(lst)           # in-place O(n)

# k largest / smallest
heapq.nlargest(3, lst)
heapq.nsmallest(3, lst, key=lambda x: x[1])

# max-heap trick: negate values
heapq.heappush(h, -val)
Top-K pattern: maintain heap of size K → O(n log K)

bisect (sorted insert / search)

import bisect
a = [1,3,5,7]
bisect.bisect_left(a, 5)   # 2 — leftmost pos
bisect.bisect_right(a, 5)  # 3 — rightmost pos
bisect.insort(a, 4)        # insert + keep sorted
USE — rank queries, lower/upper bound, count elements in range: bisect_right(a,hi) - bisect_left(a,lo)

itertools

import itertools as it

it.combinations([1,2,3], 2)   # (1,2)(1,3)(2,3)
it.permutations([1,2,3], 2)    # ordered pairs
it.product([0,1], repeat=3)     # cartesian product
it.chain([1,2],[3,4])           # flatten iterables
it.groupby(sorted_lst, key=fn)   # MUST be pre-sorted!
it.accumulate([1,2,3])          # [1, 3, 6]
it.islice(gen, 5)               # first 5 from generator

Builtins & Comprehensions

# enumerate, zip, sorted, map, filter, reduce
for i, v in enumerate(lst, start=1): ...
for a, b in zip(l1, l2): ...          # stops at shorter
dict(zip(keys, vals))                 # dict from two lists
sorted(lst, key=lambda x: (-x[1], x[0]))

from functools import reduce
reduce(lambda a, b: a*b, [1,2,3,4])  # 24

# List / dict / set comprehensions
squares = [x**2 for x in range(10) if x%2==0]
freq = {k: v for k, v in Counter(lst).items()}
flat = [x for sub in matrix for x in sub]
TRAPzip truncates to shortest; use zip_longest for padding.

Generators & Iterators

# Generator function — lazy, memory-efficient
def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Generator expression
gen = (x**2 for x in range(10**6))  # no memory spike
next(gen)

# Custom iterator
class Range:
    def __init__(self, n): self.n = n; self.i = 0
    def __iter__(self): return self
    def __next__(self):
        if self.i >= self.n: raise StopIteration
        self.i += 1; return self.i - 1

Decorators & Context Managers

from functools import wraps, lru_cache

# Decorator pattern
def timer(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        t0 = time.time()
        result = fn(*args, **kwargs)
        print(f"{fn.__name__}: {time.time()-t0:.3f}s")
        return result
    return wrapper

# Memoization
@lru_cache(maxsize=None)
def dp(n): ...

# Context manager
from contextlib import contextmanager
@contextmanager
def managed():
    # setup
    yield
    # teardown

Dataclasses & Typing

from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Union

@dataclass(order=True)
class Point:
    x: float
    y: float
    label: str = ""
    tags: List[str] = field(default_factory=list)

# typing patterns used in interviews
def process(data: List[Dict[str, float]]) -> Optional[float]: ...
def merge(a: Union[list, tuple]) -> list: ...

# Python 3.10+ union shorthand
def foo(x: int | None) -> str | int: ...

File · JSON · Pickle

import json, pickle, csv

# JSON
with open("f.json") as fh: d = json.load(fh)
json.dumps(d, indent=2, default=str)  # default handles datetime

# Pickle
with open("model.pkl", "wb") as f: pickle.dump(obj, f)
with open("model.pkl", "rb") as f: obj = pickle.load(f)

# CSV
import csv
reader = csv.DictReader(open("f.csv"))
rows = list(reader)  # list of dicts
TRAP — Never unpickle untrusted data. Use json for serialization by default.

Multiprocessing vs Threading

ThreadingMultiprocessing
GILBound by GILBypasses GIL
Use caseI/O-boundCPU-bound
MemorySharedSeparate
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

with ProcessPoolExecutor(max_workers=4) as ex:
    results = list(ex.map(fn, data_chunks))
CPU-bound ML preprocessing → ProcessPoolExecutor

50 Most Common DS/ML Python Patterns

#PatternOne-liner / Snippet
1Flatten nested list[x for sub in lst for x in sub]
2Deduplicate preserving orderlist(dict.fromkeys(lst))
3Frequency mapCounter(lst)
4Group by keydefaultdict(list); dd[k].append(v)
5Sliding windowfor i in range(len(a)-k+1): window=a[i:i+k]
6Two pointersl,r=0,len(a)-1; while l<r:
7Transpose matrixlist(zip(*matrix))
8Chunk list[lst[i:i+k] for i in range(0,len(lst),k)]
9Running max/minitertools.accumulate(lst, max)
10Sort dict by value descsorted(d.items(), key=lambda x:-x[1])
11Merge dicts{**d1, **d2} or d1 | d2 (3.9+)
12All unique?len(lst)==len(set(lst))
13Intersection of listslist(set(a) & set(b))
14Top-K frequentCounter(lst).most_common(k)
15Binary searchbisect.bisect_left(a, target)
16Memoize recursive@lru_cache(maxsize=None)
17Infinite loop with breaksentinel pattern with while True
18Unpackinga, *rest, z = lst
19Named tuplePoint = namedtuple('Point',['x','y'])
20Default arg mutable trapuse None; assign if arg is None: arg=[]
21Conditional expressionx if cond else y
22String to list of charslist(s); back: ''.join(lst)
23Count occurrences in strings.count('a')
24Check substring'ab' in s
25String paddings.zfill(5); s.ljust(10,'*')
26Cartesian productitertools.product(a, b)
27Combinations/Permutationsitertools.combinations(a,2)
28Reduce to single valuefunctools.reduce(op, lst, init)
29Batch/chunk generatorislice(iter(lst), batch_size)
30Deep copyimport copy; copy.deepcopy(obj)
31Priority queueheapq.heappush(h,(priority,item))
32Parse int safelyint(s) if s.lstrip('-').isdigit() else None
33Timing blocktime.perf_counter() (not time.time())
34Regex extractre.findall(r'\d+', s)
35Path handlingfrom pathlib import Path; Path(p).stem
36Environment variableos.environ.get('KEY','default')
37Chain iterablesitertools.chain.from_iterable(lists)
38Pairwise iteratezip(lst, lst[1:])
39Singleton pattern__new__ override or module-level instance
40Abstract base classfrom abc import ABC, abstractmethod
41Dataclass frozen@dataclass(frozen=True) → hashable
42walrus operatorwhile chunk := f.read(1024): ...
43Exception chainingraise ValueError("msg") from e
44Custom exceptionclass AppError(Exception): pass
45Type narrowingisinstance(x, (int,float))
46Sparse matrix dictdefaultdict(lambda: defaultdict(int))
47Reverse string/lists[::-1]; lst[::-1]
48Power set[c for r in range(n+1) for c in combinations(lst,r)]
49GCD/LCMmath.gcd(a,b); math.lcm(a,b) (3.9+)
50Enumerate + unpackfor i,(a,b) in enumerate(zip(l1,l2)):
02

NumPy

numpy

Array Creation

np.array([1,2,3], dtype=np.float32)
np.zeros((3,4)); np.ones((2,3)); np.eye(4)
np.arange(0,10,2)          # [0,2,4,6,8]
np.linspace(0,1,5)          # 5 evenly-spaced
np.full((3,3), 7)
np.random.seed(42)
np.random.randn(3,4)        # std normal
np.random.randint(0,10,(5,))
dtype: float32 saves 2x memory vs float64

Shape & Reshape

a.shape; a.ndim; a.size; a.dtype
a.reshape(3,4)          # returns view if possible
a.reshape(-1, 4)         # infer first dim
a.flatten()               # always copy
a.ravel()                 # view if possible
a.T                       # transpose
a[np.newaxis, :]          # add axis → (1,n)
np.expand_dims(a, axis=0)
np.squeeze(a)             # remove size-1 dims
TRAPreshape may return view; modifying it changes original. Use .copy() to be safe.

Indexing & Slicing

a[1, 2]             # row 1, col 2
a[1:3, :2]          # rows 1-2, cols 0-1

# Boolean indexing
a[a > 5]              # all elements > 5
a[(a > 2) & (a < 8)] # compound condition

# Fancy indexing
a[[0,2,4]]           # select rows by index list
a[np.array([0,1]), np.array([2,3])]  # (0,2),(1,3)

# np.where
np.where(a > 0, a, 0)   # ReLU
np.where(condition)        # returns indices

Broadcasting Rules

Dims compared right-to-left.
Compatible if equal OR one of them is 1.
Missing dims padded with 1 on the left.
# Examples
(3,4) + (4,)    → (3,4)   # broadcast row
(3,1) + (1,4)  → (3,4)   # outer-product style
(1,3,4)+(2,1,4)→ (2,3,4)

# Normalize rows: subtract mean per row
a -= a.mean(axis=1, keepdims=True)
TRAP — forget keepdims=True → shape mismatch.

Aggregations & Axis Ops

a.sum(axis=0)     # sum along rows → col totals
a.mean(axis=1)    # mean of each row
a.max(); a.min()
a.std(); a.var()
a.cumsum(axis=0)  # running sum per col
np.argmax(a, axis=1)  # index of max per row
np.argsort(a)[::-1]   # descending sort idx
np.unique(a, return_counts=True)
axis=0 → collapse rows (operate down); axis=1 → collapse cols (operate across)

Stack · Concat · Split

np.vstack([a, b])          # rows (axis=0)
np.hstack([a, b])          # cols (axis=1)
np.concatenate([a,b], axis=1)
np.stack([a,b], axis=0)   # new dim

np.split(a, 3, axis=0)   # equal parts
np.array_split(a, 5)      # unequal ok

Linear Algebra

np.dot(a, b)          # matrix multiply
a @ b                 # same, cleaner
np.linalg.inv(a)
np.linalg.det(a)
U, S, Vt = np.linalg.svd(a, full_matrices=False)
eigenvals, eigenvecs = np.linalg.eig(a)
np.linalg.norm(v)           # L2 norm
np.linalg.norm(v, ord=1)   # L1 norm
SVD reconstruction: A ≈ U · diag(S) · Vt (keep top-k singular values)

Performance & Vectorization

# Vectorize any function
vfn = np.vectorize(my_fn)  # slow; just a loop under hood

# Prefer ufuncs
np.add(a, b); np.multiply(a, b)
np.log(a); np.exp(a); np.sqrt(a)

# Pairwise distance (no loops)
diff = a[:, np.newaxis] - b[np.newaxis, :]
dists = np.linalg.norm(diff, axis=-1)

# Contiguous memory check
a.flags['C_CONTIGUOUS']   # row-major
a = np.ascontiguousarray(a)
PERF — Replace Python loops with broadcasting or einsum. np.einsum('ij,jk->ik', a, b) = matmul.
03

Pandas

pandas

Creation & Reading

pd.DataFrame({'a':[1,2], 'b':[3,4]})
pd.read_csv('f.csv', parse_dates=['date'],
            dtype={'id': 'int32'},
            usecols=['id','val'])        # memory tip
pd.read_parquet('f.parquet')            # faster for large data
df.to_csv('out.csv', index=False)

loc · iloc · query

df.loc[df['age'] > 25, ['name','age']]  # label-based
df.iloc[0:5, 2:4]                       # positional
df.query("age > 25 and city == 'BLR'")  # readable
df.at[idx, 'col']    # scalar: faster than loc
df.iat[0, 1]          # scalar: faster than iloc
TRAP — Chained indexing df[cond]['col'] = v → SettingWithCopyWarning. Use df.loc[cond,'col'] = v.

assign · apply · map · replace

# assign — returns new df (chain-friendly)
df.assign(
    bmi = lambda x: x.weight / x.height**2,
    age_group = lambda x: pd.cut(x.age, bins=[0,18,60,99])
)
# apply — row or col
df['col'].apply(lambda x: x*2)
df.apply(lambda row: row['a']+row['b'], axis=1)

# map — element-wise on Series
df['cat'].map({'A':1,'B':2})
df['col'].replace({-1: np.nan})  # replace vals
FASTER — Replace apply with vectorized ops or np.where / np.select for 10–100× speedup.

groupby · agg · transform

df.groupby('dept')['salary'].mean()
df.groupby(['dept','yr']).agg(
    mean_sal=('salary','mean'),
    n=(       'id',     'count')
)

# transform — returns same index (for new col)
df['dept_mean'] = df.groupby('dept')['sal'].transform('mean')
df['rank_in_dept'] = df.groupby('dept')['sal'].transform('rank', ascending=False)

# filter groups
df.groupby('cat').filter(lambda g: g['val'].mean() > 5)
TRAPagg drops NaN by default. Use min_count=1 on sum for explicit NaN propagation.

merge · join · concat

pd.merge(left, right, on='id', how='left')
pd.merge(a, b, left_on='aid', right_on='bid')
pd.merge(a, b, on='id', how='outer', indicator=True)

# concat — stack vertically or horizontally
pd.concat([df1, df2], ignore_index=True)     # rows
pd.concat([df1, df2], axis=1)               # cols

# self-join: find pairs
pd.merge(df, df, on='key', suffixes=('_x','_y'))

pivot · pivot_table · melt · explode

# pivot — unique index/columns required
df.pivot(index='date', columns='metric', values='val')

# pivot_table — handles duplicates with aggfunc
pd.pivot_table(df, values='sales',
               index='region', columns='quarter',
               aggfunc='sum', fill_value=0)

# melt — wide → long
df.melt(id_vars=['id'], value_vars=['q1','q2'],
        var_name='quarter', value_name='sales')

# explode — list col → multiple rows
df.explode('tags').reset_index(drop=True)

Missing Values

df.isnull().sum()
df.dropna(subset=['col'], how='any')
df.fillna({'a': 0, 'b': df['b'].median()})
df['col'].fillna(method='ffill')         # forward fill
df['col'].interpolate('linear')         # time-series

# Flag + fill pattern
df['col_missing'] = df['col'].isnull().astype(int)
df['col'].fillna(df['col'].mean(), inplace=True)

Datetime & String Ops

# Datetime
df['dt'] = pd.to_datetime(df['dt'])
df['dt'].dt.year; .dt.month; .dt.dayofweek
df['dt'].dt.to_period('M')       # monthly period
df.resample('W', on='dt')['sales'].sum()

# String accessor
df['name'].str.lower()
df['name'].str.contains('BLR', na=False)
df['name'].str.split(',').str[0]
df['name'].str.extract(r'(\d+)')

Rolling Windows & Rank

df['ma7'] = df['sales'].rolling(7).mean()
df['ema'] = df['sales'].ewm(span=7).mean()
df['rolling_std'] = df['sales'].rolling(7).std()

df['rank'] = df['score'].rank(method='dense', ascending=False)
df['pct_rank'] = df['score'].rank(pct=True)

Top 30 Pandas Interview Questions

  • Second highest salary per deptgroupby + rank(method='dense')
  • Running totalgroupby().cumsum()
  • YoY growth %groupby().pct_change()
  • First/last event per usergroupby().nth(0) or sort + drop_duplicates(keep='first')
  • Find rows where col A > col Bdf[df.A > df.B]
  • Cohort retention table — pivot of first_event_month vs activity_month
  • Deduplicate keeping latest — sort by date then drop_duplicates(subset='id', keep='last')
  • Count distinct users per groupgroupby().nunique()
  • Lag a columndf['col'].shift(1)
  • Fill gaps in time seriesresample().asfreq().fillna()
  • Wide to longmelt()
  • Flatten a list columnexplode()
  • Apply multiple agg to same colagg(['mean','std','count'])
  • Percentiles per groupgroupby().quantile([.25,.5,.75])
  • Index of max per groupgroupby()['col'].idxmax()
  • Cross-tabulationpd.crosstab(df.a, df.b, normalize='index')
  • Correlation matrixdf.corr()
  • Sample stratifiedgroupby().apply(lambda x: x.sample(frac=0.1))
  • Bin continuous variablepd.cut() / pd.qcut()
  • Multi-key sortsort_values(['a','b'], ascending=[True,False])
  • Combine string colsdf['full'] = df.a + ' ' + df.b
  • Parse JSON columndf['col'].apply(json.loads)
  • Flag anomaly (Z-score)(df.x - df.x.mean()) / df.x.std() > 3
  • Pivot then heatmap — pivot_table → seaborn heatmap
  • Merge on nearest timestamppd.merge_asof()
  • Stack/unstack MultiIndexdf.stack() / unstack()
  • Memory reduce — downcast int/float, category for low-cardinality cols
  • Apply that returns Seriesdf.apply(fn, axis=1, result_type='expand')
  • Chained operationsdf.pipe(fn1).pipe(fn2)
  • Validate no nulls after mergedf.isnull().sum() after merge
04

SQL

sql

Window Functions

-- ROW_NUMBER / RANK / DENSE_RANK
SELECT *,
  ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) rn,
  RANK()       OVER (PARTITION BY dept ORDER BY salary DESC) rnk,
  DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) dr
FROM employees;

-- LEAD / LAG — compare to adjacent rows
SELECT user_id, event_dt,
  LAG(event_dt)  OVER (PARTITION BY user_id ORDER BY event_dt) prev_dt,
  LEAD(event_dt) OVER (PARTITION BY user_id ORDER BY event_dt) next_dt
FROM events;
RANK skips after ties; DENSE_RANK does not; ROW_NUMBER always unique

Running Totals & Moving Averages

-- Running sum
SELECT dt, revenue,
  SUM(revenue) OVER (ORDER BY dt
                     ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) cum_rev
FROM sales;

-- 7-day moving average
SELECT dt, revenue,
  AVG(revenue) OVER (ORDER BY dt
                     ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) ma7
FROM sales;

-- Partition + running total
SUM(sales) OVER (PARTITION BY region ORDER BY dt)

CTEs & Self-Joins

-- CTE
WITH ranked AS (
  SELECT *, DENSE_RANK() OVER (PARTITION BY dept ORDER BY sal DESC) dr
  FROM emp
)
SELECT * FROM ranked WHERE dr = 2;   -- 2nd highest per dept

-- Recursive CTE (hierarchy)
WITH RECURSIVE tree AS (
  SELECT id, parent, name, 1 depth FROM org WHERE parent IS NULL
  UNION ALL
  SELECT o.id, o.parent, o.name, t.depth+1
  FROM org o JOIN tree t ON o.parent = t.id
)
SELECT * FROM tree;

CASE WHEN & Pivoting

-- CASE WHEN
SELECT id,
  CASE
    WHEN score >= 90 THEN 'A'
    WHEN score >= 70 THEN 'B'
    ELSE 'C'
  END grade
FROM students;

-- Manual pivot (conditional aggregation)
SELECT user_id,
  SUM(CASE WHEN product='A' THEN revenue ELSE 0 END) rev_A,
  SUM(CASE WHEN product='B' THEN revenue ELSE 0 END) rev_B
FROM orders
GROUP BY user_id;

Date Functions

-- BigQuery / Standard SQL
DATE_DIFF(end_dt, start_dt, DAY)
DATE_TRUNC(dt, MONTH)            -- first day of month
FORMAT_DATE('%Y-%m', dt)
TIMESTAMP_DIFF(ts2, ts1, MINUTE)
EXTRACT(YEAR FROM dt)

-- Cohort month
DATE_TRUNC(MIN(event_dt) OVER (PARTITION BY user_id), MONTH) cohort_month

Top 40 SQL Interview Patterns

  • Nth highest salaryDENSE_RANK() = N
  • Users active ≥ N consecutive days — date diff from row_number trick
  • First purchase per userROW_NUMBER()=1 ORDER BY purchase_dt
  • Retention D1/D7/D30 — self-join or date_diff on first_event
  • Cohort analysisDATE_TRUNC(first_event, MONTH) + cross join
  • Running totalSUM() OVER (ORDER BY dt)
  • YoY growthLAG(rev,12) OVER (ORDER BY month)
  • Duplicate rowsGROUP BY … HAVING COUNT(*) > 1
  • Deduplicate keep latestROW_NUMBER()=1 ORDER BY updated_at DESC
  • Users who did A then B — self-join with time ordering
  • Percentage of totalSUM(x)/SUM(x) OVER ()
  • Rank within groupRANK() OVER (PARTITION BY …)
  • MedianPERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY val)
  • ModeGROUP BY val ORDER BY COUNT DESC LIMIT 1
  • Histogram bucketsFLOOR(val/10)*10 + GROUP BY
  • Pivot without native pivotSUM(CASE WHEN …)
  • Non-null countCOUNT(col) vs COUNT(*)
  • String aggregationSTRING_AGG(col, ', ') OVER (…)
  • JSON field extract (BQ)JSON_VALUE(col,'$.field')
  • Unpivot — UNION ALL of each column as rows
  • Gap and island — row_number minus dense_rank trick
  • ANTI JOIN patternLEFT JOIN … WHERE right.id IS NULL
  • Funnel drop-off — step counts with conditional sum
  • Session definition — event 30-min gap = new session
  • Spend per user per month with zero-fill — cross join calendar + LEFT JOIN
  • Moving averageAVG() OVER (ROWS BETWEEN N-1 PRECEDING AND CURRENT ROW)
  • Percentile rankPERCENT_RANK() OVER (…)
  • Top N per group — CTE with ROW_NUMBER + WHERE rn <= N
  • Transitive closure — recursive CTE
  • Self-referential tablee.manager_id = m.id self-join
  • CROSS JOIN for combinations — avoid accidental; used for date spine
  • Conditional countCOUNTIF(cond) or SUM(CASE WHEN cond THEN 1 ELSE 0 END)
  • A/B test significance — compute mean, variance per variant in SQL
  • Users with ALL of a set of actionsHAVING COUNT(DISTINCT action) = N
  • Latest value per keyLAST_VALUE() OVER (… ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
  • Rolling 30-day unique usersRANGE BETWEEN INTERVAL 29 DAY PRECEDING AND CURRENT ROW
  • QUALIFY (BQ) — filter window functions without subquery: QUALIFY ROW_NUMBER()=1
  • Unnest array (BQ)CROSS JOIN UNNEST(arr) AS elem
  • NULL-safe compareIS NOT DISTINCT FROM or COALESCE
  • EXCEPT / INTERSECT — set operations between queries
05

Matplotlib

matplotlib

Figure · Axes API

import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 3, figsize=(12,6),
                          sharex=True)
ax = axes[0][1]

ax.plot(x, y, 'o-', color='steelblue', linewidth=2, label='train')
ax.scatter(x, y, c=labels, cmap='viridis', alpha=0.6)
ax.bar(cats, vals, color=colors)
ax.hist(data, bins=30, density=True, alpha=0.7)
ax.boxplot(data, vert=True, patch_artist=True)

ax.set_xlabel('X'); ax.set_ylabel('Y')
ax.set_title('Title')
ax.legend(loc='upper right')
ax.annotate('peak', xy=(xp, yp), xytext=(xp+1, yp+10),
            arrowprops=dict(arrowstyle='->'))
fig.tight_layout()
fig.savefig('plot.png', dpi=150, bbox_inches='tight')

Common Interview Viz Tasks

  • Loss curve — two ax.plot() on same axes, ax.legend()
  • Confusion matriximshow(cm, cmap='Blues') + text() annotations
  • Feature importance — horizontal bar: ax.barh(features, importances)
  • Distribution compare — overlapping histograms with alpha
  • Scatter with colorbarsc=ax.scatter(...,c=vals); fig.colorbar(sc)
  • Multiple subplotsfig,axes=plt.subplots(1,3,figsize=(15,4))
  • Log scaleax.set_yscale('log')
  • ROC curveax.plot(fpr,tpr); ax.plot([0,1],[0,1],'--')
06

Seaborn

seaborn

Key APIs

import seaborn as sns
sns.set_theme(style='darkgrid')

# Distribution
sns.histplot(df, x='val', hue='class', kde=True)
sns.kdeplot(df, x='val', fill=True)

# Categorical
sns.boxplot(data=df, x='cat', y='val', hue='group')
sns.violinplot(data=df, x='cat', y='val')
sns.countplot(data=df, x='cat', order=df['cat'].value_counts().index)
sns.barplot(data=df, x='cat', y='val', ci=95)   # + CI bars

# Relational
sns.scatterplot(data=df, x='a', y='b', hue='label', size='weight')

# Multi-panel
sns.pairplot(df, hue='target', diag_kind='kde')
sns.catplot(data=df, x='x', y='y', col='group', kind='box')

# Heatmap
sns.heatmap(corr_matrix, annot=True, fmt='.2f',
            cmap='coolwarm', vmin=-1, vmax=1)

When to prefer Seaborn over Matplotlib

TaskUse
Correlation matrixsns.heatmap(df.corr())
Distribution by classsns.histplot(hue=...)
Pair relationshipssns.pairplot()
Confidence intervalssns.barplot(ci=95)
Count of categoriessns.countplot(order=...)
Custom scatter stylingMatplotlib scatter more flexible
Custom subplot layoutMatplotlib fig,axes more control
07

scikit-learn

sklearn

End-to-End Pipeline

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import GradientBoostingClassifier

num_pipe = Pipeline([
    ('imp', SimpleImputer(strategy='median')),
    ('sc',  StandardScaler()),
])
cat_pipe = Pipeline([
    ('imp', SimpleImputer(strategy='most_frequent')),
    ('ohe', OneHotEncoder(handle_unknown='ignore', sparse_output=False)),
])
pre = ColumnTransformer([
    ('num', num_pipe, num_cols),
    ('cat', cat_pipe, cat_cols),
])
model = Pipeline([
    ('pre', pre),
    ('clf', GradientBoostingClassifier(n_estimators=200)),
])
model.fit(X_train, y_train)
model.predict_proba(X_test)[:,1]
KEY — Pipeline prevents data leakage: all transforms fit on train only.

Preprocessing APIs

# Scalers
StandardScaler()         # zero mean, unit var
MinMaxScaler((0,1))       # range scaling
RobustScaler()           # robust to outliers

# Encoders
OrdinalEncoder()
LabelEncoder()           # for y only; not features
OneHotEncoder(drop='first')

# Imputers
SimpleImputer(strategy='mean'/'median'/'most_frequent'/'constant')
KNNImputer(n_neighbors=5)

# Feature selection
SelectKBest(f_classif, k=10)
SelectFromModel(lasso, threshold='mean')
VarianceThreshold(threshold=0.0)   # remove zero-var
TRAPLabelEncoder on features → ordinal relationship implied. Use OHE for categoricals.

Cross-Validation & Tuning

from sklearn.model_selection import (
    cross_val_score, StratifiedKFold,
    GridSearchCV, RandomizedSearchCV
)

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=cv, scoring='roc_auc')

param_grid = {
    'clf__n_estimators': [100,200],
    'clf__max_depth':    [3,5,None],
}
gs = GridSearchCV(model, param_grid, cv=5,
                  scoring='roc_auc', n_jobs=-1)
gs.fit(X_train, y_train)
gs.best_params_; gs.best_score_
RandomizedSearchCV: n_iter=50 often beats full GridSearch at fraction of cost

Metrics APIs

from sklearn.metrics import (
    accuracy_score, roc_auc_score,
    f1_score, classification_report,
    confusion_matrix, mean_squared_error,
    mean_absolute_error, r2_score,
    ndcg_score, average_precision_score
)

# Classification
roc_auc_score(y_test, proba[:,1])
f1_score(y_test, y_pred, average='macro')
print(classification_report(y_test, y_pred))

# Regression
mean_squared_error(y_test, y_pred, squared=False)  # RMSE

# Ranking
ndcg_score([y_true_ranked], [y_score_ranked], k=10)

Model Persistence

import joblib, pickle

# joblib — preferred for sklearn (handles numpy arrays)
joblib.dump(model, 'model.joblib')
model = joblib.load('model.joblib')

# Pickle
with open('model.pkl','wb') as f: pickle.dump(model, f)

# Full pipeline save (best practice)
joblib.dump(pipeline, 'full_pipeline.joblib')
# Includes preprocessor — no need to save separately
BEST PRACTICE — Always serialize the full Pipeline, not just the model. Preprocessors must be included.
08

PyTorch

pytorch

Tensors

torch.tensor([1,2,3], dtype=torch.float32)
torch.zeros(3,4); torch.randn(2,3)
torch.arange(0,10,2)

t.shape; t.dtype; t.device
t.view(-1,4)    # like reshape (must be contiguous)
t.reshape(-1,4) # safer — copies if needed
t.unsqueeze(0); t.squeeze()
t.permute(2,0,1)     # like numpy transpose
t.contiguous()

# numpy interop
t.numpy()              # CPU tensor only
torch.from_numpy(arr)  # shares memory!
TRAPview() requires contiguous tensor. Use reshape() to be safe.

Autograd

x = torch.randn(3, requires_grad=True)
y = (x**2).sum()
y.backward()
x.grad             # dy/dx

# Disable grad (inference)
with torch.no_grad():
    out = model(x)

# Detach from graph
t.detach()
t.detach().cpu().numpy()   # full convert chain

# Zero grads BEFORE backward
optimizer.zero_grad()
loss.backward()
optimizer.step()
TRAP — Forgetting zero_grad() → gradients accumulate across batches.

nn.Module

import torch.nn as nn

class MLP(nn.Module):
    def __init__(self, in_d, hid, out_d):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(in_d, hid),
            nn.BatchNorm1d(hid),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(hid, out_d)
        )
    def forward(self, x):
        return self.net(x)

model = MLP(128, 256, 10)
model.parameters()    # for optimizer
model.state_dict()    # weight dict

Dataset & DataLoader

from torch.utils.data import Dataset, DataLoader

class MyDS(Dataset):
    def __init__(self, X, y):
        self.X = torch.FloatTensor(X)
        self.y = torch.LongTensor(y)
    def __len__(self): return len(self.y)
    def __getitem__(self, idx): return self.X[idx], self.y[idx]

loader = DataLoader(MyDS(X_tr, y_tr), batch_size=64,
                    shuffle=True, num_workers=4,
                    pin_memory=True)  # GPU transfer speed
PERFnum_workers=4 + pin_memory=True significantly reduces GPU idle time.

Training Loop

device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = model.to(device)
opt   = torch.optim.AdamW(model.parameters(), lr=1e-3)
crit  = nn.CrossEntropyLoss()
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=50)

for epoch in range(epochs):
    model.train()
    for X_b, y_b in train_loader:
        X_b, y_b = X_b.to(device), y_b.to(device)
        opt.zero_grad()
        loss = crit(model(X_b), y_b)
        loss.backward()
        nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        opt.step()
    sched.step()

    model.eval()
    with torch.no_grad():
        val_loss = sum(crit(model(X_b.to(device)), y_b.to(device))
                       for X_b, y_b in val_loader)

Save · Load · GPU

# Save weights only (recommended)
torch.save(model.state_dict(), 'model.pth')
model.load_state_dict(torch.load('model.pth'))

# Save full model (less portable)
torch.save(model, 'full.pt')

# Checkpoint
torch.save({'epoch': epoch, 'model': model.state_dict(),
            'opt': opt.state_dict(), 'loss': loss}, 'ckpt.pt')

# GPU ops
torch.cuda.is_available()
torch.cuda.memory_allocated()
model.half()        # FP16 mixed precision
TRAPload_state_dict requires model architecture to already exist. Load weights only after instantiating model.

Common Layers Quick Ref

LayerKey paramsOutput shape
nn.Linear(in,out)in_features, out_features(B, out)
nn.Conv2d(in,out,k)in_ch, out_ch, kernel_size, stride, padding(B,out_ch,H',W')
nn.Embedding(V,d)num_embeddings, embedding_dim(B,T,d)
nn.LSTM(d,h)input_size, hidden_size, num_layers, batch_first(B,T,h), (h_n,c_n)
nn.MultiheadAttentionembed_dim, num_heads(B,T,d)
nn.BatchNorm1d(d)num_features(B,d)
nn.Dropout(p)p=drop probsame shape
Conv2d output H' = floor((H + 2*padding - kernel) / stride) + 1
09

TensorFlow / Keras

tensorflow · keras

Sequential & Functional API

import tensorflow as tf
from tensorflow import keras

# Sequential
model = keras.Sequential([
    keras.layers.Dense(256, activation='relu', input_shape=(128,)),
    keras.layers.BatchNormalization(),
    keras.layers.Dropout(0.3),
    keras.layers.Dense(10, activation='softmax'),
])

# Functional (multi-input / skip connections)
inp = keras.Input(shape=(128,))
x   = keras.layers.Dense(256, activation='relu')(inp)
x   = keras.layers.Dropout(0.2)(x)
out = keras.layers.Dense(1, activation='sigmoid')(x)
model = keras.Model(inp, out)

Compile · Fit · Evaluate

model.compile(
    optimizer=keras.optimizers.Adam(lr=1e-3),
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

callbacks = [
    keras.callbacks.EarlyStopping(patience=5, restore_best_weights=True),
    keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=3),
    keras.callbacks.ModelCheckpoint('best.h5', save_best_only=True),
]

history = model.fit(
    X_train, y_train,
    epochs=100, batch_size=64,
    validation_split=0.2,
    callbacks=callbacks
)
model.evaluate(X_test, y_test)

Dataset API

ds = tf.data.Dataset.from_tensor_slices((X, y))
ds = (ds.shuffle(1000)
        .batch(64)
        .prefetch(tf.data.AUTOTUNE)   # overlap compute + IO
        .cache())                      # cache after first epoch

# From generator
ds = tf.data.Dataset.from_generator(my_gen,
     output_signature=(
         tf.TensorSpec(shape=(128,), dtype=tf.float32),
         tf.TensorSpec(shape=(), dtype=tf.int32),
     ))

Custom Training Loop

opt  = keras.optimizers.Adam()
crit = keras.losses.SparseCategoricalCrossentropy()
acc  = keras.metrics.SparseCategoricalAccuracy()

@tf.function   # compile to graph for speed
def train_step(X_b, y_b):
    with tf.GradientTape() as tape:
        preds = model(X_b, training=True)
        loss  = crit(y_b, preds)
    grads = tape.gradient(loss, model.trainable_variables)
    opt.apply_gradients(zip(grads, model.trainable_variables))
    acc.update_state(y_b, preds)
    return loss

Save & Load

# SavedModel format (recommended)
model.save('model_dir/')
model = keras.models.load_model('model_dir/')

# Legacy HDF5
model.save('model.h5')
model = keras.models.load_model('model.h5')

# Weights only
model.save_weights('w.ckpt')
model.load_weights('w.ckpt')
TRAP — Custom layers / losses need get_config() implemented for serialization. Otherwise use custom_objects arg on load.

Common Interview Tasks (TF)

  • Binary classifier — sigmoid output, binary_crossentropy
  • Multi-class — softmax output, sparse_categorical_crossentropy (int labels) or categorical_crossentropy (one-hot)
  • Regression — linear output, mse loss
  • Embedding layerEmbedding(vocab_size, dim, input_length=T)
  • 1D Conv for sequencesConv1D(filters,kernel_size,activation='relu')
  • Bidirectional LSTMBidirectional(LSTM(64, return_sequences=True))
  • Transfer learning — load base, base.trainable=False, add head
  • Mixed precisiontf.keras.mixed_precision.set_global_policy('mixed_float16')
10

One-Page Cheat Sheets

NumPy Cheat Sheet

  • np.array / zeros / ones / eye / arange / linspace / random.randn
  • .shape .dtype .reshape(-1,4) .T .flatten() .ravel()
  • a[a>0] / a[[0,2]] / np.where(cond,x,y)
  • .sum/.mean/.std axis=0/1 keepdims=True
  • np.argmax/.argsort/.unique(return_counts=True)
  • np.vstack/hstack/concatenate/stack/split
  • a @ b / np.linalg.svd/inv/norm/eig
  • np.einsum('ij,jk->ik', a, b)
  • Broadcasting: align right, size-1 expands
  • np.log/exp/sqrt/abs/clip/sign

Pandas Cheat Sheet

  • read_csv/parquet | to_csv(index=False)
  • df.loc[cond, cols] | df.iloc[r,c] | df.query()
  • df.assign(col=lambda x:...) | df.pipe()
  • groupby().agg(name=('col','func'))
  • groupby().transform('mean') — same index
  • merge(on, how='left/inner/outer')
  • pivot_table | melt | explode
  • isnull().sum() | fillna | dropna
  • .str. | .dt. | rolling().mean()
  • rank(method='dense') | cut | qcut

SQL Cheat Sheet

  • ROW_NUMBER/RANK/DENSE_RANK OVER (PARTITION BY … ORDER BY …)
  • LAG(col,n,default) / LEAD(col,n)
  • SUM() OVER (ORDER BY dt ROWS BETWEEN …)
  • WITH cte AS (…) SELECT … FROM cte
  • CASE WHEN … THEN … ELSE … END
  • DATE_TRUNC(dt,'MONTH') / DATE_DIFF / EXTRACT
  • HAVING COUNT(DISTINCT id) = N
  • LEFT JOIN … WHERE b.id IS NULL (anti-join)
  • STRING_AGG(col,',') OVER (…)
  • BQ: QUALIFY ROW_NUMBER()=1

sklearn Cheat Sheet

  • Pipeline([('pre',ColumnTransformer),('clf',model)])
  • StandardScaler | MinMaxScaler | RobustScaler
  • SimpleImputer(strategy='median') | KNNImputer
  • OneHotEncoder(handle_unknown='ignore')
  • train_test_split(stratify=y)
  • StratifiedKFold(n_splits=5, shuffle=True)
  • GridSearchCV(cv=5, n_jobs=-1, scoring='roc_auc')
  • roc_auc_score | f1_score(average='macro') | RMSE
  • SelectKBest | SelectFromModel | VarianceThreshold
  • joblib.dump/load for full pipeline

PyTorch Cheat Sheet

  • torch.tensor | .to(device) | .detach().cpu().numpy()
  • .reshape/.view/.squeeze/.unsqueeze/.permute
  • requires_grad=True | .backward() | .grad
  • optimizer.zero_grad() → loss.backward() → step()
  • nn.Sequential | nn.Linear | Conv2d | Embedding | LSTM
  • Dataset.__len__/__getitem__ | DataLoader(shuffle,num_workers)
  • model.train() / model.eval()
  • with torch.no_grad(): for inference
  • clip_grad_norm_(params, 1.0)
  • torch.save(model.state_dict())

TF/Keras Cheat Sheet

  • Sequential([Dense,BN,Dropout]) | Model(inp,out)
  • model.compile(optimizer,loss,metrics)
  • EarlyStopping(patience=5,restore_best_weights=True)
  • ReduceLROnPlateau | ModelCheckpoint
  • model.fit(X,y,validation_split=0.2,callbacks=…)
  • tf.data.Dataset.shuffle.batch.prefetch(AUTOTUNE)
  • @tf.function for graph compilation
  • GradientTape().gradient(loss, trainable_vars)
  • model.save('dir/') | load_model('dir/')
  • Activation: 'relu'/'sigmoid'/'softmax'/'tanh'
11

Interview Traps & Forgotten APIs

Python Traps

  • Mutable default argdef f(lst=[]) persists across calls. Use None.
  • Late binding closures — lambda in loop captures last value. Use default=val.
  • is vs ==is checks identity; == checks value. x is None always; never x == None.
  • Integer caching — CPython caches -5 to 256; a is b for these ints even if different vars.
  • copy vs deepcopy — shallow copy still shares nested mutable objects.
  • zip truncates — use itertools.zip_longest to pad.
  • dict ordering — insertion order guaranteed Python 3.7+.
  • float precision0.1+0.2 != 0.3; use math.isclose.
  • global vs nonlocalnonlocal for enclosing scope; global for module scope.

NumPy / Pandas Traps

  • View vs copy — slicing returns view; fancy indexing returns copy. Modifying view changes original.
  • NaN comparisonsnp.nan != np.nan. Use np.isnan().
  • Integer overflow — numpy int32 overflows silently. Check dtype.
  • SettingWithCopyWarning — chained indexing on copy. Always use df.loc[].
  • apply on large DF — O(n); replace with vectorized ops for perf.
  • groupby NaN keys — NaN keys dropped by default. Use dropna=False (pandas 1.1+).
  • merge duplicates — many-to-many merge multiplies rows. Always verify shape.
  • inplace=True — doesn't always return None; sometimes returns copy. Just reassign.
  • object dtype — string cols stored as object; use pd.StringDtype() for explicit strings.

PyTorch / TF Traps

  • Forgot zero_grad() — gradients accumulate. Call before every backward.
  • model.eval() missing — Dropout/BN behave differently in train mode. Always switch.
  • tensor on different devices — operations between CPU and GPU tensors crash.
  • detach before numpy().detach().cpu().numpy() — all three needed.
  • BCE loss + sigmoid — use BCEWithLogitsLoss (numerically stable); skip manual sigmoid.
  • CrossEntropyLoss — expects raw logits NOT softmax. Adding softmax double-applies.
  • Keras fit vs custom loopmodel(x, training=True) must pass training flag for BN/Dropout in custom loops.
  • tf.function retracing — input shape changes cause retrace. Use input_signature.
  • Memory leak in loop — storing loss tensor (not scalar) in list. Use loss.item().

Frequently Forgotten APIs

APILibraryWhat it does
np.einsumNumPyFlexible tensor contraction; matmul, trace, outer product
np.broadcast_toNumPyCreate view with broadcast shape (no copy)
df.pipe(fn)PandasApply function to df for clean chaining
df.assign()PandasAdd/modify cols returning new df (chain-safe)
pd.merge_asofPandasNearest-key merge for time-series
df.explodePandasList column → multiple rows
QUALIFYBigQueryFilter on window function without subquery
SelectFromModelsklearnFeature selection using estimator importance
ColumnTransformer remainder='passthrough'sklearnPass untransformed cols through
clip_grad_norm_PyTorchGradient clipping — prevents exploding grads
pin_memory=TruePyTorchFaster CPU→GPU tensor transfer
@tf.functionTFCompile Python to TF graph for speed
prefetch(AUTOTUNE)TFOverlap preprocessing with compute
itertools.groupbyPythonGroup consecutive elements — MUST pre-sort
bisect.insortPythonInsert into sorted list O(n) with binary search

Top 30 Debugging Interview Questions

QuestionRoot CauseFix
Why is loss NaN from step 1?Learning rate too high or log(0)Reduce LR; add epsilon to log
Val loss lower than train loss?Dropout active at eval; data leakageCall model.eval(); check splits
Model predicts same class alwaysClass imbalance; saturated sigmoidWeight classes; check final activation
CUDA OOM mid-trainingBatch too large; graph accumulationReduce batch size; detach in metric loops
Gradients all zeroDead ReLU; wrong loss; detached tensorUse LeakyReLU; verify loss connectivity
Pandas SettingWithCopyWarningChained assignment on copyUse df.loc[cond,'col'] = val
Merge explodes row countNon-unique join keys (many-to-many)Deduplicate before merge; add validate='m:1'
Pipeline score different from manualData leakage in manual approachTrust Pipeline; check manual steps for leakage
roc_auc_score ValueErrorOnly one class in y_testEnsure stratified split
torch.from_numpy edits original arrayShares memory.copy() numpy array first
Accuracy high but model uselessClass imbalance; check baselineUse F1/AUC; check class distribution
Model converges but generalizes poorlyOverfitting; feature leakageAdd dropout; check time-based split
apply() returns NoneFunction returns None implicitlyEnsure explicit return in lambda/func
GroupKFold vs StratifiedKFoldUser-level data needs GroupKFold to prevent leakageUse GroupKFold(groups=user_id)
SQL returns fewer rows than expectedINNER JOIN drops NULLsSwitch to LEFT JOIN; verify keys