12/20/2025
How the Law School Admissions Calculator Actually Works: A Technical Deep Dive
If you've used our Law School Admissions Calculator, you've seen probability percentages for 191 law schools. But what's actually happening behind the scenes? This post walks through the algorithm in detail—including the actual formulas and code.
The Core Insight: Acceptance Rate Is the Anchor
Most admissions calculators assume that being at a school's median LSAT and GPA gives you a 50% chance of admission. This is wrong.
Think about it: Yale Law School has roughly a 6% acceptance rate. If you're at their median LSAT (174) and median GPA (3.94), you're competitive—but you're also competing against thousands of other applicants with similar or better stats. The idea that median = 50% doesn't match reality at selective schools.
Our model uses a different approach:
probability = acceptance_rate × multiplier(composite_score)
The acceptance rate is the baseline. Your position relative to the school's percentiles determines whether you're multiplied above or below that rate.
Composite Score: Combining LSAT and GPA
To compare an applicant against a school's distribution, we calculate a composite score using Z-score normalization:
// Calculate normalized scores relative to school's distribution
const lsatRange = lsat75 - lsat25; // Interquartile range
const gpaRange = gpa75 - gpa25;
// Z-scores: how many IQRs away from median
const lsatZ = (lsat - lsat50) / lsatRange;
const gpaZ = (gpa - gpa50) / gpaRange;
// Weighted combination
const composite = (lsatWeight * lsatZ + GPA_WEIGHT * gpaZ) / (totalWeight / 2);
How to interpret composite scores:
| Composite Score | Meaning |
|---|---|
| 0 | At median for both LSAT and GPA |
| +1 | Roughly at 75th percentile overall |
| +2 | Far above 75th percentile |
| -1 | Roughly at 25th percentile overall |
| -2 | Far below 25th percentile |
Tier-Based LSAT Weighting
Here's something not obvious: LSAT weight varies by school tier.
function getLsatWeight(rank: number): number {
if (rank <= 14) return 1.1; // T14: nearly balanced with GPA
if (rank <= 50) return 1.4; // T15-50: slight LSAT advantage
if (rank <= 100) return 1.6; // T51-100: LSAT more dominant
return 1.8; // Below T100: LSAT very dominant
}
Why does this happen?
T14 schools receive so many applicants with 170+ LSATs that they can afford to weight GPA almost equally. These schools are also more "holistic" in their review—they have the luxury of being selective beyond just numbers.
As you move down the rankings, schools increasingly rely on LSAT as the primary differentiator. A standardized test is easier to compare across institutions with varying grade inflation. By the time you reach schools outside the T100, LSAT is weighted nearly 1.8x more than GPA.
Splitter and Reverse-Splitter Handling
One of the trickiest parts of any admissions model is handling splitters (high LSAT, low GPA) and reverse splitters (high GPA, low LSAT).
A naive weighted average would treat a 175 LSAT / 3.3 GPA the same as a 160 LSAT / 3.9 GPA if they happened to average out equally. But that's not how admissions works. Being above median on any stat provides some protection.
We implement this through composite floors:
if (category === 'splitter') {
// LSAT is above median - calculate strength
const lsatAdvantage = Math.max(0, lsatZ);
// Floor: composite can't drop below -0.5 + bonus for LSAT strength
const splitterFloor = -0.5 + lsatAdvantage * 0.5;
composite = Math.max(composite, splitterFloor);
}
else if (category === 'reverse-splitter') {
// GPA is above median - calculate strength
const gpaAdvantage = Math.max(0, gpaZ);
// Floor: composite can't drop below -0.6 + bonus for GPA strength
// Slightly lower floor than splitters (GPA weighted less)
const reverseSplitterFloor = -0.6 + gpaAdvantage * 0.4;
composite = Math.max(composite, reverseSplitterFloor);
}
Key detail: Splitters get a floor of -0.5, while reverse splitters get a floor of -0.6. This reflects reality—LSAT is the more important factor, so being strong on LSAT provides slightly more protection.
The LSAT Floor Penalty: Why Reverse Splitters Face Limits
This is one of the most important features of the model.
Law schools have implicit hard cutoffs below certain LSATs where admission becomes nearly impossible regardless of GPA. A 4.0 GPA cannot compensate for a 148 LSAT at a T50 school with a 25th percentile LSAT of 160.
We model this with a progressive penalty:
function getLsatFloorPenalty(lsat: number, lsat25: number, rank: number): number {
const pointsBelow = lsat25 - lsat;
if (pointsBelow <= 0) return 0; // No penalty if at or above 25th
let penalty = 0;
if (pointsBelow >= 15) {
// Extreme: 15+ points below 25th
penalty = 2.0 + (pointsBelow - 15) * 0.1;
} else if (pointsBelow >= 10) {
// Severe: 10-14 points below 25th
penalty = 1.0 + (pointsBelow - 10) * 0.2;
} else if (pointsBelow >= 5) {
// Moderate: 5-9 points below 25th
penalty = 0.3 + (pointsBelow - 5) * 0.14;
} else {
// Minor: 1-4 points below 25th
penalty = pointsBelow * 0.06;
}
// T14 schools are stricter
if (rank <= 14) {
penalty *= 1.3;
} else if (rank <= 50) {
penalty *= 1.15;
}
return penalty;
}
Penalty ranges by LSAT deficit:
| Points Below 25th | Penalty Range |
|---|---|
| 1-4 points | 0.06 - 0.24 |
| 5-9 points | 0.30 - 0.86 |
| 10-14 points | 1.0 - 1.8 |
| 15+ points | 2.0+ |
This prevents the calculator from showing unrealistic optimism for reverse splitters with LSATs far below a school's range.
GPA Floor Penalty: T14 Only
Elite schools also show implicit GPA cutoffs:
function getGpaFloorPenalty(gpa: number, rank: number): number {
if (rank > 14) return 0; // Only applies to T14
// T6 (HYS + CCN) have higher floors
if (rank <= 6) {
if (gpa < 3.5) return 0.5; // Heavy penalty
if (gpa < 3.7) return 0.25; // Moderate penalty
return 0;
}
// T7-14 slightly more forgiving
if (gpa < 3.3) return 0.4;
if (gpa < 3.5) return 0.2;
return 0;
}
At Harvard, Yale, and Stanford, a GPA below 3.5 is a significant hurdle regardless of LSAT. The T7-14 are slightly more forgiving, but a sub-3.3 GPA still triggers a penalty.
The Below-Both Penalty: Synergistic Disadvantage
When an applicant is below both medians, schools have zero incentive to admit them—they hurt both the LSAT median and the GPA median in the rankings calculation.
We model this with a product-based penalty:
function getBelowBothPenalty(lsatZ: number, gpaZ: number): number {
if (lsatZ >= 0 || gpaZ >= 0) return 0; // Only when BOTH below
const lsatDeficit = Math.abs(lsatZ);
const gpaDeficit = Math.abs(gpaZ);
// Synergistic harm: penalty = product of deficits
const rawPenalty = lsatDeficit * gpaDeficit * 0.4;
return Math.min(rawPenalty, 0.6); // Capped at 0.6
}
Being 0.5 IQR below on both stats results in: 0.5 × 0.5 × 0.4 = 0.1 penalty.
Being 1.0 IQR below on both stats results in: 1.0 × 1.0 × 0.4 = 0.4 penalty.
The product formula captures the compounding disadvantage of weakness in both areas.
The Multiplier Curve
Once we have the composite score, we convert it to a multiplier that scales the base acceptance rate:
function getPercentileMultiplier(composite: number, acceptanceRate: number): number {
// Ultra-selective schools: reduce multiplier (holistic factors dominate)
const selectivityFactor = acceptanceRate < 0.1 ? 0.65 : 1.0;
// Less selective schools: median applicants have better odds
const baseMultiplier =
acceptanceRate > 0.18 ? 1.7 :
acceptanceRate > 0.15 ? 1.4 :
acceptanceRate > 0.12 ? 1.2 : 1.0;
if (composite >= 1) {
// At/above 75th percentile - strong advantage
const multiplier = baseMultiplier + 1.3 + (composite - 1) * 1.5;
return Math.min(5.0, multiplier) * selectivityFactor;
} else if (composite >= 0) {
// Between median and 75th - accelerating curve
return (baseMultiplier + composite * 1.3 + composite * composite * 0.3) * selectivityFactor;
} else if (composite >= -1) {
// Between 25th and median
return Math.max(0.4, baseMultiplier + composite * (baseMultiplier - 0.5));
} else if (composite >= -2) {
// Far below 25th
return Math.max(0.15, 0.5 + (composite + 1) * 0.35);
} else {
// Very far below 25th
return Math.max(0.05, 0.15 + (composite + 2) * 0.05);
}
}
Key behaviors:
- At median (composite = 0): multiplier is roughly 1.0-1.7x depending on school selectivity
- At 75th percentile (composite = +1): multiplier is roughly 2.5-3.5x
- At 25th percentile (composite = -1): multiplier is roughly 0.4-0.5x
- Ultra-selective schools (<10% acceptance): all multipliers reduced by 35%
Probability Caps by Tier
Even with a perfect composite score, we cap probabilities based on school selectivity:
if (effectiveAcceptanceRate < 0.08) {
// Yale, Stanford, Harvard tier
probability = Math.min(probability, 30 + composite * 5);
} else if (effectiveAcceptanceRate < 0.12) {
// Chicago, Columbia tier
probability = Math.min(probability, 45 + composite * 8);
} else if (effectiveAcceptanceRate < 0.25) {
// T7-20 tier
probability = Math.min(probability, 65 + composite * 10);
} else if (effectiveAcceptanceRate < 0.4) {
// T20-40 tier
const tierCap = effectiveAcceptanceRate < 0.32 ? 65 :
effectiveAcceptanceRate < 0.37 ? 72 : 78;
probability = Math.min(probability, tierCap + composite * 5);
} else if (effectiveAcceptanceRate < 0.5) {
// T40-50 tier
probability = Math.min(probability, 82 + composite * 5);
} else {
// T50+
probability = Math.min(probability, 92 + composite * 3);
}
Why these caps?
At Yale (~6% acceptance), even a +2 composite score (far above 75th percentile on everything) maxes out around 35-40%. This reflects reality: perfect numbers don't guarantee admission at ultra-selective schools. Holistic factors—work experience, personal statement, recommendations, diversity—matter more when everyone has near-perfect stats.
Confidence Intervals
We also output a confidence range that reflects uncertainty:
const uncertainty = 3 + Math.abs(composite) * 2;
const rangeMin = Math.max(0.1, probability - uncertainty);
const rangeMax = Math.min(99, probability + uncertainty);
- Near median: ±5% range (more predictable)
- At extremes: ±7-10% range (less predictable)
Extreme composite scores are inherently less certain because there's less data on applicants at the tails of the distribution.
Data Source: ABA 509 Reports
The percentiles (25th/50th/75th for LSAT and GPA) come from ABA Standard 509 Information Reports, which law schools are required to publish annually. These show the actual distribution of enrolled students, not just admitted students.
One important note: the GPA in these reports is the LSAC-calculated UGPA, not your transcript GPA. LSAC recalculates using its own methodology, which can differ from what your school reports.
Putting It All Together
Here's the full flow:
- Normalize LSAT and GPA into Z-scores using each school's IQR
- Weight LSAT based on school tier (1.1x for T14, up to 1.8x for T100+)
- Calculate initial composite score
- Apply floors for splitters and reverse-splitters
- Apply penalties for LSAT floor violations, GPA floor violations (T14), and below-both scenarios
- Convert composite to multiplier based on selectivity
- Calculate probability as acceptance_rate × multiplier
- Cap based on tier-specific maximums
- Bound between 0.1% and 95%
The result is a prediction that respects the fundamental reality: acceptance rate is the anchor, and your percentile position determines how much above or below that baseline you fall.
Takeaway
This model is designed to reflect how law school admissions actually works—not how we might naively assume it works. The key insights:
- Acceptance rate, not median stats, is the baseline
- LSAT matters more at lower-ranked schools
- Splitters have more protection than reverse splitters
- There are hard LSAT floors that GPA can't overcome
- Perfect stats don't guarantee admission at elite schools
Understanding these dynamics can help you build a smarter school list and set realistic expectations.