Bits and Transistors — The Foundation of Everything
Every Dart variable, every Flutter widget, every app you build rests on a foundation of transistors switching between 0 and 1. Let's understand how.
The 1947 revolution
What do India's independence, a human flying at supersonic speed, and the transistor have in common? All three happened in 1947. And all three represent human resilience — our ability to break free and do extraordinary things. The transistor might seem like the least dramatic of these, but it has fundamentally changed how we live. When we hold a phone, billions of transistors sit in our palm, quietly performing intense calculations. Every photo we take, every message we send, every app we open — all of it powered by these tiny switches. But what exactly is a transistor? At its core, a transistor is a switch that can be controlled by electricity. Apply a voltage to its "gate", and it opens — allowing current to flow. Remove the voltage, and it closes. Simple enough. But here's the remarkable part: a modern transistor can switch between open and closed states up to 100 billion times per second. By the time we blink, a single transistor has switched more times than the number of seconds in three thousand years. This speed is what makes computation possible. We're not doing maths with pencil and paper. We're doing maths with switches that flip faster than we can comprehend. And from these humble switches, we build everything — from simple calculators to the Dart code running our Flutter apps.
Binary: the language of switches
A switch has two states: on or off. In computing, we represent these as 1 and 0. This is binary — the simplest possible number system.
Why not use more states? We could design a transistor with ten different voltage levels to represent digits 0–9. But distinguishing between subtle voltage differences is error-prone. Noise, heat, and manufacturing variations all introduce uncertainty. With only two states — clearly on or clearly off — the system becomes robust. We trade complexity for reliability.
So we use binary. And just as decimal uses place values (ones, tens, hundreds), binary uses powers of two:
In decimal, the number 42 means "four tens plus two ones." In binary, the number 00101010 means "one 32 plus one 8 plus one 2" — which is also 42. Same value, different representation.
Each binary digit is called a bit. It's the smallest unit of information a computer can store.
From bits to bytes
A single bit can represent only two values: 0 or 1, yes or no, true or false. That's not very useful on its own. We need to group bits together.
The standard grouping is 8 bits, called a byte. Why 8? Partly historical accident, partly practical. With 8 bits, we can represent 2⁸ = 256 different values (0 through 255). That's enough for:
• All uppercase and lowercase English letters
• Digits 0–9
• Punctuation and common symbols
• Some room to spare
The ASCII encoding standard assigns each character a number. 'A' is 65, 'B' is 66, 'a' is 97, and so on. This is how text becomes numbers, and numbers become bits. When we type the letter 'A', somewhere in our computer the bits 01000001 are being stored.
Larger values require more bytes. A 32-bit integer uses 4 bytes. A 64-bit integer uses 8 bytes. And Dart's int type, on native platforms, uses 64 bits — capable of representing values up to about 9 quintillion.
Logic gates: combining switches
A single transistor tells us if something is on or off. But real computation requires combining multiple signals. This is where logic gates come in.
A logic gate takes one or more binary inputs and produces one binary output according to a simple rule. The three fundamental gates are AND, OR, and NOT:
AND outputs 1 only when both inputs are 1. Think of it as multiplication: 1 × 1 = 1, anything else is 0.
OR outputs 1 when at least one input is 1. Think of it as "either or both."
NOT flips the input: 0 becomes 1, and 1 becomes 0.
These three gates, combined in various ways, can compute anything. Every processor in existence — from the chip in a digital watch to the one running your Dart code — is built from billions of these gates, organised into circuits that add, compare, store, and transform data.
Representing numbers
With bits, bytes, and logic gates, we can start representing actual numbers. But there's a question: how do we handle negative numbers? The simplest approach is unsigned representation. An 8-bit unsigned integer can hold values from 0 to 255. All 256 possible bit patterns map to non-negative numbers. But we often need negative numbers. The clever solution is two's complement. In this system, the leftmost bit indicates the sign: 0 for positive, 1 for negative. But it's not just a sign flag — the entire number representation changes.
// 8-bit two's complement examples
00000001 = 1
00000010 = 2
01111111 = 127 // largest positive
10000000 = -128 // most negative
11111111 = -1 // all 1s means -1
11111110 = -2
Why this strange pattern? Because it makes arithmetic work correctly. When we add 1 (00000001) and -1 (11111111) using normal binary addition, we get 100000000 — but the 9th bit overflows and is discarded, leaving 00000000. Zero. The maths just works.
Dart's int type uses 64-bit two's complement on native platforms. This gives us a range from about -9.2 quintillion to +9.2 quintillion — more than enough for most applications. On the web, integers are represented differently (using JavaScript's number type), but Dart abstracts this away for us.
Representing everything else
Numbers are straightforward, but what about text, images, and sound? Everything in a computer must ultimately become a sequence of bits. For text, we use encoding schemes. ASCII assigns numbers 0–127 to common characters. But ASCII only covers English. Unicode expands this dramatically, assigning numbers (called "code points") to over 150,000 characters from every writing system on Earth — including emoji.
// Dart code points
print('A'.codeUnitAt(0)); // 65
print('a'.codeUnitAt(0)); // 97
print('€'.codeUnitAt(0)); // 8364
print('🎯'.runes.first); // 127919
The dart emoji 🎯 is Unicode code point U+1F3AF (127919 in decimal). It doesn't fit in a single byte, or even two bytes. Dart internally uses UTF-16 encoding, where this emoji is represented as a "surrogate pair" — two 16-bit code units working together. This is why '🎯'.length returns 2, not 1. We'll explore this in depth in the Strings episode.
Images are just grids of colour values. Each pixel might be stored as three bytes: one for red intensity, one for green, one for blue. A 1920×1080 image has over 2 million pixels — about 6 megabytes of raw data.
Sound is sampled many times per second (44,100 times for CD quality), with each sample stored as a number representing the sound wave's amplitude at that moment.
Everything reduces to bits.
Why this matters for Dart
All of this might feel abstract. We're here to learn Dart and build Flutter apps, not design CPUs. So why does it matter?
Because understanding the foundation helps us understand the behaviour of our code. When a Dart int overflows, it's because we've exceeded the 64-bit limit. When floating-point arithmetic produces unexpected results, it's because of how IEEE 754 represents decimal numbers in binary. When strings behave strangely with emoji, it's because of Unicode encoding.
The Dart types we'll study in this series — bool, int, double, String — are abstractions built on these foundations. In the next episode, we'll start with the simplest: the boolean. A single bit of truth.
// Everything starts here
bool isReady = true; // One bit of information
int count = 42; // 64 bits of integer
double pi = 3.14159; // 64 bits of floating-point
String name = 'Dart'; // Multiple bytes of UTF-16
Welcome to the journey. Let's build our understanding from the ground up.
Bits and Transistors Quiz
7 questions
Test your understanding of binary representation and the foundations of computing.