Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Julia  Module 5 – Arrays, indexing and broadcasting

Julia for MATLAB, NumPy and R users

Move array code from MATLAB, NumPy or R to Julia: 1-based indexing, column-major loops, copies and views, operators, missing values and 20 idioms, each run.

  • Intermediate
  • 15 minutes
  • Examples run with julia 1.13.1 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Translate common MATLAB, NumPy and R array idioms into Julia
  • Avoid the copy, view and aliasing differences that cause silent bugs in ported code
  • Explain why loops are fast in Julia and when a vectorised library call still wins
  • Choose Julia's forms for characters, ranges and missing values where MATLAB, NumPy and R differ

Before you start

What you’ll learn · Before you start

What you will learn

  • Translate common MATLAB, NumPy and R array idioms into Julia
  • Avoid the copy, view and aliasing differences that cause silent bugs in ported code
  • Explain why loops are fast in Julia and when a vectorised library call still wins
  • Choose Julia's forms for characters, ranges and missing values where MATLAB, NumPy and R differ

Before you start

On this page

A loop that adds up a 4000×4000 matrix row by row, the order that suits NumPy’s row-major order: Storing a matrix row by row, so that the last index changes fastest from one memory slot to the next. NumPy arrays use it unless told otherwise; Julia, MATLAB and R store column by column. arrays, took several times as long in Julia as the same loop column by column, and no message said why. A port from MATLAB, NumPy or R can go wrong in this way without any error: the code runs, and the order, the copies or the arithmetic are quietly different.

This lesson collects those differences in one place, each shown by a run recorded on Julia 1.13.1: where indices start and how matrices are stored, which assignments copy and which share, what *, ^ and ' mean, when a loop beats vectorised code and when a library call beats both, how characters, ranges and missing values differ, and twenty everyday idioms translated and run.

Julia counts from 1 and stores by column, as MATLAB and R do

Two choices decide how array code is written, the first index and the order of storage, and Julia makes the same two as MATLAB and R:

Language First element A matrix is stored
Julia x[1], last x[end] column by column
MATLAB x(1), last x(end) column by column
R x[1] column by column
NumPy x[0], last x[-1] row by row, by default

The sources agree. Julia’s list of differences says that Julia indexes from 1 and keeps its arrays column-major order: Storing a matrix column by column, so that the first index changes fastest from one memory slot to the next: [1 2 3; 4 5 6] is stored as 1, 4, 2, 5, 3, 6. Julia stores every Array this way. More in “Arrays: vectors, matrices and column-major storage”, in the order Fortran uses, while NumPy keeps them in the order of C, row by row, unless told otherwise. MathWorks says MATLAB stores elements with a column-major layout by default, and R’s manual says its arrays are filled in the order Fortran uses, with the first subscript moving fastest. Step through the same 2×3 matrix in both layouts:

Interactive · step by step [1 2 3; 4 5 6] in memory: row by row and column by column Step through both layouts. Each box is one memory slot, in order from the top.
[1 2 3; 4 5 6] in memory: row by row and column by column, step 5 of 5. Showing NumPy: row-major, slot 1: 1, slot 2: 2, slot 3: 3, slots 4 to 6: 4, 5, 6, Julia: column-major, slot 1: 1, slot 2: 4, slot 3: 2, slots 4 to 6: 5, 3, 6. In focus: NumPy: row-major, Julia: column-major.NumPy: row-majorlast index fastestslot 1: 1a[0, 0]slot 2: 2a[0, 1]slot 3: 3a[0, 2]slots 4 to 6: 4, 5, 6row a[1, :]Julia: column-majorfirst index fastestslot 1: 1A[1, 1]slot 2: 4A[2, 1]slot 3: 2A[1, 2]slots 4 to 6: 5, 3, 6the rest, column by column

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. NumPy's order. NumPy stores an array row by row unless told otherwise: its default C order keeps the elements of a row next to each other.
  2. A row in a row. Slots 1 to 3 hold the first row, 1, 2 and 3, with 0-based indices a[0, 0] to a[0, 2]; slots 4 to 6 hold the second row.
  3. Julia's order. Julia stores the same matrix column by column: slot 2 holds A[2, 1] = 4, the element below the first one.
  4. Columns in a row. Slots 3 and 4 hold the second column, 2 and 5, and slots 5 and 6 the third, 3 and 6. A loop is fastest when it walks the slots in order.
  5. Port the loop order too. A NumPy loop with the column index innermost walks its memory in order. Copied unchanged to Julia, it jumps a whole column between reads: 4,000 slots on a 4000×4000 matrix.

