Julia Module 5
Arrays, indexing and broadcasting interview questions
Interview questions on arrays, indexing and broadcasting: 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 to mid
How do [1, 2, 3], [1 2 3] and [1; 2; 3] differ in Julia?
[1, 2, 3] and [1; 2; 3] build the same thing, a Vector{Int64} of size (3,): commas list elements, and semicolons stack them vertically, which for single numbers also gives a vector. [1 2 3] puts the values side by side in one row, so it is a 1×3 Matrix{Int64}, a two-dimensional array that happens to have one row. The difference shows as soon as code expects a vector: push!([1 2 3], 4) throws a MethodError, and a method written for a Vector argument does not accept the matrix. Julia has true one-dimensional arrays, so a column of numbers is a vector of size (3,), not a 3×1 matrix as in MATLAB. Write commas for a list of values, spaces and semicolons for a matrix, and check with size when unsure.
From the lesson Arrays: vectors, matrices and column-major storage
-
Fresher to mid
When does indexing a Julia array copy, and when does it share memory?
-
Fresher to mid
What does the dot in sin.(x) and x .+ 1 do, and why does x + 1 fail for a vector x?
The dot tells Julia to apply the function or operator to every element: sin.(x) calls sin on each element of x, and x .+ 1 adds 1 to each, returning a new array. Without the dot, operators on arrays keep their meaning from linear algebra, where a vector plus a number is undefined, so x + 1 throws a MethodError whose message suggests x .+ 1. Any function works with a dot, including your own, and arguments of different shapes are combined by stretching dimensions of length 1. The dots of one expression fuse into a single loop with no temporary arrays, and y .= … or @. y = … writes the result into an existing array: over a million values, 2 .* x .+ 1 allocated one 8 MB array and y .= 2 .* x .+ 1 allocated nothing.
From the lesson Broadcasting, dot syntax and loop fusion
-
Fresher to mid
Why does sum(M; dims = 2) return a matrix in Julia, and how do you get a vector instead?
sum(M; dims = 2) adds along the second dimension, the columns of each row, and keeps that dimension in the result with length 1, so a 2×3 M gives a 2×1 Matrix, printed [6; 15;;]. Keeping it is deliberate: the result lines up with M, so M ./ sum(M; dims = 2) divides each row by its own total in one broadcast. When a plain vector is needed, vec(sum(M; dims = 2)) or dropdims(sum(M; dims = 2); dims = 2) gives [6, 15], and sum.(eachrow(M)) builds the vector directly. The same holds for maximum, minimum, count, mean and mapreduce with dims.
From the lesson Reductions, reshaping and the dims keyword
-
Fresher to mid
Name three differences that break a line-by-line port of NumPy array code to Julia.
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to senior
A loader collects sensor readings with rows = [] and push!(rows, value). The analysis that runs on rows allocates gigabytes and is far slower than a colleague's version on the same file. What do you look for first, and what do you change in the loader and in the analysis?
- What it tests
- Recognising an abstract element type with typeof or eltype, knowing why a Vector{Any} stores each value as its own object, and fixing the type where the data enters.
- First step
- Print typeof(rows): the empty literal [] is a Vector{Any} and stays one whatever is pushed into it, so every reading is a separate heap object and every operation on it is chosen while the program runs. Then measure one analysis call with @allocated before and after giving the vector its type.
- Follow-ups
-
- How much memory do a million Float64 readings take in each kind of vector?
- The file also has gaps. Which element type would you choose then?
- Where else can an abstract element type slip in?
From the lesson Arrays: vectors, matrices and column-major storage
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to senior
A feature step computes the mean of every 7-day window for each station of a 2,000 × 365 matrix, with sum(A[s, d:d+6]) / 7 inside two loops, and one call allocates about 86 MB. Where does the memory go, how do you bring it to zero, and how do you show that the results did not change?
- What it tests
- Knowing that slices copy, counting what each window allocates, using views and a preallocated output, and checking a faster version against the original.
- First step
- Count the slices: 2,000 stations × 359 start days make 718,000 windows, and each sum(A[s, d:d+6]) first copies 7 values into a new vector. Measure one call with @allocated, then read each window with @view and pass the output matrix in instead of creating it.
- Follow-ups
-
- Why does a view version that returns a new matrix still allocate?
- Would a running sum be better than views?
- How do you test that the fast version gives the same answers?
From the lesson Indexing, slicing, copies and views
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to senior
A cleaning step replaces sensor spikes with r[r .> 500] .= NaN, and every daily mean computed afterwards with sum(r) / length(r) is NaN for a day that had a spike. What went wrong, what would you do instead, and how do you keep a record of what was removed?
- What it tests
- How NaN spreads through sums, choosing between a mask, missing with skipmissing and filtering, and keeping counts of removed data for the report.
- First step
- A single NaN makes any sum NaN, so marking spikes with NaN works only if every later step skips them. Decide what a spike means for each result: left out of the mean, counted in the report, kept in the raw log. Then keep the mask, because it is the record of what was removed.
- Follow-ups
-
- Why not use NaN and isnan everywhere?
- How do you average the valid readings without building a new array?
- What should the report say about the removed readings?
From the lesson Indexing, slicing, copies and views
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to senior
A model-evaluation function computes errors = predictions .- targets and returns a 1,000 × 1,000 matrix instead of 1,000 errors, with no error message. What happened, how do you find the cause, and how do you write the function so that it cannot happen again?
- What it tests
- Applying the broadcast shape rules to a column and a row, knowing which operations make a row (an adjoint, permutedims, a 1×n literal or file), and checking shapes where data enters a function.
- First step
- Print size(predictions) and size(targets). A column of 1,000 against a row of 1,000, for example targets read as a 1 × 1,000 matrix or written as targets', broadcasts to 1,000 × 1,000, because each dimension is 1 in one of the two arguments. Then find where the row was made.
- Follow-ups
-
- Why did no error appear?
- How do you make the function reject mismatched shapes?
- What does the mistake cost when the data grows?
From the lesson Broadcasting, dot syntax and loop fusion
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A simulation advances its state with u = u .+ dt .* f.(u) for 10 million steps on a vector of a million values, and a profile shows most of the time in memory allocation. How do you make the step allocate nothing, and what can go wrong when you do?
- What it tests
- Knowing that = rebinds to a new array while .= writes in place, keeping every operation fused, avoiding non-constant globals in the loop, and the aliasing pitfalls of updating an array in place.
- First step
- Measure one step with @allocated: u .+ dt .* f.(u) is fused, but it builds a new array of a million values on every step, about 8 MB, so the whole run allocates about 80 TB in total. Write into u instead, with u .+= dt .* f.(u) or @. u += dt * f(u), in a function that takes dt as an argument.
- Follow-ups
-
- Why did the in-place version still allocate 80 bytes in a script?
- When is updating u in place wrong?
- Does f need to be written for arrays?
From the lesson Broadcasting, dot syntax and loop fusion
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to senior
A NumPy program writes a 3 × 4 array of station readings to a binary file in its default order, and the Julia code that reads it calls reshape(values, 3, 4). Every value is there, but they are in the wrong places. Why, and how do you read the file correctly?
- What it tests
- Knowing that NumPy's default order is row-major and Julia's column-major, that reshape keeps the storage order, and when to reshape into the swapped shape and permute.
- First step
- Write down the order of the 12 values in the file: NumPy's default order stores the first row, then the second. Julia's reshape fills the first column first. So reshape into the swapped shape, 4 × 3, where each column is one of the file's rows, and then decide whether you need permutedims to get 3 × 4.
- Follow-ups
-
- When can you keep the 4 × 3 matrix and skip permutedims?
- How do you test the reader?
- What changes for an array with three dimensions?
From the lesson Reductions, reshaping and the dims keyword
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A filter-design routine rearranges its matrix of complex coefficients with H' before writing it out, and every phase in the file has the wrong sign. A colleague switches to transpose(H), and a later version that stores 2 × 2 blocks as the elements breaks again. Explain both failures and choose the right call.
- What it tests
- Knowing that ' conjugates as well as transposes, that transpose and ' work recursively on matrix elements, and that permutedims rearranges data without touching the elements.
- First step
- Compare H'[2, 1] with H[1, 2]: the adjoint returns the conjugate, so a coefficient with phase φ comes back with −φ. Then ask what the code needs: linear algebra, where the conjugate transpose is often right, or a rearrangement of data for a file, where permutedims is the tool.
- Follow-ups
-
- Why does transpose break for a matrix of blocks?
- Does H' copy the matrix?
- When is H' exactly what you want?
From the lesson Reductions, reshaping and the dims keyword
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to senior
A MATLAB function that normalises a working copy of its input matrix was ported to Julia line by line, and since the port the caller's data changes after every call. Nothing fails. Find the cause, fix the function, and say how you would catch this kind of bug in the rest of the port.
- What it tests
- Knowing that Julia's assignment shares arrays where MATLAB copies on write, choosing between copy and an in-place function marked with !, and testing that inputs are left unchanged.
- First step
- Look for MATLAB's copy idiom, a line such as B = A followed by writes into B. In MATLAB, B gets values of its own when it is changed; in Julia, B is a second name for the caller's matrix. Confirm it with B === A inside the function.
- Follow-ups
-
- Should the fixed function copy, or work in place?
- Why did no test catch it?
- Which other MATLAB habits hide the same problem?
From the lesson Julia for MATLAB, NumPy and R users
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A NumPy routine that walks a 5000 × 5000 array row by row was ported to Julia with the same loop order and the same arithmetic. It gives the right answers but runs several times slower than the team expected. Explain the cause and the fix, and how you would confirm it.
- What it tests
- Knowing that NumPy's default layout is row-major and Julia's column-major, how loop order meets the memory system, and how to time two versions without fooling yourself.
- First step
- Check which index the inner loop runs over: in the NumPy order it is the column index, so in Julia every step jumps a whole column, 5,000 values, ahead in memory. Time both orders on the same matrix, as the best of several runs after a first call that compiles.
- Follow-ups
-
- What if the algorithm must handle one row at a time?
- Would transposing the matrix once help?
- How large must a matrix be before the order matters?
From the lesson Julia for MATLAB, NumPy and R users
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes