Python interview questions for data roles, with answers.

Data analyst and data scientist interviews test practical Python: cleaning a messy file, grouping, joining and explaining what you found. Here are 35 questions on core Python, pandas and NumPy, each with code that runs on pandas 3.0, including five coding tasks of the kind interviewers set.

Skillcept OnlineSources checked 17 Oct 202618 min readHow we write these guides

Key takeaways

  • Analyst and data science interviews test practical Python more than puzzles: reading a messy file, cleaning it, grouping, joining and explaining the result.
  • Know five pandas moves cold: loc and iloc, groupby with agg, merge, pivot_table, and vectorised conditions with np.where or pd.cut.
  • pandas 3.0 made copy-on-write the only mode: chained assignment such as df["a"][mask] = 0 never changes df, so use one .loc call.12
  • In pandas 3.0, text columns get the new str dtype by default instead of object, which old answers and tutorials still show.2
  • AI is part of the job: 39% of data and analytics job postings in India mention AI (Indeed Hiring Lab, September 2025), so be ready to explain how you check code an AI tool wrote.14

What Python rounds test

Analyst and data science interviews in India usually test Python through the work itself: a file to clean, a question to answer with pandas, a short function to write. Algorithm puzzles come up less often than in software interviews, and how much depends on the role.

RoleHow to prepare
Data Analystpandas: filtering, grouping, merging, dates and missing values; basic Python typesQuestions 1–8 and 14–24, then clean one real dataset end to end
Data ScientistAll of that, plus NumPy, functions and classes, and reusable codeEvery question here, then a modelling project you can explain line by line
AI/ML EngineerClean, tested code: generators, decorators, classes, error handling, APIsQuestions 9–12 in depth, and code you’d be happy for a colleague to review

Under each role: the Python it’s usually tested on.

Rounds take the same three shapes as SQL rounds: an online test (often “what does this code print?” plus a short problem), a live round in a notebook or shared editor, and a take-home dataset. In a live round, narrate: say what you expect each line to return before you run it. Most analyst rounds also include SQL, covered in our SQL interview questions.

The example data

The pandas answers all use this small DataFrame of an online shop’s orders, plus a table of customer segments. It has the traps interviewers like: a missing amount, repeat customers, two orders on one date and a customer with no segment. Paste it into a notebook first.

import numpy as np
import pandas as pd

orders = pd.DataFrame({
    "order_id":   [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008],
    "customer":   ["Aarav", "Diya", "Aarav", "Kabir", "Meera", "Diya", "Rohan", "Kabir"],
    "city":       ["Pune", "Ahmedabad", "Pune", "Delhi", "Chennai", "Ahmedabad", "Kolkata", "Delhi"],
    "order_date": pd.to_datetime(["2026-09-02", "2026-09-05", "2026-09-20", "2026-10-01",
                                  "2026-10-02", "2026-10-04", "2026-10-04", "2026-10-11"]),
    "category":   ["Fashion", "Fashion", "Electronics", "Home", "Fashion", "Home", "Fashion", "Home"],
    "amount":     [1798.0, 2499.0, 1999.0, 3398.0, 1299.0, 998.0, 2499.0, np.nan],
    "status":     ["delivered", "delivered", "cancelled", "delivered", "returned",
                   "delivered", "delivered", "delivered"],
})

segments = pd.DataFrame({
    "customer": ["Aarav", "Diya", "Kabir", "Sana"],
    "segment":  ["Gold", "Silver", "Gold", "New"],
})

Core Python: 13 questions

Types, functions and the habits that make code readable. Online tests often show a snippet and ask what it prints, so run each of these and check you predicted the output.

1. What is the difference between mutable and immutable types?Easy

A mutable object can change in place: lists, dicts and sets. An immutable one can’t: numbers, strings, tuples and frozensets; “changing” a string makes a new one. Two names can point at the same mutable object, so a change through one shows through the other. Only immutable (hashable) values can be dict keys or set members.

