sizinginternationalalgorithmecommercedeveloper-guide

International Clothing Size Conversion: US, EU, UK, and Asian Systems

· 6 min read · Martin Hejda

International clothing size conversion is a problem that looks simple — “US 8 = EU 38, right?” — until you try to implement it reliably. The underlying issue is that numeric size labels in different systems are not consistently anchored to body measurements. Brands apply them differently, grading practices vary, and “EU 38” from a French brand is not guaranteed to fit the same body as “EU 38” from an Italian brand.

The only robust conversion goes through body measurements: convert to millimeters, then back to the target size system. Here’s how.


Why lookup tables fail

A size conversion lookup table (US 8 → EU 38 → UK 12) is technically a table of average correspondences, not a defined mathematical relationship. These averages assume:

  1. That the brands involved grade their sizes to industry norms (many don’t)
  2. That the user’s body proportions match the assumed standard
  3. That the “US 8” they own and the “EU 38” they’re considering were designed with the same proportions in mind

All three assumptions are frequently violated. Size conversion lookup tables have failure rates of 20–40% in practice (the user gets the converted size and it doesn’t fit).


The measurement-anchored approach

Instead of converting between size labels, convert via body measurements:

US Size Label → Body Measurement Range (mm) → Predicted Body Dimension → Target Size Label

The prediction API gives you the user’s body dimensions. The size charts give you what body dimensions each label in the target system is designed for.

# US women's sizes (approximate body measurement ranges, in mm)
# These are general reference values; brand charts vary
US_WOMENS_TOPS = {
    "0":   {"chest": (775, 815), "waist": (585, 625), "hip": (830, 870)},
    "2":   {"chest": (815, 855), "waist": (625, 665), "hip": (870, 910)},
    "4":   {"chest": (855, 895), "waist": (665, 710), "hip": (910, 950)},
    "6":   {"chest": (895, 935), "waist": (710, 750), "hip": (950, 990)},
    "8":   {"chest": (935, 975), "waist": (750, 790), "hip": (990, 1030)},
    "10":  {"chest": (975, 1020), "waist": (790, 835), "hip": (1030, 1075)},
    "12":  {"chest": (1020, 1070), "waist": (835, 885), "hip": (1075, 1125)},
    "14":  {"chest": (1070, 1120), "waist": (885, 935), "hip": (1125, 1175)},
    "16":  {"chest": (1120, 1175), "waist": (935, 990), "hip": (1175, 1230)},
}

EU_WOMENS_TOPS = {
    "32":  {"chest": (775, 815), "waist": (585, 625), "hip": (830, 870)},
    "34":  {"chest": (815, 855), "waist": (625, 665), "hip": (870, 910)},
    "36":  {"chest": (855, 895), "waist": (665, 710), "hip": (910, 950)},
    "38":  {"chest": (895, 935), "waist": (710, 750), "hip": (950, 990)},
    "40":  {"chest": (935, 975), "waist": (750, 790), "hip": (990, 1030)},
    "42":  {"chest": (975, 1020), "waist": (790, 835), "hip": (1030, 1075)},
    "44":  {"chest": (1020, 1070), "waist": (835, 885), "hip": (1075, 1125)},
    "46":  {"chest": (1070, 1120), "waist": (885, 935), "hip": (1125, 1175)},
    "48":  {"chest": (1120, 1175), "waist": (935, 990), "hip": (1175, 1230)},
}

UK_WOMENS_TOPS = {
    "4":   {"chest": (775, 815), "waist": (585, 625), "hip": (830, 870)},
    "6":   {"chest": (815, 855), "waist": (625, 665), "hip": (870, 910)},
    "8":   {"chest": (855, 895), "waist": (665, 710), "hip": (910, 950)},
    "10":  {"chest": (895, 935), "waist": (710, 750), "hip": (950, 990)},
    "12":  {"chest": (935, 975), "waist": (750, 790), "hip": (990, 1030)},
    "14":  {"chest": (975, 1020), "waist": (790, 835), "hip": (1030, 1075)},
    "16":  {"chest": (1020, 1070), "waist": (835, 885), "hip": (1075, 1125)},
    "18":  {"chest": (1070, 1120), "waist": (885, 935), "hip": (1125, 1175)},
    "20":  {"chest": (1120, 1175), "waist": (935, 990), "hip": (1175, 1230)},
}

