C-Order vs Fortran-Order in NumPy: Which Memory Layout Should You Use?
Choose between NumPy's default C-order and Fortran-order memory layouts for performance and interop. A decision guide with a comparison table, trade-offs, and a timing validation.
04 Dec 2025, 09:44 UTC

You allocate a 2-D array, run a column-wise reduction, and it feels slower than it should. Or you pass an array to a Fortran library and get a copy warning you didn't expect. Both symptoms trace back to one decision: which memory layout your NumPy arrays use.
NumPy stores data in two ways. C-order (row-major) is the default and matches C/C++ memory layout. Fortran-order (column-major) matches Fortran libraries like LAPACK. The choice affects performance and interoperability, and the practical rule is simple: keep C-order unless you have a specific Fortran-interop or column-major algorithm requirement.
The decision and its constraints
When you call np.zeros or np.empty on a 2-D array, NumPy lays out memory in C-order by default: the last axis varies fastest. For a 3×4 array of float64, C-order stores row 0 (8 bytes × 4 elements), then row 1, then row 2. F-order stores column 0, then column 1, and so on.
Three constraints shape the decision:
- Converting between layouts copies data unless the source already has the target layout, which can cause memory spikes on large arrays.
- Slicing,
reshape, andravelcan return views that silently change effective layout. - Small arrays rarely benefit from layout tuning; overhead dominates.
C-order vs F-order at a glance
| Consideration | C-order (row-major) | Fortran-order (column-major) |
|---|---|---|
| Default in NumPy | Yes | No |
| Fast for | Row-wise ops like np.sum(axis=1), broadcasting along the last axis | Column-wise ops like np.sum(axis=0) |
| Interop | C/C++, most C extensions, most BLAS builds | Fortran libraries (LAPACK), f2py |
| Conversion function | np.ascontiguousarray(a) | np.asfortranarray(a) |
| Both flags true | 1-D arrays, or any shape with a dimension of size 1 | Same condition |
Trade-offs: when F-order actually helps
F-order is not a magic speed-up. If your code mixes row and column operations, the cost of non-native access can negate the benefit. The gain appears only for large arrays with repeated column access. On a 1000×1000 array, np.sum(a, axis=0) is faster on an F-order array because each column is contiguous in memory.
Conversely, np.dot and many BLAS-backed operations are tuned for C-order. Forcing F-order may degrade performance unless your BLAS is column-major aware. Most builds are, but verify your OpenBLAS or MKL documentation before relying on F-order for matrix multiplication.
Concrete validation: timing layout-dependent reductions
Create both layouts and time the same reduction to see the difference on your hardware. Run this in a Python environment with NumPy installed:
import numpy as np
import timeit
n = 1000
a_c = np.random.rand(n, n) # C-order by default
a_f = np.asfortranarray(a_c) # F-order copy
print(a_c.flags.c_contiguous, a_c.flags.f_contiguous) # True False
print(a_f.flags.c_contiguous, a_f.flags.f_contiguous) # False True
print(np.shares_memory(a_c, a_f)) # False — conversion copied
t_row_c = timeit.timeit(lambda: a_c.sum(axis=1), number=100)
t_col_c = timeit.timeit(lambda: a_c.sum(axis=0), number=100)
t_col_f = timeit.timeit(lambda: a_f.sum(axis=0), number=100)
print(f"C-order row sum: {t_row_c:.3f}s")
print(f"C-order col sum: {t_col_c:.3f}s")
print(f"F-order col sum: {t_col_f:.3f}s")Exact timings depend on your hardware and BLAS build, but expect the F-order column sum to beat the C-order column sum on large arrays, and the C-order row sum to beat the C-order column sum. If the differences are negligible, your array is too small for layout tuning to matter.
Checking whether conversion copies
np.asfortranarray returns a view if the input is already F-contiguous; otherwise it allocates a new array. Verify with np.shares_memory — it should be False after a real conversion. This check matters because converting a large array mid-pipeline can double memory usage temporarily.
Watch out for views that change layout
Transposing a C-order array with .T gives an F-contiguous view, not a copy. If you pass that view to a C extension expecting C-order, the extension may silently copy it. Use a.flags.c_contiguous and a.flags.f_contiguous before passing arrays across library boundaries.
When to use F-order
- You are passing arrays to Fortran-compiled code (f2py, LAPACK) that expects column-major layout.
- Your algorithm is dominated by column-wise access on large arrays.
- You are building a matrix for a column-major library and want to avoid a copy at the boundary.
In all other cases, leave the default C-order. If only one array needs F-order, convert just that array with np.asfortranarray rather than changing the whole pipeline.
Limitations and verification
The performance benefit is workload-specific. Time your own reduction or loop with timeit before committing to F-order. Confirm your BLAS behavior for matrix multiplication, and for interop validation, pass an F-order array to a Fortran-compiled function and check for correct results and no copy warnings. Layout is an engineering trade-off, not a default you should change globally.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.