The order decides which loop is fast. This program adds up a 250 × 250 matrix and a 4000 × 4000 one, each twice, with the row index innermost and then with the column index innermost, and keeps the best of several runs of each:

row_vs_column.jl: one sum in two loop orders Julia · row_vs_column.jl
# The same sum over a small and a large matrix, column by column and then row by row.
function sum_by_columns(A)
    s = 0.0
    for j in axes(A, 2)
        for i in axes(A, 1)          # down a column: the order Julia stores A in
            s += A[i, j]
        end
    end
    return s
end
function sum_by_rows(A)
    s = 0.0
    for i in axes(A, 1)
        for j in axes(A, 2)          # along a row: each step jumps a whole column ahead
            s += A[i, j]
        end
    end
    return s
end
function best_time(f, A, runs)
    t = Inf
    for _ in 1:runs
        t = min(t, @elapsed(s = f(A)))
        s == -1 && println("never")  # uses the result, so the call cannot be skipped
    end
    return t
end
for n in (250, 4000)
    A = [Float64((i + 2j) % 7) for i in 1:n, j in 1:n]
    same = sum_by_columns(A) == sum_by_rows(A)
    runs = n < 1000 ? 50 : 5
    tc = best_time(sum_by_columns, A, runs)
    tr = best_time(sum_by_rows, A, runs)
    println(n, "×", n, " (", round(sizeof(A) / 2^20; digits = 1), " MiB): same sum ", same, "; by columns ",
            round(tc * 1000; digits = 2), " ms, by rows ", round(tr * 1000; digits = 2), " ms, ratio ", round(tr / tc; digits = 1))
end
row_vs_column.jl, run with Julia 1.13.1 Python · row_column_session.py
from session import files, run

# One sum over a 250×250 and a 4000×4000 matrix in the two loop orders, each timed as the best of several runs.
files("row_vs_column.jl")
run("""
julia row_vs_column.jl
""")

Output

$ julia row_vs_column.jl
250×250 (0.5 MiB): same sum true; by columns 0.06 ms, by rows 0.05 ms, ratio 1.0
4000×4000 (122.1 MiB): same sum true; by columns 16.23 ms, by rows 50.11 ms, ratio 3.1

This output changes from run to run: The times differ from run to run and from computer to computer; the sums, the sizes and the order of the loops do not.

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 row_column_session.py

Both orders gave the same sums. On the small matrix, half a megabyte, they took about the same time; on the large one, 122 MiB, the row-by-row loop, the natural order for NumPy, took several times as long, as the ratios in the run show. Each of its steps jumps 4,000 slots ahead in memory, while the column order reads the slots one after another, and the run shows the cost only once the matrix is large. Julia’s performance tips turn this into a rule of thumb: in a column-major array, the loop over the first index belongs innermost.

Key idea

Julia shares MATLAB’s and R’s 1-based, column-major arrays and differs from NumPy in both, so a ported loop over a matrix belongs with the first index innermost.

Assignment shares and slices copy, unlike MATLAB and NumPy

The second set of differences is about copies, and here each language made its own choice:

aliasing_bug.jl: assignments, slices, views and += Julia · aliasing_bug.jl
# Habits from MATLAB and NumPy that change other data in Julia, or leave it unchanged.
A = [1.0 2.0; 3.0 4.0]
B = A                    # MATLAB would give B values of its own; here B is a second name
B[1, 1] = 0.0
println("after B = A and B[1, 1] = 0: A = ", A)
A = [1.0 2.0; 3.0 4.0]
B = copy(A)              # an independent copy
B[1, 1] = 0.0
println("after B = copy(A) and B[1, 1] = 0: A = ", A)
col = A[:, 1]            # in NumPy a basic slice is a view; here it is a copy
col .= 0.0
println("after col = A[:, 1] and col .= 0: A = ", A)
col = @view A[:, 1]      # a view, for when writing through it is the point
col .= 0.0
println("after col = @view A[:, 1] and col .= 0: A = ", A)
x = [1, 2, 3]
y = x
x += [1, 1, 1]           # x = x + [1, 1, 1]: a new vector, now named x
println("after y = x and x += [1, 1, 1]: x = ", x, ", y = ", y)
y = x
x .+= 1                  # .+= writes into the vector that x and y both name
println("after y = x and x .+= 1: x = ", x, ", y = ", y)
aliasing_bug.jl, run with Julia 1.13.1 Python · aliasing_session.py
from session import files, run

