Skills

Python or C++ in quant finance: choose by the system you want to build

KnowQuant research desk · Rewritten 13 September 2026 · 8 min read

Should a quant learn Python or C++?

Neither language is the general answer, because they are not competing for the same work. Choose by the system you want to build: research iteration and data and ML pipelines are commonly Python-centred, while a small, well-identified set of components with hard latency or throughput budgets is where a native language earns its cost. Where both appear in one stack they are joined at a deliberate interface - and whether either is fast enough is a question you settle by measuring, not by choosing a language.

What this piece establishes

  • The question hides the real decision. "Python or C++" sounds like a choice between two tools for one job.
  • What determines whether a system is fast. Before either language matters, five things usually matter more.
  • Where each language earns its place. Two things that table is not saying.
  • A worked design example. Hypothetical, and deliberately ordinary.

The question hides the real decision

"Python or C++" sounds like a choice between two tools for one job. It is not. The two languages mostly sit in different parts of a trading or research stack, and the engineers who look fluent in this world are not the ones who picked a side - they are the ones who can say which component belongs where, and defend it with a measurement.

So the useful version of the question is: what am I building, what has to be true about it, and where does that constraint actually bite?

What determines whether a system is fast

Before either language matters, five things usually matter more. Diagnosing the wrong one is how teams spend a quarter rewriting something that was never the bottleneck.

  • Algorithm and complexity. A better method beats a faster implementation of a worse one, and no amount of native code rescues an accidental quadratic.
  • I/O and the network. If a job spends most of its wall clock waiting on object storage, a database or a socket, the language of the compute is close to irrelevant.
  • Data layout. Contiguous arrays, column versus row orientation, how much of the working set fits in cache, and how many copies you make on the way.
  • Memory and allocation behaviour. Allocation on a hot path, garbage or refcount churn, and copies created implicitly by convenient code.
  • Operational constraints. Scheduling, other tenants on the box, kernel and driver behaviour, warm-up state, and what the system does at its worst moment rather than its median one.

Language and runtime come after those, and they matter most where per-operation overhead and predictability dominate: interpreter dispatch, dynamic typing, and - for anything measured at the tail - pauses you do not control.

The C++ Core Guidelines put the discipline bluntly in Per.6: Don't make claims about performance without measurements: "The field of performance is littered with myth and bogus folklore." Its remedy is the one worth carrying into an interview - measure before you claim, and measure your own key operations if you cannot measure the whole system. That guidance applies to this article too, which is why it publishes no speed figures at all: the example below is a correctness demonstration, and the timing instructions are for you to run in your own environment.

Where each language earns its place

ContextTypical primary toolWhat the constraint actually is
Research iteration and backtestingPythonTime from idea to answer; correctness and reproducibility over runtime
Data and ML pipelinesPython, with native libraries and columnar engines underneathThroughput and memory over large batches; the heavy loops already run in compiled code
Order execution and market data handlingC++, sometimes another native languagePer-message latency and, more importantly, tail predictability
Simulation and heavy numerical kernelsEither, often Python calling a native kernelTotal compute, parallelism, numerical care
Internal tools, services, web productsWhatever the team's platform isDelivery speed, reliability, maintainability
Scroll the table sideways, or focus it and use the arrow keys.

Two things that table is not saying. It is not saying C++ makes a system low-latency - a careless C++ service that allocates on its hot path and blocks on a lock will lose to a well-designed Python one that never touches that path. And it is not saying Python cannot be used in trading; it appears in production research, analytics and decision tooling at firms that also run native execution paths. Jane Street's public engineering advert, read on 13 September 2026, names OCaml as its primary language with Python used for research, data and machine-learning work - one firm's stated choice, and a useful reminder that the real-world answer is sometimes neither of the two languages in this article's title.

Vectorised does not mean free

Much Python numerical performance comes from not being in Python: array libraries push the inner loop into compiled code. NumPy's own broadcasting documentation makes the trade-off explicit - broadcasting vectorises operations so looping happens in C rather than Python, usually without needless copies, but there are cases where it uses memory inefficiently and slows the computation down. Which is the same lesson again: the abstraction is a tool with a cost model, not a speed setting.

A worked design example

