← Methods repository
CONCEPTFOUNDATIONALPYTHON · R5 citations

ICD-9-CM Legacy Diagnosis and Procedure Codes

The US clinical modification of the WHO's Ninth Revision of the International Classification of Diseases, used for diagnosis (Volumes 1-2) and inpatient procedure coding (Volume 3) in all US HIPAA-covered healthcare transactions from 1979 through 30 September 2015; any RWE study whose data window touches pre-October-2015 encounters must handle ICD-9-CM code lists alongside their ICD-10 successors.

Data Standardcoding-systemdata-standardprimitiveicd-9icd-9-cmclaimsdiagnosis-codesprocedure-codes
On this page
Methods reference only. Use primary source citations and local policy before applying this in a study protocol, regulatory submission, payer dossier, or clinical decision.
In plain language

ICD-9-CM is the older US diagnosis and procedure coding system that was used on all hospital and insurance billing records from the early 1980s until 30 September 2015, when the US switched to ICD-10-CM and ICD-10-PCS. Every diagnosis code in a pre-2015 insurance claim is an ICD-9-CM code — a short number like 250.00 meaning type 2 diabetes — and researchers studying anything that happened before October 2015 must include those older codes in their search lists alongside the newer ICD-10 codes. Leaving out the ICD-9-CM codes is one of the most common silent errors in claims research: the database returns no result, which looks exactly like a patient who did not have the condition, when in fact the analyst simply searched with the wrong code set for that time period.

When to use it
Use ICD-9-CM code lists whenever the study window or lookback period includes records from before 1 October 2015; use ICD-10-CM for post-transition records; use both in dual-era studies.
These are complementary, not alternatives: claims analysis is the study-design framework; ICD-9-CM is the vocabulary layer within it.
Watch out for
Far fewer codes (~14,000 vs ~70,000+ in ICD-10-CM), no laterality, no encounter type, lower specificity for injuries and complications, and no longer maintained.
ICD-9-CM alone is a coding vocabulary, not a study design; it must be embedded within a valid claims-analysis framework (enrollment windows, time-zero, phenotype algorithms) to produce valid research estimates.

ICD-9-CM

(International Classification of Diseases, 9th Revision, Clinical Modification) was the mandatory coding system for all US HIPAA-covered electronic health transactions from the early 1980s through 30 September 2015. On 1 October 2015, the US transitioned to ICD-10-CM for diagnoses and ICD-10-PCS for inpatient procedures.

Because the overwhelming majority of administrative claims databases extend back well before that date, every analyst working with pre-October-2015 data — trend analyses, interrupted time series, long lookback windows, or any cohort that accrued members before the transition — will encounter ICD-9-CM codes in the raw data and must handle them correctly.

Code structure and format

ICD-9-CM has three volumes. Volumes 1-2 are the tabular and alphabetic index of diagnosis codes: a 3-to-5-character string whose core is a 3-digit numeric chapter (001-999) with up to two additional decimal digits of specificity. In raw claims files the decimal point is almost never stored: the code "25000" in the `DGNS_CD` field means 250.00 (diabetes mellitus, type 2, not stated as uncontrolled), not the integer 25000. Analysts must normalize between the flat and the decimal representation before any matching.

Volumes 1-2 also contain two supplementary classifications that use letter prefixes:

V codes

(V01-V91), which capture factors influencing health status and contact with health services (e.g., V58.x = aftercare following surgery; V45.x = postsurgical state), and E codes (E000-E999), which classify the external cause of injury or poisoning. E codes require their own format validation because their numeric range overlaps with the 3-digit diagnosis chapters.

In total, ICD-9-CM contains approximately 14,000 diagnosis codes — far fewer, and far less specific, than ICD-10-CM's 70,000+: there is no laterality (no left vs right), no encounter type (no "initial encounter" vs "subsequent encounter" vs "sequela"), and no seventh-character extension.

This specificity gap is not just aesthetic; it is an accuracy limitation that validated phenotype algorithms must account for.

Volume 3 procedure codes

