Sign in

Patrick Dubroy

@dubroy.com
1.6K followers 293 following 670 posts

Programmer & researcher, co-creator of ohmjs.org. 🇨🇦 🇩🇪 🇪🇺 Co-author of wasmgroundup.com — learn Wasm by building a simple compiler in JavaScript. Prev: CDG/HARC, Google, BumpTop

PostsRepliesMedia
Patrick Dubroy @dubroy.com · 26/05/2026
There is also patrickcollison.com/fast, and @thorstenball.com reminded me of this from nat.org
120
Patrick Dubroy @dubroy.com · 25/05/2026
Tag yourself, I'm "future altered by doing request"
A diagram illustrating a request-response communication pattern between two services, labelled "Figure 2: Requests for Work"

It shows two white boxes, labelled "service A" and "service B" on a teal background. It clockwise order, it reads:

(service A) hopeful for the future…decides to issue request → request → (service B) minding own business → (service B) future altered by doing request → response → (service A) hopes fulfilled, the future is now
120
Patrick Dubroy @dubroy.com · 25/05/2026
New blog post: Fast is better than slow → dubroy.com/blog/fast-is...
About 10 years ago, I realized all the best programmers I had worked with had something in common: they were fast. By that I mean that they moved quickly: we’d discuss a problem and an hour or two later they’d already have a patch ready or a prototype to show off.

It took me a while, but eventually I realized: they weren’t fast because they were great programmers, they were great programmers because they were fast.

Think about it — if you’re fast, you get data more quickly. That helps you make better decisions, sooner. It also means you learn faster, and over longer periods it means you learn more. Being fast also means you can try out multiple approaches to a problem and pick the best one.

A lot of people push back on this because it sounds like hustle culture. But there are lots of ways to move faster that don’t involve working long hours. Jamie Brandon has written a pair of excellent posts on this: Speed matters and Moving faster. You should go and read those if you haven’t already.

I have a few suggestions of my own — things that are a bit more about the messy reality of working as a software engineer than they are about coding per se. And I’m slightly embarrassed to admit that, unlike Jamie, they took me more than a decade to learn.
5103
Patrick Dubroy @dubroy.com · 01/04/2026
TIL: Python signal handling → github.com/pdubroy/til/...
Python signal handling
I've written signal handlers in Python many times before, but never looked deeply into the exact mechanics until today.

Here's a simple example of a SIGINT handler:

import signal


def handler(signum, frame):
    print('Signal handler called with signal', signum)


signal.signal(signal.SIGINT, handler)
Some questions I had, along with the answers that I discovered:

Q: When does handler run, and how it is interleaved with whatever's happening on the main thread?

Answer:

A Python signal handler does not get executed inside the low-level (C) signal handler. Instead, the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point (for example, at the next bytecode instruction).

and

Python signal handlers are always executed in the main Python thread of the main interpreter, even if the signal was received in another thread.

(From the doc for the signal module)

Q: Isn't this kind of scary? So the main thread can be preempted at an arbitrary point to run the signal handler?

Answer:

Unfortunately, yes! This also means:

Warning: Synchronization primitives such as threading.Lock should not be used within signal handlers. Doing so can lead to unexpected deadlocks.

So the restrictions are pretty similar to C — see the signal-safety(7) man page.Q: Ok, so what's the recommended pattern?

A: On option is to just assign to a global variable in the handler (shutdown_requested = True), and do the actual handling in your main loop or whatever. But in many cases you may need some way to park/wake the code in the main loop. In C you can use the self-pipe trick; Python has built-in support for this with signal.set_wakeup_fd().

How deadlock can occur
It's interesting to look at exactly how deadlock can occur. Here's the implementation of Event from cpython/Lib/threading.py:

    def __init__(self):
        self._cond = Condition(Lock())
        self._flag = False
    
    # ...
    
    def wait(self, timeout=None):
        with self._cond:
            signaled = self._flag
            if not signaled:
                signaled = self._cond.wait(timeout)
            return signaled
So the main thread could get preempted after acquiring the lock, but before the wait. Then suppose the signal handler tried to use the set() method on Event. Here's how it's defined:

    def set(self):
        with self._cond:
            self._flag = True
            self._cond.notify_all()
…so you'd have a self-deadlock when the main thread tried to acquire a lock again in the handler.
0110
Patrick Dubroy @dubroy.com · 24/03/2026
New post on the @ohmjs.org blog — Inside Ohm's PEG-to-Wasm compiler → ohmjs.org/blog/2026/03... v18 is now more than 50x faster for real-world grammars while using about 10% of the memory 🔥 …this post goes into the details of how it's built.
Bar chart comparing performance improvements across three technologies: JSON shows 15.6ms (v17) reduced to 0.35ms (v18), a 44.6x speed increase; LiquidHTML shows 2,250ms (JS) reduced to 36ms (Wasm), a 62.8x speed increase; and ES5 shows 3,308ms (JS) reduced to 55ms (Wasm), a 60.3x speed increase. Blue bars represent 'Before' measurements and green bars represent 'After' measurements
0104
Patrick Dubroy @dubroy.com · 10/03/2026
Also I finally put together a consulting/ page, for anyone who's interested in working with me. dubroy.com/consulting/ Did I mention I still have availability this year? 😇
Consulting
I do technical advising and freelance development for companies big and small.

Some recent examples:

Part-time advising for a small startup, doing regular 1-on-1s with the CTO and selected ICs. Advised on technical architecture and team issues, and helped them hire a Head of Engineering and their first Staff Engineer.
Worked with HCI pioneer Michel Beaudouin-Lafon and his research group to build a fully incremental processing pipeline for Asciidoc with Ohm.
For a research group investigating parametric CAD/CAE systems, implemented a GPU-based interpreter (in Rust and WGSL) for rendering implicit surfaces.
For engineering projects, I’m especially interested in work where I can combine my deep systems expertise with frontend development and UX work.
260
Patrick Dubroy @dubroy.com · 10/03/2026
What I'm up to now → dubroy.com/now/ ∙ Teaching another Scratch course ∙ Still have availability in 2026 for consulting ∙ Ohm v18 is now ~50x faster than v17 🔥 ∙ Invited talk at MoreVMs next week ∙ Live pair programming with @jason.energy next Thursday at ~17:30 CET!
Spring has sprung over here; I’m enjoying the sunshine.

I’m teaching another Scratch course at my kids’ Montessori school. It’s 90 minutes once a week for 5 weeks, and this time, the theme is “programming artificial life”:

Learn programming and create your own interactive digital creature. First, you’ll design your character (on paper or on the iPad). Then you learn how to give it behaviour in Scratch. Let it walk across the screen, search for food, decide when it needs to sleep. Turn your iPad into a virtual world!

