AtWhatMileage
", so a foreach in a
would // silently collapse to one line. // // Assembled *and joined* here, so what the markup receives is a finished string // and echoing it is the only thing left to do. That division is not tidiness. The // earlier version left the implode() in the markup, which meant two places had to // agree on whether these variables were lists or text; they stopped agreeing, and // /methodology/ died with "implode(): Argument #1 must be of type array, string // given" on a live site while every other page kept working -- a whole page lost // to a disagreement about a type, in a file no test renders. A variable the markup // only prints cannot be got wrong that way. $sevLines = []; foreach ($sev as $hint => $pts) { // 24 puts the digits in the same column as the "+ 4.0" lines below. $sevLines[] = ' ' . str_pad((string)$hint, 24) . number_format((float)$pts, 1); } $sevBlock = implode("\n", $sevLines); $scoreLines = []; $last = count($wRows) - 1; foreach ($wRows as $i => [$k, $label, $why]) { $scoreLines[] = ' ' . str_pad(number_format((float)($w[$k] ?? 0), 2), 5) . ' × ' . str_pad((string)$k, 9) . ($i < $last ? '+' : ''); } $scoreBlock = implode("\n", $scoreLines); $importedOn = (string)($t['imported_at'] ?? ''); $computedOn = (string)($t['computed_at'] ?? ''); // "engine, transmission, drivetrain and cooling", however many there turn out to be. $costlyPhrase = ''; if (count($costly) === 1) { $costlyPhrase = (string)$costly[0]; } elseif ($costly !== []) { $costlyPhrase = implode(', ', array_slice($costly, 0, -1)) . ' and ' . (string)$costly[count($costly) - 1]; } ?> Methodology

How these numbers are computed

Every figure on this site is derived from public federal records by arithmetic that is written out below in full. Nothing is estimated by hand, nothing is editorial, and no manufacturer has any input into it. One step in the chain rests on a large assumption, and rather than bury it in a footnote this page says which one and what it does to the numbers.

1. Where the data comes from

Three public datasets from the US National Highway Traffic Safety Administration, all of them free, keyless and republishable:

Current contents of the database
Source What it contributes Rows in use
ODI owner complaints (FLAT_CMPL) The failure description, the date, and — critically — the odometer reading at the time of failure 1,943,536
Recall campaigns (FLAT_RCL_PRE_2010 and FLAT_RCL_POST_2010) Campaign counts, defect and remedy text, and the potential-units figure used as a fleet-size proxy 134,500
Vehicle catalogue (vPIC and the complaint file itself) The make, model and model-year universe the site is organised by 66,828

Of the 1,943,536 complaints, 1,092,729 carry a usable odometer reading, and those are the only ones that contribute to any mileage figure on the site. A reading is discarded when it is absent, zero, or above 400,000 miles, which is the point past which the field is almost always a typing error rather than a very tired car. Coverage begins with model year 1996, because complaint volume before the OBD-II era is too thin to compute anything defensible from, and ends with 2027.

Complaints are unverified. NHTSA publishes what owners submit. Nobody inspects the car, the diagnosis is the owner's, and the odometer reading is whatever they typed. Complaint counts are also driven by how many of a model were sold and by how much publicity a defect received. That is why nothing on this site is presented as a failure rate, and why the number this site is actually built around is the mileage, which has no obvious reason to be biased by any of those things.

2. From a paragraph of prose to a named failure

A raw complaint is a component code and a few hundred words of narrative. To be counted it has to be turned into one named failure — "timing chain stretch", not "ENGINE". A hand-written catalogue of 114 failure modes does that. Each mode carries a list of phrases to look for, the system it belongs to, a severity hint, a typical repair-cost range, and a priority.

Classification is first-match-wins in priority order: the most specific and most consequential patterns are tested first, so a report that mentions both a fire and a warning light is filed as a fire. Reports that match nothing are filed under a single visible catch-all rather than being silently dropped, and the unmatched phrases are logged so the catalogue can be extended. One complaint produces exactly one failure mode, which is what makes the counts on this site add up.

Where this goes wrong. Phrase matching cannot read. A complaint saying "the dealer told me it was not the transmission" contains the word transmission. Errors of this kind are rare enough not to move a median but real enough to mention, and they are one reason no page here draws a conclusion from a handful of reports.

