Published 10/23/2023

Zig vs JavaScript

By Asher White

Zig-zagging stairs

Zig has been making waves lately as a modern alternative to C. It’s a very interesting language, with a focus on complete control and easy cross-compiling. I previously wrote an article comparing Zig to the low-level language Rust. But, how does Zig compare to a high-level scripting language like JavaScript? This blog post will examine the respective backgrounds of Zig and JS, along with their ease-of-use, performance and applications.

Background

Zig and JavaScript come from very different backgrounds—JS is a mature scripting language; Zig is a brand-new systems programming language. JavaScript was originally developed in 1995 by Brendan Eich and has been gradually improved ever since. It was designed from the start to run in web browsers and give web pages interactivity. So, it was designed as a cross-platform, sandboxed interpreted language that could safely run untrusted code on any device capable of running a web browser. As an interpreted language, JavaScript depends on a runtime—at this point there are three major open-source runtimes, all of them using JIT-compilation to some extent to get maximum performance. The most popular is Google’s V8, used in Chrome, Node.js, Deno, etc. Mozilla has SpiderMonkey, used in Firefox, and then Apple has JavaScriptCore, used in Safari and also in Bun. More on Bun later.

Zig was started in 2016 by Andrew Kelley, but is still a ways away from 1.0. (As of June 2023, the latest stable release is 0.10.1.) It’s followed a more typical open-source development model, and it’s usable even if it’s not completely stable yet. It was designed as a more pragmatic alternative to C, so it was purposefully designed to be a stripped-down, intuitive and low-level language. It has a self-hosted compiler with an LLVM backend, so it produces small, self-contained executables for a wide variety of platforms. Impressively, Zig can be cross-compiled out-of-the-box, something that’s rare among low-level languages.

So, Zig and JavaScript come from very different backgrounds, and JavaScript is a much more mature, widely-used language. But how do they compare in terms of ease-of-use?

Usability

JavaScript is much easier to use than Zig, but Zig lets you do things that JavaScript doesn’t. JS was designed as a language that exists inside a web browser, Zig was designed as the kind of language that you might implement the lowest-level parts of a web browser in. So, especially for beginning programmers, JavaScript is far more forgiving and easy to use. On the other hand, Zig gives you way more control over what the machine code that will be executed actually looks like.

For example, compare the implementations of a function that takes an array of numbers, appends doubles of all the numbers, and then returns the new array and the sum of all the elements. In JavaScript:

const doubleArray = (array) => {
  const newArray = [...array];
  array.forEach((n) => newArray.push(n * 2));
  return {
    array: newArray,
    sum: newArray.reduce((x, y) => x + y, 0),
  };
};

const array = [83, 8915, 0.214587, 3289.378, 8712, 12];
console.log(doubleArray(array)); // Logs
// {
//   array: [ 83, 8915, 0.214587, 3289.378, 8712, 12, 166, 17830, 0.429174, 6578.756, 17424, 24 ],
//   sum: 63034.777761
// }

Compared to Zig…

const std = @import("std");

const array = [_]f64{83, 8915, 0.214587, 3289.378, 8712, 12};

const SumArray = struct {
  array: []f64,
  sum: f64,
};

fn doubleArray(orig_array: []const f64, allocator: std.mem.Allocator) !SumArray {
  var array_list = std.ArrayList(f64).init(allocator);
  try array_list.appendSlice(orig_array);
  var sum: f64 = 0;
  for (orig_array) |n| {
    try array_list.append(n * 2);
    sum += (n * 3);
  }
  return .{
    .array = try array_list.toOwnedSlice(),
    .sum = sum,
  };
}

pub fn main() !void {
  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
  const allocator = gpa.allocator();
  const doubled_array = try doubleArray(&array, allocator);
  try std.io.getStdOut().writer().print("{}", .{doubled_array});
  // Prints
  // double-array.SumArray{ .array = { 8.3e+01, 8.915e+03, 2.14587e-01, 3.289378e+03, 8.712e+03, 1.2e+01, 1.66e+02, 1.783e+04, 4.29174e-01, 6.578756e+03, 1.7424e+04, 2.4e+01 }, .sum = 6.3034777761e+04 }
}

Ouch. As you can see, the extra control Zig gives comes at the cost of a lot of ease-of-use and a much bigger opportunity for error. It goes to show the different mindsets of the two languages—Zig’s is to assume nothing about the program and give the programmer ultimate control, JavaScript’s is actually to restrict control and provide an easy interface to what most coders want.

So, for example, someone should almost never learn to code using Zig. JavaScript is much better suited to that. Additionally, higher-level applications like interfaces and web services are better for JavaScript than Zig. On the other hand, doing things like writing an operating system, a video game engine or a web server (the part that handles the low-level processing of connections, not the web service part) would be impossible or inefficient in JavaScript but might work well with Zig. Another advantage Zig has is its performance.

Speed

Comparing the performance of Zig and JavaScript is really comparing apples and oranges—they’re completely different kinds of languages. To look at a bunch of micro benchmarks, check out these programming language benchmarks. These benchmarks show mixed results—in general, however, Zig is faster than JavaScript with much lower memory consumption. Sometimes, a Bun JS implementation is faster than a standard Zig implementation (more on Bun later).

What does this show? Not much. The intended applications of each language are different, so each excels in its own field. The Pareto principle (also known as the 80/20 rule) applies well here. In many applications, roughly 20% of the code gets 80% of the CPU time, whereas the 80% of the code used to bring everything together only gets 20% of the CPU time. Zig would be a good candidate for a 20% language—it is slower to write, optimize and compile, but an optimized Zig program will generally run much faster than a JavaScript program. But, for the 80% of the program used to bring everything together but that isn’t a performance bottleneck (i.e., improving its performance only brings marginal gains), JavaScript works excellently because it’s much faster to write, optimize (there’s not much manual optimization you can do) and compile (it’s basically instant).

What does that actually mean? If you wrote a web service from scratch in Zig, it would probably only be marginally faster than a web service with the performance-critical but generic areas written in Zig, but the implementation-specific code written in JavaScript. However, the completely Zig implementation would take significantly longer to write, test and develop than the Zig/JavaScript version.

So, which is faster—JavaScript or Zig? As a rule of thumb, JavaScript is faster to develop in, Zig is faster to run. Which language to use depends on which part of the application you’re writing—and you might also consider using a higher-level-but-still-fast language like Julia, or Rust more on the lower-level end.

This section dealt with running JS or Zig on your computer either with a JS runtime like Node.js or as a standalone executable. However, different rules apply when running inside the browser.

Inside the browser

While JavaScript was designed from the ground up to run inside the browser, Zig also can using WebAssembly. Which language should you use? Like in the previous section, it’s a balancing act of trading off the added complexity and size of WebAssembly with its potential performance gains. However, there’s a couple of extra factors in the browser environment:

  • The internet slows everything down. The biggest bottleneck in many websites isn’t computation speed, it’s loading stuff over the network. Even on fast, modern networks, things like negotiating connections, starting new requests and multiplexed connections can slow things down. On those sites, replacing well-written JavaScript with WebAssembly won’t actually make things any faster, and might even slow things down (WebAssembly is generally bigger than the equivalent JavaScript).
  • WebAssembly isn’t native code. It’s almost native code. It’s optimized bytecode. But it still needs to be quickly interpreted, just like JavaScript. And, WebAssembly can’t call any OS functions or interface directly with the browser—it generally has to go through JavaScript. So, if DOM or browser APIs are the bottleneck in your website, replacing JavaScript with WebAssembly will often make things slower.
  • WebAssembly isn’t always faster, even for pure computation. The JavaScript runtimes in modern browsers are almost ridiculously complex, with several different execution paths to eke the most performance out of JS. So, the right kind of computation benefits more from JIT compilation & optimization (like JS has), than it does with ahead-of-time optimization like Zig does.
  • Zig doesn’t always compile properly to WebAssembly. Zig is a new language, and WebAssembly is more of a niche target, so a Zig program that compiles for other platforms might not compile or might not run in WebAssembly.

That said, should you ever use Zig in the browser? Sometimes, for true computation-heavy applications, a low-level language shines, even in WebAssembly. But, is Zig the best candidate? On the plus side, it tends to produce small binaries, which is great for the web. On the other hand, there isn’t great language support for WebAssembly yet. Another language like Rust or even AssemblyScript might be a better fit.

If JavaScript is better than Zig in the browser, what are the ideal applications for Zig?

Zig applications

Zig is a very low-level language, so it’s best for code that you’ll write once and then use from lots of other code. For example, Zig is good fit for operating systems, language runtimes, game runtimes, graphics libraries, databases, etc. For some concrete examples, check out some of the most popular projects on GitHub that use Zig.

The first item on that list (as of June 2023) is Bun. Bun is very interesting in this comparison because it’s a JavaScript runtime that uses Zig. Like Zig itself, Bun is a newer project that hasn’t hit 1.0 yet, but it’s already posting some very impressive performance numbers by combining the high performance of Zig and JavaScriptCore (Apple’s open-source JS runtime).

JavaScript applications

JavaScript has broader applications than Zig, as can be seen from the most popular repositories in JavaScript or TypeScript. Many of the most popular are specifically for use inside the browser, but others are server-side libraries or standalone GUI applications. As varied as the applications of JS can be, they mostly centre around the internet—either client-side or sever-side, JavaScript is the language of the web.

So, what’s better—JavaScript or Zig? The simplest answer—both together! You can run JavaScript in Bun to take advantage of Zig’s higher performance, or interface with a Zig DB from a JS web service, or call Zig compiled to WebAssembly from JavaScript in a website. Of course, there’s a limited ecosystem in Zig at the moment, but it’s growing steadily, with projects like Bun already standing out. By combining JavaScript with a low-level language like Zig, Rust or C++, you maximize your efficiency and craft the highest-quality program.

Ready to get started, or want some more information?

Whether you want a marketing website, a fullstack web app or anything in between, find out how we can help you.

Contact us for a free consultation

© 2024 Broch Web Solutions. All rights reserved.