The bee-like thing above is my creature, of course. It flaps its wings, gets happy when you feed it ants, and loves when you “pet” it with your cursor.

I still have availability for new consulting projects in 2026. I also finally put together a consulting page in case you’re curious how I could help you. I’m always happy to chat about potential projects, so feel free to get in touch.

Over the past few weeks, I’ve been finishing up the last little bits of WebAssembly support in Ohm, and officially announced the v18 beta. I’ve continued to make performance improvements, and am pretty psyched that it’s now about 50x faster on real-world grammars 🔥.

Speaking of Ohm, I’ve been invited to do a talk at the MoreVMs Workshop next week. So I’ve been spending time preparing that.

And I’ll be doing live pair programming with Jason Lengstorf on Learn with Jason on Thursday, March 19. You should tune in!
1100
Patrick Dubroy @dubroy.com · 07/03/2026
TIL: Multithreaded WebAssembly → github.com/pdubroy/til/... (corrected)
Just like in JavaScript, you can do shared memory multithreading in WebAssembly! I've long known this was possible, but until the other day, had never actually played with it myself, so I decided to put together a small, self-contained example.

(This is for Node, but it's pretty much the same in the browser.)Details
Structured cloning of WebAssembly.Module
Normally you'd instantiate a Wasm module with WebAssembly.instantiate, which gives you a module instance. Here, we use WebAssembly.compile, which gives us a WebAssembly.Module. This is a stateless object that is structured-cloneable, which allows it to be safely shared across realm boundaries.

Serialization (an implicit part of structured cloning) of WebAssembly modules is defined in §3 of the WebAssembly Web API, which says:

Engines should attempt to share/reuse internal compiled code when performing a structured serialization, although in corner cases like CPU upgrade or browser update, this might not be possible and full recompilation may be necessary.

Shared memory
WebAssembly.Memory also support structured cloning. When we pass shared: true, the buffer property is a SharedArrayBuffer:

The structured clone algorithm accepts SharedArrayBuffer objects and typed arrays mapped onto SharedArrayBuffer objects. In both cases, the SharedArrayBuffer object is transmitted to the receiver resulting in a new, private SharedArrayBuffer object in the receiving agent (just as for ArrayBuffer). However, the shared data block referenced by the two SharedArrayBuffer objects is the same data block, and a side effect to the block in one agent will eventually become visible in the other agent.

Atomic add
The last piece of the puzzle is the i32.atomic.rmw.add instruction used in the addId function:

  (func (export "add") (result i32)
    ;; mem[0] += workerId
    i32.const 0
    global.get 0
    i32.atomic.rmw.add))
This instruction is defined in the threads proposal (a Stage 4 proposal, so not finalized yet), which defines "a new shared linear memory type and some new operations for atomic memory access".

i32.atomic.rmw.add is equivalent to LOCK XADD on x86. As described in the threads proposal:
0101
Patrick Dubroy @dubroy.com · 07/03/2026
The playground is awesome! Btw you might want to change the CSS for the shortcuts…it resolving to Fire Code for me, which has ligatures for many of these things, which makes it confusing. Adding `font-variant-ligatures: none` seems to fix it.
110
Patrick Dubroy @dubroy.com · 03/03/2026
TIL: Fibonacci hashing → github.com/pdubr...
It comes up rarely, but on a few projects I've wanted a dead simple hash table implementation. Most recently, it was for an experiment in the Ohm WebAssembly compiler. When I'm compiling a grammar, I assign each rule name a unique ID, but I wanted a fixed size cached (e.g. 8 or 32 items) keyed by rule ID.

I discovered Fibonacci hashing, aka "Knuth's muliplicative method":