# Assignment, slices, views and updating operators, each followed by the data it did or did not change.
files("aliasing_bug.jl")
run("""
julia aliasing_bug.jl
""")

Output

$ julia aliasing_bug.jl
after B = A and B[1, 1] = 0: A = [0.0 2.0; 3.0 4.0]
after B = copy(A) and B[1, 1] = 0: A = [1.0 2.0; 3.0 4.0]
after col = A[:, 1] and col .= 0: A = [1.0 2.0; 3.0 4.0]
after col = @view A[:, 1] and col .= 0: A = [0.0 2.0; 0.0 4.0]
after y = x and x += [1, 1, 1]: x = [2, 3, 4], y = [1, 2, 3]
after y = x and x .+= 1: x = [3, 4, 5], y = [3, 4, 5]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 aliasing_session.py

B = A followed by B[1, 1] = 0 changed A, because B is a second name for the same matrix; copy(A) gave an independent one. Julia’s list of differences states it plainly: giving an array a second name copies nothing. MATLAB behaves otherwise. MathWorks’ page on copies explains that MATLAB has no references to values: an array is passed by value, and a shared copy is separated the first time one side changes, which it calls copy-on-write: Sharing one copy of data between names until one of them is changed, and copying it only then. MATLAB treats arrays this way, so after B = A a change to B leaves A as it was; Julia does not copy at all., so changing B there leaves A alone.

Slices go the other way. col = A[:, 1] followed by col .= 0 left A unchanged, because a Julia slice is a copy, while NumPy’s documentation says basic indexing always creates views, so the same line in NumPy would have cleared the column. In Julia, @view A[:, 1] is the view, and writing through it cleared the column. Updating operators differ as well: the list of differences notes that NumPy’s += works in place and Julia’s does not, and the run showed it, as x += [1, 1, 1] made a new vector and left y with the old values, while x .+= 1 wrote into the vector that both names reach.

You write Julia MATLAB NumPy
B = A, then change B changes A A unchanged changes A
c = A[:, 1], then change c A unchanged A unchanged changes A
x += y a new x — in place

A backup that is not one, and a write that goes nowhere

Wrong, coming from MATLAB: backup = A before changing A, which keeps no backup in Julia. Wrong, coming from NumPy: col = A[:, 1] followed by writes into col, which never reach A. Right: backup = copy(A), and col = @view A[:, 1] or A[:, 1] .= … when the matrix itself must change.

Checkpoint

Check: 1-based indexing and column-major storage · Assignment, slices, views and +=

  1. Question 1 of 2 A loop ported from NumPy runs for i in axes(A, 1) on the outside and for j in axes(A, 2) inside, adding A[i, j]. Which change makes it read Julia's memory in order?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Swap the loops, so that i, the row index, runs innermost

    The order that is fast in a row-major language is the slow one in a column-major language; swap the loops, or loop over eachcol(A).

  2. Question 2 of 2 A MATLAB habit, ported: saved = A and then A[1, 1] = 99. What is saved[1, 1] in Julia?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 99

    Julia's list of differences warns that assignment gives an array a second name and copies nothing. Use saved = copy(A).

A star multiplies matrices, and a dot works element by element

Operators are where a line-by-line port changes the arithmetic without an error. Julia keeps the meanings of linear algebra for *, ^ and ', as MATLAB does, and asks for a dot for element-wise work:

