Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
35
Oscar, an open-source contributor agent architecture - Oscar is a project from the Go community that aims to develop AI agents that assist open source maintainers. I think this is a great idea. Open source is a load-bearing pillar in our modern digital infrastructure and maintainers need help. If AI agents were able to reduce the maintenance burden, it could reduce burnout, improve project longevity, and encourage new development. Excited to see where this goes. Move on to ESM-only - A proper update on JavaScript’s move-to-ESM fiasco. I was pleasantly surprised to learn that tools like Vite are helping push the community migration forward. One clear takeaway is that new packages should publish in ESM-only (no dual-publishing of ESM + CommonJS). Last week, I ended up republishing Checkboxland as ESM-only, in large part due to the influence of this post. My keyboard ergonomics journey as an engineer - A nice write-up on keyboard ergonomics from my former co-worker Grant. This post...
3 months ago

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Bryan Braun - Blog

Breaking down circular dependencies in JavaScript

I write a lot of JavaScript but circular dependencies have always been kind of a mystery to me. The error messages always seem random and inscrutable, and sometimes there’s no error message at all! I wanted to understand this topic better, so I ran a series of experiments and wanted to share what I learned. Let’s break down circular dependencies in JavaScript. What are circular dependencies? Circular dependencies happen when your JavaScript import statements result in a loop: The loop can consist of two files, three files, or more. Any time your import statements create a loop like this, there’s a risk that your code won’t work as expected. How do you know when you have circular dependencies? There’s no easy way built into the language! In JavaScript, a circular dependency often manifests as a seemingly unrelated error (like a ReferenceError or TypeError). This is different from many other programming languages, which often tell you directly that your imports are bad: Python: ImportError Go: import cycle not allowed So why can’t JavaScript come out and say ⚠️ CircularDependencyError? It’s because JavaScript modules are designed to be loaded and executed on-the-fly. When your browser loads a web page and starts executing its first JavaScript file, it has no idea how many more files are still coming. Those files could still be sitting on servers on the other side of the world. This is a very different situation than a Go or Python program, where the import system can analyze the whole dependency tree before executing a single line of code. Stepping through a circular dependency in JavaScript The best way to explain the errors that JavaScript gives us is to step through a circular dependency scenario: Click to view a larger version of this image. Here’s what we see on each step: Step 1: On line 1 of index.js, execution pauses to download a.js so its value a can be imported. Step 2: Upon downloading a.js, execution continues in a.js but pauses on line 1 to download b.js, so its value b can be imported. Step 3: Upon downloading b.js, execution continues in b.js and finds an import on line 1 pointing back at a.js (a circular import). Step 4: a.js is already downloaded, but it has no exports defined because we haven’t executed anything past line 1 in a.js at this point. Thus, we cannot fulfill the import in b.js. Step 5: Execution continues in b.js with a remaining uninitialized. When a is called on line 3, the program errors with: ReferenceError: Cannot access 'a' before initialization. To summarize, the circular dependency results in code being executed with uninitialized values. This could result in various errors, like the ReferenceError above. Why do circular dependencies sometimes not cause errors? JavaScript’s imports are described as “Live Bindings.” This means that the imported value can start out uninitialized (due to circular dependencies) and become fully useable once the rest of code has been evaluated. In other words, some circular dependencies are error-free because they “work themselves out” before you call the affected code. I once worked in a code-base that was chock full of circular imports but none of them ever caused any issues. Why? It’s because all the code was defined in functions, none of which would be called until after everything was loaded. To demonstrate, we can update the last scenario to work in a similar way: Click to view a larger version of this image. Steps 1-4 are the same as above but things start to change at step 5: Step 5: a remains uninitialized but instead of being called directly, it is placed in a function definition (no error). Step 6: With b.js completed, execution in a.js continues down to line 3, which defines the export for a. From this point on, any code calling a will get an initialized value, as a result of the live bindings. Step 7: We call a() successfully, which in-turn calls b(). Ultimately, all the code gets called with no errors. To summarize, by the time we actually call that “uninitialized a”, the live bindings have updated its value and it’s no longer uninitialized. We’re safe because the value of a is only retrieved when the variable is actually used. Now, I wouldn’t recommend this as a way of fixing dependency issues. I’d feel better about removing the circular dependencies altogether. Still, I’ll bet there are plenty of production apps with circular dependencies that currently rely on this behavior. Preventing circular dependencies While JavaScript may not have built-in circular dependency checking, we still have options for preventing these issues. 3rd-party tools like madge and eslint-plugin-import can perform static analysis on your JavaScript codebase and detect circular dependencies before they become unwieldy. Some monorepo tools like NX and Rush have similar features built-in to their workflows. Of course, the best prevention is a well-organized codebase, with a clear hierarchy for shared code. What about Node / Bun / Webpack / etc? The examples I shared above are focused on the “ES modules in the browser” use-case, but JavaScript runs in a lot of different contexts. Server-side JavaScript doesn’t need to download its source over the network (making it more like Python) and bundling tools like Webpack can combine all the code into a single file. Are circular dependencies an issue in these scenarios? In short, yes. In my experiments, I was surprised to find that the error outcomes for browser, server, and bundler were basically the same. For example, with Webpack, the import statements were removed but the combined code still produced the same error: // b.js console.log('b.js:', a); // ReferenceError: Cannot access 'a' before initialization const b = 'B'; // a.js console.log('a.js:', b); const a = 'A'; I should also mention that while Node.js produced the same error when using the import syntax (ESM), it behaved differently when using the require syntax (CommonJS): $ node node-entry.cjs (node:13010) Warning: Accessing non-existent property 'Symbol(nodejs.util.inspect.custom)' of module exports inside circular dependency (Use `node --trace-warnings ...` to show where the warning was created) (node:13010) Warning: Accessing non-existent property 'constructor' of module exports inside circular dependency (node:13010) Warning: Accessing non-existent property 'Symbol(Symbol.toStringTag)' of module exports inside circular dependency It's nice that the warnings say "circular dependency" explicitly, when using CommonJS. This makes sense when you consider that CommonJS is an entirely different import system that doesn’t conform to the ECMAScript Modules spec. Comparing the two is comparing apples and oranges! Conclusion Circular dependencies can be confusing but it makes a lot more sense when you walk through the scenarios step by step. As always, nothing beats an experiment for getting a clear understanding of something like this. If you want a closer look at my test results, feel free to check out the repo.