# Japanese/Korean sizes are typically based on bust measurement in cm, labeled as the cm value
# e.g., JP/KR 80 corresponds to bust 79–81cm
JP_KR_WOMENS_TOPS = {
    "74":  {"chest": (755, 775)},
    "77":  {"chest": (775, 795)},
    "80":  {"chest": (795, 815)},
    "83":  {"chest": (815, 845)},
    "86":  {"chest": (845, 875)},
    "89":  {"chest": (875, 905)},
    "92":  {"chest": (905, 935)},
    "96":  {"chest": (935, 975)},
    "100": {"chest": (975, 1015)},
    "104": {"chest": (1015, 1055)},
}

The conversion algorithm

def find_size_for_measurements(
    chest_mm: int,
    waist_mm: int,
    hip_mm: int,
    size_chart: dict,
    priority: list[str] | None = None
) -> dict:
    """
    Find the best-fitting size label in a size chart for given body measurements.
    
    priority: list of dimension names in order of importance.
    Defaults to ["chest", "waist", "hip"].
    """
    if priority is None:
        priority = ["chest", "waist", "hip"]
    
    measurements = {"chest": chest_mm, "waist": waist_mm, "hip": hip_mm}
    
    best_size = None
    best_score = float("inf")
    
    for size_label, ranges in size_chart.items():
        score = 0.0
        total_weight = 0.0
        
        for i, dim in enumerate(priority):
            if dim not in ranges or dim not in measurements:
                continue
            
            weight = 1.0 / (i + 1)  # First priority has weight 1.0, second 0.5, etc.
            total_weight += weight
            
            low, high = ranges[dim]
            value = measurements[dim]
            
            if low <= value <= high:
                score += 0  # Perfect fit
            elif value < low:
                score += weight * (low - value)
            else:
                score += weight * (value - high)
        
        if total_weight > 0:
            normalized_score = score / total_weight
            if normalized_score < best_score:
                best_score = normalized_score
                best_size = size_label
    
    confidence = "HIGH" if best_score < 10 else "MEDIUM" if best_score < 30 else "LOW"
    
    return {
        "size": best_size,
        "confidence": confidence,
        "score": round(best_score, 1)
    }

def convert_across_systems(
    chest_mm: int,
    waist_mm: int,
    hip_mm: int
) -> dict:
    """
    Convert body measurements to size labels across all supported systems.
    """
    results = {
        "us": find_size_for_measurements(chest_mm, waist_mm, hip_mm, US_WOMENS_TOPS),
        "eu": find_size_for_measurements(chest_mm, waist_mm, hip_mm, EU_WOMENS_TOPS),
        "uk": find_size_for_measurements(chest_mm, waist_mm, hip_mm, UK_WOMENS_TOPS),
        "jp_kr": find_size_for_measurements(chest_mm, waist_mm, hip_mm, JP_KR_WOMENS_TOPS, priority=["chest"]),
    }
    
    return {
        system: {
            "size": r["size"],
            "confidence": r["confidence"],
            "label": f"{system.upper()} {r['size']}"
        }
        for system, r in results.items()
    }

Integration with body measurement prediction

import requests

