Unexpectedly lovely things about Recurse Center

I first got admitted to Recurse Center back in 2013 when it was still called “Hacker School”, but had to cancel for logistical reasons and did not overcome the logistical barriers to spend the requisite amount of time in NYC until they started piloting 1-week mini-batches recently. (I am now hooked and ready to commit to kicking down those logistical barriers so I can go back for more.)

This is a quick recap of nontechnical reasons I really enjoyed my week at RC.

What is Recurse Center?

The official description can be found here. I’ll attempt to paraphrase it below.

Think of Recurse Center (RC) as a writer’s retreat for coders. It’s not a bootcamp: you need to have some base level of programming ability in order to figure out how you want to direct yourself, but not a “h4rdc0r3” level. Just as good writers have many different styles and levels of experience levels, Recurse admits a wide variety of backgrounds and experience levels. It’s not a school; study groups and most events are entirely self-organized by attendees. It’s more like a do-ocracy. Things will happen if someone makes them happen. Tools are built to enable more people to make things happen.

Unlike many bootcamps and schools, it’s free to attend. You take care of your own lodging, food, and transportation, but underrepresented minorities can apply for need-based grants if that’s not financially feasible.

Why is this appealing?

There is a lot of joy, agency, and growth to be found in the act of learning, these characteristics are frequently at odds with corporate metrics. While software is a tool which can be wielded in many different ways, I’ve felt increasingly alienated by cultural aspects of the gentrifying modern “tech industry” which feel apathetic toward disadvantaged demographics within the systems the industry affects.

Recurse sounded like a technical environment I’ve been seeking to recapture since MIT: a bubble of energy, mixed interests and backgrounds, and no obligation to make things to impress VCs, only to learn and build things that are important to you.

There’s something else that happens at RC that bootcamps can’t provide: you get an opportunity to develop your “volitional muscles.” In other words, to learn how to direct yourself. Many people flounder while at RC. This can mean not being productive, not feeling good about your work, not knowing what to work on, or knowing what you need to do but not being able to do it. Floundering is the process of figuring out how to set your own direction. At the beginning, it will be hard, and every decision you make will test your abilities, but with work and time, you’ll become stronger and you’ll be able to make larger and larger decisions more easily. This is only possible because RC’s unique structure allows you to make your own decisions about your education.

While acknowledging that the freedom of having time to make things not for money is a financial privilege, it is an incredibly fulfilling opportunity, enhanced by the synchronicity that other Recursers have rearranged their lives so that you can all embark into this environment together.

It felt extremely welcoming

This is a rare phenomenon.

Although I usually have a great time in online chatrooms where no one can see my face, I often feel on guard whenever I physically enter an unfamiliar technically-focused space, such as a conference or interview (depending on the environment, sometimes even when I’m the interviewer).

I am an AFAB, non-white person of extremely short stature. I frequently receive appearance-based assumptions during first meetings in such spaces; usually: explicitly voiced assumptions that I have just started in this field (I’ve been doing software jobs since 2007), am still in school, or am in a nontechnical or less-technical role. The cognitive dissonance can run deep: I have had an entire conversational exchange with a machinist where he literally believed he was talking to my white cis-male friend next to me even though he was hearing and responding to my voice, and then was surprised when he realized my mouth was making the words. Once when I tried to recruit someone at a job fair, he relentlessly assumed for a surprisingly long time despite clear evidence to the contrary that I was a token nontechnical recruiter and the cis-male PM next to me was the engineer, until the PM got uncomfortable and corrected him (thanks, buddy). I did not follow up.

When I’m on guard, I may respond defensively in ways I’m not proud of. This kind of environment is largely defused at RC in a bunch of tangible ways that helped the space feel welcoming.


It’s easier to feel comfortable existing in a marginalized identity when you are around other people with marginalized identities. RC was significantly more visibly diverse than any technically-focused space I’ve been in since college, and it felt really nice.