3 months ago 40 votes
Technology you don’t have to think about

“Civilization advances by extending the number of important operations which we can perform without thinking about them.” – Alfred North Whitehead Effective technology takes our most time-consuming tasks and moves them into the background so we can focus on other important things. We can see this happening on the civilization level with the infrastructure we’ve built to get inexpensive food, water, energy, and transportation. But it also works on the personal level. Whether it’s a high-quality dishwasher or well-designed personal-finance software, technology is our primary tool for saving time and redirecting our attention. But not all technology is successful at this. The power of technology is abstraction and a poor abstraction is worse than having no technology at all. Case in point: a smart light-bulb that never saves you enough time to recover the time you spent setting it up. Simon Sarris has a great post describing some of these issues: “Many modern devices (and apps) really excel at squishing tradeoffs into weird shapes. They are better thought of as little imps that sneak into homes and ask for more and more of your attention. They want to gently claw at your eyes and ears. They want to put notifications on your phone and remind you that you need to interact with them, or buy more of them, so that they might become even more convenient.” Simon Sarris, Careful Technology Much of our technology has small hidden costs. A new app on your phone, an audible hum in the background, a recommended cleaning every six months, recurring manual software updates, monthly emails in your inbox, increased risk of a breaker trip, reduced counter-top space, parts that need replaced, a small monthly fee, a new username and password, batteries to recharge, parts to recycle, etc, etc. Each cost seems small but with enough bad technology you face death by a thousand cuts. You lose time, peace, and other more difficult-to-quantify things, like “the cozier feeling of home.” We have to be discerning about the technologies we let into our lives. I’m tired of technology that trades one set of problems for another. I want that technology you don’t have to think about.

3 months ago 35 votes
You can’t fix a problem you don’t understand

A few weeks ago, I was building a server-side API client. I had written the code and tested it in isolation. Everything looked good. Unfortunately, when I included it in the main service, I started seeing errors. I decided to try asking an AI tool for suggestions. I gave it the error message and a bunch of context. It gave me a solution with a detailed explanation. The errors went away. But the solution didn’t sit right with me. It was a bit complex, introducing more layers of code and various protections. The errors were gone, but I couldn’t clearly explain why it worked, and that was bothering me. While the code was being reviewed, I decided to take another look. I brought back the error and spent some time digging into the stack trace. That’s when I made the discovery: it was an environment issue. All I needed to do was set an environment variable and the issue would be fixed. The AI-provided code had been masking the real issue, quietly suppressing the error, and hiding the truth in its complexity. Now this is the part of the post where I’m supposed to criticize AI programming tools. I won’t be doing that. This isn’t an AI problem. I remember the first time I tried to fix a memory leak. Certain iPhone users would load the webpage, interact for a while, and then randomly the webpage would crash. We struggled to diagnose the issue (Safari’s devtools weren’t great at the time). We thought we fixed it several times but the issue kept coming back. Why? Because we didn’t understand the problem. We kept digging and eventually we found it: one of our dependencies was storing massive amounts of data on the window object and it wasn’t getting cleaned up. We added a cleanup step and the problem was gone for good. Since then, I’ve adopted a mantra: you can’t fix a problem you don’t understand. It doesn’t matter if the “fix” comes from AI, Stack Overflow, or trial-and-error. If I don’t understand the problem, I feel unsettled until I do.

5 months ago 45 votes
Made in 2024