operators.jl: products, powers, the adjoint and two divisions Julia · operators.jl
# The symbols that mean different things in Julia, MATLAB, NumPy and R, with what Julia computes.
A = [1 2; 3 4]
B = [5 6; 7 8]
println("A * B  = ", A * B)
println("A .* B = ", A .* B)
println("A^2    = ", A^2)
println("A .^ 2 = ", A .^ 2)
C = [1+2im 3; 0 1]
println("C'     = ", C')
S = [2.0 1.0; 1.0 3.0]
b = [3.0, 5.0]
println("S \\ b  = ", S \ b, ", and S * (S \\ b) = ", S * (S \ b))
println("7 / 2 = ", 7 / 2, ", 7 ÷ 2 = ", 7 ÷ 2)
operators.jl, run with Julia 1.13.1 Python · operators_session.py
from session import files, run

# Matrix and element-wise products and powers, the adjoint, a linear solve and the two divisions.
files("operators.jl")
run("""
julia operators.jl
""")

Output

$ julia operators.jl
A * B  = [19 22; 43 50]
A .* B = [5 12; 21 32]
A^2    = [7 10; 15 22]
A .^ 2 = [1 4; 9 16]
C'     = Complex{Int64}[1 - 2im 0 + 0im; 3 + 0im 1 + 0im]
S \ b  = [0.8, 1.4], and S * (S \ b) = [3.0, 5.0]
7 / 2 = 3.5, 7 ÷ 2 = 3

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 operators_session.py

A * B was the matrix product [19 22; 43 50] and A .* B the element-wise one, A^2 the matrix power [7 10; 15 22] and A .^ 2 each element squared. C' conjugated as well as transposed, and S \ b solved the system S x = b, which S * (S \ b) confirmed. 7 / 2 gave the float 3.5 and 7 ÷ 2 the integer 3, as the lesson on integer division showed.

Meaning Julia MATLAB, NumPy and R
Matrix product A * B MATLAB A * B, NumPy A @ B, R A %*% B
Element-wise product A .* B MATLAB A .* B, NumPy and R A * B
Element-wise power A .^ 2 MATLAB A.^2, NumPy A ** 2, R A^2
Conjugate transpose A' MATLAB A', NumPy A.conj().T
Solve A x = b A \ b MATLAB A\b, NumPy linalg.solve(A, b), R solve(A, b)

The NumPy rows come from NumPy’s own guide for MATLAB users, and the R rows from its manual, which defines A * B as the element by element product and A %*% B as the matrix product. The trap is the *: in NumPy and R it multiplies element by element, in Julia it is the matrix product, so a port that keeps the * computes something else, or fails when the shapes do not fit a matrix product.

Matrix Calculator Multiply two small matrices in exact fractions and compare the result with A * B and A .* B.

Loops are compiled, so vectorise for clarity or for a library

Julia’s list of differences says that in R, performance requires vectorization, and its Python section that in Julia loops are fast, with no need to write vectorised code for performance. The first lesson of this track measured a plain loop against sum. Here the question is the other one: when does a vectorised call still pay?

vectorise_or_loop.jl: a weighted mean, and a matrix product Julia · vectorise_or_loop.jl
# A vectorised expression against a loop, and a library matrix product against loops.
using LinearAlgebra
BLAS.set_num_threads(1)                         # one thread, so the comparison is about the method
x = collect(range(0, 1; length = 1_000_000))
w = collect(range(1, 2; length = 1_000_000))
vectorised(x, w) = sum(x .^ 2 .* w) / sum(w)    # the MATLAB or NumPy habit: a temporary array
function looped(x, w)
    num = 0.0
    den = 0.0
    for i in eachindex(x, w)
        num += x[i]^2 * w[i]
        den += w[i]
    end
    return num / den
end
vectorised(x, w); looped(x, w)
println("weighted mean of squares: ", round(vectorised(x, w); digits = 6), " and ", round(looped(x, w); digits = 6))
println("bytes allocated: vectorised ", @allocated(vectorised(x, w)), ", loop ", @allocated(looped(x, w)))
function matmul_loops(A, B)
    C = zeros(size(A, 1), size(B, 2))
    for j in axes(B, 2), k in axes(A, 2), i in axes(A, 1)   # i innermost, down the columns
        C[i, j] += A[i, k] * B[k, j]
    end
    return C
end
n = 600
A = [sin(i + j) for i in 1:n, j in 1:n]
B = [cos(i - j) for i in 1:n, j in 1:n]
println("same product: ", matmul_loops(A, B) ≈ A * B)
best(f) = minimum(@elapsed(f(A, B)) for _ in 1:3)
println("A * B was ", round(best(matmul_loops) / best(*); digits = 1), " times as fast as the loops, on one thread")
vectorise_or_loop.jl, run with Julia 1.13.1 Python · vectorise_session.py
from session import files, run

# A vectorised expression against a loop, and BLAS's matrix product against three nested loops.
files("vectorise_or_loop.jl")
run("""
julia vectorise_or_loop.jl
""")

Output

$ julia vectorise_or_loop.jl
weighted mean of squares: 0.388889 and 0.388889
bytes allocated: vectorised 8011840, loop 0
same product: true
A * B was 4.6 times as fast as the loops, on one thread

This output changes from run to run: The time ratio differs from run to run and from computer to computer; the means, the bytes and the products do not.

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 vectorise_session.py

The weighted mean of squares came out the same both ways, 0.388889, but the vectorised habit, x .^ 2 .* w, built a temporary array of a million values, 8,011,840 bytes, which the loop did not need. The matrix product went the other way: A * B on Float64 matrices hands the work to BLAS, the optimised library behind LinearAlgebra’s products, and even on one thread it was several times faster than three nested loops written in the right order. Vectorise when it names a library operation or reads more clearly, and loop, or fuse with dots, when the formula is your own. Answer three questions about your own computation:

Interactive · answer and compare A loop, a broadcast or a library call? Answer three questions about your computation. The table marks what fits and says why the others do not.
Does a library function compute exactly this, such as a matrix product or a linear solve?
Is it one formula applied to every element on its own?
Does it run many times, inside a time step or an outer loop?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Four ways to write an array computation in Julia, against memory, speed and how the code reads
Way of writing it Memory Speed Reading
A plain loop Looks like: for i in eachindex(x, w) … end Good 0 bytes for the weighted mean of a million values Good compiled like the functions of Base Mixed more lines, and the index ranges must be right
A fused broadcast Looks like: @. y = 3x^2 + 2x + 1 Good one result array, or 0 bytes with .= into an existing one Good one pass over the data, as the dots guarantee Good close to the formula on paper
Vectorised with temporaries Looks like: sum(x .^ 2 .* w) / sum(w) Poor 8,011,840 bytes for one temporary of a million values Mixed an extra pass over memory for each temporary array Good reads like the MATLAB or NumPy original
A library call Looks like: A * B or A \ b Mixed allocates its result like any function that returns an array Good the BLAS product beat three nested loops several times over Good one call that names the operation
How the answers decide
  • “Does a library function compute exactly this, such as a matrix product or a linear solve?” Yes: adds a caveat to A plain loop and A fused broadcast. A tuned library beats a straightforward loop: on one thread, A * B ran several times faster than three nested loops in this lesson.
  • “Does a library function compute exactly this, such as a matrix product or a linear solve?” No, it is a formula of my own: rules out A library call. No library computes your formula, so write it yourself: as a loop or a fused broadcast it is compiled like library code.
  • “Is it one formula applied to every element on its own?” No, elements depend on each other: rules out A fused broadcast and Vectorised with temporaries. A broadcast applies one formula to each element on its own; a recurrence or a sum over neighbours is a loop.
  • “Does it run many times, inside a time step or an outer loop?” Yes, many times: rules out Vectorised with temporaries. Temporaries are allocated again on every call: about 8 MB per call for a million values, so a time step should fuse or loop.
  • “Does it run many times, inside a time step or an outer loop?” No, once: adds a caveat to Vectorised with temporaries. Acceptable once, but the same habit inside a loop allocates on every pass.

In short: a library call for products, solves and other operations that a library provides, a fused broadcast for a formula applied element by element, and a plain loop for everything that combines elements in its own way.

Checkpoint

Check: The meaning of *, ^, ' and the dot · Loops, broadcasts and library calls

  1. Question 1 of 3 A NumPy line C = A * B with two 3×3 arrays is ported to Julia unchanged. What does Julia compute?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: The matrix product, which NumPy writes A @ B

    The port runs and returns a 3×3 matrix of different numbers: the most dangerous kind of difference.

  2. Question 2 of 3 x and w hold a million Float64 values each. About how much does sum(x .^ 2 .* w) allocate?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: About 8 MB, one temporary array

    A temporary array is the cost of the vectorised habit; a loop or sum(f, itr) form avoids it.

  3. Question 3 of 3 Which of these is better left to a library call than written as your own loop?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: The product of two 600×600 matrices

    Vectorise to reach a tuned library or for clarity, not for speed of your own formulas.

Characters, ranges and missing values differ in small ways

A handful of smaller differences catch ported code as well, and this program runs into each of them:

traps.jl: quotes, ranges and missing values Julia · traps.jl
# Strings, ranges and missing values, where the habits of other languages mislead.
println(typeof('a'), " and ", typeof("a"), "; 'a' * 'b' = ", 'a' * 'b')
try
    Meta.parse("x = 'ab'")
catch err
    println(strip(last(split(sprint(showerror, err), "── "))))
end
r = 1:1_000_000
println(typeof(r), ": ", sizeof(r), " bytes for a million values; collect(1:5) = ", collect(1:5))
data = [21.5, missing, 22.4]
println("sum(data) = ", sum(data), ", sum(skipmissing(data)) = ", sum(skipmissing(data)))
println("missing == 1 is ", missing == 1, ", NaN == NaN is ", NaN == NaN)
try
    NA
catch err
    println(first(split(sprint(showerror, err), '\n')))
end
traps.jl, run with Julia 1.13.1 Python · traps_session.py
from session import files, run

# Characters and strings, a lazy range, missing values, and a name that R users expect.
files("traps.jl")
run("""
julia traps.jl
""")

Output

$ julia traps.jl
Char and String; 'a' * 'b' = ab
character literal contains multiple characters
UnitRange{Int64}: 16 bytes for a million values; collect(1:5) = [1, 2, 3, 4, 5]
sum(data) = missing, sum(skipmissing(data)) = 43.9
missing == 1 is missing, NaN == NaN is false
UndefVarError: `NA` not defined in `Main`

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 traps_session.py

Single quotes make a Char and double quotes a String, so 'ab' does not even parse: the message says a character literal contains multiple characters. The list of differences tells R users the same: in Julia, single quotes are for characters only. 1:1_000_000 is a UnitRange of 16 bytes, which computes its values when asked, as the manual says of a:b for MATLAB users, and collect turns a range into a vector. R’s NA does not exist in Julia, where missing values are the missing object: sum of a vector with a missing is missing, as R’s manual says any operation on an NA becomes NA, and skipmissing leaves the gaps out, which gave 43.9. A missing compared with anything is missing, and a NaN is not equal even to itself.

Habit In Julia
'text' for a string (Python, R) "text"; 't' is one Char
NA (R), or NaN as a gap missing, with skipmissing and ismissing
a range is a vector a lazy range; collect(1:5) for a vector
x[-1] for the last (NumPy) x[end]
x[-1] to drop the first (R) x[2:end]
variables shared by a whole script functions have local scope, as the scope lesson showed

R’s manual describes negative indices as the values to be excluded, which is why x[-1] means all but the first in R and the last element in NumPy, and is a BoundsError in Julia.

Checkpoint

Check: Characters, ranges and missing values

  1. One question What is sum([1.0, missing, 2.0])?

    Choose one answer.

    Show the answer: One question

    Answer: missing

    Use skipmissing to leave gaps out, and ismissing to find them.

Twenty idioms, translated and run

Here are twenty idioms as a Julia programmer writes them, each run once with its result, and below the program the forms they translate from:

translation_table.jl: twenty idioms with their results Julia · translation_table.jl
# Twenty everyday idioms as Julia writes them, each with what it returned.
x = [4, 1, 7, 2]
A = [2.0 1.0; 1.0 3.0]
b = [3.0, 5.0]
idioms = [
    "zeros(3)" => zeros(3),
    "zeros(2, 2)" => zeros(2, 2),
    "range(0, 1; length = 5)" => range(0, 1; length = 5),
    "collect(0:0.25:1)" => collect(0:0.25:1),
    "x[end]" => x[end],
    "x[2:end]" => x[2:end],
    "length(x)" => length(x),
    "findall(>(2), x)" => findall(>(2), x),
    "x[x .> 2]" => x[x .> 2],
    "cumsum(x)" => cumsum(x),
    "sort(x; rev = true)" => sort(x; rev = true),
    "repeat([1, 2], 3)" => repeat([1, 2], 3),
    "reshape(1:6, 2, 3)" => reshape(1:6, 2, 3),
    "vec([1 2; 3 4])" => vec([1 2; 3 4]),
    "A \\ b" => A \ b,
    "A .^ 2" => A .^ 2,
    "permutedims([1 2; 3 4])" => permutedims([1 2; 3 4]),
    "size(A, 1)" => size(A, 1),
    "sum(A; dims = 2)" => sum(A; dims = 2),
    "string(\"run-\", 7)" => string("run-", 7),
]
for (k, (code, value)) in enumerate(idioms)
    println(lpad(k, 2), "  ", rpad(code, 25), repr(value))
end
translation_table.jl, run with Julia 1.13.1 Python · translation_session.py
from session import files, run

# The Julia side of twenty translated idioms, with what each returned.
files("translation_table.jl")
run("""
julia translation_table.jl
""")

Output

$ julia translation_table.jl
 1  zeros(3)                 [0.0, 0.0, 0.0]
 2  zeros(2, 2)              [0.0 0.0; 0.0 0.0]
 3  range(0, 1; length = 5)  0.0:0.25:1.0
 4  collect(0:0.25:1)        [0.0, 0.25, 0.5, 0.75, 1.0]
 5  x[end]                   2
 6  x[2:end]                 [1, 7, 2]
 7  length(x)                4
 8  findall(>(2), x)         [1, 3]
 9  x[x .> 2]                [4, 7]
10  cumsum(x)                [4, 5, 12, 14]
11  sort(x; rev = true)      [7, 4, 2, 1]
12  repeat([1, 2], 3)        [1, 2, 1, 2, 1, 2]
13  reshape(1:6, 2, 3)       [1 3 5; 2 4 6]
14  vec([1 2; 3 4])          [1, 3, 2, 4]
15  A \ b                    [0.8, 1.4]
16  A .^ 2                   [4.0 1.0; 1.0 9.0]
17  permutedims([1 2; 3 4])  [1 3; 2 4]
18  size(A, 1)               2
19  sum(A; dims = 2)         [3.0; 4.0;;]
20  string("run-", 7)        "run-7"

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 translation_session.py

The numbers in the tables are the lines of the run. The MATLAB and NumPy forms follow NumPy’s guide for MATLAB users and MathWorks’ page on zeros, and the R forms R’s own manual:

From MATLAB Julia, line
zeros(3), a 3×3 matrix zeros(3, 3); zeros(3) is a vector (1)
linspace(0, 1, 5) range(0, 1; length = 5) (3)
x(end) x[end] (5)
find(x > 2), x(x > 2) findall(>(2), x) (8), x[x .> 2] (9)
repmat(a, m, n) repeat(a, m, n), as in (12)
x(:) vec(x) (14)
A\b, A.^2, size(A, 1) A \ b (15), A .^ 2 (16), size(A, 1) (18)
From NumPy Julia, line
np.zeros(3) zeros(3) (1)
x[-1] x[end] (5)
np.nonzero(x > 2) findall(>(2), x) (8), counting from 1
np.tile([1, 2], 3) repeat([1, 2], 3) (12)
a.reshape(2, 3, order='F') reshape(a, 2, 3) (13)
linalg.solve(A, b), A ** 2 A \ b (15), A .^ 2 (16)
A.T, A.shape[0] permutedims(A) (17), size(A, 1) (18)
A.sum(axis=1) sum(A; dims = 2) (19)
From R Julia, line
seq(0, 1, by = 0.25) collect(0:0.25:1) (4)
x[-1], all but the first x[2:end] (6)
x[x > 2] x[x .> 2] (9)
rep(c(1, 2), times = 3) repeat([1, 2], 3) (12)
matrix(1:6, nrow = 2) reshape(1:6, 2, 3) (13)
solve(A, b) A \ b (15)
t(A) permutedims(A) (17)
paste("run-", 7, sep = "") string("run-", 7) (20)

Line 13 is the one to read twice: reshape(1:6, 2, 3) gave [1 3 5; 2 4 6], filled by columns as R’s matrix and MATLAB’s reshape fill it, while NumPy’s reshape fills by rows unless it is given order='F', as its guide notes. Line 19 differs in its numbering: NumPy counts axes from 0, so its axis=1, the columns of each row, is Julia’s dims = 2, and Julia keeps the reduced dimension, which NumPy’s sum removes unless it is given keepdims=True. Packages differ too: plotting and data frames come from packages in Julia rather than from the language, and the scientific-computing module of this track covers them.

Checkpoint

Check: Translating everyday idioms

  1. One question NumPy's np.arange(1, 7).reshape(2, 3) is [[1, 2, 3], [4, 5, 6]]. Which Julia line gives the same matrix?

    Choose one answer.

    Show the answer: One question

    Answer: permutedims(reshape(1:6, 3, 2))

    NumPy fills a reshape by rows unless given order='F'; to copy its result in Julia, reshape to the swapped shape and swap back with permutedims.

Practise: port six snippets

Translate, then check the traps

Port these to Julia functions and check each on a small input on your machine: MATLAB y = x(end:-1:1); NumPy a[a < 0] = 0 on a copy of the input; R x[-length(x)]; MATLAB B = A; B(1, 1) = 0 without changing A; NumPy M.reshape(2, 3) from the values 1 to 6; and MATLAB s = sum(A, 2) returned as a vector. For each, write down the Julia line that would have been wrong if typed the old way.

The module’s last self-check mixes the translations of this lesson. Its first questions come from whichever idea you know least well so far, and each answer explains itself in the terms of the language the habit came from:

Practice that adapts to you

Practise this lesson

12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 In which of these languages is the first element of a vector at index 1? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 1 of 12

    Answer:

    • Julia
    • MATLAB
    • R

    Julia shares 1-based indexing, and column-major storage, with MATLAB and R; NumPy differs in both.

  2. Question 2 of 12 A NumPy line reads a[-2], the second-to-last element. What is the Julia form?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: a[end-1]

    Count back from end: a[end] is the last element, a[end-1] the one before.

  3. Question 3 of 12 Ported from NumPy: col = A[:, 1] and then col[1] = 0. What happens to A in Julia?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: Nothing; col is a copy

    Slices copy in Julia and in MATLAB, and are views in NumPy; @view gives Julia a view.

  4. Question 4 of 12 In NumPy, y = x followed by x += 1 changes y too. In Julia, y = x and x += [1, 1, 1]: what does y hold?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: The old values, because x += … built a new vector

    The list of differences notes that NumPy's updating operators work in place and Julia's do not; .+= does.

  5. Question 5 of 12 For a square matrix A, what is A^2 in Julia?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: The matrix product A * A

    Without a dot, *, ^ and ' follow linear algebra in Julia, as in MATLAB.

  6. Question 6 of 12 R's A * B multiplies two matrices element by element. What is the Julia form?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: A .* B

    R's and NumPy's * is Julia's .; Julia's is R's %*% and NumPy's @.

  7. Question 7 of 12 Why is a plain loop in Julia about as fast as Base's own functions?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: It is compiled to machine code for the types of its arguments

    In R and in Python, speed comes from moving loops into compiled libraries; in Julia your loop is compiled code.

  8. Question 8 of 12 A time step written as u = u + dt * (a .* u .^ 2) runs 100,000 times over a million values. What do you change first?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: Dot everything and write in place: @. u += dt * a * u^2

    Vectorised habits cost memory in Julia; fuse with dots and write into the existing array.

  9. Question 9 of 12 What is 'a' in Julia?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: A Char, one character

    'ab' does not parse: a character literal contains multiple characters.

  10. Question 10 of 12 Which value marks an unknown reading in a Julia vector, where R would write NA?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: missing

    missing spreads through arithmetic like NA, and skipmissing leaves it out of reductions.

  11. Question 11 of 12 MATLAB's zeros(3) is a 3×3 matrix. What does Julia's zeros(3) return?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: A vector of 3 zeros

    Write every size you mean: zeros(3, 3) for a matrix, zeros(3) for a vector.

  12. Question 12 of 12 NumPy's A.sum(axis=0) sums down each column. Which Julia call adds up the same numbers?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: sum(A; dims = 1)

    Add 1 to a NumPy axis number to get Julia's dims, and expect a kept dimension of length 1.

Interview questions

Warm-up (fresher to 2–5 years): name three differences that break a line-by-line port of NumPy array code to Julia.

Indices start at 1, so a[0] is an error and a[-1] must become a[end]. Storage is column-major, where NumPy’s default is row-major, so a loop with the column index innermost, fast in NumPy, jumps through memory in Julia: summing a 4000×4000 matrix row by row took several times as long as column by column. And the copy rules are reversed for slices: NumPy’s basic slices are views, so writing into a[:, 0] changes a, while Julia’s A[:, 1] is a copy and the write is lost; @view gives the view. Two more follow from the operators: NumPy’s * is element-wise and Julia’s is the matrix product, and NumPy’s += updates in place where Julia’s builds a new array unless written .+=.

Two porting problems follow on from here: a MATLAB function that corrupts its caller’s data, and a NumPy loop that runs several times slower in Julia. Note the first thing you would measure or print in each case before you open its first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. 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.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Julia, MATLAB and R count from 1 and store matrices by column; NumPy counts from 0 and stores by row by default, so ported loops need the first index innermost.
  • B = A shares the array in Julia, as in NumPy and unlike MATLAB, while A[:, 1] copies, as in MATLAB and unlike NumPy; copy and @view say which one you mean.
  • *, ^ and ' keep their linear-algebra meanings, a dot makes any operation element-wise, and NumPy’s and R’s * is Julia’s .*.
  • Loops and fused broadcasts are compiled, so vectorised code with temporaries gains no speed and costs memory, while a library call such as A * B still beats hand-written loops.
  • 'a' is a Char, 1:5 is a lazy range, missing replaces NA, and skipmissing leaves gaps out of a sum.
  • The twenty translated idioms run as shown; reshape fills by columns and dims = 2 is NumPy’s axis=1.

Terms in this lesson

copy-on-write
Sharing one copy of data between names until one of them is changed, and copying it only then. MATLAB treats arrays this way, so after B = A a change to B leaves A as it was; Julia does not copy at all.
row-major order
Storing a matrix row by row, so that the last index changes fastest from one memory slot to the next. NumPy arrays use it unless told otherwise; Julia, MATLAB and R store column by column.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.