At a lot of companies, “diversity” often just means hiring more white and East Asian cis-women from top colleges and upper-middle-class backgrounds. Disclaimer: as an East Asian MIT graduate, I cannot claim to draw conclusions about how RC felt to people outside that spectrum. However, it genuinely felt like RC repped an unusually wide range of ethnic minorities and international folks, offered lots of tangible support resources for gender minorities, and strove to support socioeconomic diversity in the form of need-based grants and a wiki guide to getting by inexpensively in NYC.

Value: Discourage assumptions

There were a bunch of skits on the first day to demonstrate RC’s social expectations designed to make the space feel more welcoming.

  • Identify -isms and other subtle microaggressions that may make people feel like they don’t belong.
  • Gracefully accept feedback about assumptive language when offered.
  • Remember that technical background cannot be inferred from physical appearance. Everyone present here was admitted on purpose.

Value: Encourage learning

  • Don’t put down people for not knowing things; instead, encourage questions.
  • Don’t try to jump in and backseat drive other people’s conversations without context; instead, ask if you can join and participate.
  • Don’t correct people about minutiae that aren’t necessary to the conversation.

People were super friendly

Lots of people were there with the shared goal of meeting other people, so it was easy to strike up conversations or reach out and talk. There were separate areas designated for interaction and for heads-down work, which made it easier to signal whether you were interested in pairing/talking or doing your own thing.

I really liked the study groups! I dropped into a Haskell study group, a theorem prover study group, and a linguistics puzzle club without knowing anything about the topics beforehand, and people were extremely encouraging and willing to explain things!

If RC has felt like a good fit to you but you’re on the fence about applying, all I can say is that it was an invaluable experience and I wish I did it much earlier in life.

RC Minimax - Getting the most out of a Recurse Center mini-batch

How does one actualize years of pent-up anticipation into a single week of Recurse Center?

Set intentions

Set goals for the week

