Memory Layout & Strides

Reviewed & published by Brayan K

Memory layout describes the physical order in which an array's elements are stored, and strides are the byte steps NumPy takes to walk along each axis — together they explain why some operations are fast and others are slow.

Part of the free NumPy course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

You'll inspect an array's .flags and .strides, compare C (row-major) and F (column-major) order, see how transpose just swaps strides, and force a contiguous copy with ascontiguousarray.

What You'll Learn in This Lesson

1 Flags and Order

Under the hood an array is just a flat block of bytes plus a shape. The .flags attribute tells you how that block is organized. A default array is C-contiguous (row-major): each row is laid out end to end. Build one with order="F" and it becomes F-contiguous (column-major) instead.

import numpy as np

a = np.arange(12).reshape(3, 4)
print(a.flags['C_CONTIGUOUS'])   # True
print(a.flags['F_CONTIGUOUS'])   # False

# Build the same values in Fortran order
b = np.array([[1, 2, 3], [4, 5, 6]], order='F')
print(b.flags['F_CONTIGUOUS'])   # True
print(b.flags['C_CONTIGUOUS'])   # False

# ✅ Expected output:
# True
# False
# True
# False

2 Strides and Transpose

.strides reports how many bytes to step to advance one index along each axis. For a C-contiguous int64 array of shape (2, 3) the strides are (24, 8): 8 bytes to the next column, 24 to the next row. Transposing does not move any data — it simply swaps the strides, which is why .T is instant even on huge arrays.

import numpy as np

c = np.arange(6).reshape(2, 3)
print(c.strides)   # (24, 8)

t = c.T            # transpose: strides swap, no copy
print(t.strides)             # (8, 24)
print(t.flags['C_CONTIGUOUS'])   # False
print(t.flags['F_CONTIGUOUS'])   # True

# ✅ Expected output:
# (24, 8)
# (8, 24)
# False
# True

3 Contiguity, Speed, and ravel order

Iterating along the axis whose elements are adjacent in memory is cache-friendly and fast; iterating across the gaps is slow. When a non-contiguous array (like a transpose) is about to feed many operations, make a contiguous copy with np.ascontiguousarray. ravel flattens an array, and its order argument lets you read it out in C or F order.

import numpy as np

m = np.arange(6).reshape(2, 3)

print(m.ravel())            # C order: [0 1 2 3 4 5]
print(m.ravel(order='F'))   # F order: [0 3 1 4 2 5]

# A transpose is non-contiguous; force a fresh C-contiguous copy
cont = np.ascontiguousarray(m.T)
print(cont.flags['C_CONTIGUOUS'])   # True

# ✅ Expected output:
# [0 1 2 3 4 5]
# [0 3 1 4 2 5]
# True

On a big array, a contiguous copy lets the CPU stream through the data instead of jumping around — that is the whole reason a slow transposed operation often speeds up after ascontiguousarray.

🎯 YOUR TURN: Fill in the Blanks

Replace each ___ so the program checks contiguity and flattens an array in column-major order.

import numpy as np

arr = np.arange(8).reshape(2, 4)

# Is it stored row-major?
print(arr.___['C_CONTIGUOUS'])   # fill in: flags  -> True

# Flatten column by column
print(arr.ravel(___='F'))        # fill in: order  -> [0 4 1 5 2 6 3 7]

# Expected output:
# True
# [0 4 1 5 2 6 3 7]

Expected output: True then [0 4 1 5 2 6 3 7]. (Answers: flags, order.)

⚠️ Common Errors & Quick Reference

❌ Assuming a transpose is contiguous

.T only swaps strides, so the result is usually not C-contiguous.

✅ Fix: check .flags, and call np.ascontiguousarray(arr.T) when an op needs contiguous data.

❌ Confusing layout with values

C and F order hold the same numbers; only the byte arrangement differs.

✅ Fix: use order= when you care about memory or flattening direction, not when comparing values.

TaskCode
Check contiguitya.flags['C_CONTIGUOUS']
See byte stepsa.strides
Build column-majornp.array(x, order='F')
Force contiguous copynp.ascontiguousarray(a)
Flatten in F ordera.ravel(order='F')

🎯 Mini Challenge: Convert to Fortran Order

Start from a C-contiguous array, convert it to Fortran order with np.asfortranarray, and confirm the strides change while the values stay identical.