So here's the idea: Let's say our hash table is 1024 slots large, and we want to map an arbitrarily large hash value into that range. The first thing we do is we map it using the above trick into the full 64 bit range of numbers. So we multiply the incoming hash value with 2^64/φ ≈ 11400714819323198485. (the number 11400714819323198486 is closer but we don't want multiples of two because that would throw away one bit) Multiplying with that number will overflow, but just as we wrapped around the circle in the flower example above, this will wrap around the whole 64 bit range in a nice pattern, giving us an even distribution across the whole range from 0 to 2^64. To illustrate, let's just look at the upper three bits. So we'll do this:

size_t fibonacci_hash_3_bits(size_t hash)
{
    return (hash * 11400714819323198485llu) >> 61;
}
All we have to do to get an arbitrary power of two range is to change the shift amount. So if my hash table is size 1024, then instead of just looking at the top 3 bits I want to look at the top 10 bits. So I shift by 54 instead of 61. Easy enough.

It turns out the Linux kernel has used this for ~6 years; here's the comment from include/linux/hash.h:

/*
 * This hash multiplies the input by a large odd number and takes the
 * high bits.  Since multiplication propagates changes to the most
 * significant end only, it is essential that the high bits of the
 * product be used for the hash value.
 *
 * Chuck Lever verified the effectiveness of this technique:
 * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf
 *
 * Although a random odd number will do, it turns out that the golden
 * ratio phi = (sqrt(5)-1)/2, or its negative, has particularly nice
 * properties.  (See Knuth vol 3, section 6.4, exercise 9.)
 *
 * These are the negative, (1 - phi) = phi**2 = (3 - sqrt(5))/2,
 * which is very slightly easier to multiply by and makes no
 * difference to the hash distribution.
 */
#define GOLDEN_RATIO_32 0x61C88647
#define GOLDEN_RATIO_64 0x61C8864680B583EBull
Why would you use this?
(I may get some details of this explanation wrong, because hashing and hash table sizing are a surprisingly complex subject!)

If I understand correctly, it makes sense to use this if (a) you don't have access to a good hash function, and (b) you want power-of-two (not prime) table sizes; and/or (c) you want the bucket calculation operation to be as fast as possible. (A multiplication plus a shift is significantly faster than modulo/division.)
030
Patrick Dubroy @dubroy.com · 01/03/2026
My 80/20, grug-brained personal productivity system: - Cool stuff: URLs, books, movies, etc. I want to remember. - Useful stuff: how/where/etc for things I do a few times a year. - Cooked: for cooking/baking: what recipe (URL or book), any adjustments I made, how it turned out.
Raycast Notes search interface, with three notes highlighted with blue rectangles: 'Useful stuff’ (preview shows “Replacement parts for Roborock…”), 'Cool stuff’, and 'Cooked’ (preview shows “2026-02-12: Made gyoza again…”).
3100
Patrick Dubroy @dubroy.com · 25/02/2026
Here's my first creature.
050
Patrick Dubroy @dubroy.com · 25/02/2026
Starting another Scratch course at my kids' (Montessori) school today. A bit different this time — the theme is "artificial life". Taking some inspiration from @shiffman.lol's natureofcode.com
Programmierung künstlichen Lebens in Scratch

Lerne Programmieren und erstelle dein eigenes, interaktives, digitales Wesen. Zuerst entwirfst du deine Figur (auf Papier oder auf dem iPad). Dann lernst du, wie du ihr in Scratch Verhalten gibst. Lass sie über den Bildschirm laufen, nach Futter suchen, entscheiden, wann sie schlafen muss. Dein iPad wird zu einer virtuellen Welt!

Programming Artificial Life in Scratch
Learn programming and create your own interactive digital creature. First, you design your character (on paper or on the iPad). Then you learn how to give it behaviour in Scratch. Let it walk across the screen, search for food, decide when it needs to sleep. Your iPad becomes a virtual world!
1130
Patrick Dubroy @dubroy.com · 25/02/2026
slowmo.dev by @seflless.bsky.social is pretty damn cool — "Slow down, pause, or speed up time of any web content." Here's how it works.
 * This library intercepts time at multiple levels to slow down (or speed up)
 * all animations on a web page.
 *
 * ## How it works:
 *
 * 1. **requestAnimationFrame patching**: We replace window.requestAnimationFrame
 *    with a wrapper that passes modified timestamps to callbacks. Time-based
 *    animations that use the timestamp parameter will automatically slow down.
 *
 * 2. **performance.now() patching**: We replace performance.now() to return
 *    virtual time. Libraries that use this for timing will be affected.
 *
 * 3. **Date.now() patching**: We replace Date.now() to return virtual epoch
 *    milliseconds. Libraries like Motion/Framer Motion use this for timing.
 *
 * 4. **setTimeout/setInterval patching**: We scale delays by inverse of speed
 *    so timed callbacks fire at the expected virtual time.
 *
 * 5. **Web Animations API**: We poll document.getAnimations() and modify the
 *    playbackRate of all Animation objects. This affects CSS animations,
 *    CSS transitions, and element.animate() calls.
 *
 * 6. **Media elements**: We set playbackRate on video/audio elements.
 *
 * ## Limitations:
 *
 * - Frame-based animations (that increment by a fixed amount per frame without
 *   using timestamps) cannot be smoothly slowed down.
 *
 * - Animations created by libraries that cache their own time references
 *   before we patch may not be affected. The Chrome extension runs at
 *   document_start to minimize this issue.
070
Patrick Dubroy @dubroy.com · 24/02/2026
TIL: Set difference vs symmetric difference → github.com/pdubr...
I just spent an annoying 30 minutes debugging an issue caused by a silly mistake —

I wanted to verify that two sets had the same contents. So I wrote something like this:

assert(newSet.difference(oldSet).size === 0, 'sets are different!')
But this doesn't detect if oldSet has some items that aren't in newSet! What I should have been using was symmetricDifference:

assert(newSet.symmetricDifference(oldSet).size === 0, 'sets are different!')
Both of these methods are Baseline 2024 features.

On the names
Swift's name for difference is subtracting, which less confusing imo.

I decided to see where the naming was discussed on the original TC39 proposal, and found tc39/proposal-set-methods#7, with a the following comment from tabatkins

Sorry for this being back-and-forth, but difference has the same lack of implicit ordering as complement did — it's not immediately, intuitively clear which element's values are retained. minus and subtract are both good; removeAll, while it implies mutation semantics, is also extremely clear and good in this regard.

Related: "symmetricDifference" vastly exceeds my design instincts for what is an allowable level of spelling difficulty in an API. "symmetric" is not an easy word to spell (my fingers just now tried to type it with a single "m"!), and combined with another 10 letters after, it's huge and terrible. xor has a non-obvious meaning for many people, including native English speakers, but it's short and easy to spell; unsure if it's good enough or not.

Interestingly, the conclusion in that thread was to use except. There was further discussion in #24: Method names should pick a theme, but I couldn't figure out where (or why) they decided on difference.
120
Patrick Dubroy @dubroy.com · 23/02/2026
TIL: api-extractor → github.com/pdubroy/til/...
The new version of ohm-js (v18), is now in beta. It's written in TypeScript, whereas v17 was written in JavaScript with manually-updated type definitions.

I was looking for a way to make sure that I don't accidentally make changes to the API, which led me to api-extractor:

API Extractor is a TypeScript analysis tool that produces three different output types:

API Report - API Extractor can trace all exports from your project's main entry point and generate a report to be used as the basis for an API review workflow.

.d.ts Rollups - Similar to how Webpack can "roll up" all your JavaScript files into a single bundle for distribution, API Extractor can roll up your TypeScript declarations into a single .d.ts file.

API Documentation - API Extractor can generate a "doc model" JSON file for each of your projects. This JSON file contains the extracted type signatures and doc comments. The api-documenter companion tool can use these files to generate an API reference website, or you can use them as inputs for a custom documentation pipeline.

How I'm using it
In each package, I have an api-extractor.json like this:

{
  "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
  "mainEntryPointFilePath": "./dist/index.d.ts",
  "apiReport": {
    "enabled": true,
    "reportFolder": "./"
  },
  "newlineKind": "lf",
  "dtsRollup": {
    "enabled": false
  },
  "docModel": {
    "enabled": false
  },
  "messages": {
    "extractorMessageReporting": {
      "ae-missing-release-tag": { "logLevel": "none" },
      "ae-forgotten-export": { "logLevel": "none" }
    }
  }
}
Note that I'm only using API reports for now — no .d.ts rollup or documentation. (I'm using tsdown as well, which already bundles my types into a single .d.ts.)Then, in my package.json, I have a package script named api-report:

{
  "name": "@ohm-js/compiler",
  "version": "18.0.0-beta.8",
  // ...
  "scripts": {
    "api-report": "api-extractor run",
    // ...
  },
  // ...
}
This script runs in CI. When it's run, if the API report has changed, I get an error like this:

> @ohm-js/compiler@18.0.0-beta.8 api-report /Users/pdubroy/dev/ohmjs/ohm/packages/compiler
> api-extractor run


api-extractor 7.56.3  - https://api-extractor.com/

Using configuration from ./api-extractor.json
Analysis will use the bundled TypeScript version 5.8.2
Warning: You have changed the API signature for this project. Please copy the file "temp/compiler.api.md" to "compiler.api.md", or perform a local build (which does this automatically). See the Git repo documentation for more info.

