Chasing Show HN Oblivion
Chasing Show HN Oblivion
We've pretty bad at posting on HN. But can we figure out how to do worse? The goal of a Show HN post is usually to get visibility and feedback. We can use score as a proxy for that.
So what's the worst way you can post?
You could post on a Thursday about your project in Swift. You could post it on Christmas Day, when apparently no one has.
But weβre getting ahead of ourselves - let's look at the data
A low-effort Guide to the Worst Possible Time, Day, and Title to Post on HN.
Because if your fate is ruled by chance you might as well try to control your destiny - in either direction.
Tips
There is no attempt to provide causation, only correlation. There's also no attempts at statistical rigor here. You can do better by importing this dataset (all queries provided as examples) in Trilogy Studio and hack around yourself.
Our data
We're going to primarily use the BigQuery Hacker News dataset, which contains all posts since 2006.
We will pull in Bigquery github data, though this data is stale. If you have a better source, please reach out! (issue on trilogy-public-data would be great).
For purposes here, we'll typically filter to the below. You can view each query for details.
type = 'story'
- Titles starting with
Show HN
- 2024β2025 only
- Excluded deleted/dead items
Where should you host your app?
Definetely not GitLab. People upvote that all day.
Host Query
import post;
where
type = 'story'
and is_show_hn
and not deleted and not dead
and create_time.year>2024
select
domain,
avg(score) as avg_domain_score,
count(id) as sample_size
order by
sample_size desc
limit 25;
π Best (Worst) Day to Post - Any Day That's Not Fool's Day?
Findings:
- Presidents. People love them. Great time to post to get ignored.
- Fools: rush in and upvote. Is your post a joke? Skip this.
- Not trying to post on holidays? Thursdays are your jam. You'll only get upvotes from the dedicated crowd.
Which Day Query
import post;
import std.date;
where type = 'story' and is_show_hn and create_time.year>2024 and not deleted and not dead
select
CASE
# New Year's Day
WHEN create_time.month = 1 and create_time.day = 1 THEN 'New Years Day'
# Valentine's Day
WHEN create_time.month = 2 and create_time.day = 14 THEN 'Valentines Day'
#Presidents Day (3rd Monday in February)
WHEN create_time.month = 2 and create_time.day_of_week_name = 'Monday'
and create_time.day >= 15 and create_time.day <= 21 THEN 'Presidents Day'
# St. Patrick's Day
WHEN create_time.month = 3 and create_time.day = 17 THEN 'St. Patricks Day'
#April Fools' Day
WHEN create_time.month = 4 and create_time.day = 1 THEN 'April Fools Day'
#Memorial Day (last Monday in May)
WHEN create_time.month = 5 and create_time.day_of_week_name = 'Monday'
and create_time.day >= 25 THEN 'Memorial Day'
# Independence Day
WHEN create_time.month = 7 and create_time.day = 4 THEN 'Independence Day'
# Labor Day (1st Monday in September)
WHEN create_time.month = 9 and create_time.day_of_week_name = 'Monday'
and create_time.day <= 7 THEN 'Labor Day'
# Halloween
WHEN create_time.month = 10 and create_time.day = 31 THEN 'Halloween'
#Veterans Day
WHEN create_time.month = 11 and create_time.day = 11 THEN 'Veterans Day'
#Thanksgiving (4th Thursday in November)
WHEN create_time.month = 11 and create_time.day_of_week_name = 'Thursday'
and create_time.day >= 22 and create_time.day <= 28 THEN 'Thanksgiving'
# Black Friday (day after Thanksgiving)
WHEN create_time.month = 11 and create_time.day_of_week_name = 'Friday'
and create_time.day >= 23 and create_time.day <= 29 THEN 'Black Friday'
#Christmas Eve
WHEN create_time.month = 12 and create_time.day = 24 THEN 'Christmas Eve'
# Christmas
WHEN create_time.month = 12 and create_time.day = 25 THEN 'Christmas'
# New Year's Eve
WHEN create_time.month = 12 and create_time.day = 31 THEN 'New Years Eve'
else create_time.day_of_week_name
end::string::day_of_week_name as fun_label,
id.count,
avg(score) as avg_score,
order by avg_score desc;
π°οΈ Dead Times of Day
Observations:
- Honestly, no idea what to make of this.
- There's a few bad times. Pick one.
Time of Day Query
import post;
where type = 'story' and is_show_hn and create_time.year>2024 and not deleted and not dead
SELECT
create_time.hour,
id.count,
avg(score) as avg_score;
πͺ¦ Post Length: Brevity is the Soul of Sorrow
That looks like it might go up and to the right - let's call it a positive correlation with length. 0 is the classic link only.
So the sweet spot - 0-100. Keep it brief enough, and you can avoid those upvotes!
Length Query
import hacker_news;
where type = 'story' and is_show_hn and create_time.year>2024 and not deleted and not dead
select
round( len(text) /100, 0) as size_bucket,
avg(score) as avg_score,
count(id) as sample_size
having sample_size
>5
;
The Non-Popular Words
Repeat offenders:
"Marketing"
- Huh, not a winner."Crypto"
β Dated ideas are a good one.
Keyword Query
import hacker_news;
auto title_word <- unnest(
split(lower(title), ' ')
);
where type = 'story' and is_show_hn and create_time.year>2024 and not deleted and not dead
select
title_word,
avg(score) as avg_score,
count(id) as sample_size
having sample_size
>25
order by avg_score asc
limit 25
;
Warning
The bigquery data used for GitHub is pretty stale, so these queries use a longer lookback - all data since 2015. Let's hope people's preferences are timeless.
Does Your License Matter?
People love Creative Commons? Stay away from that.
License Query
import github as github;
import post;
property id.github_base_url <- lower(REGEXP_EXTRACT(url, '(https://github\.com/[^/]+/[^/]+)'));
property id.text_github_url <- lower(REGEXP_EXTRACT(text, '(https://github\.com/[^/]+/[^/]+)'));
auto any_github_url <- coalesce(github_base_url, text_github_url);
# not all posts have github URLs
merge github.repo_url into ~any_github_url;
where
type = 'story' and is_show_hn and not deleted and not dead
and create_time.year>2015
select
github.license,
avg(score) as avg_license_score,
count(id) as sample_size
;
Does Your Language Matter?
Whatever you do, don't post something related to Rust. In Rust you can trust to get upvotes.
Language Query
import github as github;
import post;
property id.github_base_url <- lower(REGEXP_EXTRACT(url, '(https://github\.com/[^/]+/[^/]+)'));
property id.text_github_url <- lower(REGEXP_EXTRACT(text, '(https://github\.com/[^/]+/[^/]+)'));
auto any_github_url <- coalesce(github_base_url, text_github_url);
merge github.repo_url into ~any_github_url;
where type = 'story' and is_show_hn and not deleted and not dead
and create_time.year>2015
select
github.language,
avg(score) as avg_language_score,
count(id) as sample_size
order by
sample_size desc
limit 25;