Evan Hahn's blog
Subscribe with RSS or email, if you want.
- Notes from "On Writing Well"
- Notes from December 2025
- Prediction: Mastodon will outlive Bluesky
- Notes from "Bad Company: Private Equity and the Death of the American Dream"
- How I implemented relative imports with Pyodide
- I made a little audio speed calculator
- Notes from November 2025
- Draft: Stopping bad guys from using my open source project (feedback wanted)
- Notes from "The Story of the Typewriter"
- Experiment: making TypeScript immutable-by-default
- Fizz Buzz without conditionals or booleans
- "Understanding Unicode": my October 2025 talk at Longhorn PHP
- Kirby Air Riders demo impressions (from a big fan of the original)
- Notes from "Tor: From the Dark Web to the Future of Privacy"
- Notes from October 2025
- Scripts I wrote that I use all the time
- Notes from September 2025
- People read your blog in many different ways
- @ts-ignore is almost always the worst option
- JS fetch converts string request bodies to UTF-8
- Notes from August 2025
- Notes from July 2025
- Notes from "The Weather Machine: A Journey Inside the Forecast"
- Notes from "Where Wizards Stay Up Late: The Origins of the Internet"
- Local LLMs versus offline Wikipedia
- Simple macOS script to extract text from images (OCR)
- How I build software quickly
- Notes from June 2025
- Notes from "Empire of AI: Dreams and Nightmares in Sam Altman's OpenAI"
- An unfinished post: "Compressing short Unicode strings with BOCU-1"
- Getting over my grudge against the periodic table
- Rainbow "code doodle"
- When Array uses less memory than Uint8Array (in V8)
- Notes from May 2025
- Simple script to sort Markdown lists
- List of "tech for good" job boards
- Things I wish I knew about Ring Fit Adventure
- Notes from April 2025
- UI tip: maybe don't round percentages to 0% or 100%
- Takeaways from The Economist's style guide book
- Notes from "Life in Code: A Personal History of Technology"
- Notes from March 2025
- Cheatsheet for Rink, the unit-aware calculator
- Everything we know about the next Zelda game, as of March 2025
- Filling in the gaps of the internet
- Notes from "Beyond Measure: The Hidden History of Measurement"
- Why "alias" is my last resort for aliases
- An ode to my favorite mobile game
- Notes from February 2025
- How to play a file in reverse with mpv
- LLMs: harmful to technical innovation?
- Notes from January 2025
- My experience upgrading from iPhone 11 Pro to iPhone 16 Plus
- Whisper transcription vs. iPhone 11 Pro and iPhone 16 Plus: an informal test
- My failed attempt to shrink all npm packages by 5%
- string-timing-safe-equal, a new Node package
- A black cat in 2032 bytes
- Makeshift hot reload: my submission to HTMHell 2024
- 95% is very different from 99%
- A little trick to reduce Zoom bandwidth
- How does SQLite's "changes" API handle no-op UPDATEs?
- MIME types and atom bombs
- My notes from the Lua 5.4 reference manual
- How to download every game from js13kgames
- setBigTimeout
- Mapping Mario Kart 8's real-life courses
- Remember The Milk script to get task permalink
- Announcing Helmet v8
- Python: how to change the extension of a pathlib.Path
- Using ESLint to (help) avoid non-deterministic randomness
- How I played Halo: Master Chief Collection in chronological order
- My programming beliefs as of July 2024
- How to set a Zsh option only if supported
- Progressive enhancement for content
- "Understanding Iterables": my April 2024 talk at ChicagoJS
- Five apps in two kilobytes
- Clojure: distinct versus dedupe
- Remember The Milk script to estimate selected tasks
- How to use crypto.timingSafeEqual with strings
- What's the best way to concatenate Uint8Arrays?
- systemd-inhibit: a built-in Linux alternative to macOS's "caffeinate" command
- import myModule from "./my-module.torrent": requiring Node modules from BitTorrent
- Remember The Milk CSV export script
- The world's smallest PNG
- Torrent snapshot of my open source projects, as of 2024
- Introducing Sqids Crystal
- When static types make your code shorter
- How to do integer division in Crystal
- How sleep mode works on the Anbernic RG35XX (with GarlicOS)
- Introducing NONOGRAM NIGHTS
- How to use the JavaScript Compression Streams API to (de)compress strings
- Conditional Express middleware
- Track flights on iOS without installing anything
- How big is a kilobyte?
- How does Swift decode "weird" JSON numbers?
- Short C program that repeats a string forever
- "JavaScript and the farmer emoji": my talk at the Chicago JavaScript Meetup
- My mnemonic to remember tar commands on Linux
- How to set the (deprecated) valign attribute with JavaScript
- Subscribe with email if you want
- Play a jingle on Git commits
- Which Zelda dungeons are actually called "dungeons"?
- Proof of concept: drop-in JSON replacement that produces smaller payloads
- A picross game in 1024 bytes
- What's the largest possible PNG?
- You might not need Helmet.js
- Python's nonstandard JSON encoding
- Crystal: how to compute a CRC32 checksum
- Getting the UTF-16 bytes of JavaScript strings
- Getting the UTF-32 bytes of JavaScript strings
- Working with the UTF-8 bytes of JavaScript strings
- Introducing UTF-21, a toy character encoding
- My favorite little Markdown feature: reordering ordered lists
- How to compute the distance between two Cartesian points in JavaScript
- Why does "👩🏾🌾" have a length of 7 in JavaScript?
- Converting UIColors to CSS colors
- Everything we know about The Legend of Zelda: Tears of the Kingdom, as of April 2023
- Nested array permutations in JavaScript
- The lone developer problem
- Everything we know about The Legend of Zelda: Tears of the Kingdom, as of February 2023
- How to format SQLite BLOB columns as hex
- 9 years maintaining a sorta-popular open source package: lessons learned
- How I fixed broken Wi-Fi on my 2012 Mac Mini running Zorin OS
- Everything we know about The Legend of Zelda: Tears of the Kingdom, as of January 2023
- Everything we know about The Legend of Zelda: Tears of the Kingdom, as of December 2022
- Ruby: how to get the MD5 hash of a file
- Everything we know about The Legend of Zelda: Tears of the Kingdom, as of November 2022
- Everything we know about The Legend of Zelda: Tears of the Kingdom, as of September 2022
- Re-implementing JavaScript's == in JavaScript
- Consider Node's built-in test runner
- Everything we know about The Legend of Zelda: Tears of the Kingdom, as of August 2022
- The difference between "=" and "IS" in SQLite
- A decade of dotfiles
- Disable SwiftLint for a file
- Everything we know about The Legend of Zelda: Tears of the Kingdom, as of May 2022
- How to read tab-separated values (TSV) files in Python
- Remember The Milk script to find tasks with invalid start times
- Everything we know about The Legend of Zelda: Tears of the Kingdom, as of April 2022
- When stringify doesn't return a string
- How to block Google's FLoC tracking with Express
- Should you use semicolons in JavaScript?
- How to reverse an array in JavaScript
- Profit, not welfare
- The hype train
- Gotchas with Express query parsing (and how to avoid them)
- An interview with me on JavaScript Jabber
- "Fancy code is exclusionary"
- My entry to JS1k 2018: "Zap"
- Show only production dependencies with npm
- On modern censorship
- Import Pinboard bookmarks into Standard Notes
- On multi-paradigm languages
- "Facebook Wins, Democracy Loses"
- Clojure's "zero?" has some quirks
- Petition to open source Flash
- Lessons learned from last week's cyberattack
- Think of the journalists
- Ball lightning
- On becoming comfortable with privacy violations
- "Six other times the US has banned immigrants"
- Z-index seems so simple...
- Prevent Homebrew from gathering analytics
- Automatically ls when changing directories in zsh
- "If my code helped one developer at least once"
- "X marks gender-neutral"
- "The last thing we all need is for the 'data' economy to destroy another medium"
- Detect global JavaScript variables with iframes
- Parse URLs with <a> tags in JavaScript
- How to transfer Android Neko Atsume saves using adb
- Disable ESLint for a file
- The CSP module is the largest part of Helmet
- Vim's :x command: like :wq but better
- Only customize where you are unusual
- Programming languages and their ecosystems
- How to clear all inline styles from an HTML element
- Overwriting document.head in strict mode on Safari
- Pietime, my entry to JS1k 2015
- "This Incredible Hospital Robot Is Saving Lives. Also, I Hate It"
- Scrape Delicious bookmarks with 3 command-line tools
- Skip the header of a file with Python's CSV reader
- "Raising the wall" for open source
- Install a list of Atom packages from a file
- Thoughts on CoffeeScript after ECMAScript 6
- Disable the beer emoji in Homebrew
- The Internet no longer a wild west
- A deep dive into Express's static middleware
- Mostly on screens, at scales unimaginable
- A quick romp through default values in CoffeeScript
- Install Node Version Manager without the source line
- "About Feminism"
- "Writing the code was typically less than half the problem"
- "Is it more readable? Is it more clear?"
- The Internet as public infrastructure
- "We Are All Intelligence Officers Now"
- Strangecoin
- Dealing with the User-Agent of Python's Requests library
- "Is jQuery too big for mobile?"
- Publishing a simple package to npm
- Understanding Express.js
- Disable JSHint for a file
- Introducing cyborg.txt: robots.txt utilities for Node
- Proteus
- The Underhanded C Contest
- Two-factor authentication list
- An experiment: syntax highlighting for the English language
- Origin: my entry to js13kgames 2013
- Visualizing cracking AES-256
- Gribbagrab your resources
- Four CSS tips that have changed me
- Vim's dw_red for terminal Vim
- "JavaScript folks are really nuts"
- Understanding Express.js version 3
- Concept: making asynchronous loading look synchronous
- Resources from my 6th semester at the University of Michigan
- The intended purposes of JavaScript and the Internet
- "All magic comes with a price"
- Vim's killer feature
- Backbone.js is larger than you think
- University of Michigan students can use Virtual Sites to do cross-browser testing
- "Using the web in 2013 means using JavaScript"
- HTML and the "living standard"
- Circles 1K
- Why should AI be humanlike?
- Are we shook up?
- Resources from my 5th semester at the University of Michigan
- Why GNU grep is fast
- Smoothing out setTimeout in CoffeeScript
- "Ramadan code"
- Detect the Vimium Chrome extension
- Under 1% of English words are alphabetical
- "Technology itself does not transform anything"
- "10 Timeframes"
- Private members in CoffeeScript
- First impressions of Rails
- JavaScript/CoffeeScript sleepsort
- On my internship at UniversityNow
- Meeting the computer halfway
- Nicer committing with Vim
- Start Vim and temporarily ignore your .vimrc
- Evan Hahn's lil' pages
- Timeline of sexist incidents in geek communities
- Hardware Vim petal
- CoffeeScript's existential operator
- Airplane mode
- Cancel a git-flow branch
- Make console methods work in Internet Explorer
- Apple and the majority
- Is it worth cheating Caltrain?
- Week three of Vim
- Maurice Sendak
- The most important decisions are non-technical
- LinkedIn's app is HTML5
- Caesar shift in JavaScript and CoffeeScript
- What the Google-Oracle trials mean for coders
- Doing without jQuery
- Douglas Crockford on making JSON a standard
- Resources from my 4th semester at the University of Michigan
- Quietly awesome
- Papyrus Watch
- The Midwest Mentality
- "Inventing on principle"
- "We actually think people have to pay for content."
- Evil sum and average of a JavaScript array
- Nintendo's changing logo
- Post-active state for links
- I love CoffeeScript
- Remove one element by index from JavaScript array
- Resources from my 3rd semester at the University of Michigan
- "Traditional" for loops in CoffeeScript
- The comprehensive guide to disabling selection on the web
- Newline necessary at the end of JavaScript files?
- How to code Tic-Tac-Toe (and a Lua implementation)
- Detect the RockMelt browser from JavaScript
- Randomly generate either -1 or 1 in JavaScript
- How do I Jasmine: a tutorial
- Naps and their effectiveness
- Old Notational Velocity icon
- Use text-to-speech if you can't proofread aloud
- Only organize when you cannot search
- Euler method calculator in C++
- A mindless to-do list
- How to: play custom Portal maps on macOS
- And the blog begins