city = "pune"
city.upper()            # 'PUNE' is a new string; city is still 'pune'

cities = ["Pune", "Delhi"]
same = cities           # a second name for the same list
same.append("Jaipur")
cities                  # ['Pune', 'Delhi', 'Jaipur']

point = (18.52, 73.86)
point[0] = 0            # TypeError: tuples can't be changed

2. List, tuple, set or dict: when do you use which?Easy

A list is an ordered collection you’ll change. A tuple is a fixed record. A set holds unique values and answers “is it in there?” fast. A dict maps keys to values. Membership tests on sets and dicts take about the same time however big they get; on a list they get slower as it grows.

order_ids = [1001, 1002, 1002]          # list: ordered, duplicates allowed
location  = ("Pune", 411001)            # tuple: a fixed record, can be a dict key
cities    = {"Pune", "Delhi", "Pune"}   # set: {'Pune', 'Delhi'}
price     = {"kurta": 1299, "shoes": 2499}

"Pune" in cities        # True, fast even for millions of items
price["kurta"]          # 1299

3. What is the difference between is and ==?Easy

== asks whether two values are equal. is asks whether two names point at the very same object. Use is only for None (and True/False checks you really mean).

a = [1, 2]
b = [1, 2]
a == b          # True: same value
a is b          # False: two separate lists

x = None
x is None       # the right way to test for None

4. Shallow copy or deep copy?Medium

A shallow copy makes a new outer container but shares the objects inside it. A deep copy copies everything, all the way down. It matters as soon as you have a list of lists or a dict of lists.12

import copy

stores  = [["Pune", 120], ["Delhi", 60]]
shallow = stores.copy()          # new outer list, same inner lists
deep    = copy.deepcopy(stores)  # copies the inner lists too

shallow[0][1] = 999
stores[0][1]     # 999: the inner list is shared
deep[0][1]       # 120

5. Why is a list as a default argument a bug?Medium

Python evaluates a default value once, when the function is defined, not on each call. A mutable default is therefore shared between calls and keeps growing. Use None and create the list inside.10

def add_item(item, basket=[]):       # created once
    basket.append(item)
    return basket

add_item("kurta")     # ['kurta']
add_item("shoes")     # ['kurta', 'shoes']: the same list again

def add_item(item, basket=None):     # the fix
    if basket is None:
        basket = []
    basket.append(item)
    return basket

6. Write list, dict and set comprehensionsEasy

A comprehension builds a list, dict or set from a loop in one expression, with an optional filter. In round brackets it becomes a generator expression, which produces values one at a time instead of building a list.

prices = {"kurta": 1299, "shoes": 2499, "bottle": 499}

[p for p in prices.values() if p > 1000]        # list: [1299, 2499]
{name: round(p * 0.9) for name, p in prices.items()}   # dict: 10% off
{len(name) for name in prices}                  # set: {5, 6}
sum(p for p in prices.values())                 # generator expression: 4297

7. What do *args and **kwargs do?Easy

*args collects any number of positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. The names are convention: the stars do the work. Arguments after *args can only be passed by keyword.

def total(*amounts, discount=0, **details):
    print(details)                  # {'city': 'Pune'}
    return sum(amounts) - discount

total(1299, 2499, discount=100, city="Pune")   # 3698

8. What is a lambda, and when would you use one?Easy

A lambda is a small, nameless function written as one expression. Use it where a function is needed briefly, such as a sort key; write a normal def for anything longer or reused.

products = [("kurta", 1299), ("shoes", 2499), ("bottle", 499)]
sorted(products, key=lambda p: p[1], reverse=True)
# [('shoes', 2499), ('kurta', 1299), ('bottle', 499)]

9. What is a generator, and why use one?Medium

A generator is a function that uses yield to hand back values one at a time, pausing between them. It never holds the whole sequence in memory, which is why it suits big files and streams. It can be read only once.

def read_rows(path):
    with open(path) as f:
        for line in f:                       # one line at a time
            yield line.rstrip("\n").split(",")