3. At what mileage: the percentiles

This is the number the site exists for. For one vehicle and one failure mode, every reported odometer reading is sorted and the 10th, 25th, 50th, 75th and 90th percentiles are taken by linear interpolation between the two readings that bracket each position — the same definition Excel and NumPy use:

// Stats::percentile(), miles sorted ascending idx = p × (n − 1) lo = floor(idx), hi = ceil(idx) value = miles[lo] + (miles[hi] − miles[lo]) × (idx − lo)

For fleet-wide figures — one failure mode across every vehicle, which can be hundreds of thousands of readings — the same percentiles are taken from a fixed-width histogram of 2,500-mile buckets, interpolated inside the winning bucket. That is accurate to about half a bucket, which is far finer than the source data deserves: owner-reported odometers cluster hard on round numbers, and a great many "exactly 100,000 mile" failures happened somewhere near 100,000 miles.

For the same reason, displayed mileages are rounded to the precision the sample can carry — to the nearest 1,000 miles above a hundred readings, 2,500 above twenty-five, and 5,000 below that. Publishing “92,431 miles” from eleven reports would imply a precision that never existed. The exact interpolated values are still printed, unrounded, in the table of record on each failure-mode page and returned by the API.

The cumulative curve on each vehicle page is the same readings counted at every 10,000-mile mark: the share of that vehicle's reports that had been filed by each odometer reading. It is a reported- complaint curve, not a survival curve, and it cannot fall.

4. How many of these cars exist — the hard part

A complaint count on its own measures popularity as much as it measures trouble. Correcting for that needs a denominator, and NHTSA does not publish sales. The only free signal in the data is recall paperwork: every campaign states how many units it potentially affects. Dividing that figure evenly across the model years the campaign covers gives a per-year unit estimate, and the largest single campaign for a model year is taken as the fleet proxy — the largest, not the sum, because campaigns overlap and would otherwise double-count the same cars.

// per model year fleet_proxy = MAX(campaign potential units ÷ model years covered) rate_per_1k = 1000 × complaints ÷ fleet_proxy

Every page that prints a rate also names where its denominator came from, because the three cases are not equally trustworthy:

Where a fleet-size estimate comes from, and how much weight it carries
Fleet source Meaning Effect on the page
recall Estimated from this exact model year's own recall campaigns Rate published; confidence may reach high
model_median Borrowed: the median proxy of other years of the same model Rate published and labelled; confidence capped at moderate
none No campaign ever covered this vehicle No rate at all; ranked on raw volume; confidence capped at low

This is a lower bound, and it is the weakest assumption on the site. A recall campaign covers the units built in a particular window, which is usually fewer than every car of that model year ever sold. A fleet proxy that is too small makes the rate too large. It is applied identically to every vehicle, so comparisons within a model year stay meaningful; but a single rate, read on its own, should be treated as an upper estimate of complaints per thousand, not a measurement.

5. Severity: how bad, not how often

Each complaint earns severity points — a base weight for the kind of failure it is, plus what actually happened in that specific report:

// Stats::severityPoints() base weight by severity hint crash reported + 4.0 fire reported + 6.0 injury reported + 8.0 + 2.0 per further injury, capped at +12 death reported +20.0 +10.0 per further death, capped at +30 severity_index = total points ÷ number of reports

Injuries and deaths dominate deliberately. The caps are there so that one catastrophic multi-fatality report cannot by itself define a model year, which would make the severity index a record of a single tragedy rather than a property of the car. Because the index is an average per report, a vehicle with two hundred trim complaints and one fire scores lower than a vehicle whose only three reports are all fires — which is the correct reading of “how bad is what gets reported here”, and the reason severity is never shown without a report count beside it.

6. The risk score, 0 to 100

Five components, each normalised to 0–1, then weighted. The two ranked components are percentile ranks within the same model year: a 2010 is compared only with other 2010s. Comparing a 2010 with a 2022 would measure the calendar, because a 2010 has had twelve more years in which someone could file.

// Stats::riskScore(), clamped to 0-100 score = 100 × ( )
The five components and their current weights
Weight Component Why it is in the score
45% Reports per 1,000, ranked within the model year How often owners complained, adjusted for how many of the car exist.
25% Average severity weight, ranked within the model year What kind of failures they were, and what happened when they failed.
15% Share of dated reports filed below 36,000 miles A failure at 30,000 miles says something a failure at 160,000 does not.
10% Share of reports in the expensive systems Weights the difference between a four-figure repair and a trim clip.
5% Recall campaigns, saturating at 8 A regulator ordering repairs is independent evidence, but it saturates.

