Fake Black Friday Deals: Check the Real Discount
Are Black Friday deals real? Compare the sale price with your own 30-day median and 90-day low, not the was price. Research findings and a Python checker.
Published · Updated · By Omar Eldeeb
To spot a fake Black Friday deal, ignore the "was" price and compare the sale price with what the item actually cost before the sale: the median of your own daily price checks over the last 30 days, and the lowest price in the last 90. If the sale price isn't below both, the discount is mostly marketing. That needs price history, so start recording now.
This guide shows what research has found about Black Friday "was" prices, defines the three numbers to compare, and gives a Python script that turns daily price snapshots into a verdict per product.
What the research found
The most detailed public studies come from Which?, the UK consumer group. Its press release of 26 November 2024 analysed 227 deals on popular products from eight big home and tech retailers during the Black Friday fortnight of 2023 (20 November to 1 December), using price histories from an independent pricing provider. It found:
- 92% of the deals were the same price or cheaper at other times of the year.
- For six in 10 deals, the higher "was" price had been in place less than half of the time.
- 14 "deals" used a higher price that hadn't been charged once at that retailer in the previous 12 months.
(Which?: Nine in 10 Black Friday deals cheaper or same price at other times of the year.)
These are UK retailers and UK rules, and the study measured one year. I haven't found a US study with the same method, so treat it as a warning about how "was" prices work rather than a US statistic.
What a "was" price actually is
There's no single definition, so check whose number you're looking at:
| Source | What the higher price means |
|---|---|
| Amazon "Typical" / "Was" price | "The median price paid on Amazon in the last 90 days", excluding prices paid during limited-time deals (Amazon) |
| Amazon "List Price" | "A validated price customers can find on Amazon and at other retailers", also known as the MSRP (same page) |
| Other retailers | Their own reference price, often a list price or MSRP. Read the small print on the product page |
The actor's originalPrice |
The was-price the page shows, kept only when it's higher than price. It records the retailer's claim, not a verified history |
In the US, the FTC's Guides Against Deceptive Pricing (16 CFR 233.1) say a former price used in a comparison should be the "actual, bona fide price at which the article was offered to the public on a regular basis" for "a reasonably substantial period of time" (16 CFR 233.1). That's background, not legal advice. As a shopper, the useful point is that you can run the same test yourself.
A real example
On 2026-10-18 the actor's Google Shopping mode returned a Best Buy offer for the Sony WH-1000XM5 at $254.99, with a was-price of $398 and a "35% OFF" badge. The same row carried Google's own price insight for that product: "Typically $199–$300". In the same run, Sony's own listing showed a was-price of $400. On 2026-10-20 the same Best Buy listing was $269.99, still against $398, now "32% OFF".
The $398 is close to the manufacturer's list price, not to anything the product typically sold for that month. That's the gap this guide measures. The Google Shopping comparison guide shows how to get those rows.
Three numbers to compare
For each product, on the sale day:
| Number | How to compute it | What it tells you |
|---|---|---|
| Claimed discount | (was price − sale price) ÷ was price | What the retailer says |
| Real discount | (30-day median − sale price) ÷ 30-day median | How much below its normal recent price |
| 90-day low | The lowest daily price in the 90 days before | Whether it's been this cheap, or cheaper, recently |
Use the median rather than the mean, because one short flash sale would drag a mean down. Use the 90-day low as a second check, because a median can be gamed: if a price is raised three weeks before Black Friday, the 30-day median rises with it, and a "discount" back to the old price looks real. Which?'s finding that "was" prices were often in place less than half the time is this pattern.
Collect the history
You need one price per product per day from at least 30 days before the sale. For Black Friday on November 27, 2026, that means starting by October 28. The Black Friday price tracker guide sets up a daily scheduled run and exports every run to price_history.csv with these columns:
scrapedAt,site,url,name,price,originalPrice,currency,inStock,seller,changeType,previousPriceIf you ran with onlyChanges, the file only has rows for days when something changed. That's fine: a missing day means "same as last time", and the script carries the last price forward.
The checker
import csv
import sys
from collections import defaultdict
from datetime import date, datetime, timedelta
from statistics import median
HISTORY, SALE_DAY = sys.argv[1], date.fromisoformat(sys.argv[2])
def num(x):
return float(x) if x not in ("", None) else None
# Group snapshot rows by product URL, oldest first.
rows = defaultdict(list)
for r in csv.DictReader(open(HISTORY, encoding="utf-8")):
r["day"] = datetime.fromisoformat(r["scrapedAt"].replace("Z", "+00:00")).date()
rows[r["url"]].append(r)
def daily_prices(snaps, start, end):
"""One price per day. With onlyChanges, a missing day means 'unchanged',
so carry the last known price forward."""
snaps = sorted(snaps, key=lambda r: r["scrapedAt"])
out, last, i, d = {}, None, 0, start
while d <= end:
while i < len(snaps) and snaps[i]["day"] <= d:
last = num(snaps[i]["price"]) or last
i += 1
if last is not None:
out[d] = last
d += timedelta(days=1)
return out
print(f"{'product':<16}{'sale':>8}{'was':>8}{'claimed':>9}{'30d med':>9}{'90d min':>9}{'real':>7} verdict")
for url, snaps in rows.items():
first = min(r["day"] for r in snaps)
series = daily_prices(snaps, first, SALE_DAY)
sale = series.get(SALE_DAY)
if sale is None:
continue
before = {d: p for d, p in series.items() if d < SALE_DAY}
last30 = [p for d, p in before.items() if d >= SALE_DAY - timedelta(days=30)]
last90 = [p for d, p in before.items() if d >= SALE_DAY - timedelta(days=90)]
if len(last30) < 21:
print(f"{snaps[0]['name'][:15]:<16} not enough history ({len(last30)} days)")
continue
was = next((num(r["originalPrice"]) for r in reversed(snaps) if r["day"] <= SALE_DAY and num(r["originalPrice"])), None)
claimed = (was - sale) / was * 100 if was else 0.0
med30, min90 = median(last30), min(last90)
real = (med30 - sale) / med30 * 100
if sale > min90:
verdict = f"not the low: was {min90:.2f} within 90 days"
elif claimed - real > 15:
verdict = "inflated 'was' price"
elif real >= 10:
verdict = "real discount"
else:
verdict = "normal price"
print(f"{snaps[0]['name'][:15]:<16}{sale:>8.2f}{(was or 0):>8.2f}{claimed:>8.0f}%{med30:>9.2f}{min90:>9.2f}{real:>6.0f}% {verdict}")Run it as python real_discount.py price_history.csv 2026-11-27. To show all three outcomes, here it is on a small made-up history (the products and prices are invented, the file format is the real export format):
scrapedAt,site,url,name,price,originalPrice,currency,inStock,seller,changeType,previousPrice
2026-09-25T11:00:00Z,amazon-us,https://example.com/headphones-a,Headphones A,248.00,349.99,USD,True,,new,
2026-11-09T11:00:00Z,amazon-us,https://example.com/headphones-a,Headphones A,299.99,349.99,USD,True,,price-up,248.00
2026-11-27T11:00:00Z,amazon-us,https://example.com/headphones-a,Headphones A,249.99,349.99,USD,True,,price-down,299.99
2026-09-25T11:00:00Z,walmart-us,https://example.com/blender-b,Blender B,89.99,,USD,True,,new,
2026-11-27T11:00:00Z,walmart-us,https://example.com/blender-b,Blender B,59.99,89.99,USD,True,,price-down,89.99
2026-09-25T11:00:00Z,target,https://example.com/lego-c,Building Set C,129.00,199.00,USD,True,,new,
2026-10-20T11:00:00Z,target,https://example.com/lego-c,Building Set C,124.00,199.00,USD,True,,price-down,129.00
2026-11-27T11:00:00Z,target,https://example.com/lego-c,Building Set C,119.00,199.00,USD,True,,price-down,124.00product sale was claimed 30d med 90d min real verdict
Headphones A 249.99 349.99 29% 299.99 248.00 17% not the low: was 248.00 within 90 days
Blender B 59.99 89.99 33% 89.99 89.99 33% real discount
Building Set C 119.00 199.00 40% 124.00 124.00 4% inflated 'was' price- Headphones A is the pre-sale hike. Against its 30-day median it looks 17% off, but it sold for $248 in October, less than the "deal". Only the 90-day low catches this.
- Blender B is a real deal: no was-price before the sale, and the sale price is a third below every recent price.
- Building Set C claims 40% off a $199 was-price it never sold at during the history. The real discount is 4%.
I also ran it on our real three-day test history with today's date as the sale day. It printed not enough history (2 days) for every product, which is what you'll see if you start tracking a week before Black Friday.
The thresholds (a 15-point gap between claimed and real, 10% for a real discount, 21 days of data out of 30) are starting points. Adjust them for your category.
Where the numbers mislead
- Coupons and checkout discounts.
priceis the price on the page. Clip-on coupons, Subscribe & Save and cart discounts may not be in it. Google Shopping's Sellers mode has a separatecouponfield for offers that show one. - A different seller. On Amazon, the price can move because the featured offer moved to another seller. Keep the
sellercolumn and compare like with like. - Pack sizes and variants. If a listing switches from a 2-pack to a 3-pack, compare
unitPrice. If you care about one colour or size, track its entry invariants[]rather than the default price. - Out-of-stock days. A price shown while
inStockis false isn't a price anyone paid. Filter those rows out before the median if your category goes out of stock often. - Member-only prices. The scraper sees what a signed-out visitor sees. A Prime-exclusive or Walmart+ price may be lower than the one recorded.
When you don't have history
If you only start looking on the day, two quick checks help. On Amazon, the "Was" price is, by Amazon's definition, already a 90-day median of what customers paid, so it's a more meaningful anchor than a bare List Price. On Google Shopping, the product page can show a typical price range, and the actor's Products and Sellers modes return it as priceInsight. Neither replaces your own history: they're someone else's summary of it.
For tracking Amazon prices over long periods, see Amazon price history without Keepa. For when prices usually fall across the season, see the Thanksgiving to Cyber Monday price timeline, and for the full year of sales, the holiday sales calendar. The free e-commerce price tracker page builds the tracking input.
Disclosure: I built the actor and this site. The checker works on any CSV with these columns, whatever collected it.
Sources: Which?, 26 Nov 2024, Amazon: How Amazon offers genuine deals to customers, 16 CFR 233.1 (Cornell LII), E-commerce Product Scraper Pro.
Quick answers
Are Black Friday deals real?
Some are, many aren't as big as they look. Which? (UK) checked 227 Black Friday deals from 2023 and found 92% were the same price or cheaper at other times of the year. The only reliable test is the item's own recent price history.
What does the was price on Amazon mean?
Amazon says its "Typical" or "Was" price is the median price paid on Amazon in the last 90 days, excluding prices paid during limited-time deals. Its List Price is a validated price, also known as the MSRP. Other retailers use their own definitions.
How many days of price history do I need to judge a Black Friday deal?
At least 30 days for a median, and 90 days to catch a price that was raised a few weeks before the sale. For Black Friday on November 27, 2026, a 30-day baseline has to start by October 28.
Is it illegal to show a fake was price in the US?
This isn't legal advice. The FTC's Guides Against Deceptive Pricing (16 CFR 233.1) say a former price used in a comparison should be the actual, bona fide price at which the item was offered to the public on a regular basis for a reasonably substantial period of time. Ask a lawyer about a specific case.