ICD-9-CM Volume 3 is the inpatient procedure coding system used exclusively on UB-04 institutional claims (i.e., facility/hospital claims). Codes are 2 to 4 digits (2-digit category plus up to 2 decimal digits), stored without the decimal in claims data: "8154" = 81.54 (total knee replacement).

Volume 3 covered procedures across all body systems and was the only procedure coding system on inpatient institutional claims until it was replaced by ICD-10-PCS on 1 October 2015. Volume 3 codes do not appear on professional (physician) claims; those used CPT/HCPCS Level II codes throughout.

When building procedure phenotypes from inpatient claims before October 2015, analysts must include Volume 3 codes alongside CPT codes sourced from line-item professional claims.

Why a current RWE analyst still needs ICD-9-CM

The retirement date was 1 October 2015, but the data do not disappear. Any study with one of the following design characteristics requires era-aware code-list management:

  1. Pre/post transition data: A cohort accruing members between 2012 and 2018 will have index dates on both sides of the October 2015 cutoff. Baseline covariates for a 2014 index date require ICD-9-CM code lists; the outcome window may span into ICD-10-CM era records. Applying only ICD-10-CM code lists will silently drop all pre-October 2015 events, deflating baseline comorbidity counts and creating apparent outcome gaps.
  2. Trend and interrupted time series analyses: ITS models that include pre-2015 data show an artifactual discontinuity at the transition: the apparent rate of many conditions changes not because the underlying incidence changed but because ICD-9-CM and ICD-10-CM have different levels of specificity, and because clinicians and coders adjusted their behavior during the transition. This is a form of measurement artifact that must be modeled (e.g., as a step indicator at the transition) or the ITS estimator will absorb the coding change into the intervention effect estimate.
  3. Lookback windows for prevalent conditions: A study with a 2016 or 2017 index date and a 12-month baseline lookback window will have baseline diagnosis history entirely or partly in the ICD-9-CM era. Omitting the ICD-9-CM codes for diabetes, hypertension, heart failure, or cancer from the lookback query will undercount comorbidity burden.
  4. Algorithm transfer: A validated ICD-9-CM phenotype algorithm (e.g., the Quan 2005 comorbidity adaptation of the Elixhauser index) does not automatically transfer to ICD-10-CM. The General Equivalence Mappings (GEMs) produced by CMS approximate one-to-many cross-walks between the two systems, but GEMs are not a substitute for re-validation because (a) the code structures differ fundamentally, (b) some ICD-9-CM codes have no direct ICD-10-CM equivalent, and (c) clinical coding practices shifted at transition. Algorithm developers must re-derive and re-validate phenotype definitions separately for each era.

Pros, cons, and trade-offs — specific and comparative

  • vs ICD-10-CM/PCS (post-October 2015): ICD-10-CM offers 5× more diagnosis codes, laterality, encounter type, and higher granularity. ICD-10-PCS is far more specific than Volume 3 for inpatient procedures. The accuracy advantage of ICD-10-CM is real but heterogeneous — some conditions (e.g., hypertension, type 2 diabetes) were already well-specified in ICD-9-CM; others (e.g., fractures, injuries, complications) gain enormously from laterality and encounter type. Practical trade-off: ICD-9-CM data cover a much longer historical period and are therefore irreplaceable for long-term trend studies, natural history research, and analyses requiring large person-time before a new drug's approval.
  • vs CPT/HCPCS for procedures: CPT codes cover both inpatient and outpatient procedures on professional claims and were unchanged by the 2015 transition. For outpatient procedures in the pre-2015 era, an analyst uses CPT from professional claims exactly as in the post-2015 era. ICD-9-CM Volume 3 adds inpatient procedural coding on institutional claims only; after 2015 that role is filled by ICD-10-PCS. Practical trade-off: A procedure code list for inpatient procedures in a multi-era study needs both Volume 3 codes for pre-October 2015 and ICD-10-PCS codes for post-October 2015; CPT on professional claims is available and consistent across both eras.
  • vs OMOP SNOMED standard concepts: OMOP maps ICD-9-CM source codes to SNOMED standard concepts so that queries can be written against the standard vocabulary rather than era-specific code lists. This is the correct approach when the data infrastructure supports it, but it depends on the OMOP mapping tables being current and complete; for rare codes or V/E code combinations, manual inspection of the source-to-standard mapping is warranted.