rows = read_rows("orders.csv")   # nothing is read yet
next(rows)                       # reads the first line only

10. How do try, except, else and finally work?Medium

try runs the risky code; except handles a named error; else runs only if nothing went wrong; finally always runs, error or not. Catch specific exceptions: a bare except: hides real bugs.

def parse_amount(text):
    try:
        return float(text.replace("₹", "").replace(",", ""))
    except (ValueError, AttributeError):    # bad text, or None
        return None

parse_amount("₹1,299")    # 1299.0
parse_amount("N/A")       # None

try:
    f = open("orders.csv")
except FileNotFoundError:
    print("No file")
else:
    print(f.readline())   # only if open() worked
    f.close()
finally:
    print("Done")         # always

11. Explain classes, self, and class versus instance attributesMedium

A class is a blueprint for objects. __init__ sets up each new object, and self is the object itself. A class attribute is shared by every instance; an instance attribute belongs to one. A subclass inherits methods and can extend them, calling the parent with super().

class Order:
    currency = "INR"                      # class attribute: shared

    def __init__(self, order_id, amount):
        self.order_id = order_id          # instance attributes
        self.amount = amount

    def with_discount(self, pct):
        return self.amount * (1 - pct / 100)

    def __repr__(self):
        return f"Order({self.order_id}, {self.amount})"

class UPIOrder(Order):                    # inherits from Order
    def __init__(self, order_id, amount, upi_ref):
        super().__init__(order_id, amount)
        self.upi_ref = upi_ref

o = UPIOrder(1001, 1798, "UPI123")
o.with_discount(50), o.currency           # (899.0, 'INR')

12. What is a decorator? Write oneHard

A decorator is a function that takes a function and returns a new one that adds behaviour around it, such as timing, logging or retrying. @timed above a function is shorthand for load_orders = timed(load_orders). functools.wraps keeps the original function’s name and docstring.

import time
from functools import wraps

def timed(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter() - start:.3f}s")
        return result
    return wrapper

@timed
def load_orders(path):
    return pd.read_csv(path)

13. How do you handle a missing dict key?Easy

d[key] raises KeyError if the key is missing. d.get(key, default) returns a default instead. defaultdict creates the missing value for you, and Counter counts things.11

from collections import Counter, defaultdict

stock = {"kurta": 5}
stock.get("shoes", 0)                   # 0, no KeyError

by_city = defaultdict(list)
for city, order_id in [("Pune", 1001), ("Delhi", 1004), ("Pune", 1003)]:
    by_city[city].append(order_id)      # no need to check the key first
by_city["Pune"]                         # [1001, 1003]

Counter(["Pune", "Delhi", "Pune"])      # Counter({'Pune': 2, 'Delhi': 1})

pandas: 14 questions

The heart of a data interview. Each answer runs on the orders DataFrame above.

14. What is the difference between a Series and a DataFrame?Easy

A Series is one labelled column of values. A DataFrame is a table: several Series sharing one index. Single brackets with one name give a Series; double brackets give a DataFrame.

s  = orders["amount"]       # Series
df = orders[["amount"]]     # DataFrame with one column
type(s).__name__, type(df).__name__     # ('Series', 'DataFrame')
orders.shape                            # (8, 7): rows, columns

15. loc or iloc?Easy

loc selects by label and includes the end of a slice. iloc selects by position and excludes the end, like Python lists. loc also takes a boolean mask, which is how you filter and pick columns in one step.3

o = orders.set_index("order_id")

o.loc[1001:1003, ["customer", "amount"]]   # labels 1001 to 1003: 3 rows, end included
o.iloc[0:3, [0, 4]]                        # positions 0, 1, 2: end excluded
o.loc[o["amount"] > 2000, "customer"]      # mask and column together

16. Filter rows on several conditionsEasy

Combine conditions with & (and), | (or) and ~ (not), and wrap each one in brackets: Python’s and/or don’t work on whole columns. isin, between (both ends included) and query keep long filters readable. A NaN compares as False, so order 1008 never passes.

