danny's blog

Building an anti-résumé the hard way

Not interested in an aimless screed on failure and imposter syndrome? I get it. Click here for the final product — no hard feelings.

Unsolicited context

If you knew me in college, you’d know I was an anxious wreck.1 I had put all of my proverbial eggs into the computer science basket — there was no real “backup” option. Moreover, data science had recently been crowned the best thing ever to exist, and I wanted in (without any of those pesky advanced degrees. More school? Yuck!). To put it lightly, the stakes were high.

Everybody knows that the best only way to get experience is to already have experience.2 I really wanted to do data science, but internships were sparse and the odds were not in my undergraduate favor. But rather than invest time and energy into the objectively more rewarding LeetCode and Chill skill tree, I decided to shave yaks by obsessing over my résumé. Specifically, I had the bright idea that a résumé written in LaTeX (sorry, embarrassing, I mean LaTeX3) was at worst neutral, and at best positive (if it stumbled upon someone who made the same poor decision could appreciate my efforts). So I downloaded texstudio, found a template, and got to work.

So?

Fast forward: it’s been more than 5 years since I’ve graduated, and lately, life has been coming at me fast. I’ve been reflecting on those same sources of anxiety, the fear that I don’t deserve my professional success, and on the personal and professional mistakes I’ve made along the way. A major source of my insecurity is the idea that I am hiding my failures – both past and ongoing – not just from others, but from myself, as well.

I figure it would be pretty funny – and maybe effective – to embrace and even highlight these failures. Who really cares about that time I almost failed linear algebra and questioned my career decisions? Who among us hasn’t been hired, realized they’re woefully out of their depth, and felt desperate to prove their worth?

The anti-résumé or “failure résumé” isn’t a new idea, but to the best of my knowledge, nobody in my personal and professional circles has one. The more I’ve learned from my peers, the more I’m aware that we’re all rolling with the punches — and we should embrace that! Failure gives us texture and life is too short to be squeaky-clean.

Insofar as change starting with me, here are my goals:

  1. Design a version of my résumé with honest failures — let’s put these in red.
  2. Automatically sync my about me page with my latest résumés (plural!).
  3. ???
  4. Never be anxious again!

Let’s get technical

Here’s what a block of experience looks like in my résumé source and what it looks like when rendered:

\experience
	{Applecart}
	{Data Scientist}
	{May 2018 -- May 2019}
	{
		\begin{itemize}[leftmargin=0em, topsep=0.35em, itemsep=0em]
			\item Developed and scaled de-duplication and entity resolution pipelines, enabling vital political targeting campaigns.
			\item Optimized internal matching pipelines via automatic parameter tuning, yielding an average F1 score increase of 12\%.
		\end{itemize}
	}

A screenshot of how LaTeX compiles and renders the listed experience

If you have some experience with programming, you might be able to figure out what’s going on. But still, what are all those \backslashes \for? And why am I typing some things in {braces}?

To put it simply, I’m defining a LaTeX command (or “function”) called \experience, which takes 4 inputs – {employer}, {title}, {employment range}, and a {variable body of text} – to produce a consistent template of a single unit of experience. Every experience listed on my résumé is defined using the \experience command to ensure formatting consistency. Don’t worry too much about what’s going on with \begin{itemize}; just know that each \item becomes a bullet point.

What I’d like to do is keep the same \experience command, but add a few bullet points in red that beautifully highlight these failures. LaTeX makes it easy for us to vary text color by using the \textcolor command with two arguments: a named color and the text. For example, \textcolor{red}{Hello, World} would render Hello, World. To simplify future examples, I’ll make a new command \failitem that just takes an item’s text and makes it red.

Let’s try updating that same experience with one or two failures:

\experience
	{Applecart}
	{Data Scientist}
	{May 2018 -- May 2019}
	{
		\begin{itemize}[leftmargin=0em, topsep=0.35em, itemsep=0em]
			\item Developed and scaled de-duplication and entity resolution pipelines, enabling vital political targeting campaigns.
			\item Optimized internal pipelines via automatic parameter tuning, yielding an average F1 score increase of 12\%.
			\failitem{When interviewing for an internship, I was very lucky and got asked a question I had -- very randomly -- studied in depth the night before. I am \textit{very} confident I would have failed the question had I not seen it before.}
			\failitem{Wasted weeks of effort trying to evaluate the outputs of a large-scale unsupervised algorithm with the same clarity as a supervised algorithm. Only once it was too late did I realize we should have instead proposed approaching the problem as a supervised learning task.}
		\end{itemize}
	}