def international_size_profile(
    gender: str,
    height_cm: float,
    weight_kg: float,
    region: str = "GLOBAL"
) -> dict:
    """
    Generate size recommendations across US, EU, UK, and JP/KR systems
    from height and weight inputs.
    """
    # Get predicted body dimensions
    response = requests.post(
        "https://dimensionspot-bodysize-engine.p.rapidapi.com/v1/predict",
        json={
            "input_data": {
                "input_unit_system": "metric",
                "subject": {"gender": gender, "input_origin_region": region},
                "anchors": {
                    "body_height": int(height_cm * 10),
                    "body_mass": weight_kg
                }
            },
            "output_settings": {
                "calculation": {"target_region": region, "body_build_type": "CIVILIAN"},
                "requested_dimensions": {
                    "specific_dimensions": [
                        "chest_circumference",
                        "waist_circumference_natural",
                        "hip_circumference"
                    ]
                },
                "output_format": {"include_range_95": True, "confidence_score_threshold": 60}
            }
        },
        headers={
            "X-RapidAPI-Key": "YOUR_API_KEY",
            "X-RapidAPI-Host": "dimensionspot-bodysize-engine.p.rapidapi.com"
        }
    )
    
    dims = {
        dim_id: d.get("value")
        for dim_id, d in response.json().get("body_dimensions", {}).items()
    }
    
    chest = dims.get("chest_circumference")
    waist = dims.get("waist_circumference_natural")
    hip = dims.get("hip_circumference")
    
    if not all([chest, waist, hip]):
        return {"error": "Could not predict required dimensions"}
    
    size_chart = convert_across_systems(int(chest), int(waist), int(hip))
    
    return {
        "body_dimensions": {
            "chest_cm": round(chest / 10, 1),
            "waist_cm": round(waist / 10, 1),
            "hip_cm": round(hip / 10, 1)
        },
        "size_recommendations": size_chart,
        "note": "Sizes are recommendations based on standard charts. Brand-specific charts may differ by 1 size."
    }

Men’s size systems

Men’s tops use different conventions. US men’s tops use chest circumference in inches as the size label; dress shirts use neck × sleeve length.

# Men's tops: US sizes are chest circumference in inches (labeled S/M/L/XL or numeric)
US_MENS_LETTER_SIZES = {
    "XS": {"chest": (850, 900)},
    "S":  {"chest": (900, 960)},
    "M":  {"chest": (960, 1020)},
    "L":  {"chest": (1020, 1080)},
    "XL": {"chest": (1080, 1145)},
    "XXL": {"chest": (1145, 1220)},
}

# EU men's sizes (typically numeric, same scale as women's + 10)
EU_MENS_LETTER_SIZES = {
    "44": {"chest": (850, 900)},
    "46": {"chest": (900, 960)},
    "48": {"chest": (960, 1020)},
    "50": {"chest": (1020, 1080)},
    "52": {"chest": (1080, 1145)},
    "54": {"chest": (1145, 1220)},
}

Important caveats

This is a starting point, not a guarantee. Size chart data here represents general industry averages. Actual brand charts deviate — sometimes significantly. For production use, supplement with brand-specific chart data collected from brand websites or their own size APIs.

The waist-chest conflict. Bodies don’t always conform to the assumed proportion. If a user’s chest fits size M but their waist fits size L, there’s no single correct answer. Surface this explicitly: “Your chest measurement fits EU 40, but your waist measurement fits EU 42. We recommend EU 42 for a more comfortable fit; choose EU 40 if you prefer a fitted look.”

Asian market sizes. Japanese and Korean sizing conventions have evolved and now often follow international conventions for foreign brands. Check whether a specific brand uses JP sizing (chest in cm), K-sizing (similar), or has adopted S/M/L internationally. Chinese sizing (CN) similarly varies by brand.


The lookup table approach fails because it treats size labels as a fixed mapping when they’re not. Anchoring conversion to body measurements and treating size charts as the brand-specific mapping from measurements to labels produces dramatically more reliable results — and lets you surface the uncertainty honestly when a user’s body spans two sizes.

Try DimensionsPot

Free tier — 100 requests/month, no credit card required.

Get API on RapidAPI