mask = (orders["status"] == "delivered") & (orders["amount"] > 2000)
orders[mask]                                         # 1002, 1004, 1007

orders[orders["city"].isin(["Pune", "Delhi"])]
orders[orders["amount"].between(1000, 2000)]
orders.query("status == 'delivered' and amount > 2000")

17. Count values and find duplicatesEasy

value_counts counts each value, largest first. nunique counts distinct values. duplicated marks the repeats (the first one isn’t marked), and drop_duplicates removes them. Use subset to decide which columns define a duplicate.

orders["city"].value_counts()
orders["customer"].nunique()                         # 5
orders.duplicated(subset=["customer"]).sum()         # 3 repeat orders
orders.drop_duplicates(subset=["customer"], keep="first")   # each customer's first order

18. Group and aggregate: revenue by cityMedium

Named aggregation gives each output column a clear name. Note what the result hides: sum and mean skip NaN silently, so Delhi shows 2 orders but only one amount. Check for missing values before you trust a total.4

summary = (orders[orders["status"] == "delivered"]
           .groupby("city", as_index=False)
           .agg(orders=("order_id", "count"),
                revenue=("amount", "sum"),
                avg_order=("amount", "mean")))
# Delhi: orders 2, revenue 3398.0, avg_order 3398.0, because order 1008 has no amount

19. agg or transform?Medium

agg returns one row per group. transform returns one value per original row, lined up with the index, so you can add a group figure as a new column, such as each order’s share of its city’s revenue. It’s the pandas version of a SQL window function.

orders["city_avg"] = orders.groupby("city")["amount"].transform("mean")
orders["share_of_city"] = (orders["amount"]
                           / orders.groupby("city")["amount"].transform("sum"))

20. Explain merge types, and check a merge didn’t go wrongMedium

merge joins on columns, with how set to inner, left, right, outer or cross, just like SQL joins. Two arguments catch mistakes: validate raises an error if the keys aren’t as unique as you expect, and indicator=True adds a _merge column showing where each row came from. join merges on the index; concat stacks frames.5

merged = orders.merge(segments, on="customer", how="left",
                      validate="many_to_one", indicator=True)
merged["_merge"].value_counts()
# both 6, left_only 2: Meera and Rohan have no segment

21. pivot or pivot_table?Medium

pivot only reshapes, so it fails if any index–column pair appears twice. pivot_table aggregates the duplicates (the default is the mean) and can fill the gaps. In real data, reach for pivot_table.

orders.pivot_table(index="city", columns="category", values="amount",
                   aggfunc="sum", fill_value=0)

orders.pivot(index="city", columns="category", values="amount")
# ValueError: Delhi has two Home orders

22. apply or a vectorised operation?Medium

apply with a Python function runs once per row, which is slow on big data. Vectorised operations work on the whole column at once in compiled code. For conditions use np.where, np.select or pd.cut; for lookups use map. Watch the NaN: pd.cut leaves it missing, while np.where quietly puts it in the “else” bucket.

# Slow: a Python function per row
orders["band"] = orders["amount"].apply(lambda x: "Large" if x >= 2500 else "Small")

# Fast: one operation on the whole column
orders["band"] = np.where(orders["amount"] >= 2500, "Large", "Small")

# Several bands, NaN stays NaN
orders["band"] = pd.cut(orders["amount"], bins=[0, 1000, 2500, np.inf],
                        labels=["Small", "Medium", "Large"], right=False)

# Lookups
orders["is_sale"] = orders["status"].map({"delivered": 1, "cancelled": 0, "returned": 0})

23. How do you handle missing values?Medium

First count them and ask why they’re missing: a blank amount might mean a failed payment, not a zero. Then choose: drop the rows, fill them (a median, or a group’s median), or keep them and flag them. Never test for NaN with ==: NaN isn’t equal to itself.6

orders["amount"].isna().sum()        # 1
orders["amount"].mean()              # 2070.0, from the 7 known amounts