The expensive systems, for the costly component, are . A failure there is a bill measured in thousands; a failure in the infotainment system is an annoyance. Both are real complaints, and the score should not treat them as equal.

The result is banded, and the band is what the colour on every page means:

  • Severe 75 – 100
  • High 60 – 74
  • Elevated 40 – 59
  • Moderate 20 – 39
  • Low 0 – 19

A score of 30 does not mean “a 30% chance of trouble”. It means this vehicle sat around the 30th mark of a 0–100 scale built from how its own model year behaved. Half of everything on sale in a good year still scores in the lower half, because the scale is relative by construction. What the score is good for is ordering: 8,416 vehicles currently carry one, and every one of them can be ranked against its own contemporaries.

7. Confidence

Every score is published with a confidence label, which depends on the sample size behind it and on how the fleet proxy was obtained:

// RiskScorer::confidence() fleet_source = none → low reports ≥ 60 and fleet_source = recall → high reports ≥ 25 → moderate otherwise → low

A low-confidence score is not a wrong score; it is a score with a wide error bar that this page cannot compute honestly and therefore will not pretend to. Read it as a hint to look at the underlying report count, which is always shown beside it.

8. When this site publishes nothing

Two floors, both of them deliberate, because a number computed from too little data is worse than an admission:

The sample-size floors, and what a page shows when it falls below one
Floor Rule What you see instead
15 Fewer than 15 reports on a vehicle: no risk score, no band, no rank The raw counts, and “not rated”
8 Fewer than 8 odometer readings for a failure mode: no percentile spread The median alone if any readings exist, otherwise an empty ruler that says why it is empty

Pages built entirely on data below these floors are marked noindex and kept out of the sitemap. They remain reachable, because “we know of four reports and cannot place them” is still a useful answer to somebody standing in front of the car, but they are not offered to search engines as though they were findings.

9. What this data cannot tell you

The most valuable thing a methodology page can do is mark its own edges, so here they are, plainly.

It cannot tell you how likely a failure is. Only a small and unknown fraction of owners ever file with a federal agency. Every rate here is complaints per thousand, not failures per thousand, and the gap between those two is large and unmeasurable from this data.

It cannot tell you about the specific car in front of you. Two examples of the same model year, one maintained and one not, differ from each other far more than two model years differ on any page here. A full service history and a pre-purchase inspection beat every number on this site, and nothing here is a substitute for either.

It cannot see failures nobody reports. Wear items, routine servicing and anything an owner considers normal are largely absent, because people complain to a safety regulator about safety, not about brake pads. Absence of a failure mode is therefore weak evidence, especially on a car that was never sold in large numbers.

It is biased toward the newsworthy. A defect covered in the press generates complaints from owners who might otherwise have said nothing, which inflates counts for famous problems and famous models. Mileage percentiles are much more resistant to this than counts are, which is why they are the headline figure on every page.

Generation boundaries are inferred. Where the site groups model years into a generation, it does so by clustering the model years present in the data, not from manufacturer documentation. They are usually right and occasionally a year out.

10. Reproducing, checking and correcting this

The source data is public and the arithmetic is on this page, so any figure here can be recomputed from scratch by anyone who wants to check it. The computed pages also have machine-readable twins, carrying the report counts and the unrounded percentiles, so the numbers can be verified without scraping the HTML: a model year is at /api/v1/vehicle/{make}/{model}/{year}, one failure mode across the whole fleet at /api/v1/mode/{mode}, and the mileage tool at /api/v1/mileage/{make}/{model}/{year}. The API index lists every endpoint with its parameters, and each page links to its own JSON twin from the document head.

Every failure mode Every manufacturer The mileage tool

Corrections. If a failure mode is mis-named, a generation boundary is wrong, or a figure on any page does not follow from the formulas above, that is a bug in this site rather than a difference of opinion, and it will be fixed. The database was last rebuilt from source on and the scores were last recomputed on . All data is from the US National Highway Traffic Safety Administration, which does not endorse this site.