Jane Street's ML engineering internship preparation has two connected goals: write reliable code with another person, then explain how data, evaluation, and runtime constraints shape an ML system. A model name is a starting point. The useful discussion is what the model can know, how you measure it, and what happens when its assumptions fail.
This guide includes an original 12-point forecasting exercise with exact baseline results, leakage checks, and a coding follow-up. It uses synthetic data, not Jane Street market data or a private assessment. The 2027 title is a preparation label; the current public internship listing does not explicitly identify a calendar-year recruiting cohort.
![]()
What does the current public listing confirm?
Jane Street's New York ML Engineer Internship listing describes an initial technical coding interview over Zoom followed by an onsite with 2–4 technical rounds, including 1–2 focused on ML engineering. It seeks practical experience training models, working on an ML library, or optimizing an ML workflow. Finance experience is not required.
Use that description to divide preparation between coding and applied ML reasoning. Confirm your office, cohort, stages, timing, and permitted tools with recruiting. The public listing does not guarantee every 2027 invitation or specify a universal private question set.
Begin with PracHub's time-series forecasting practice question, then work through the smaller exercise below. Its purpose is to make the evaluation assumptions inspectable before adding a larger model.
How should you approach the coding screen?
Jane Street's general software-engineering guidance emphasizes collaborative coding in a real language and recommends the language you know best. It does not award extra credit for choosing OCaml. Prepare to clarify an operation, implement a small correct slice, test it, and adapt when the interviewer changes a requirement.
Rehearse stateful tasks such as code folding, board simulation, a small language runtime, or marketplace reconciliation. State which invariant each operation preserves. When a test fails, walk through the smallest failing input before making an edit. When offered a hint, explain what changed in your model of the problem.
For this internship, keep the same working style during ML discussion: fix the prediction contract, compute a baseline, examine a failure, and revise the smallest affected part. The exercise below makes that transition concrete.
Forecasting mock: what can the model know at prediction time?
You observe one integer value at the end of each equally spaced time step:
| Step | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Value | 10 | 12 | 11 | 13 | 14 | 16 | 15 | 17 | 18 | 20 | 19 | 21 |
Prediction contract: at the end of origin t, observe values through t and forecast y[t+1]. The target arrives one step later. The metric is mean absolute error, MAE, in the same units as the target. This is sequential one-step forecasting, not a multi-step forecast issued once at the beginning of a holdout.
| Split | Forecast origins | Target steps | Use |
|---|---|---|---|
| Training | 2–5 | 3–6 | Fit a simple drift parameter |
| Validation | 6–8 | 7–9 | Compare two candidates and select one |
| Final test | 9–11 | 10–12 | Evaluate the frozen choice |
At validation origin 6, target step 6 from the last training example is already observed. At origin 7, the newly observed value from step 7 can become an input for the next prediction. That is permitted by the sequential one-step contract. It would be leakage under a different contract that forbids incorporating observations after origin 6.

Compare a persistence baseline with a fitted drift
Persistence predicts the latest observed value: prediction = y[t]. The second candidate predicts y[t] + drift, where drift is the mean one-step change over training examples only.
Training changes are −1, +2, +1, and +2. Their mean is +1. Freeze that parameter while comparing validation forecasts:
| Origin | Actual next value | Persistence | Drift +1 | Absolute error: persistence / drift |
|---|---|---|---|---|
| 6 | 15 | 16 | 17 | 1 / 2 |
| 7 | 17 | 15 | 16 | 2 / 1 |
| 8 | 18 | 17 | 18 | 1 / 0 |
Validation MAE is 4/3 ≈ 1.333 for persistence and 1.000 for drift. Select drift using validation, then examine the final test once:
| Origin | Actual next value | Persistence | Drift +1 | Absolute error: persistence / drift |
|---|---|---|---|---|
| 9 | 20 | 18 | 19 | 2 / 1 |
| 10 | 19 | 20 | 21 | 1 / 2 |
| 11 | 21 | 19 | 20 | 2 / 1 |
Test MAE is 5/3 ≈ 1.667 for persistence and 4/3 ≈ 1.333 for drift. Keep the selected drift frozen for this reported comparison. The tiny improvement on three test points provides no credible evidence of general financial predictive value. It verifies the exercise's arithmetic and evaluation boundary.
Code that makes label availability explicit
The helper uses one-based forecast origins and exact rational arithmetic. Integer observations are the toy input contract. Offline scoring reads the target to measure error; fitting must reject labels beyond its cutoff.
from fractions import Fraction
def validate(values, origins):
if not values or any(type(v) is not int for v in values):
raise ValueError("nonempty integer series required")
if not origins or len(set(origins)) != len(origins):
raise ValueError("nonempty unique origins required")
if any(type(t) is not int or not 1 <= t < len(values) for t in origins):
raise ValueError("invalid one-based forecast origin")
def fit_drift(values, origins, label_cutoff):
validate(values, origins)
if type(label_cutoff) is not int or not 1 <= label_cutoff <= len(values):
raise ValueError("invalid label cutoff")
if any(t + 1 > label_cutoff for t in origins):
raise ValueError("training label not available by cutoff")
return Fraction(sum(values[t] - values[t - 1] for t in origins), len(origins))
def score(values, origins, drift):
validate(values, origins)
if not isinstance(drift, Fraction):
raise ValueError("use an exact Fraction drift")
rows = [(t, Fraction(values[t - 1]) + drift, values[t]) for t in origins]
mae = sum(abs(predicted - actual) for _, predicted, actual in rows) / len(rows)
return rows, mae
For this fixture, call fit_drift(y, [2,3,4,5], 6), then score origins [6,7,8] and [9,10,11] with the frozen Fraction(1). The validation and test target sets do not overlap. A local check changed all values after step 6 and confirmed the training drift remained unchanged.
Catch leakage before choosing a larger model
A feature called “tomorrow's value” reproduces the target and would report zero error. It is unavailable at prediction time, so that result is meaningless. A centered rolling average can leak more subtly by including y[t+1] in a feature associated with origin t.
Preprocessing can leak too. Training feature values at origins 2–5 have mean 12.5. All 12 observations have mean 15.5. Fitting a normalizer on the full series lets later values influence earlier training. Fit learned transformations on the training slice, then apply the frozen transformation to validation and test.
| Risk | Question to ask | Corrective action |
|---|---|---|
| Future feature | Was this value available at origin t? | Use an as-of join or trailing-only feature |
| Delayed label | Was the training target known by the fit cutoff? | Exclude examples whose labels had not arrived |
| Overlapping horizon | Does a training label extend into the next fold? | Remove incompatible origins or use an appropriate gap |
| Full-data preprocessing | Which rows fitted the transform? | Fit within each training fold |
| Repeated test tuning | Did the test result influence a choice? | Reserve a new untouched evaluation period |
If the interviewer changes the horizon to two steps and the fitting cutoff remains step 6, training origin 5 is no longer eligible: its label window ends at step 7. Origins 2–4 remain eligible because their windows end by step 6. Rebuild the target contract and splits rather than reusing the one-step scores.
Scikit-learn's TimeSeriesSplit supports chronological folds and a gap parameter. A chronological index alone does not solve feature availability, revised data, delayed labels, or overlapping target windows. Those remain properties of your dataset construction.