orders.dropna(subset=["amount"])                                  # drop
orders["amount"].fillna(orders["amount"].median())                # fill
orders["amount"].fillna(orders.groupby("city")["amount"].transform("median"))  # fill by city
orders["amount_missing"] = orders["amount"].isna()               # flag

np.nan == np.nan                     # False: use isna()

24. Parse dates and summarise by monthMedium

Convert text to datetimes with pd.to_datetime and give the format: Indian files often write day first (05/09/2026 is 5 September), which a guess can read the wrong way. errors="coerce" turns bad dates into NaT. The .dt accessor pulls out parts, and resample groups a date index by period.7

raw = pd.Series(["05/09/2026", "20/09/2026", "not a date"])
pd.to_datetime(raw, format="%d/%m/%Y", errors="coerce")   # 2026-09-05, 2026-09-20, NaT

orders["month"]   = orders["order_date"].dt.to_period("M")
orders["weekday"] = orders["order_date"].dt.day_name()

monthly = orders.set_index("order_date").resample("MS")["amount"].sum()

25. Running totals, moving averages and growthHard

These are the pandas versions of SQL window functions: cumsum for a running total, rolling for a moving average, shift for the previous row (SQL’s LAG) and pct_change for growth. rolling(7) counts rows; on a date index, rolling("7D") counts calendar days, so gaps don’t stretch the window.

daily = orders.groupby("order_date")["amount"].sum()

daily.cumsum()                           # running total
daily.rolling(7, min_periods=1).mean()   # last 7 rows
daily.rolling("7D").mean()               # last 7 calendar days
daily.shift(1)                           # previous row's value
daily.pct_change()                       # change on the previous row

26. Why didn’t my assignment change the DataFrame?Hard

Because of chained assignment. Since pandas 3.0, copy-on-write is the only mode: every indexing step behaves like a copy, so df["col"][mask] = value updates a temporary copy, never df, and pandas warns with ChainedAssignmentError. The old SettingWithCopyWarning is gone. Do the selection and the assignment in one .loc call.12

# Never works in pandas 3.0: orders is unchanged
orders["amount"][orders["status"] == "cancelled"] = 0

# Works
orders.loc[orders["status"] == "cancelled", "amount"] = 0

27. A DataFrame is too big for memory. What do you do?Hard

Measure first with memory_usage(deep=True). Then load less: only the columns you need (usecols), smaller types (category for repeated text such as city names, downcast numbers), or the file in chunks that you aggregate as you go. If it still doesn’t fit, move the work into a database with SQL.8

orders.memory_usage(deep=True).sum()

orders["city"] = orders["city"].astype("category")
orders["order_id"] = pd.to_numeric(orders["order_id"], downcast="integer")

cols = ["order_id", "city", "amount"]
pd.read_csv("orders.csv", usecols=cols, dtype={"city": "category"})

totals = {}
for chunk in pd.read_csv("orders.csv", usecols=cols, chunksize=100_000):
    for city, amount in chunk.groupby("city")["amount"].sum().items():
        totals[city] = totals.get(city, 0) + amount

NumPy: 3 questions

pandas is built on NumPy, so interviewers check that you know why it’s fast and how its shapes work.

28. Why is a NumPy array faster than a Python list?Easy

An array holds one data type in one block of memory, and its operations run as loops in compiled code. A list holds separate Python objects, and a loop over it runs in Python, one item at a time. Writing whole-array operations instead of loops is called vectorisation.

prices = np.array([1299, 2499, 1899, 499])
prices * 0.9                                   # one vectorised operation
[p * 0.9 for p in [1299, 2499, 1899, 499]]     # a Python loop, item by item
prices.dtype                                   # one type for every element

29. What is broadcasting, and what does axis mean?Medium

axis=0 works down the rows (one result per column); axis=1 works across the columns (one result per row). Broadcasting lets arrays of different shapes combine: compare the shapes from the right, and each pair of sizes must be equal or one of them must be 1. Below, a (2, 3) array divides by a (2, 1) array.9