A screenshot of how LaTeX compiles and renders the listed experience with the failures

Sweet! All that’s left is to sprinkle a bunch of \failitems throughout the document and that’s the new anti-résumé, right? Right?

…but I want to eat my cake, too!

Unless I make a hilarious mistake, I don’t really intend to send my anti-résumé anywhere4 — it’s just nice to look at! That said, I do want to maintain – and even extend! – this document. But let’s be real: it’s not practical to try and juggle both of these documents at once. I’m not going to comment-out my failures, update my real résumé, compile the correct version, un-comment the failures, then compile the anti-résumé.

“So,” you say, “only compile the failures if we want them compiled. That’s an if statement, right?” And yeah, that sounds right to me! But how do we translate our condition – if we're compiling the anti-résumé, include the failures, otherwise exclude them – into actual LaTeX code? And where will we inject that information? And how much manual involvement will it require?

The goal is simple: run one command to produce both the résumé and the anti-résumé. To do this, we’ll need to:

  1. Introduce a build variable SHOWFAILURES that toggles between 0 (false) or 1 (true).
  2. Introduce if statements into the source that conditionally include failures based on SHOWFAILURES.
  3. Write a command that generates both documents.

Let’s go in order and see what dragons await us.

Introducing SHOWFAILURES

To start, here’s what texstudio runs under the hood when I update my résumé and compile it:

# Compile `Danny_Vilela_Resume.tex` into `Danny_Vilela_Resume.pdf`.
pdflatex -synctex=1 -interaction=nonstopmode Danny_Vilela_Resume.tex

If you’re familiar with the terminal and shell programming, you’ll know that we can set temporary local variables before running a command, such that those variables are only defined for that command. For example:

# Set temporary variable `MESSAGE`.
MESSAGE="Hello, world" python -c "import os; print(os.getenv('MESSAGE'))"
# Hello, world

# `MESSAGE` is no longer defined.
python -c "import os; print(os.getenv('MESSAGE'))"
# None

Likewise, we can introduce a new local variable SHOWFAILURES and set it to 1 when we want the pdflatex command to compile the anti-résumé, otherwise 0 when we don’t.

# Compile our regular résumé.
SHOWFAILURES=0 pdflatex -synctex=1 -interaction=nonstopmode Danny_Vilela_Resume.tex

# Compile the anti-résumé to `Danny_Vilela_Anti_Resume.pdf`.
# Note: we specify `jobname` to change the output file name. 
SHOWFAILURES=1 pdflatex -synctex=1 -interaction=nonstopmode -jobname=Danny_Vilela_Anti_Resume Danny_Vilela_Resume.tex

Examining state in LaTeX

In LaTeX, you can’t directly access environment variables in the same way you would in a more “batteries-included” language like Bash or Python. You certainly can access variables, but it effectively requires your LaTeX compiler to run a shell command and parse the output. Not ideal, but I’ve seen and approved of worse!

I started by building off of egreg’s phenomenal contribution here, which helps us access an environment variable:

\usepackage{catchfile}