Coding follow-up: maintain a trailing mean
Suppose the partner asks for the mean of the last W observed values at every eligible origin. Start with slicing and summing, which costs O(W) per output. A hint suggests keeping a running sum. Preserve the same output contract and change the update mechanism:
from collections import deque
def trailing_means(values, width):
if type(width) is not int or width <= 0:
raise ValueError("positive window width required")
if any(type(v) is not int for v in values):
raise ValueError("integer series required")
window, running, output = deque(), 0, {}
for t, value in enumerate(values, 1):
window.append(value); running += value
if len(window) > width:
running -= window.popleft()
if len(window) == width:
output[t] = Fraction(running, width)
return output
For W=3, the feature at origin 6 is (13+14+16)/3 = 43/3; at origin 8 it is (16+15+17)/3 = 16. Neither requires a future observation. Say explicitly that this helper supplies a feature; it is not the selected drift model and its output is not an additional reported test result.
The running algorithm takes O(n) time and O(W) window space. This implementation also stores O(n) output entries. A streaming version can emit each output instead of retaining the dictionary. Do not claim total O(W) memory while materializing every output.
The complete local suite passed 1,227 checks, including 1,200 rolling-window comparisons against an independent slice-and-sum oracle, widths 1–12, and seeded integer series of lengths 1–100. Other checks verified the exact forecast results, invalid inputs, label cutoff, future-data independence of fitting, and the two-step eligibility change. No neural-network training or production latency benchmark was run.
Turn the model exercise into an ML-system decision
For an original deployment discussion, assume one CPU worker, a 100 MB feature-memory budget, and a 50 ms p99 response target. These are supplied exercise constraints, not Jane Street requirements or measured performance.
Begin with the simple baseline. Its arithmetic is cheap, but the service still needs timely input, schema validation, versioned transformations, and defined behavior when the latest observation is missing. Measure the end-to-end path before choosing a larger model: fetch, feature construction, inference, serialization, and queue wait.
For a training framework, name the dataset snapshot, cutoff, split definitions, code version, dependency versions, transform artifact, model artifact, and promotion decision. A seed does not reproduce data that has changed. Reject incompatible feature schemas before serving an artifact, and keep a compatible prior model and transform for rollback.
Monitor missing or late inputs, feature distributions, inference failures, latency tails, and prediction error once labels arrive. Avoid retraining on an alert alone: first distinguish a broken data pipeline from a genuine change in the relationship you are modeling. Jane Street's public ML page describes noisy, changing financial data and training/inference infrastructure; it supplies context, not a private assessment specification.
A seven-day preparation plan with concrete outputs
| Day | Work | Output |
|---|---|---|
| 1 | Confirm the loop and solve a stateful coding task | Working code, tests, and clarified assumptions |
| 2 | Define the forecasting contract | Origin/target table and as-of availability rules |
| 3 | Compute both baselines | Exact validation/test results and a frozen choice |
| 4 | Audit leakage; change the horizon | Rebuilt eligibility and split boundaries |
| 5 | Optimize the trailing feature | Running-sum code checked against a simple oracle |
| 6 | Design training and serving | Artifact contracts, monitoring and rollback plan |
| 7 | Run an interactive mock and project discussion | Changed-requirement notes and honest project evidence |
Rehearse a project you actually built, including a failed experiment, the evidence that changed your mind, and the part you personally owned. Keep data-science, ML research, and ML engineering role descriptions separate. Use the question bank's forecasting, framework, compiler and stateful-code tasks as transferable practice rather than predictions of your interview.
Sources and scope
Reviewed October 8, 2026. The public listing is current role guidance, not an explicitly dated 2027 schedule. All data, calculations, budgets and code examples here are original synthetic practice material.
- Jane Street ML Engineer Internship, New York — current role and interview description.
- Preparing for a Jane Street software-engineering interview — collaborative coding and language guidance.
- Machine learning at Jane Street — public ML and infrastructure context.
- Scikit-learn TimeSeriesSplit — chronological folds and gap parameter.