Here are some things I made in 2024: Music Box Fun: Advanced Editing (a new major feature): Adds multiple-note selection for bulk operations on notes (like deletion, copy/paste, nudging and dragging) Also includes a “space editor” for arbitrarily adding/removing space anywhere in the song Includes other niceties like note highligting during playback and pitch highlight on hover Music Box Fun songs I made: Elliott’s Theme (Stardew Valley) in 15-note and 30-note versions Bluey Theme Song Jupiter Theme (The Planets) 11 new projects added to Let’s Get Creative, now available at a new domain name: https://letsgetcreative.today The Firefly Building in Minecraft (if you know, you know) 13 blog posts on bryanbraun.com, including a companion repo to my post on unusual Git IDs. I’m happy with this list. It was a year of many challenges. In February we got hit by a flooded basement and an emergency hospitalization which left my wife with limited mobility for a month. It was a difficult time and I’ll be forever grateful for the family and friends who helped us get through it. It was also a year of growth for my kids in particular, bringing many new parenting challenges. At the same time, I have so much to be grateful for. My wife and I hit our fifteenth anniversary and our relationship has grown stronger despite (or perhaps because of) the storms we’ve weathered. Every year brings new opportunities and challenges and it’s a privilege to have a committed partner that I can face them with. 🚵🏻‍♀️🚵🏻‍♂️ I wish you all the very best in 2025.

6 months ago 68 votes

More in technology

PSA: part of your Kagi subscription fee goes to a Russian company (Yandex)

Today I learned that Kagi uses Yandex as part of its search infrastructure, making up about 2% of their costs, and their CEO has confirmed that they do not plan to change that. To quote: Yandex represents about 2% of our total costs and is only one of dozens of sources we use. To put this in perspective: removing any single source would degrade search quality for all users while having minimal economic impact on any particular region. The world doesn’t need another politicized search engine. It needs one that works exceptionally well, regardless of the political climate. That’s what we’re building. That is unfortunate, as I found Kagi to be a good product with an interesting take on utilizing LLM models with search that is kind of useful, but I cannot in good heart continue to support it while they unapologetically finance a major company that has ties to the Russian government, the same country that is actively waging a war against Ukraine, an European country, for over 11 years, during which they’ve committed countless war crimes against civilians and military personnel. Kagi has the freedom to decide how they build the best search engine, and I have the freedom to use something else. Please send all your whataboutisms to /dev/null.

2 days ago 3 votes
Alvik Fight Club: A creative twist on coding, competition, and collaboration

What happens when you hand an educational robot to a group of developers and ask them to build something fun? At Arduino, you get a multiplayer robot showdown that’s part battle, part programming lesson, and entirely Alvik. The idea for Alvik Fight Club first came to life during one of our internal Make Tanks, in […] The post Alvik Fight Club: A creative twist on coding, competition, and collaboration appeared first on Arduino Blog.

4 days ago 6 votes
Vote for the July 2025 + Post Topic

Past ads get a second chance.

5 days ago 9 votes
How a Hibernate deprecation log message made our Java backend service super slow

It was time to upgrade Hibernate on that one Java monolithic1 backend service that my team was responsible for. We took great precautions with these types of changes due to the scale of the system, splitting changes into as many small parts as possible and releasing them as often as possible. With bigger changes we opted for running a few instances of the new version in parallel to the existing one. Then came Hibernate 5.2. Hibernate 5.2 introduced a new warning log to indicate that the existing API for writing queries is deprecated. Hibernate's legacy org.hibernate.Criteria API is deprecated; use the JPA javax.persistence.criteria.CriteriaQuery instead Every time you used the Criteria API it would print the line. Just one little issue there. Can you see it? Every time you used the Criteria API it would print the line. In a poorly written Java backend service, one HTTP request can make multiple queries to the database. With hundreds of millions of HTTP requests, this can easily balloon to billions of additional logs a day. Well, that’s exactly what happened to our service, resulting in the CPU usage jumping up considerably and the latency of the service being negatively impacted. We didn’t have the foresight to compare every metric against every instance of the service, and when the metrics were summarized across all instances, this increase was not that noticeable while both new and existing instances of the service were running. Aside from the service itself, this had negative effects downstream as well. If you have a solution for collecting your service logs for analysis and retention, and it’s priced on the amount of logs that you print out, then this can end up being a very costly issue for you. We resolved the issue by making a configuration change to our logger that disabled these specific logs. This does make me wonder who else may have been impacted by this change over the years and what that impact might’ve looked like regarding the resource usage on a world-wide scale. I’m not blaming the Hibernate developers, they had good intentions, but the impact of an innocent change like that was likely not taken into account for large-scale services. Last I heard, the people behind Hibernate are a very small team, and yet their software powers much of the world, including critical infrastructure like the banking system. I’m well aware that we’re talking about Hibernate releases that were released around the time I was still a junior developer (2016-2018). Some call it technical debt, others call it over half a decade of neglect. unmaintaned monoliths suck, but so do unmaintained microservices. ↩︎

5 days ago 12 votes
The History of Windows XP

NT Vincit Omnia

6 days ago 12 votes