When NOT to use — and when ICD-9-CM is actively misleading or dangerous

  • Post-October 2015 data only: For a study whose entire enrollment and follow-up falls after 1 October 2015, ICD-9-CM codes will never appear in raw claims. Building ICD-9-CM code lists is wasted effort that introduces confusion and the risk of accidentally mixing code systems.
  • As a substitute for re-validation: Mapping ICD-9-CM codes via GEMs and treating the result as a validated ICD-10-CM phenotype is not re-validation. GEMs are approximate and asymmetric (the forward and backward maps are not inverses of each other). Use GEMs as a starting point, then empirically evaluate the derived code list in the data of interest.
  • When applied without era-conditioning: A query that applies an ICD-9-CM code list to a post-2015 claim or an ICD-10-CM code list to a pre-2015 claim will return zero matching rows for that era — which looks identical to a true absence of the condition. The failure is silent. Always condition code lists on the claim's service date relative to the transition cutoff.
  • For external-cause coding (E codes) without recognizing their format: E codes (E000-E999) overlap numerically with the 3-digit chapter codes if the "E" prefix is dropped during extraction. A process that strips the first character from all codes will misclassify E codes. Always validate that E and V codes are preserved with their prefixes in the extract.

Data-source operational depth

  • Medicare FFS (MedPAR/institutional + carrier/professional): ICD-9-CM diagnosis codes appear in `DGNS_CD_1` through `DGNS_CD_25` on both inpatient (MedPAR) and outpatient (carrier/outpatient) claims up to the 30 September 2015 service date. Volume 3 procedure codes appear in `ICD_PRCDR_CD_1` through `ICD_PRCDR_CD_6` on MedPAR (inpatient) only. Admissions that straddle the transition date (admitted pre-October 2015, discharged post-October 2015) may have been coded in either system depending on the fiscal year rules applied by CMS; treat discharge date as the coding-system determinant. Principal diagnosis is in position 1; secondary diagnoses in positions 2+. The "present on admission" (POA) indicator distinguishes pre-admission from hospital-acquired diagnoses on inpatient records and is important for outcome ascertainment and patient safety studies.
  • Commercial claims (MarketScan, Optum): Same ICD-9-CM field structure; the transition date applies uniformly. Some vendors retain the decimal point in the stored code; verify the format contract for the specific dataset before writing matching logic.
  • EHR problem lists and encounter diagnoses: EHR records may carry ICD-9-CM codes on historical problem list entries even after the transition, because problems entered before October 2015 were not necessarily back-coded to ICD-10-CM. Cross-sectional extracts of EHR diagnosis data therefore require era-aware handling: use service/encounter date, not extract date, to determine the applicable coding system.
  • Registry linkage: Disease registries that link to claims (SEER-Medicare, tumor registries) retain the ICD-9-CM codes from the linked claims records. SEER tumor site coding uses ICD-O-3 (not ICD-9-CM), but cause-of-death and comorbidity data in the linked claims file are subject to the same era logic.

Relationship to GEMs and crosswalk mapping

CMS published the General Equivalence Mappings (GEMs) to facilitate the transition. The forward GEM maps each ICD-9-CM code to one or more ICD-10-CM codes; the backward GEM maps ICD-10-CM codes back to ICD-9-CM. Most ICD-9-CM codes map to multiple ICD-10-CM codes (one-to-many), and many ICD-10-CM codes map to multiple ICD-9-CM codes (many-to-one in the backward direction).

GEMs are published on the CMS website and are the standard starting point for cross-era code translation, but they are approximate and should be reviewed clinically for each phenotype of interest.

Decision diagram