import numpy as np

x = np.arange(12).reshape(3, 4)
print(x.strides)                 # (32, 8)

xf = np.asfortranarray(x)
print(xf.strides)                # (8, 24)
print(xf.flags['F_CONTIGUOUS'])  # True
print(np.array_equal(x, xf))     # True  (same values)

# ✅ Expected output:
# (32, 8)
# (8, 24)
# True
# True

❓ Frequently Asked Questions

Lesson complete — memory layout unlocked!

You can now read .flags and .strides, tell C from F order, explain why transpose is free, and force a contiguous copy with ascontiguousarray when speed matters.

Practice quiz

What does C order (the NumPy default) mean for a 2D array?

  • Row-major: rows are stored end to end
  • Column-major: columns first
  • Random order
  • Compressed order

Answer: Row-major: rows are stored end to end. C order is row-major, storing one full row after another.

What does F order mean?

  • Float order
  • Column-major (like Fortran)
  • Fast order
  • Filtered order

Answer: Column-major (like Fortran). F order is column-major, storing one full column after another.

What do an array's .strides report?

  • The element count
  • The dtype name
  • The bytes to step to advance one index along each axis
  • The number of dimensions

Answer: The bytes to step to advance one index along each axis. .strides give the byte steps to move one index along each axis.

What is a.flags['C_CONTIGUOUS'] for a default np.arange(12).reshape(3,4)?

  • False
  • None
  • An error
  • True

Answer: True. A default array is C-contiguous, so the flag is True.

What happens to the strides when you transpose with .T?

  • They are swapped, with no data copied
  • They double
  • They reset to (8, 8)
  • The data is reordered in memory

Answer: They are swapped, with no data copied. Transpose just swaps the strides over the same bytes; no data moves.

After c.T on a C-contiguous array c, the result is usually:

  • Still C-contiguous
  • F-contiguous, not C-contiguous
  • Neither layout
  • A scalar

Answer: F-contiguous, not C-contiguous. Transposing a C-contiguous array makes it F-contiguous, not C-contiguous.

What does np.arange(6).reshape(2,3).ravel(order='F') return?

  • [0 1 2 3 4 5]
  • [5 4 3 2 1 0]
  • [0 3 1 4 2 5]
  • [0 2 4 1 3 5]

Answer: [0 3 1 4 2 5]. Column-major flattening reads down columns: [0 3 1 4 2 5].

Which function forces a fresh C-contiguous copy of an array?

  • np.ascontiguousarray
  • np.ravel
  • np.reshape
  • np.transpose

Answer: np.ascontiguousarray. np.ascontiguousarray makes a contiguous copy the CPU can stream through.

For a C-contiguous int64 array of shape (2, 3), what are the strides?

  • (8, 8)
  • (24, 8)
  • (3, 2)
  • (8, 24)

Answer: (24, 8). 8 bytes to the next column and 24 to the next row, so (24, 8).

Do C order and F order of the same array hold different values?

  • Yes, the values change
  • Only for floats
  • No, same values, only the byte arrangement differs
  • Only for 3D arrays

Answer: No, same values, only the byte arrangement differs. Layout changes only the physical arrangement, never the values.

Continue this course

Frequently asked questions

What is the difference between C order and F order in NumPy?

C order (row-major, the default) stores a 2D array one full row after another, so elements in the same row sit next to each other in memory. F order (column-major, like Fortran and MATLAB) stores one full column after another, so elements in the same column are adjacent. The values are identical either way; only their physical arrangement in memory differs, which you can see through the array's strides.

What are strides in a NumPy array?

Strides are the number of bytes you must step in memory to move one index along each axis. For a C-contiguous int64 array of shape (3, 4) the strides are (32, 8): step 8 bytes to the next column, 32 bytes to the next row. Many operations like transpose and slicing just change the strides instead of copying data, which is why they are so cheap.

Why does memory layout affect performance?

CPUs read memory in contiguous chunks (cache lines), so iterating along the axis whose elements are adjacent is much faster. On a C-contiguous array, working row by row is cache-friendly; on the same data transposed, the elements you need are scattered and the CPU stalls. When an operation runs slowly on a non-contiguous array, np.ascontiguousarray makes a contiguous copy that the next operations can stream through quickly.