Skip to main content

Quickstart

This guide will get you from zero to running your first concurrent JavaScript program on the BEAM in under 5 minutes.

Prerequisites

Before you begin, make sure you have:
  • Erlang/OTP 26+ - Arc runs on the BEAM VM
  • Gleam 1.0+ - Arc is written in Gleam
  • Git - To clone the repository
See the installation guide for detailed setup instructions.

Run your first program

1

Clone the Arc repository

git clone https://github.com/your-org/arc.git
cd arc
2

Build Arc

Arc uses Gleam’s build system:
gleam build
This compiles Arc to BEAM bytecode. The first build may take a minute as it downloads dependencies.
3

Run a sample program

Arc includes several example programs. Let’s start with the simplest one:
gleam run examples/simple.js
You should see output showing two processes communicating:
<0.XXX.0>: Hello from main
<0.YYY.0>: Hello from child
Each <0.XXX.0> is a BEAM process ID (PID). You’ve just run concurrent JavaScript on the BEAM!
4

Try the interactive REPL

Arc includes a read-eval-print loop for interactive experimentation:
gleam run
You’ll see the Arc prompt:
arc -- JavaScript on the BEAM
Run /help for commands, Ctrl+C to exit.

>
Try spawning a process interactively:
> const pid = Arc.spawn(() => { Arc.log('Hello from', Arc.self()) })
Hello from <0.XXX.0>
> Arc.self()
<0.YYY.0>

Example programs

Arc includes several examples demonstrating different concurrency patterns. All examples are in the examples/ directory.

Simple message passing

The most basic example shows parent-child communication:
simple.js
const pid = Arc.spawn(() => {
	const message = Arc.receive();
	Arc.log(message);
	Arc.log(`${Arc.self()}: Hello from child`);
});

Arc.send(pid, `${Arc.self()}: Hello from main`);
Run it:
gleam run examples/simple.js

Counter actor

A stateful process that responds to messages - the classic actor pattern:
counter_actor.js
var parent = Arc.self();

var counter = Arc.spawn(() => {
	var count = 0;
	while (true) {
		var msg = Arc.receive();
		if (msg.type === 'inc') {
			count = count + msg.n;
			Arc.log('counter: incremented by', msg.n, '-> now', count);
		}
		if (msg.type === 'get') {
			Arc.send(msg.from, { type: 'value', count: count });
		}
		if (msg.type === 'stop') {
			Arc.log('counter: stopping with final value', count);
			Arc.send(msg.from, { type: 'stopped', count: count });
			return;
		}
	}
});

Arc.send(counter, { type: 'inc', n: 10 });
Arc.send(counter, { type: 'inc', n: 5 });
Arc.send(counter, { type: 'get', from: parent });

var result = Arc.receive(1000);
Arc.log('Main got counter value:', result.count);
Run it:
gleam run examples/counter_actor.js
Output:
counter: incremented by 10 -> now 10
counter: incremented by 5 -> now 15
Main got counter value: 15

Concurrent processes

Multiple processes running in parallel on the BEAM scheduler:
concurrent.js
function run(name, delay) {
	Arc.log(Arc.self(), 'is starting');
	let i = 1;
	while (true) {
		Arc.log(`${name} tick`, i);
		i = i + 1;
		Arc.sleep(delay);
	}
}

Arc.spawn(() => run('[Process A]', 50));
Arc.spawn(() => run('    [Process B]', 200));

Arc.log('  [Main] sleeping... watch A and B interleave!');
Arc.sleep(800);
Run it:
gleam run examples/concurrent.js
You’ll see Process A and Process B logging at different rates, interleaved by the BEAM scheduler.

Ping pong

Classic Erlang-style bidirectional message passing:
ping_pong.js
var pong_pid = Arc.spawn(() => {
	while (true) {
		var msg = Arc.receive();
		Arc.log('pong received:', msg.text, 'from', msg.from);
		Arc.send(msg.from, { text: 'pong', count: msg.count + 1, from: Arc.self() });
		if (msg.count >= 5) {
			return;
		}
	}
});

var count = 0;
Arc.send(pong_pid, { text: 'ping', count: count, from: Arc.self() });

while (count < 5) {
	var reply = Arc.receive(2000);
	if (reply === undefined) {
		Arc.log('Timed out!');
		count = 999;
	} else {
		Arc.log('ping received:', reply.text, 'count:', reply.count);
		count = reply.count;
		if (count < 5) {
			Arc.send(pong_pid, { text: 'ping', count: count, from: Arc.self() });
		}
	}
}
Run it:
gleam run examples/ping_pong.js

Ring benchmark

Pass a message around N processes M times - a classic Erlang concurrency benchmark:
ring.js
var N = 100; // number of processes in the ring
var M = 10;  // number of laps around the ring

var first = Arc.self();
var prev = first;

var i = N;
while (i > 0) {
	var next = prev;
	prev = Arc.spawn(() => {
		while (true) {
			var msg = Arc.receive();
			if (msg.type === 'stop') {
				return;
			}
			Arc.send(next, { type: 'token', lap: msg.lap, hops: msg.hops + 1 });
		}
	});
	i = i - 1;
}

Arc.log('Ring of', N, 'processes created. Sending token for', M, 'laps...');
Arc.send(prev, { type: 'token', lap: 0, hops: 0 });

var lap = 0;
while (lap < M) {
	var msg = Arc.receive(5000);
	lap = msg.lap + 1;
	Arc.log('Lap', lap, 'complete -', msg.hops, 'hops');
	if (lap < M) {
		Arc.send(prev, { type: 'token', lap: lap, hops: 0 });
	}
}
Run it:
gleam run examples/ring.js
This creates 100 lightweight processes and passes a token around the ring 10 times (1000 total message passes).

REPL commands

The Arc REPL includes several helpful commands:
/help   - Show help message
/clear  - Clear the console
/reset  - Reset the REPL state (new heap, fresh globals)
/exit   - Exit the REPL
/heap <expr> - Evaluate expression and show heap information

Process isolation demo

Arc processes are truly isolated - each spawned process gets a copy of the VM:
mutability.js
let i = 0;
const main = Arc.self();

Arc.spawn(() => {
	i = 10;
	Arc.log(Arc.self(), 'I set `i` to', i);
	Arc.send(main);
});

Arc.log(main, 'but I see that `i` is', i);
Run it:
gleam run examples/mutability.js
Output:
<0.XXX.0> I set `i` to 10
<0.YYY.0> but I see that `i` is 0
The child process’s mutation doesn’t affect the parent!

Next steps

API reference

Learn about all Arc primitives and built-in APIs

Core concepts

Understand Arc’s design and architecture

Actor programming

Master concurrent programming patterns

Architecture

Understand how Arc works under the hood

Build docs developers (and LLMs) love