Hypothetical, and deliberately ordinary. A research team has a signal that consumes five years of minute bars across 3,000 instruments. Offline, it takes eleven minutes to recompute, which is annoying but survivable. The team now wants the same signal evaluated intraday, with a budget of 200 milliseconds from the arrival of a bar to an updated value.

Step one - do not rewrite anything yet. Profile the offline job. Say it splits: 70% reading and decompressing parquet, 22% one feature computation, 8% everything else.

Step two - read the profile honestly. A C++ rewrite of the computation alone attacks 22% of a job whose dominant cost is I/O. That is the classic wrong move, and it is the one a good interviewer is listening for.

Step three - separate the two problems. Offline and intraday are different systems with different constraints. Offline wants throughput: better storage layout, column pruning, partitioning, caching decompressed blocks. Intraday does not re-read five years of history at all - it updates incrementally from the state it already holds.

Step four - restate the intraday budget. Incremental update on the arrival of a bar touches one instrument and a bounded window. Written as a vectorised incremental update in Python, measure it. If it lands in single-digit milliseconds against a 200 ms budget, you are finished, and the honest engineering answer is "no native component required".

Step five - narrow the native component if, and only if, the measurement justifies it. Suppose the budget were 200 microseconds instead, and measurement showed per-call overhead dominating. Then the candidate for a native implementation is the one hot function, not the pipeline: a small, well-tested component with a clear interface, called from the Python system that still owns orchestration, configuration and research reproducibility. You have added one boundary, not a second stack.

Step six - defend the interface. Every language boundary costs something: serialisation or copying, build complexity, two debugging stories, and a duplicated-logic risk if the offline and online versions of the signal drift apart. State that cost, then state why the latency requirement is worth it. That last sentence is the actual answer to "Python or C++".

A small runnable example

A rolling z-score over a one-dimensional series, written the way a reviewer would want to see it: an explicit input contract, stated conventions for the awkward cases, and a check suite that pins the behaviour down. It is an educational exercise in vectorised numerical code, not a trading signal and not a recommendation to trade on one.

The design decisions worth noticing before the code. Windows are taken with sliding_window_view, so each window's mean and standard deviation are computed directly rather than reconstructed by subtracting two large cumulative sums - the shortcut that loses precision on a long series sitting at a high price offset. The division is done with np.divide(..., out=..., where=...), so a zero-variance window is never divided at all rather than divided and then patched up. And the conventions are written down: the first window - 1 positions are NaN, a window longer than the series returns all NaN, and a window with zero variance yields 0.0.

import numpy as np
from numpy.lib.stride_tricks import sliding_window_view


def rolling_zscore(x, window):
    """Rolling z-score of a 1-D finite float series.

    Conventions:
      - the first window - 1 positions are NaN;
      - a window longer than the series returns all NaN;
      - a window with zero variance yields 0.0 (no division is performed).

    Educational example. Not a trading signal.
    """
    a = np.asarray(x, dtype=float)
    if a.ndim != 1:
        raise ValueError("x must be one-dimensional")
    if not np.all(np.isfinite(a)):
        raise ValueError("x must contain only finite values")
    if isinstance(window, bool) or not isinstance(window, (int, np.integer)):
        raise TypeError("window must be an integer")
    if window < 1:
        raise ValueError("window must be at least 1")

    out = np.full(a.shape, np.nan)
    if window > a.size:
        return out

    w = sliding_window_view(a, window)
    mean = w.mean(axis=-1)
    sd = w.std(axis=-1)
    last = a[window - 1 :]
    out[window - 1 :] = np.divide(
        last - mean, sd, out=np.zeros_like(mean), where=sd > 0.0
    )
    return out