flowchart TD
  A[Claims record service_date] --> B{service_date < 2015-10-01?}
  B -->|Yes: ICD-9-CM era| C[Apply ICD-9-CM diagnosis codes\nVolumes 1-2: numeric 001-999,\nV01-V91, E000-E999]
  B -->|Yes: inpatient only| D[Apply Volume 3 procedure codes\n2-4 digit, e.g. 81.54 TKR]
  B -->|No: ICD-10-CM era| E[Apply ICD-10-CM diagnosis codes\n70000+ codes with laterality\nand encounter type]
  B -->|No: inpatient only| F[Apply ICD-10-PCS procedure codes\n7-character alphanumeric]
  C --> G[Flat-to-decimal normalization\n25000 -> 250.00\nV5811 -> V58.11\nE8100 -> E810.0]
  E --> H[Match against ICD-10-CM code list]
  G --> I[Match against ICD-9-CM code list]
  I --> J[Combine era-conditioned results]
  H --> J
  D --> K[Volume 3 code list]
  K --> J
  F --> L[ICD-10-PCS code list]
  L --> J
  J --> M[Complete multi-era phenotype]
Era-aware code dispatch for a study spanning the 1 October 2015 ICD-9-CM-to-ICD-10-CM transition. Applying only one era's code list silently drops 45-55% of events in a 2014-2017 study window.

Worked example

Scenario

A pharmacoepidemiology team is studying hospitalization rates for diabetes-related complications in a commercial claims database from January 2014 through December 2017. This window spans the ICD-9-CM-to-ICD-10-CM transition (1 October 2015). The team wants to count all inpatient hospitalizations with a primary diagnosis of type 2 diabetes with uncontrolled hyperglycemia. Before the transition this is ICD-9-CM 250.02 (diabetes mellitus, type 2, uncontrolled); after the transition the nearest ICD-10-CM equivalent is E11.65 (type 2 diabetes mellitus with hyperglycemia). If the team queries only ICD-10-CM E11.65, they will miss all events from 2014 through September 2015; if they query only ICD-9-CM 250.02, they will miss all events from October 2015 through December 2017.

Dataset

Monthly inpatient hospitalization counts from a claims database, 2014-2017. "Pre" = service date before 1 Oct 2015 (ICD-9-CM era); "Post" = service date on or after 1 Oct 2015 (ICD-10-CM era). Counts shown for three query strategies.

YearEraICD-9-CM only (250.02)ICD-10-CM only (E11.65)Dual-era (correct)
2014Pre1200120
2015 Jan-SepPre90090
2015 Oct-DecPost02828
2016Post0112112
2017Post0115115
FIG. 1 — DESIGN TIMELINE
ICD-9-CM vs ICD-10-CM coding eras in a 2014-2017 study window
ICD-9-CM vs ICD-10-CM coding eras in a 2014-2017 study window

Steps

1The study window covers 48 months (Jan 2014 - Dec 2017). The ICD-9-CM era covers 21 months (Jan 2014 - Sep 2015); the ICD-10-CM era covers 27 months (Oct 2015 - Dec 2017).
2ICD-9-CM-only query total = 120 + 90 + 0 + 0 + 0 = 210 hospitalizations, all from the pre-transition era; zero events are captured after Oct 2015.
3ICD-10-CM-only query total = 0 + 0 + 28 + 112 + 115 = 255 hospitalizations, all from the post-transition era; zero events are captured before Oct 2015.
4Dual-era (correct) query total = 120 + 90 + 28 + 112 + 115 = 465 hospitalizations, because the correct ICD-9-CM code is applied to pre-transition claims and the correct ICD-10-CM code is applied to post-transition claims.
5The ICD-9-CM-only strategy captures 210 / 465 = 0.45 of all true events — missing 255 events (all of 2016-2017 and Q4 2015).
6The ICD-10-CM-only strategy captures 255 / 465 = 0.55 of all true events — missing 210 events (all of 2014 through September 2015).
7Neither single-era query is usable for a rate trend. The ICD-9-CM-only rate would appear to drop to zero in October 2015 — a step artifact at the coding transition, not a true clinical change.

Result

Correct dual-era total = 210 + 255 = 465 hospitalizations over 48 months. ICD-9-CM-only captures 210 / 465 = 0.45 (45%) of true events. ICD-10-CM-only captures 255 / 465 = 0.55 (55%) of true events. Using a single-era code list in a multi-era study introduces a measurement artifact equivalent to ignoring 45-55% of the outcome events depending on which era is omitted.

Trade-offs

