NumPy & SciPy for Scientific Computing Module 2
Indexing, reshaping and vectorised operations interview questions
Interview questions on indexing, reshaping and vectorised operations: warm-ups, then what each harder question tests and how a good answer starts.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
What is the difference between a view and a copy in NumPy, and how can you tell which one you have?
-
Fresher
What does a[a > 0] return, and how is it different from np.where(a > 0, a, 0)?
-
Fresher
What is the difference between np.concatenate and np.stack?
Answered in the lesson Reshaping, stacking and splitting arrays
-
Fresher
What is a ufunc, and why is np.add(a, b) faster than a Python loop over the elements?
Answered in the lesson Universal functions and vectorised arithmetic
-
Fresher
For an array of shape (5, 4), what do sum(axis=0) and sum(axis=1) return, and what does keepdims=True change?
Answered in the lesson Reductions, axes, NaN and masked data
-
Fresher
What is the difference between np.sort and np.argsort, and when do you need a stable sort?
Answered in the lesson Sorting, searching, counting and set operations
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
A helper standardises the first three columns of the array it receives: block = data[:, :3], then block -= block.mean(axis=0) and block /= block.std(axis=0). Callers report that their own arrays change. Explain why, and fix the helper without copying the whole input.
- What it tests
- Knowing that basic slices are views and that in-place operators write through them, estimating what a fix costs, and testing that a function leaves its input alone.
- First step
- Name the mechanism: data[:, :3] is basic indexing, so block is a view over the caller's buffer, and -= and /= write into it. np.shares_memory(block, data) confirms it. Then decide the contract: a helper that returns a new result must not write to its input, so either copy only the three columns or let the arithmetic build a new array.
- Follow-ups
-
- Is block = data[:, :3].copy() the only fix, and what does each fix cost in memory?
- How would a test catch this before it reached a caller?
- What changes if a caller passes data[:, [0, 1, 2]] instead of the whole array?
From the lesson Indexing, slicing and views
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A service loads a 2 GB float64 array for each request and keeps its first 100 rows in an in-memory cache keyed by file. The cached rows add up to a few megabytes, yet the process is killed for using too much memory after a few dozen requests. Explain what is happening and fix it.
- What it tests
- Knowing that a view keeps its base array alive, estimating the memory that really stays allocated, and choosing a fix that releases it.
- First step
- Put numbers on both sides. With 10,000 float64 values a row, 100 rows are 8 MB, but arr[:100] is a view whose base is the whole 2 GB array, so 40 cached files hold about 80 GB. Check one entry: entry.base is the full array and entry.base.nbytes is 2 GB. Then cache arr[:100].copy(), which owns its 8 MB.
- Follow-ups
-
- How would you find a leak like this in a running service?
- Would np.ascontiguousarray(arr[:100]) or np.array(arr[:100]) fix it?
- When is it right for a cache to keep views on purpose?
From the lesson Indexing, slicing and views
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A teammate counts 10 million event codes, integers from 0 to 999, with counts[codes] += 1. No error is raised, but the counts add up to 1,000 and every code looks equally rare on the dashboard. Explain what happened, replace the line, and say which replacement you would choose and why.
- What it tests
- Knowing that x[idx] += 1 reads, adds and writes back, so a repeated index counts once, and choosing between np.bincount and np.add.at.
- First step
- Read the symptom: a total of 1,000 for 10 million events means that each code was counted exactly once. counts[codes] += 1 reads counts[codes] into a copy, adds 1 and writes the copy back, so every repeat of a code writes the same 1. Count every occurrence instead: counts = np.bincount(codes, minlength=1000).
- Follow-ups
-
- When would you use np.add.at instead of np.bincount?
- What changes if the codes arrive as floats from a CSV file?
- How would you test the fix?
From the lesson Boolean masks and fancy indexing
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
frames has shape (100, 480, 640). frames[7, :, [10, 20, 30]] should give 480 rows by 3 columns, but its shape is (3, 480), and the code that follows quietly works on transposed data. Explain the rule behind the shape and give two ways to get (480, 3).
- What it tests
- Knowing where NumPy puts the axis of advanced indexes that a slice separates, and indexing so that the shape is the one intended.
- First step
- Count the advanced indexes: the integer 7 is one and the list [10, 20, 30] the other, and the slice in the middle separates them. NumPy's rule then puts the axis they produce, length 3, first and the sliced axis of 480 after it. Index in two steps instead, frames[7][:, [10, 20, 30]], or use np.take(frames[7], [10, 20, 30], axis=1).
- Follow-ups
-
- Why does frames[:, :, [10, 20, 30]] keep the order of the axes?
- Is the result a view or a copy, and what does it cost?
- How would a test have caught the swapped shape?
From the lesson Boolean masks and fancy indexing
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A model expects image batches as (batch, channels, height, width). A colleague converts a (32, 224, 224, 3) batch with batch.reshape(32, 3, 224, 224). The shapes match and training runs, but accuracy stays poor. What went wrong, how do you fix it, and what does the fix cost?
- What it tests
- Knowing that reshape keeps the order of the values and never moves axes, choosing transpose or moveaxis instead, and knowing when a contiguous copy is needed.
- First step
- Check one pixel. In the original, batch[0, 10, 20] holds the three channel values of one pixel side by side. After reshape(32, 3, 224, 224) they are no longer at out[0, :, 10, 20]: reshape kept the flat order and cut it into new axes, mixing pixels and channels. Moving an axis needs np.transpose(batch, (0, 3, 1, 2)) or np.moveaxis(batch, -1, 1).
- Follow-ups
-
- Is the transposed batch a copy, and when would you make one?
- How would a test catch the mistake?
- When is reshape the right tool for image data?
From the lesson Reshaping, stacking and splitting arrays
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A script builds a table of 100,000 rows with result = np.vstack([result, row]) inside a loop and takes many seconds, although 10,000 rows took a fraction of a second. Explain why the time grows so fast, estimate the work, and rewrite it.
- What it tests
- Knowing that every join copies, estimating the quadratic cost of growing an array, and choosing a list or a preallocated array instead.
- First step
- Each vstack makes a new array and copies everything joined so far, so call k copies k rows: about n²/2 row copies for n rows, 5 billion for 100,000 rows, where copying each row once would be 100,000. Collect the rows in a Python list and call np.array(rows) or np.vstack(rows) once at the end.
- Follow-ups
-
- How much faster is the list version, and why?
- When is preallocating with np.empty the better choice?
- What if the number of rows is not known in advance?
From the lesson Reshaping, stacking and splitting arrays
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A daily report computes conversion = purchases / visits for 50,000 pages. Pages with no visits show inf or NaN on the dashboard, and the job's log fills with RuntimeWarnings. Make pages without visits show "no data", stop the warnings for the right reason, and say how you would test the fix.
- What it tests
- Using where= with out= to avoid the division instead of hiding its warnings, choosing a fill that cannot be mistaken for a rate, and testing the edge cases.
- First step
- Name the cause: visits is 0 for some pages, so the division runs and produces inf (x / 0) and NaN (0 / 0). Silencing it with np.errstate would keep those values. Avoid the division instead: start out as an array of NaN, the marker for no data, and divide only where visits > 0, with np.divide(purchases, visits, out=out, where=visits > 0).
- Follow-ups
-
- Why NaN as the fill rather than 0?
- What would np.errstate(divide="ignore", invalid="ignore") hide that matters?
- Which inputs belong in the test?
From the lesson Universal functions and vectorised arithmetic
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
Brightening a uint8 image with img + 60 turns some bright pixels nearly black, and averaging two frames with (a + b) / 2 gives values that are far too small in bright areas. Explain both, and fix them without float copies of every frame where they are not needed.
- What it tests
- Knowing that the input dtypes choose the ufunc loop, so uint8 arithmetic wraps at 256, and choosing a wider dtype, clipping or an overflow-free formula with a stated memory cost.
- First step
- Work one pixel through: 200 + 60 = 260 does not fit uint8, which holds 0 to 255, so the uint8 loop keeps 260 − 256 = 4, a dark pixel. In (a + b) / 2 the sum wraps before the division: 200 + 100 becomes 44, and 44 / 2 = 22. Do the arithmetic in a wider type, for example with dtype=np.int16, and clip back to 0 to 255.
- Follow-ups
-
- What does each fix cost in memory for a 1920 × 1080 colour frame?
- Is there a way to average two uint8 frames without any wider array?
- Why did no warning appear?
From the lesson Universal functions and vectorised arithmetic
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A course has 300 students and 6 papers, and the results table holds NaN for papers a student missed. You are asked for a ranking. Compare ranking by np.nansum and by np.nanmean, say what each does to students who missed papers, and propose the rule you would use.
- What it tests
- Seeing that the choice of reduction is a decision about fairness, knowing what the nan-functions divide by, and handling students with too few marks explicitly.
- First step
- Work a small case: a student with 92, 88 and 85 who missed one paper has a nansum of 265, below a student with four marks averaging 73, but a nanmean of 88.33, above nearly everyone. nansum treats a missed paper as 0 and nanmean ignores it. Count the papers sat per student with np.count_nonzero(~np.isnan(marks), axis=1) before choosing.
- Follow-ups
-
- What would you do with a student who sat only one of the six papers?
- How do you break ties, and which NumPy call keeps that order?
- How would you show the rule to the people who read the ranking?
From the lesson Reductions, axes, NaN and masked data
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Your p95 latency computed with NumPy from 1,000 samples differs from the p95 your monitoring dashboard shows for the same samples. Explain how both numbers can be correct, how you would check what each one does, and what you would standardise on.
- What it tests
- Knowing that percentiles of finite samples depend on the method, and that monitoring tools often estimate from bucketed or sketched data, and making a choice that is stated and reproducible.
- First step
- Separate the two possible causes. First the method: np.percentile defaults to linear interpolation, while other tools may use another of the nine Hyndman and Fan methods or a nearest-rank rule. Second the data: many monitoring systems estimate percentiles from histogram buckets or sketches, not from the raw samples. Compute NumPy's p95 with several methods and compare.
- Follow-ups
-
- How far apart can two methods be for 1,000 samples, and when does it matter?
- What changes if the dashboard estimates from histogram buckets?
- Which method would you put in a service-level objective, and why?
From the lesson Reductions, axes, NaN and masked data
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A dashboard shows the 50 fastest of the day's 20 million delivery times, refreshed every minute, and the job that builds it sorts the whole array each time. What would you do instead, why is it faster, and how do you return the 50 in order with ties broken by delivery ID?
- What it tests
- Choosing partition over a full sort for the k smallest, explaining the cost difference, and keeping a defined order for ties when partition does not.
- First step
- Only the 50 smallest values matter, so a full sort does far more work than needed. np.argpartition(times, 49)[:50] finds their positions with work that grows linearly with n; sorting just those 50 then costs almost nothing. Partition gives no order and no rule for ties, so finish with np.lexsort((ids[top], times[top])).
- Follow-ups
-
- What if several deliveries tie with the 50th time?
- How much faster is it, and what limits the gain?
- Could you avoid touching all 20 million values every minute?
From the lesson Sorting, searching, counting and set operations
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
An event log of 5 million rows repeats an order ID whenever a message was sent twice. Keep the first occurrence of each ID in the original order, report how many rows were duplicates, and explain why np.unique on its own gives a different answer.
- What it tests
- Knowing what np.unique returns and in which order, using return_index and return_counts, and counting duplicates without a Python loop.
- First step
- np.unique sorts the IDs, so on its own it loses the original order. Ask it for the first positions instead: values, first, counts = np.unique(ids, return_index=True, return_counts=True). Sorting first restores the order of first appearance, so rows[np.sort(first)] keeps one row per ID, and len(ids) - len(values) is the number of duplicate rows.
- Follow-ups
-
- Why not use np.unique_values or np.unique_counts here?
- How would you keep the last occurrence instead of the first?
- What does this cost for 5 million rows, and when would you use a hash table instead?
From the lesson Sorting, searching, counting and set operations
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
All NumPy & SciPy for Scientific Computing interview questions · The NumPy & SciPy for Scientific Computing track