def run_checks():
    rng = np.random.default_rng(7)

    x = rng.standard_normal(5000)
    z = rolling_zscore(x, 50)
    assert np.all(np.isnan(z[:49]))
    first = x[:50]
    assert abs(z[49] - (x[49] - first.mean()) / first.std()) < 1e-12

    with np.errstate(all="raise"):
        flat = rolling_zscore(np.full(100, 3.5), 10)
    assert np.all(flat[9:] == 0.0)

    prices = 10_000.0 + np.cumsum(rng.standard_normal(1000) * 0.01)
    zp = rolling_zscore(prices, 20)
    direct = np.array([
        (prices[i] - prices[i - 19 : i + 1].mean()) / prices[i - 19 : i + 1].std()
        for i in range(19, prices.size)
    ])
    assert np.allclose(zp[19:], direct, atol=1e-9)

    assert np.all(rolling_zscore(np.arange(5.0), 1) == 0.0)
    assert np.all(np.isnan(rolling_zscore(np.arange(5.0), 9)))

    for bad_x, bad_w in (
        ([[1.0, 2.0]], 2),
        (np.array([1.0, np.nan]), 2),
        (np.arange(5.0), 0),
        (np.arange(5.0), 2.5),
    ):
        try:
            rolling_zscore(bad_x, bad_w)
        except (TypeError, ValueError):
            continue
        raise AssertionError("expected a rejection")

    return "all checks passed"


print(run_checks())
python example. Select the text to copy it; focus the block and use the arrow keys to scroll.

We ran exactly that code before publishing it, on Python 3.13.12 with NumPy 2.4.3: run_checks passes, including the zero-variance case under np.errstate(all="raise"), which is how you prove the division was genuinely skipped rather than silently warned about. Our test suite re-runs the same block on every build, so the code on this page and the code we checked cannot drift apart.

No speed figures are published here on purpose, because a timing from our container would tell you nothing reliable about yours. If you want one, run it yourself: wrap the call in timeit.repeat(..., repeat=5, number=1), take the minimum rather than the mean, hold the array size, dtype and window fixed, and record your CPU, Python and NumPy versions alongside the number. Then compare against a plain Python loop over the same input if you want to see where the overhead actually sits. A ratio without that context is folklore, which is the point Per.6 is making.

One more thing worth carrying into an interview: "faster" is never the whole property. The cumulative-sum trick this version deliberately avoids is genuinely quicker on paper, and it quietly loses precision when a long series sits far from zero. Choosing the stable formulation and saying why is the answer that lands.

What to learn, by target

Practice in the shape of the work, not as a language tour.

If you are aiming atPractise thisA project that demonstrates it
Research and signalsNumPy and pandas or Polars, vectorised thinking, statistics, reproducible environments, testing numerical codeA backtest with an explicit data contract, a seeded test suite and honest accounting of look-ahead
Data and ML platformColumnar formats, pipeline orchestration, schema evolution, profiling large jobs, SQLA pipeline that ingests a public dataset, validates it and reports its own data-quality failures
Execution and low latencyModern C++, memory model and allocation behaviour, cache and data layout, lock-free basics, measuring tail latencyA market-data decoder with a latency histogram and a stated measurement method
Infrastructure and reliabilityLinux, networking, containers, observability, one systems languageA service with load generation, dashboards and a documented failure mode
Internal products and toolingThe team's application stack, API design, testing, usable interfacesA tool a real user adopts, with the feedback that shaped it
Scroll the table sideways, or focus it and use the arrow keys.

Two practical notes. SQL appears constantly and is consistently underrated by candidates preparing for these roles. And teams do choose other languages deliberately - OCaml, Java, Go, Rust, TypeScript for internal products - so arriving with one strong systems language and demonstrably good judgement travels better than a shallow tour of five.

How to answer it in an interview

Say which system you are talking about. Name the constraint - throughput, latency, tail predictability, iteration speed. Say how you would measure it. Then choose, and name the cost of the boundary you have introduced. Candidates who answer "C++, because it is faster" are answering a question nobody working on the problem is asking.

If you want to see which functions are actually being hired for and what each firm's engineering stack is, the skills index and the firm profiles record what employers themselves state in their adverts, with sample sizes attached.

Sources and further reading

Primary material read while writing this piece. Firm specific practices are attributed to the firm that published them and should not be read as industry wide.

skillspythonc++engineeringsystems

See which systems firms are staffing

The skills index records what employers name in their own adverts, with sample sizes, so you can check a language or stack against current demand rather than against advice. The engineering guide covers how the five distinct functions differ once you are inside.

Live opportunities

Act on this: live roles it covers

Open vacancies we track at quant firms, grouped by role and city. Adverts are anonymised.

Hiring for these roles? Brief the desk on a hire or check your fit for a role.

Research desk

Speak to a quant market specialist

Ask what a firm really pays, how a desk hires, or where your profile sits. One reply from the research desk, in confidence, no obligation.

Related research