Pros of this
ICD-9-CM is the only coding system present in pre-October 2015 US claims data; no alternative exists for historical research. It covers a much longer calendar window (1979-2015) than any ICD-10-CM data can provide.
Pros of this
Understanding ICD-9-CM format and structure is a prerequisite for correctly operationalizing any ICD-9-CM-era claims phenotype.

Runnable example

Era-aware ICD-9-CM / ICD-10-CM code dispatch: flat-to-decimal normalization, regex format validators for diagnosis codes, V codes, E codes, and Volume 3 procedure codes, and an era-conditioned lookup function that applies the correct code list based on a claim's service date relative to the 1 October 2015 transition.

import re
from datetime import date

# ---------------------------------------------------------------------------
# ICD-9-CM transition cutoff (US HIPAA enforcement date)
# ---------------------------------------------------------------------------
ICD9_CUTOFF = date(2015, 10, 1)   # claims with service_date < this date use ICD-9-CM

# ---------------------------------------------------------------------------
# Regex validators — all operate on the DECIMAL form (with dot)
# ---------------------------------------------------------------------------
# Numeric diagnosis codes: 3-digit chapter, optional 1-2 decimal digits
_DX_NUMERIC = re.compile(r"^\d{3}(\.\d{1,2})?$")
# V codes: V followed by 2 digits, optional 1-2 decimal digits
_DX_V       = re.compile(r"^V\d{2}(\.\d{1,2})?$", re.IGNORECASE)
# E codes: E followed by 3 digits, optional 1 decimal digit
_DX_E       = re.compile(r"^E\d{3}(\.\d)?$", re.IGNORECASE)
# Volume 3 procedure codes: 2 digits, optional 1-2 decimal digits
_PX_VOL3    = re.compile(r"^\d{2}(\.\d{1,2})?$")


def flat_to_decimal(code: str) -> str:
    """Convert a flat ICD-9-CM code stored without decimal to decimal form.

    Rules:
      - V codes: insert decimal after V + 2 digits (e.g., V5811 -> V58.11)
      - E codes: insert decimal after E + 3 digits (e.g., E8100 -> E810.0)
      - Volume 3 procedure (2 leading digits, <=4 total): insert decimal after 2nd digit
        (e.g., 8154 -> 81.54)
      - Numeric diagnosis: insert decimal after 3rd digit (e.g., 25000 -> 250.00)
    """
    c = code.strip().upper()
    if not c:
        return c

    if c.startswith("V"):
        digits = c[1:]
        if len(digits) > 2:
            return "V" + digits[:2] + "." + digits[2:]
        return c   # bare V + 2 digits, no further specificity

    if c.startswith("E"):
        digits = c[1:]
        if len(digits) > 3:
            return "E" + digits[:3] + "." + digits[3:]
        return c   # bare E + 3 digits (e.g., E812 is valid 3-char E code)

    # Numeric — could be Volume 3 (2-digit chapter) or diagnosis (3-digit chapter)
    # Volume 3: exactly 2 leading digits before specificity; max 4 digits total
    # Diagnosis: 3 leading digits; max 5 digits total
    # Distinguish by first character (Volume 3 chapters 01-99, diagnoses 001-999)
    # A reliable heuristic: raw flat code length determines which decimal position to use.
    if len(c) <= 4 and c[:2].isdigit() and int(c[:2]) <= 99:
        # Ambiguous — caller should specify context; default to Volume 3 if <=4 chars
        # and likely procedure context (passed via flag); here we default to diagnosis
        pass
    if len(c) >= 3:
        return c[:3] + ("." + c[3:] if c[3:] else "")
    return c


def decimal_to_flat(code: str) -> str:
    """Remove the decimal point: '250.00' -> '25000', 'V58.11' -> 'V5811'."""
    return code.strip().replace(".", "").upper()


def is_valid_icd9_dx(code: str) -> bool:
    """Return True if `code` (decimal form) is a syntactically valid ICD-9-CM diagnosis code."""
    c = code.strip().upper()
    return bool(_DX_NUMERIC.match(c) or _DX_V.match(c) or _DX_E.match(c))