(My goals)

  1. Feel excited about computers again.
  2. Connect with a lot of self-directed and interesting people. (It turns out this is an extremely effective way to achieve goal #1.)
  3. Explore a technical area I have little to no experience in.

Ask: What can you get here that you can’t get elsewhere?

For me, it’s the people and their shared learning intentions. I can get heads-down coding time anywhere I’m alone with wifi, a power outlet, and a place to sit.

Figure out what you specifically want to get out of being physically present at RC, and then optimize for it.

Preparing to maximize the week

I spent a few hours fiddling with my package manager, which feels like an awful lot relative to a week-long scope. Ideally, I would have done a lot more preparation. (Unfortuitously, I was sick the week before RC and then rehearsing all day for a dance performance right up until I had to get my stuff and go to the airport to redeye to NYC.)

Technical recommendations

  • Set up any nontrivial packages, such as languages, frameworks, and data-heavy libraries, that you are interested in using.
  • If you plan to fiddle with an unfamiliar technology, familiarize yourself with it beforehand. Having the basic tutorials out of the way will free up your time to collaborate and build the stuff you’re excited about.
  • Make sure there’s free space on your hard drive. This is a perpetual battle against entropy for me; disk visualizers such as Disk Inventory or WinDirStat are quite helpful.
  • Bonus: start building out a project idea if you have time! It doesn’t matter how much you “achieve”; the simple act of building will help you think it out.

Brainspace recommendations

  • Talk with people on the RC chat and welcome threads the week before you go. It’s a good way to gauge shared interests and get inspired by other people’s ideas, and you might even inspire other people with your own ideas.
  • Flesh out some project ideas and pick one that will be your top priority. You certainly don’t need to stick to it, but it’s helpful to have a well-known initial direction to kick off from, even if you change your mind mid-week.

Unexpectedly lovely things about RC

In the process of (2), I got sidetracked by many unexpected wonders (linguistics, theorem proving, feeling in solidarity while bemoaning the “but where are you from from” question) and expected distractions (talking about interesting past projects with people, fiddling with synthesizers, eating tasty food). I also enjoyed unexpected horrors during RC, such as nuking and resetting my entire package manager. Overall, it was an excellent and valuable meander through a welcome and inspiring space.

Longer retrospective here.

Publicly available

Some public Recurse resources you can peruse without being affiliated with RC!

  • Library catalog
  • Joy of Computing - inspirational collection of things Recursers have made that they’re excited about
  • Recurse blog - interesting writings about community-building, inclusivity, and profiles of various Recursers

Automated Jekyll blog tags

This blog is built on the Hyde theme for Jekyll, a light framework for building static websites which uses the Liquid templating language. It didn’t come with blog tags, so I had to poke around and find out how to implement them.

Since this blog is hosted on Github Pages, you can see all the code in my public repo, but I figured a quick walkthrough post would be easier to navigate.

Describing tags

You can describe tags within the metadata of a Jekyll post (or page, etc). I’m formatting them as follows.

layout: post
title: Automated Jekyll blog tags
tags: [web, jekyll, all-about-tags]

... Lorem ipsum dolor sit

Since tag names will eventually need to be in a URI-friendly format, I’m going to keep it super simple and stick with a tag naming convention of [\w\d-]+ (alphanumeric characters and hyphens allowed) for my blog. If you want to support spaces and other characters, you could do it with URI encoding, but I decided not to.

Displaying tags on posts/pages

You can display tags by adding a snippet like the following to a post template.

<span class="post-tags">
  {% for tag in post.tags %}
    <a class="post-tag"
       href="{{ site.baseurl }}/tag/{{ tag | slugify }}"
       >{{ tag }}</a>{% unless forloop.last %}, {% endunless %}
  {% endfor %}
  • The unless clause delimits the tags by , , leaving off a trailing delimiter.
  • Depending on what kind of Jekyll object you’re pulling tags from (a post, a page, an include), you may need to use post.tags, page.tags, or include.post.tags.
  • Regarding your site.baseurl, see notes at the end of the post about URL configuration.

In the Hyde theme, posts are described in two locations by default: /_layouts/post.html, which describes the layout of an individual blog post page, and index.html, which shows a page with multiple blog posts on it.

Handling posts with no tags

As written above, your post-tags div will appear empty on a post with no tags. However, if you’re using a cute icon like my blog does, you may want to hide this snippet when tags is empty. Apparently you can’t do a simple empty array check in Liquid; I had to resort to this StackOverflow hack.

{% capture difference %}
  {{ post.tags | size | minus:1 }}
{% endcapture %}
{% unless difference contains '-' %}
  <span class="post-tags">
{% endunless %}

I don’t want to duplicate post-tags in multiple places

I wanted my posts to appear the same on individual pages or when aggregated into the index page, so I refactored Hyde’s post display into a separate partial which I placed at _includes/post.html. Your mileage may vary based on your blog needs.

Again, if you’re using an _includes partial, you will want to use include.post.tags in your template instead of post.tags.

Auto-collecting tags across site

If you want to display all tags somewhere, you will first need to collect them from across your site within the templating language. For this, I referenced Codinfox’s blog post on how they implemented tags and categories in Jekyll.

This snippet is borrowed directly from Codinfox, but I’m duplicating it here for quick reference.

layout: default
title: Tag

{% comment %}
The following part extracts all the tags from your posts and sort tags, so that you do not need to manually collect your tags to a place.
{% endcomment %}
{% assign rawtags = "" %}
{% for post in site.posts %}
  {% assign ttags = post.tags | join:'|' | append:'|' %}
  {% assign rawtags = rawtags | append:ttags %}
{% endfor %}
{% assign rawtags = rawtags | split:'|' | sort %}

{% comment %}
The following part removes dulpicated tags and invalid tags like blank tag.
{% endcomment %}
{% assign tags = "" %}
{% for tag in rawtags %}
  {% if tag != "" %}
    {% if tags == "" %}
      {% assign tags = tag | split:'|' %}
    {% endif %}
    {% unless tags contains tag %}
      {% assign tags = tags | join:'|' | append:'|' | append:tag | split:'|' %}
    {% endunless %}
  {% endif %}
{% endfor %}

If you place it in _includes, it can now be included wherever you want to reference all site tags, such as in a sidebar.

{% include collect_tags.html %}
{% for tag in tags %}
  <a class="post-tag"
     href="{{ site.baseurl }}/tag/{{ tag | slugify }}"
     >{{ tag }}</a>{% unless forloop.last %}, {% endunless %}
{% endfor %}

Tag categories

Displaying a tag page

I wrote a layout _layouts/tagpage.html to display all posts tagged with a certain tag.

layout: default

<div class="post">
<h1>Tag: {{ page.tag }}</h1>

{% for post in site.posts %}
    According to documentation, arrays should be filterable
    with `where_exp`, but I couldn't get it to work.
  {% if post.tags contains page.tag %}
    {% include post_listing.html %}
  {% endif %}
{% endfor %}


Jekyll does not support tag pages out of the box, and its official recommendation is that if you implement them, you should manually write a separate metadata file for each tag page.

layout: tagpage
tag: jekyll
robots: noindex

I don’t have the patience for that shit, so let’s auto-generate them.

Auto-generating tag pages

To do this, we’ll need to collect all tags in our site and then output metadata for each in the format of the snippet above. Long Qian wrote a Python script to do this, which they run manually before git pushing. I wanted a much more automated solution, so I used hooks.

Jekyll hooks

Jekyll hooks are Ruby functions which can be registered to run after certain events, such as when a post is saved to disk or rendered. They are a subtype of plugins, which the build tool expects as .rb files in the _plugins directory.

My hook auto-runs on :post_write and calls a shell subprocess to run my Python script.

# Filename: _plugins/compile_tags.rb
Jekyll::Hooks.register :posts, :post_write do
  system("python _plugins/compile_tags.py")

There are many disparate ways to launch a subprocess in Ruby. I used this handy flowchart from StackOverflow to help me choose system.

Script to generate tag pages

My tag page generator script is more concise than Long Qian’s, but basically has the same effect. It scrapes all tags and generates metadata files describing tag pages which are served at tag/my-tag-name.

#!/usr/bin/env python
# Filename: __plugins/compile_tags.py

This script generates tag pages for all your post tags for a 
Jekyll site. It is invoked from a plugin after post_write.
Run it from the project root if testing.
Current convention expected for tag names is r/[-\w\d]+/

import glob
import os

POST_DIR = '_posts/'
TAG_DIR = 'tag/'

# Collect all tags from all posts.
all_tags = []
for fname in glob.glob(POST_DIR + '*.md'):
  with open(fname, 'r') as f:
    for line in f:
      line = line.strip().replace('[', '').replace(']', '')
      # Find tags & cut them.
      if line.startswith('tags: '):
        all_tags += [
          t.strip() for t in line[len("tags: "):].split(',')]
all_tags = sorted(list(set(all_tags)))
# Remove old tag pages
old_tags = glob.glob(TAG_DIR + '*.md')
for tag in old_tags:

# Create tag directory if it does not exist
if not os.path.exists(TAG_DIR):

# Write new tag pages.
layout: tagpage
tag: {tag}
robots: noindex
for tag in all_tags:
  with open(TAG_DIR + tag + '.md', 'a') as f:

That’s all! Restart your Jekyll server to make sure you’re using the new plugin, and you should see specs for tag pages generated into the tag/ directory, which you can then link to in your post layouts as {{site.baseurl}}/tag/{{tag}}.

Enjoy blogging :)

Debugging notes

Not all URLs work

If your blog is serving from somewhere other than your domain root, e.g. serving from https://me.github.io/blog/ rather than https://me.github.io/, double check that your theme uses its site.baseurl as expected. (site.baseurl is used to indicate the root folder of the Jekyll site. Confusingly, there is also a site.url; here’s a great StackOverflow explanation.)

For this blog, I have the following set in my _config.yml.

url:              https://rfong.github.io/rflog/
baseurl:          /rflog

Is the theme expecting an alternative baseurl?

The Hyde theme I’m using didn’t seem to, so I did git grep "site.baseurl" to double check its usage.

Some generated Jekyll URLs, such as post URLs, are prepended with a slash, so you just need:

{{ site.baseurl }}{{ post.url }}

For a manually written URL, you’ll want to include the slash.

{{ site.baseurl }}/public/css/poole.css
{{ site.baseurl }}/tag/{{ tag | slugify }}

I couldn’t find whether Liquid had a readily available os.path.join equivalent.