sales = np.array([[120,  80, 95],     # rows: 2 stores
                  [ 60, 110, 70]])    # columns: 3 months

sales.sum(axis=0)     # per month: [180 190 165]
sales.sum(axis=1)     # per store: [295 240]

sales / sales.sum(axis=1, keepdims=True)   # each store's monthly share

30. Filter an array with a conditionEasy

A comparison on an array gives a boolean mask, an array of True and False, which you can use to select values. np.where picks between two values element by element, and the mean of a mask is the share that’s True.

amounts = np.array([1798, 2499, 1999, 3398, 1299])
amounts[amounts > 2000]                      # array([2499, 3398])
np.where(amounts > 2000, "High", "Normal")
(amounts > 2000).mean()                      # 0.4: 2 of 5

Coding tasks: 5 questions

Short problems of the kind set in online tests and live rounds. Say the approach and its cost before you code.

31. Remove duplicates from a list, keeping the orderEasy

set() removes duplicates but loses the order. Dicts keep insertion order (a language rule since Python 3.7), so dict.fromkeys does it in one line; the loop shows you understand why.13

def dedupe(items):
    return list(dict.fromkeys(items))

def dedupe_loop(items):
    seen, out = set(), []
    for x in items:
        if x not in seen:
            seen.add(x)
            out.append(x)
    return out

dedupe(["Pune", "Delhi", "Pune", "Jaipur", "Delhi"])   # ['Pune', 'Delhi', 'Jaipur']

32. Count the words in a text and show the top 3Easy

Lower-case the text, pull out the words with a regular expression (so punctuation doesn’t stick to them), then count with Counter. most_common breaks ties in the order the words first appeared.11

import re
from collections import Counter

text = "UPI is fast. UPI is free. Cards are not free."
words = re.findall(r"[a-z']+", text.lower())
Counter(words).most_common(3)     # [('upi', 2), ('is', 2), ('free', 2)]

33. Find two products whose prices add up to a gift card’s valueMedium

The classic “two sum”. Checking every pair takes time that grows with the square of the list. A dict of prices seen so far does it in one pass: for each price, look up the partner you need.

def pair_with_sum(prices, target):
    seen = {}                          # price -> position
    for i, p in enumerate(prices):
        if target - p in seen:
            return seen[target - p], i
        seen[p] = i
    return None

pair_with_sum([499, 1299, 1899, 2499], 3798)    # (1, 3): 1299 + 2499

34. The top 2 orders by amount in each cityMedium

Sort, then take the first rows of each group; or rank inside each group and filter, which mirrors SQL’s DENSE_RANK and keeps ties. NaN sorts last, so the missing amount never makes the top.

top2 = (orders.sort_values("amount", ascending=False)
              .groupby("city")
              .head(2))

orders["rank_in_city"] = (orders.groupby("city")["amount"]
                                .rank(method="dense", ascending=False))
orders[orders["rank_in_city"] <= 2]

35. Read a messy CSV and clean itHard

The most common take-home task. Work in a fixed order: tidy the column names, parse dates with their format, clean the text, turn amounts into numbers, then remove duplicates, since rows that differ only in spacing or case match once they’re cleaned. Finish with info() to check every type.

import io

raw = io.StringIO("""Order ID, Order Date ,City,Amount
1001,02/09/2026, pune ,"₹1,798"
1002,05/09/2026,Ahmedabad,"₹2,499"
1002,05/09/2026,Ahmedabad,"₹2,499"
1003,20/09/2026,PUNE,
""")

df = pd.read_csv(raw)
df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
df["order_date"] = pd.to_datetime(df["order_date"], format="%d/%m/%Y")
df["city"] = df["city"].str.strip().str.title()
df["amount"] = pd.to_numeric(df["amount"].str.replace(r"[₹,]", "", regex=True),
                             errors="coerce")