API Extractor completed with warnings
 ELIFECYCLE  Command failed with exit code 1
191
Patrick Dubroy @dubroy.com · 19/02/2026
I'm extremely hyped about the performance of the upcoming (#Wasm-based) @ohmjs.org v18. !!! (please don't let this be a mistake in my benchmarking)
Performance comparison tables showing WebAssembly significantly outperforming JavaScript. In the "After" state: JS matching takes 3267ms with 1090.7MB memory while Wasm matching takes only 146ms with 174.5MB memory (22.4x faster). Overall, JS total is 2291ms compared to Wasm total of 100ms (22.99x speedup). Wasm uses dramatically less memory: 6.88 MB heap vs 247.52 MB for JS.
0191
Patrick Dubroy @dubroy.com · 14/02/2026
This is exactly one of the findings of the study* on tabbed browsing I did ~15 yrs ago — tab usage is bimodal. Funny to see that it (anecdotally at least) still holds up! ∗ "A Study of Tabbed Browsing Among Mozilla Firefox Users" from CHI 2010: dl.acm.org/doi/pdf/10.1...
5435
Patrick Dubroy @dubroy.com · 14/02/2026
"This speed can lead users to race past useful experiences, particularly ones that are artistically or pedagogically helpful." Slowness, Politics, and Joy: Values That Guide Technology Choices in Creative Coding Classrooms → www.mcnutt.in/assets/tatto...
There is a natural tendency in designing interfaces to try to make
them as fast as possible, to rapidly, and seamlessly [36], automate
tasks that are not essential facets of the task at hand. Yet, this speed
can lead users to race past useful experiences, particularly ones
that are artistically or pedagogically helpful.
5.1.1 Slowing Things Down. Reducing the rate at which different
tasks can be performed gives space for both artistry and learning,
giving users time for reflection and personal growth.
Reflection. slowness seemed to have value for the production
of and critical engagement with art.
PTega suggested that “in the arts, there’s a real value to slowing down and taking the hood off things. Because it lets you ask
critical questions, [such as] if you’re truly engaging with it as a
medium”. PBaku argued for the value of integrating tedium into his
workflow, noting that it is useful to “integrate procedural ways of
thinking with more manual or repetitive or more tedious works”.
He went on to describe how a photographer friend intentionally
used an older and slower computer to guide the type of works he
could create. Emphasizing the importance of user agency in this
091
Patrick Dubroy @dubroy.com · 11/02/2026
"The root cause of the Spectre and Meltdown vulnerabilities was that processor architects were trying to build not just fast processors, but fast processors that expose the same abstract machine as a PDP-11." C Is Not a Low-level Language: spawn-queue.acm.org/doi/pdf/10.1...
A large letter 'C' on the left side with the title 'Is Not a Low-level Language' where 'Not' is emphasized in brown italics. Author name 'David Chisnall' appears below, with 'Your Computer Is Not A Fast PDP-11' in brown text at the bottom right.
151
Patrick Dubroy @dubroy.com · 06/02/2026
I wrote up what I found in a short blog post — devlog: compatibility packages → dubroy.com/blog/compati...
I’ll be introducing some breaking changes in the next major version of Ohm and I’d like to make the upgrade path as smooth as possible. So I’ve been investigating patterns for specific “migration” and “compat” packages in the JS ecosystem.

I found a few interesting examples. Most of them aim to support incremental upgrades, allowing you to migrate to the new API piece by piece. This isn’t really a concern for Ohm. Mainly I’m interested in making it easy for to folks to try the new version behind a feature flag, and easily revert to the stable version if they run into any bugs.

Anyways, here’s what I found —

react-router-dom-v5-compat
Instead of upgrading and updating all of your code at once (which is incredibly difficult and prone to bugs), the backwards compatibility package enables you to upgrade one component, one hook, and one route at a time by running both v5 and v6 in parallel. Any code you haven’t touched is still running the very same code it was before. Once all components are exclusively using the v6 APIs, your app no longer needs the compatibility package and is running on v6.
020
Patrick Dubroy @dubroy.com · 02/02/2026
What I'm doing now → dubroy.com/now/ Note: I have availability for consulting projects in 2026. If you know of something interesting, please get in touch!
Freshly baked pita breads cooling on a black wire rack on a kitchen counter, with a light blue kitchen towel on the left and fresh parsley on the right. The pitas are puffy and golden-brown with a light dusting of flour on their surfaces.
0142
Patrick Dubroy @dubroy.com · 31/01/2026
TIL: HTTP Range requests → github.com/pdubr...
Did you know that HTTP supports requests for a specific range of bytes? I didn't!

I first saw this mentioned a few weeks ago in the Protomaps documentation, and then it came up again in an episode of Developer Voices about DuckDB.

MDN: HTTP range requests:

An HTTP Range request asks the server to send parts of a resource back to a client. Range requests are useful for various clients, including media players that support random access, data tools that require only part of a large file, and download managers that let users pause and resume a download.

Here's an example:

curl https://i.imgur.com/z4d4kWk.jpg -i -H "Range: bytes=0-1023" --output -

…which issues the following request:

GET /z4d4kWk.jpg HTTP/2
Host: i.imgur.com
User-Agent: curl/8.7.1
Accept: */*
Range: bytes=0-1023
I was kind of surprised that I hadn't heard about this before, but I felt better when I found out that Kris hadn't either :-)
131
Patrick Dubroy @dubroy.com · 28/01/2026
New blog post — Look for what's true → dubroy.com/blog/look-fo...
A few times in my career I’ve gotten feedback that really knocked me on my ass.

The first few times it happened, I wasn’t mature enough to learn much from it. But eventually I discovered a little trick — something that helps me set my ego aside and actually listen.

The details aren’t all that important, but to set the scene: a Principal Engineer I worked with had called me out for being overly negative in a meeting. He pointed out that, as a senior IC, I should be mindful of how my actions could affect those around me.

In hindsight, he was spot on; but in the moment, it felt mostly wrong and totally unfair.

I spent the rest of the day in a miserable mood. In the evening, I was still stewing, and considering how to respond. I think I even started drafting a rebuttal email (I know, I know).
1171
Patrick Dubroy @dubroy.com · 25/01/2026
TIL: Tidy Data → github.com/pdubr...
On Friday I took the train from Munich to Brussels, and on the way there, converted my financial planning spreadsheet to a Python script using pandas. Now, on the way back home, I'm reading about siuba and dplyr, and I learned about Hadley Wickham's concept of Tidy Data:

Tidy data is a standard way of mapping the meaning of a dataset to its structure. A dataset is messy or tidy depending on how rows, columns and tables are matched up with observations, variables and types. In tidy data:

1. Each variable is a column; each column is a variable.
2. Each observation is a row; each row is an observation.
3. Each value is a cell; each cell is a single value.
This is Codd’s 3rd normal form, but with the constraints framed in statistical language, and the focus put on a single dataset rather than the many connected datasets common in relational databases.
180
Patrick Dubroy @dubroy.com · 20/01/2026
And here's @b0rk.jvns.ca — "I need to be able to ignore a project for 3 years and then come back and be able to develop it easily" gist.github.com/jvns/5bd9283...
I like to make tiny websites. My requirements for my sites are something like:

I have maybe 20 websites (mostly static but not all)
I want to spend basically 0 time maintaining them, maybe 5 minutes every 2 months at most
I need to be able to ignore a project for 3 years and then come back and be able to develop it easily
here are some of the ways I choose tools to keep my sites running without doing a lot of maintenance. this is a draft, might clean it up and make it into a real post later

1. Make static websites
Most of my websites are static. There are a few subcategories of static websites:

Genuinely just an HTML page, like css examples
Sites generated by a static site generator, like this blog. I use Hugo and it's held up well for me for the last 10 years or so. I did try out 11ty recently for a nonprofit website I was working on and it was a really good experience.
Single-page standalone Javascript sites which are just HTML, CSS, and JS, like this questions site or sql playground or integer.exposed
Static sites which use OAuth to let the user login to an external API, like mastodon thread view, or a FitBit app I wrote one time that no longer works because Google stopped setting Access-Control-Allow-Origin: * on the FitBit API
080
Patrick Dubroy @dubroy.com · 19/01/2026
See also: dubroy.com/blog/cold-bl...
Some projects are different. You work alone, make some changes when you’re inspired, and then don’t touch it again for another year, or two, or three. You can’t run something like that as a warm-blooded project. There’s not enough activity to keep the temperature up.

A cold-blooded project is like the baby painted turtle. You can freeze it for a year and then pick it back up right where you left off.

A cold-blooded project uses boring technology. The build and test scripts don’t depend on external services that might change, break, or disappear entirely. It uses vendored dependencies.
120
Patrick Dubroy @dubroy.com · 16/01/2026
New blog post: De-Googling → dubroy.com/blog/de-goog... …in which I tell you about some great services & companies that I'm very happy to have discovered.
Since early last year, I’ve been trying to reduce my dependency on the big US tech companies, and US companies in general. Following Tim Bray, I’ve been thinking of this as de-Googling.

I don’t really want to get into the reasons, except to say that I’m under no illusion that my support (or lack of it) makes any difference to those companies. For me, it’s much more about reducing risk1, and about supporting smaller companies whose interests are more aligned with mine.

Anyways! I wanted write about my progress here, in case it’s useful to anyone else who’s also considering this.

(For a more in-depth discussion of the various alternatives, I found European Alternatives and Getting off US tech: a guide to be quite helpful.)
44710
Patrick Dubroy @dubroy.com · 10/01/2026
TIL: One-liners for downloading media → github.com/pdubroy/til/...
Two little tasks I had this week, which I learned some handy one-liners for —

(And I love that these are now one-liners thanks to uvx!)

gallery-dl for downloading GIFs from Twitter
For my Twitter archive, I wanted to download the original GIFs from a couple of my tweets. I used gallery-dl —

uvx gallery-dl 'https://twitter.com/someuser/status/123456789'
yt-dlp for downloading YouTube videos
Then, I wanted to watch a YouTube video (Cpu Caches and Why You Care by Scott Meyers) and for some reason the player wouldn't let me change the speed. For that I used yt-dlp:

uvx yt-dlp 'https://www.youtube.com/watch?v=WDIkqP4JbkE'
280
Patrick Dubroy @dubroy.com · 09/01/2026
Finally have a self-hosted version of my personal Twitter archive, so I can really, truly delete everything over there. → dubroy.com/twitter It's actually a lot of fun going through these! And orders of magnitude faster and more pleasant than on the official site.
Patrick Dubroy's Twitter Archive page listing tweets by year from 2008-2025.Screenshot of a 'Popular' tweets section showing two posts. The first tweet from May 5, 2017 (3159 likes, 1428 retweets) comments on putting heads on geometric forms to aid mental rotations, with an embedded image of academic text about body-syntonicity in problem solving and the concept of 'kobitos' (little people). The second tweet is titled 'How to get VC funding' and shows a satirical recipe with bullet points about looking at complex realities, failing to understand them, attributing failure to irrationality, and creating an idealized vision.
1131
Patrick Dubroy @dubroy.com · 07/01/2026
"Valgrind is in essence a virtual machine using just-in-time compilation techniques." Huh. I had no idea. en.wikipedia.org/wiki/Valgrind
Valgrind is in essence a virtual machine using just-in-time compilation techniques, including dynamic recompilation. Nothing from the original program ever gets run directly on the host processor. Instead, Valgrind first translates the program into a temporary, simpler form called intermediate representation (IR), which is a processor-neutral, static single assignment form-based form. After the conversion, a tool (see below) is free to do whatever transformations it would like on the IR, before Valgrind translates the IR back into machine code and lets the host processor run it. Valgrind recompiles binary code to run on host and target (or simulated) CPUs of the same architecture. It also includes a GDB stub to allow debugging of the target program as it runs in Valgrind, with "monitor commands" that allow querying the Valgrind tool for various information.

A considerable amount of performance is lost in these transformations (and usually, the code the tool inserts); usually, code run with Valgrind and the "none" tool (which does nothing to the IR) runs at 20% to 25% of the speed of the normal program.[7][8]
040
Patrick Dubroy @dubroy.com · 06/01/2026
TIL: Protomaps and PMTiles → github.com/pdubroy/til/...
I spent three years (2018–2020) working on mapping, and haven't followed the space closely since then. So I didn't hear about Protomaps when it was announced a few years ago. But, it looks very useful:

Protomaps enables frontend developers to easily "self-host" maps - it's just a file, no server setup required. It makes serving vector map tiles simple and affordable by using HTTP range requests.

There are more details in the docs:

There are three major parts of Protomaps:

PMTiles, an open archive format for pyramids of tile data, accessible via HTTP Range Requests.
An ecosystem of tools and libraries for creating, serving and manipulating PMTiles.
A cartographic "basemap" showing features in the world like roads, water bodies and labels, based on the OpenStreetMap dataset, and delivered as one big PMTiles archive.
(And I must say, my first impression is that the docs are very, very good.)
080
Patrick Dubroy @dubroy.com · 05/01/2026
"a nice chewy problem"
This is not a bad thing. This is what makes these lists useful. Your game mechanics are about posing problems, so knowing there’s clumps of problem types is very useful.

In the end, though, a problem is built out of a set of constraints. We call those rules, usually. It also, though, has a goal. Usually, if we come across a set of rules with no problem, we just play with it, and call it a toy.

Building toys is hard! Arriving at those rules and constraints to define a nice chewy problem is very challenging. You can think of a toy as a problematic object, a problem that invites you to play with it.
030
Patrick Dubroy @dubroy.com · 27/12/2025
What I'm doing now → dubroy.com/now/
A snowy winter landscape featuring a set of footprints creating a path through a white field. To the left, a tall, textured tree trunk stands near the foreground, while a large, leafless tree on the right is silhouetted against a bright, cloudy sky with the sun peeking through. In the background, a line of dark evergreens meets the horizon.
0130
Patrick Dubroy @dubroy.com · 24/12/2025
TIL: V8 Natives syntax → github.com/pdubroy/til/...
V8 has a number of built-in, runtime support functions that are written in C++. For debugging purposes, if you run with the --allow-natives-syntax flag, it's possible to call these functions directly from JavaScript with a % prefix. For example:

function getTypeTagged(node) {
  return node.type;
}

console.log('=== Optimization Status (after warmup) ===');
const taggedStatus = %GetOptimizationStatus(getTypeTagged);
console.log(`getTypeTagged status:  ${taggedStatus}`);
The full list of functions is in src/runtime/runtime.h in the V8 source. Here are a few that I found useful/interesting:

%GetOptimizationStatus
%HaveSameMap
%HasFastProperties
1111
Patrick Dubroy @dubroy.com · 07/12/2025
TIL: Monoid → github.com/pdubroy/til/...
Per [Wikipedia](https://en.wikipedia.org/wiki/Monoid):

> In abstract algebra, a _monoid_ is a set equipped with an associative binary operation and an identity element. For example, the natural numbers with addition form a monoid, the identity element being 0.

I'm sure I've come across the term before, but never in a context where it was very relevant to me — until this week!

The article [Zed Decoded: Rope & SumTree](https://zed.dev/blog/zed-decoded-rope-sumtree) covers a bunch of real-world problems in text editing that involve monoids. It's also relevant to parsing: for Ohm's [incremental parsing][sle17], we use a very similar approach in the internal syntax tree. It lets us have position-independent memo table entries, while being able to cheaply compute the absolute offset of a node on-demand.

The generalized version Zed's SumTree is also referred to as a _monoid-cached tree_.

See also:

- [Four Solutions to a Trivial Problem](https://www.youtube.com/watch?v=ftcIcn8AmSY), a talk by Guy Steele that discusses monoid-cached trees.
- Blelloch's [Prefix sums and their applications](https://www.cs.cmu.edu/~guyb/papers/Ble93.pdf) (1990)

[sle17]: https://ohmjs.org/pubs/sle2017/incremental-packrat-parsing.pdf
1110
Patrick Dubroy @dubroy.com · 05/12/2025
This document radicalized me.
The first page of Guy E. Blelloch's "Prefix Sums and Their Applications" (1990).

Abstract:

"Experienced algorithm designers rely heavily on a set of building blocks and on the tools needed to put the blocks together into an algorithm. The understanding of these basic blocks and tools is therefore critical to the understanding of algorithms. Many of the blocks and tools needed for parallel algorithms extend from sequential algorithms, such as dynamic-programming and divide-and-conquer, but others are new. This paper introduces one of the simplest and most useful building blocks for parallel algorithms: the all-prefix-sums operation. The paper defines the operation, shows how to implement it on a P-RAM and illustrates many applications of the operation. In addition to being a useful building block, the all-prefix-sums operation is a good example of a computation that seems inherently sequential, but for which there is an efficient parallel algorithm."
1161
Patrick Dubroy @dubroy.com · 03/12/2025
TIL: Printing V8 bytecode → github.com/pdubroy/til/...
Sometimes I want a lower-level picture of how my JavaScript code will be executed by the JS engine. In V8, one way to do that is to look at the bytecode for the Ignition interpeter.

In Node.js and d8 (the V8 developer shell), you can do that with --print-bytecode. But, that will dump bytecode for everything, including V8 and Node internals.

You can use --print-bytecode-filter to limit it to certain functions. E.g., if you have the following source code:

const add = (a, b) => a + b;

function onePlusTwo() {
  return add(1, 2);
}

console.log(onePlusTwo());
Then, node --print-bytecode --print-bytecode-filter=onePlusTwo myScript.js will show:

[generated bytecode for function: onePlusTwo (0x3670aa458b39 <SharedFunctionInfo onePlusTwo>)]
Bytecode length: 17
Parameter count 1
Register count 3
Frame size 24
   56 S> 0x3309ab60a9d8 @    0 : 19 03             LdaImmutableCurrentContextSlot [3]
         0x3309ab60a9da @    2 : b4 00             ThrowReferenceErrorIfHole [0]
         0x3309ab60a9dc @    4 : ce                Star0
         0x3309ab60a9dd @    5 : 0d 01             LdaSmi [1]
         0x3309ab60a9df @    7 : cd                Star1
         0x3309ab60a9e0 @    8 : 0d 02             LdaSmi [2]
         0x3309ab60a9e2 @   10 : cc                Star2
   63 E> 0x3309ab60a9e3 @   11 : 6a f9 f8 f7 00    CallUndefinedReceiver2 r0, r1, r2, [0]
   73 S> 0x3309ab60a9e8 @   16 : b3                Return
Constant pool (size = 1)
0x3309ab60a981: [TrustedFixedArray]
 - map: 0x0241845c09e1 <Map(TRUSTED_FIXED_ARRAY_TYPE)>
 - length: 1
           0: 0x0241845c63b9 <String[3]: #add>
Handler Table (size = 0)
Source Position Table (size = 9)
0x3309ab60a9f1 <Other heap object (TRUSTED_BYTE_ARRAY_TYPE)>
See Kuter Dinel's V8 bytecode reference for help in understanding the meaning of the instructions themselves.
0111
Patrick Dubroy @dubroy.com · 29/11/2025
Honestly all survey papers should include a graphical taxonomy of related work.
A timeline from 1990 to 2023, covering 50+ papers on skip lists. Each paper is represented by one of 11 different shapes (circle, square, triangle, diamond, etc.) The position of the shape represents the paper's publication date, and arrows between the shapes describe the relationships between them: either "evolved from" or "related".
0193
Patrick Dubroy @dubroy.com · 27/11/2025
Not often that I’m reading a CS paper and think, damn, now there’s a good diagram. These pretty good…love the ✂️s From ”The Ubiquitous Skip List: A Survey…” dl.acm.org/doi/pdf/10.1...
2213
Patrick Dubroy @dubroy.com · 21/11/2025
Definitely one of the cooler language playgrounds I've seen: pinky.cool.omg.lol
1103
Patrick Dubroy @dubroy.com · 19/11/2025
TIL: Branded types in TypeScript → github.com/pdubroy/til/...
Branded types in TypeScript
TypeScript is structurally typed: the type system doesn't distinguish between types that are structurally identical. By sometimes you want something more like nominal typing (like C, Java, Swift, Rust, etc.)

In TypeScript, it's possible to do that via a pattern known as branded types. I first heard about this a few months ago, but really only learned how it works the other day.

An example
The book Learning TypeScript uses the example of currency conversion. Suppose you have the following types:

type Euro = number;
type USD = number;
…and you wanted to prevent mistakes with mixing currencies, and you don't want to change the runtime representation. Enter branded types!

Branded types
Here's how we can address this with branded types:

type Currency<T> = number & { __brand: T };
type Euro = Currency<"EUR">;
type USD = Currency<"USD">;

const eur = (amount: number) => amount as Euro;
const usd = (amount: number) => amount as USD;

function usdToEur(amount: USD): Euro {
  return eur(amount * 0.87);
}
usdToEur(usd(35)); // this works
// `usdToEur(35)` fails to compiile:
//   Argument of type 'number' is not assignable to parameter of type 'USD'.
//    Type 'number' is not assignable to type '{ __brand: "USD"; }'.(2345)```
Note that this is purely in the type system — the runtime values are just numbers. As Learning TypeScript puts it:

Branded types are a useful lie to the type system: our positive numbers will never actually have that __brand property. We're just making sure that no developer accidentally provides a value of a non-branded type to a location that requires one that is branded.

Also, the name __brand is not important here — we could have used __currency or any other name we wanted.Preventing forging
When you use a regular property like __brand, it doesn't prevent someone from forging values:

import {usdToEur} from "./currency.ts";

type Greenbux = number & { __brand: "USD" };
usdToEur(50 as Greenbux);
To protect against this, you can use a Symbol for the property:

const brand = Symbol();
type Currency<T> = number & { [brand]: T };
type USD = Currency<"USD">;
If you do it this way, it's no longer purely in the types — your generated JS code will have a const brand = Symbol() (though it's never used).

To eliminate that, you can use declare const with unique symbol:

declare const brand: unique symbol;
type Currency<T> = number & { [brand]: T };
type USD = Currency<"USD">;
AFAIK, this is equivalent to the previous example, but it's a type-only declaration.
2113
Patrick Dubroy @dubroy.com · 18/11/2025
"Some compiler-development experiences are long slogs where you write code for months without ever having a thing that produces an actual executable that you can run." "My First Fifteen Compilers" by Lindsey Kuper blog.sigplan.org/2019/07/09/m...
Backend-first compiler development
Although the nanopass approach doesn’t specifically mandate implementing a compiler in a back-to-front manner — starting with code generation and working upward from there — the back-to-front approach was a hallmark of P523 in the year I took it.  For me, a first-year grad student who had never worked on compilers before, this way of organizing the work was incredibly motivating: at the end of week one of the course (and at the end of week two, and so on for each week), I had written a compiler! Admittedly, what I had at the end of week one was a compiler for an input language that wasn’t very different from the output language. But it converted code in its input language to honest-to-goodness x86 assembly code on which I could then run an off-the-shelf assembler and produce a working executable.

Some compiler-development experiences are long slogs where you write code for months without ever having a thing that produces an actual executable that you can run. But with the back-to-front nanopass approach, we got that hit of gratification every week! Furthermore, thinking of each component of the compiler as itself being a compiler was useful because it encouraged us to structure our code in a readable, modular, and maintainable way, in much the same way that parser combinator libraries support the development of readable, modular, maintainable parsers.
0121
Patrick Dubroy @dubroy.com · 14/11/2025
A new blog post — devlog: garbage collection is useful → dubroy.com/blog/garbage... Trying something new: quick, technical posts about things I'm working on. Let's see if it sticks.
devlog: garbage collection is useful
November 14, 2025
A long time ago, I spent a few years working on garbage collection in the J9 Java VM. And even though I’ve since done mostly done higher-level stuff, having a deeper knowledge of GC has continued to come in useful.

Yesterday, it was an insight from one of my favourite GC papers, A Unified Theory of Garbage Collection, which helped me solve a tricky problem.

ohm’s incremental parsing
I’m working with a team that’s using Ohm to parse text documents and render a rich text version in ProseMirror. The goal is bidirectional updates: changes in ProseMirror should propagate to the text version, and vice versa.

Ohm supports incremental parsing, which means that if you parse some text and then make a small edit, it can quickly reparse by reusing portions of the previous result.
1162
Patrick Dubroy @dubroy.com · 14/11/2025
In basically every TypeScript codebase I touch, I end up adding a `checkNotNull` function exactly like the one from the Kotlin stdlib. So useful! Surprisingly I haven't seen many TS resources which mention this specific pattern.
checkNotNull

inline fun <T : Any> checkNotNull(value: T?): T(source)

Throws an IllegalStateException if the value is null. Otherwise returns the not null value.
270
Patrick Dubroy @dubroy.com · 12/11/2025
Bjarne Stroustrup: "Subscripts and sizes should be signed" open-std.org/jtc1/sc22/wg...
I will dig into the arguments and consider alternatives, but my conclusion stands:
Use signed subscripts and sizes for span as it was deliberately designed to do.
The original use of unsigned for the STL was a bad mistake and should be corrected (eventually).
Why we have unsigned subscripts in the STL
As far as I remember (the STL is 25 years old so my memory may not be completely accurate) three
reasons were given for the STL using unsigned types for subscripts
• (As opposed to pointer subscripts) vector subscripts can’t be negative, so unsigned is obviously
the right type.
• We get one more bit to play with so we can get larger vectors; this is important on machines
with 16-bit address spaces.
• Range checking needs only one check (no need to check for less than 0).
I have heard such rationales many times over the years, but
• C/C++’s unsigned is a very odd set of types. They do not model natural numbers. In particular,
they have modular arithmetic and conversions to/from signed ints that can be very surprising.
Beware of any argument using the word “obvious”.
150
Patrick Dubroy @dubroy.com · 11/11/2025
Somewhat surprising (to me) advice in Google's C++ style guide: "Try to avoid unsigned types (except for representing bitfields or modular arithmetic). Do not use an unsigned type merely to assert that a variable is non-negative." Example of a possible bug: abseil.io/tips/227 (via @wingolog.org)
On Unsigned Integers

Unsigned integers are good for representing bitfields and modular arithmetic. Because of historical accident, the C++ standard also uses unsigned integers to represent the size of containers - many members of the standards body believe this to be a mistake, but it is effectively impossible to fix at this point. The fact that unsigned arithmetic doesn't model the behavior of a simple integer, but is instead defined by the standard to model modular arithmetic (wrapping around on overflow/underflow), means that a significant class of bugs cannot be diagnosed by the compiler. In other cases, the defined behavior impedes optimization.

That said, mixing signedness of integer types is responsible for an equally large class of problems. The best advice we can provide: try to use iterators and containers rather than pointers and sizes, try not to mix signedness, and try to avoid unsigned types (except for representing bitfields or modular arithmetic). Do not use an unsigned type merely to assert that a variable is non-negative.
1152
Patrick Dubroy @dubroy.com · 08/11/2025
A cool paper from OOPSLA '25 — An Empirical Evaluation of Property-Based Testing in Python → dl.acm.org/doi/pdf/10.1... Some interesting findings: - "Each property-based test finds about 50x as many mutations as the average unit test" - "76% of mutations were found within the first 20 inputs"
Property-based testing (PBT) is a testing methodology with origins in the functional programming community.
In recent years, PBT libraries have been developed for non-functional languages, including Python. However,
to date, there is little evidence regarding how effective property-based tests are at finding bugs, and whether
some kinds of property-based tests might be more effective than others. To gather this evidence, we conducted
a corpus study of 426 Python programs that use Hypothesis, Python’s most popular library for PBT. We
developed formal definitions for 12 categories of property-based test and implemented an intraprocedural
static analysis that categorizes tests. Then, we evaluated the efficacy of test suites of 40 projects using mutation
testing, and found that on average, each property-based test finds about 50 times as many mutations as the
average unit test. We also identified the categories with the tests most effective at finding mutations, finding
that tests that look for exceptions, that test inclusion in collections, and that check types are over 19 times
more effective at finding mutations than other kinds of property-based tests. Finally, we conducted a parameter
sweep study to assess the strength of property-based tests as a function of the number of random inputs
generated, finding that 76% of mutations found were found within the first 20 inputsTo address RQ2, assessing the effectiveness of test categories, we used mutation testing to assess the
bug-finding power of each project’s test suite. Mutation testing or mutation analysis is a method of
evaluating a test suite’s effectiveness. In a mutation test, small changes are made to a program’s
source code, called mutations. The mutations are generated by mutation operators, which are
syntactic transformation rules. Mutation operators often include swapping arithmetic operators (+
to -), flipping logical operators (and to or), and changing literals. The test suite is then run on the
mutated program, and if any test fails, then the mutation is considered caught or killed. If no test
fails, then the test suite did not identify the bug. The number of mutations caught is the test suite’s
mutation score, and a higher score is indicative of a more robust test suite.
092
Patrick Dubroy @dubroy.com · 31/10/2025
Did you know that Microsoft MakeCode (a Scratch-like coding environment) supports a variant of TypeScript called "Static TypeScript"? github.com/microsoft/px... Also described in a 2019 MPLR paper: www.microsoft.com/en-us/resear...
Static TypeScript
~ hint
This is a draft reflecting the design of Static TypeScript (STS) - not exactly what is implemented, but quite close. Comments welcome.

~
TypeScript is a typed superset of JavaScript designed to enable JavaScript developers to take advantage of code intellisense, static checking and refactoring made possible by types. TypeScript is gradually typed, meaning that types are optional. Type inference helps to assign types to untyped code.

In this document, we define a subset of TypeScript called Static TypeScript (STS, for short), which was created to make it possible to use a scripting language to program physical computers based on microcontrollers with small amounts of memory. The first use of Static TypeScript is for the micro:bit, which has 16Kb of RAM.

The best way to think of STS is as C#/Java/C++ subset of TypeScript where all types are known at compile time, which permits efficient linking to a C++ runtime (in the case of the micro:bit, the Lancaster University micro:bit DAL). STS tries to maintain as much of JavaScript semantics as much as possible, given the constraints imposed by a static typing discipline.

Goodbye to Any, Union, Intersection Types and the Bad Parts
In TypeScript, the Any type is “the one type that can represent any JavaScript value with no constraints”. In TypeScript, every program expression, value, and function must have some type, even if not explicitly provided by the programmer. Per the TypeScript Language Reference:

“in places where a type is not explicitly provided and TypeScript cannot infer one, the Any type is assumed.”

STS does not permit use of the Any, Union and Intersection types. The result of this is that STS disallows programs where a program element can denote many kinds of runtime values (for example, a variable that is a string at one point in execution and a number at another point, as specified by the union type string | number).Static TypeScript: An Implementation of a Static Compiler for the TypeScript Language.

Abstract: While the programming of microcontroller-based embeddable devices typically is the realm of the C language, such
devices are now finding their way into the classroom for CS
education, even at the level of middle school. As a result, the
use of scripting languages (such as JavaScript and Python)
for microcontrollers is on the rise.
We present Static TypeScript (STS), a subset of TypeScript
(itself, a gradually typed superset of JavaScript), and its compiler/linker toolchain, which is implemented fully in TypeScript and runs in the web browser. STS is designed to be useful in practice (especially in education), while being amenable
to static compilation targeting small devices. A user’s STS
program is compiled to machine code in the browser and
linked against a precompiled C++ runtime, producing an executable that is more efficient than the prevalent embedded
interpreter approach, extending battery life and making it
possible to run on devices with as little as 16 kB of RAM
(such as the BBC micro:bit).
This paper is primarily a description of the STS system
and the technical challenges of implementing embedded
programming platforms in the classroom.
031
Patrick Dubroy @dubroy.com · 29/10/2025
Working on a diagram for an upcoming @wasmgroundup.com blog post.
Diagram showing WebAssembly bytecode execution with a horizontal memory strip containing hexadecimal values (41 03 41 02 6c 41 04 6a 0a 46). Above the strip are labels 'instructions' and 'pc' (program counter) with arrows pointing to specific bytes. The program counter highlights byte '6c'. Below the strip, purple arrows point to different instruction bytes, labeled with their corresponding WebAssembly operations: 'i32.const' (pointing to 41 03 and 41 02), 'i32.mul' (pointing to 6c), 'i32.add' (pointing to 6a), and 'i32.eq' (pointing to 46). On the right side is a stack representation showing values '2' and '3' in rectangular boxes.
1232