% Get a variable from our environment if set, otherwise default.
\newcommand{\getEnvOrDefaultTo}[3][]{%
	\CatchFileEdef{\temp}{"|kpsewhich --var-value #2"}{\endlinechar=-1}%
	\ifx\temp\empty\def\temp{#3}\fi
	\if\relax\detokenize{#1}\relax\temp\else\let#1\temp\fi
}

% Define a new LaTeX "constant" (`\SHOWFAILURES`) that accesses the 
% `SHOWFAILURES` environment variable. If the environment variable 
% isn't set, default to 0 (i.e., do not show failures).
\getEnvOrDefaultTo[\SHOWFAILURES]{SHOWFAILURES}{0}

Let’s test this works by creating a very simple LaTeX document main.tex that prints the current value of \SHOWFAILURES:

\documentclass{article}

% << the snippet above >>

\begin{document}
    The value of SHOWFAILURES is: \SHOWFAILURES.
\end{document}

First, not showing failures:

SHOWFAILURES=0 pdflatex -synctex=1 -interaction=nonstopmode main.tex

A screenshot of a simple LaTeX document where SHOWFAILURES is correctly set to 0

Second, the document where we show failures:

SHOWFAILURES=1 pdflatex -synctex=1 -interaction=nonstopmode main.tex

A screenshot of a simple LaTeX document where SHOWFAILURES is correctly set to 1

Great! This tells us we’re able to toggle the SHOWFAILURES environment variable between 0 and 1 and have it be reflected in the document as \SHOWFAILURES. Now that we can capture the value into a LaTeX variable, we need to evaluate and compare that value within an if statement.

Here’s what an if statement with \SHOWFAILURES might look like in LaTeX:

\documentclass{article}

% << the snippet above >>

\begin{document}
    \ifnum\SHOWFAILURES=1
        NFTs lmao
    \fi
\end{document}

A screenshot of a simple LaTeX document displaying &ldquo;NFTs lmao&rdquo;

So, only if the integer \SHOWFAILURES is equal to 1 does the document display NFTs lmao.5 Otherwise, the document is blank.

Great! We now have the second piece of the puzzle: we can use if statements to display our failures only when the \SHOWFAILURES variable has captured the value 1 from the environment.

Bringing it together

Now, LaTeX knows whether we want to include failures and we know how to render a failure. All we need is a single command to generate both the résumé and the anti-résumé. Luckily, I know just enough make to totally mis-use it.

Let’s create a tiny Makefile with just one target build:

# Makefile style guide: http://clarkgrubb.com/makefile-style-guide

###################################################
## Define (preliminary/Make-specific) variables. ##
###################################################

MAKEFLAGS += --warn-undefined-variables
SHELL := bash
.SHELLFLAGS := -e -u -o pipefail -c
PHONY:

##############
## Targets. ##
##############

build:  # Build both resumes.
    SHOWFAILURES=0 pdflatex -synctex=1 -interaction=nonstopmode Danny_Vilela_Resume.tex
    SHOWFAILURES=1 pdflatex -synctex=1 -interaction=nonstopmode -jobname=Danny_Vilela_Anti_Resume Danny_Vilela_Resume.tex

The details of a build tool created in the 70s don’t really matter. What does matter is that I can now run one command to generate both PDFs: make build.

Auto-updating this website

This blog is built with Hugo and deploys on each push via GitHub Actions. In order to make sure I always have the latest version of my resume, I’ll need to extend the aforementioned Makefile to copy over the new PDFs and push a new commit. Let’s call the new target build-and-push:

# << prelude snippet above >>

build:  # Build both resumes.
    SHOWFAILURES=0 pdflatex -synctex=1 -interaction=nonstopmode Danny_Vilela_Resume.tex
    SHOWFAILURES=1 pdflatex -synctex=1 -interaction=nonstopmode -jobname=Danny_Vilela_Anti_Resume Danny_Vilela_Resume.tex

copy:  # Copy our PDFs to the blog.
    cp -v *.pdf ../../../dataframing.github.io/static

push:  # Add, commit, and push our new documents.
    cd ../../../dataframing.github.io && \
        git add static/*.pdf && \
        git commit -m "Update résumés" && \
        git push    

build-copy:      build copy
build-copy-push: build copy push

So, finally, I can just run make build-copy-push to build both PDFs, copy them over, and publish them. Likewise, I’ve added analogous build-copy and build-copy-push commands to the blog repo so that I can trigger builds from here. Beautiful.

Conclusion

Here at last: my anti-résumé. Feel free to suggest failures, rejections, or mistakes worthy of being added.6 7


  1. I used to be anxious. I still am, but I used to, too. ↩︎

  2. https://en.wikipedia.org/wiki/Ouroboros ↩︎

  3. It took me an embarrassingly long time to make that work, Please clap. ↩︎

  4. I’m very happy at ${CURRENT_EMPLOYER}, thank you very much! ↩︎

  5. NFTs: truly among the most heinous of failures. ↩︎

  6. Professional failures, rejections, or mistakes. ↩︎

  7. Yes, I’ll be adding this very blog post to the anti-résumé shortly. ↩︎