ZetCode

Dart BytesBuilder

last modified October 18, 2026

The BytesBuilder class in Dart provides an efficient way to build lists of bytes dynamically. It's useful for network protocols, file operations, and binary data manipulation.

BytesBuilder collects the added bytes and joins them when the result is requested, which avoids copying the data over and over. It is part of the dart:typed_data library.

Basic Definition

BytesBuilder is a mutable collection for building byte lists. It grows automatically as needed and provides methods to add bytes. The toBytes method returns a Uint8List with a copy of the content, and takeBytes returns the content and empties the builder.

The constructor has one named parameter, copy, which is true by default. With the default, the builder copies every list that is added; with BytesBuilder(copy: false) it keeps references to the added Uint8List objects, which is faster but means that those lists must not be modified afterwards.

Basic BytesBuilder Usage

This example shows basic byte collection using BytesBuilder.

main.dart
import 'dart:typed_data';

void main() {
  final builder = BytesBuilder();

  builder.addByte(65); // 'A'
  builder.addByte(66); // 'B'
  builder.addByte(67); // 'C'

  final bytes = builder.toBytes();
  print(bytes);
  print(String.fromCharCodes(bytes));
}

We create a BytesBuilder, add individual bytes, then extract the final byte list. The builder handles memory management internally as bytes are added. The bytes are interpreted as ASCII codes in the last line. Each added value must be in the range 0-255; larger values are truncated to the low eight bits.

$ dart main.dart
[65, 66, 67]
ABC

Adding Byte Lists

This example demonstrates adding entire lists of bytes at once.

main.dart
import 'dart:typed_data';

void main() {
  final builder = BytesBuilder();

  builder.add([1, 2, 3]);
  builder.add([4, 5, 6]);

  print(builder.toBytes());
  print('Length: ${builder.length}');
  print('Empty: ${builder.isEmpty}');
}

We add multiple byte lists which are concatenated. The length property tracks the total number of bytes added. Adding a list at once is more efficient than adding the bytes individually.

$ dart main.dart
[1, 2, 3, 4, 5, 6]
Length: 6
Empty: false

Building Binary Data

This example shows building structured binary data with BytesBuilder.

main.dart
import 'dart:typed_data';

void main() {
  final builder = BytesBuilder();
  final payload = 'Hello'.codeUnits;

  // header
  builder.add([0xAA, 0xBB]);

  // payload length (2 bytes, big endian)
  builder.addByte(payload.length >> 8);
  builder.addByte(payload.length & 0xFF);

  // payload
  builder.add(payload);

  // checksum: sum of all bytes so far, modulo 256
  final checksum = builder.toBytes().fold(0, (sum, byte) => sum + byte) & 0xFF;
  builder.addByte(checksum);

  print(builder.toBytes());
}

We construct a binary message with header, length, payload and checksum. The checksum is computed from the bytes collected so far and appended as the last byte. The sum of the bytes 170, 187, 0, 5, 72, 101, 108, 108 and 111 is 862, and 862 & 255 is 94.

$ dart main.dart
[170, 187, 0, 5, 72, 101, 108, 108, 111, 94]

Writing numbers with ByteData

BytesBuilder only accepts bytes. To write multi-byte numbers in a well-defined byte order, we can combine it with ByteData, which has methods such as setUint32 and setFloat64 with an Endian argument.

main.dart
import 'dart:typed_data';

Uint8List uint32(int value, Endian endian) {
  final data = ByteData(4)..setUint32(0, value, endian);
  return data.buffer.asUint8List();
}

void main() {
  final builder = BytesBuilder();

  builder.add(uint32(1, Endian.big));
  builder.add(uint32(1, Endian.little));
  builder.add(uint32(0xCAFEBABE, Endian.big));

  print(builder.toBytes());
}

The number 1 is stored as 0 0 0 1 in big-endian (network) order and as 1 0 0 0 in little-endian order. The helper function converts a 32-bit number to four bytes.

$ dart main.dart
[0, 0, 0, 1, 1, 0, 0, 0, 202, 254, 186, 190]

Clearing and Reusing

This example demonstrates clearing the builder for reuse. The takeBytes method returns the content and clears the builder in one step.

main.dart
import 'dart:typed_data';

void main() {
  final builder = BytesBuilder();

  builder.add([1, 2, 3]);
  print('First build: ${builder.toBytes()}');

  builder.clear();
  builder.add([4, 5, 6]);
  print('Second build: ${builder.takeBytes()}');

  print('Length: ${builder.length}');
}

The clear method discards the collected bytes. After takeBytes, the builder is empty, so its length is zero, and it can be used again. The takeBytes call is cheaper than toBytes followed by clear, because the content does not have to be copied.

$ dart main.dart
First build: [1, 2, 3]
Second build: [4, 5, 6]
Length: 0

Performance Comparison

This example compares BytesBuilder with manual list concatenation.

main.dart
import 'dart:math';
import 'dart:typed_data';

void main() {
  final random = Random(1);
  final data = List.generate(1000, (_) => random.nextInt(256));

  final stopwatch = Stopwatch()..start();
  final builder = BytesBuilder();
  for (var i = 0; i < 400; i++) {
    builder.add(data);
  }
  final fast = builder.toBytes();
  final builderTime = stopwatch.elapsedMicroseconds;

  stopwatch.reset();
  var manual = <int>[];
  for (var i = 0; i < 400; i++) {
    manual = [...manual, ...data];
  }
  final manualTime = stopwatch.elapsedMicroseconds;

  print('BytesBuilder: $builderTime μs');
  print('Manual: $manualTime μs');
  print('Same result: ${fast.length == manual.length}');
}

BytesBuilder is significantly faster for large concatenations as it minimizes copies; the manual concatenation copies the whole list again and again, so the work grows quadratically. The exact timings depend on the machine and the Dart VM; run the example to see the values for your system. The relative difference is what matters.

$ dart main.dart
BytesBuilder: 8322 μs
Manual: 1519735 μs
Same result: true

Best Practices

Source

Dart BytesBuilder Documentation

This tutorial covered Dart's BytesBuilder class with practical examples showing basic usage, performance benefits, and common patterns for byte manipulation.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.