df = df.drop_duplicates()
df.info()     # 3 rows; order_date datetime64, amount float64 with one NaN

A 7-day practice plan

About 90 minutes a day. Type every answer yourself in a notebook before you read ours, and predict each output before you run it.

DayFocusQuestionsFinish when
1Types, copies and the default-argument trap1–5You can predict what each snippet prints
2Comprehensions, functions, lambdas, generators6–9, 31–32You can write a generator from memory
3Errors, classes, decorators, dicts10–13, 33Two-sum in one pass, explained out loud
4pandas basics: selecting, filtering, counting, grouping14–19loc and iloc without a second thought
5Merges, reshaping, vectorising, missing values20–23Every merge checked with validate
6Dates, windows, copy-on-write, memory, NumPy24–30You can explain axis and broadcasting
7A mock take-home: clean and analyse a public dataset in 60 minutes34–35A clean notebook with three findings in plain words

For day 7, pick a dataset from our 15 data analyst projects on Indian public data: real files are messier than any example, which is the point.

Free, official practice material: The Python Tutorial, the pandas User Guide, NumPy: the absolute basics, and Kaggle Learn’s Python and Pandas courses.

Questions

Is Python required for a data analyst job?

Not for every role: many analyst jobs centre on SQL, Excel and Power BI. Python, mainly pandas, widens the roles you can apply for, and Data Scientist roles expect it.

How much Python do I need for a data analyst interview?

Enough to load a file, clean it, group and join it, and explain what you found: questions 1–8 and 14–24 in this guide. Classes, decorators and generators matter more for Data Scientist and AI/ML Engineer roles.

Do data interviews ask DSA or LeetCode questions?

Less than software interviews do. Expect practical problems with strings, lists and dicts, like the five coding tasks here; some product companies add an easy algorithm problem, so practise a few.

Which matters more for analysts, pandas or SQL?

Learn both: SQL gets data out of company databases, and pandas cleans and analyses it once it’s out. Most analyst rounds test SQL, and many add a pandas task.

Which pandas version should I learn in 2026?

pandas 3.0, the current release. It made copy-on-write the only mode and a new str dtype the default for text, so older tutorials that rely on chained assignment or object-dtype strings will mislead you.

Sources

  1. pandas 3.0 documentation, Copy-on-Write (CoW), read 17 Oct 2026.
  2. pandas documentation, What’s new in 3.0.0, read 17 Oct 2026.
  3. pandas 3.0 documentation, Indexing and selecting data, read 17 Oct 2026.
  4. pandas 3.0 documentation, Group by: split-apply-combine, read 17 Oct 2026.
  5. pandas 3.0 documentation, Merge, join, concatenate and compare, read 17 Oct 2026.
  6. pandas 3.0 documentation, Working with missing data, read 17 Oct 2026.
  7. pandas 3.0 documentation, Time series / date functionality, read 17 Oct 2026.
  8. pandas 3.0 documentation, Scaling to large datasets, read 17 Oct 2026.
  9. NumPy documentation, Broadcasting, read 17 Oct 2026.
  10. The Python Tutorial, More Control Flow Tools (default argument values), read 17 Oct 2026.
  11. Python documentation, collections: Container datatypes, read 17 Oct 2026.
  12. Python documentation, copy: Shallow and deep copy operations, read 17 Oct 2026.
  13. Python documentation, Built-in Types (dictionary order), read 17 Oct 2026.
  14. Indeed Hiring Lab, September 2025 labour market update, 25 Oct 2025.

People like you

They made the jump into analytics and data science.

Real learners, with written consent. Records on file.Read every story

Data Science & AI Job Guarantee Program

Learn, get hired, then pay.

Not placed after completing the program? You pay ₹0.

Completing means the 6 core modules, 5 capstones in 15 days, the competency test and signing within 30 days (on Infinity, also 75% live-class attendance). No main tuition before you earn.

Plus: your total if hired ₹71,991, and your registration comes back in full.

See plans from ₹7,999 How pay after placement works