def is_valid_icd9_px(code: str) -> bool:
    """Return True if `code` (decimal form) is a syntactically valid Volume 3 procedure code."""
    return bool(_PX_VOL3.match(code.strip()))


# ---------------------------------------------------------------------------
# Era-aware code dispatch
# ---------------------------------------------------------------------------
def era_code_filter(
    service_date: date,
    icd9_codes: set[str],
    icd10_codes: set[str],
) -> set[str]:
    """Return the code set appropriate for the service_date.

    Args:
        service_date: Date of the claim's service.
        icd9_codes:   Set of ICD-9-CM codes (flat form, no decimal).
        icd10_codes:  Set of ICD-10-CM/PCS codes (stored form in the database).

    Returns:
        The era-appropriate code set. Applying the returned set to claims from
        the same era guarantees the correct vocabulary is used.

    Pitfalls: never pass both sets to a single SQL IN() clause that spans eras.
    """
    if service_date < ICD9_CUTOFF:
        return icd9_codes
    return icd10_codes


# ---------------------------------------------------------------------------
# Example: diabetes type 2 hyperglycemia, dual-era
# ---------------------------------------------------------------------------
T2DM_HYPERGLYCEMIA_ICD9  = {"25002"}  # 250.02 flat (uncontrolled type 2 diabetes)
T2DM_HYPERGLYCEMIA_ICD10 = {"E1165"}  # E11.65 flat (type 2 DM with hyperglycemia)

def count_events(claims: list[dict]) -> int:
    """Count hospitalizations matching the era-correct diabetes hyperglycemia code."""
    n = 0
    for claim in claims:
        svc_date = date.fromisoformat(claim["service_date"])
        valid_codes = era_code_filter(svc_date, T2DM_HYPERGLYCEMIA_ICD9, T2DM_HYPERGLYCEMIA_ICD10)
        if decimal_to_flat(claim["principal_dx"]) in valid_codes:
            n += 1
    return n

# Smoke test
if __name__ == "__main__":
    test_claims = [
        {"service_date": "2014-06-01", "principal_dx": "250.02"},  # ICD-9, decimal form
        {"service_date": "2015-09-30", "principal_dx": "25002"},   # ICD-9, flat form
        {"service_date": "2015-10-01", "principal_dx": "E1165"},   # ICD-10 (transition day)
        {"service_date": "2016-03-15", "principal_dx": "E11.65"},  # ICD-10, decimal form
    ]
    assert count_events(test_claims) == 4, "Expected 4 matching events"
    assert flat_to_decimal("25000") == "250.00"
    assert flat_to_decimal("V5811") == "V58.11"
    assert flat_to_decimal("E8100") == "E810.0"
    assert flat_to_decimal("8154")  == "81.54"
    assert is_valid_icd9_dx("250.00")
    assert is_valid_icd9_dx("V58.11")
    assert is_valid_icd9_dx("E810.0")
    assert not is_valid_icd9_dx("E11.65")   # that's ICD-10
    print("All assertions passed.")

Citations

FOUNDATIONAL / METHODS
  1. [1]O'Malley KJ, Cook KF, Price MD, Wildes KR, Hurdle JF, Ashton CM. Measuring diagnoses: ICD code accuracy. Health Services Research. 2005;40(5 Pt 2):1620-1639.
  2. [2]Quan H, Sundararajan V, Halfon P, Fong A, Burnand B, Luthi JC, Saunders LD, Beck CA, Feasby TE, Ghali WA. Coding algorithms for defining comorbidities in ICD-9-CM and ICD-10 administrative data. Medical Care. 2005;43(11):1130-1139.
REPORTING & GUIDANCE
  1. [3]Elixhauser A, Steiner C, Harris DR, Coffey RM. Comorbidity measures for use with administrative data. Medical Care. 1998;36(1):8-27.
  2. [4]Centers for Medicare and Medicaid Services. ICD-9-CM Diagnosis and Procedure Codes: Abbreviated and Full Code Titles. CMS.gov. Accessed 2026-06-12.
  3. [5]Centers for Medicare and Medicaid Services. ICD-10-CM / ICD-10-PCS GEM Archive (General Equivalence Mappings). CMS.gov. Accessed 2026-06-12.