Beginner2-3 hours12+4 parts needed

Parent info

Cost: ~$28
Time: 2-3 hours
Age: 12+
Difficulty: ●●●
Soldering: No soldering needed
What they'll learn: Arduino programming, LED circuits

Parts you need

Affiliate links — we may earn a small commission

Arduino UNO R3 (or compatible)
27x LEDs (5mm, any color)
Breadboard + jumper wires
9x 220Ohm resistors

27 LEDs. 12 wires. One glowing cube that makes people say “wait, YOU built that?”

A 3x3x3 LED cube sits on your desk and cycles through light patterns — layers sweeping up, rain falling down, stars sparkling randomly. It looks like something from a sci-fi movie, but you can build it in an afternoon with parts that cost less than a pizza.

The best part? The code is short enough to fit on your phone screen, and you’ll understand every single line by the end.


What you’ll need

Part What it does Price
Arduino UNO R3 The brain. Reads your code and controls the LEDs. ~$12
27x LEDs (5mm) The lights. 9 per layer, 3 layers. Any color works. ~$5
Breadboard + jumper wires Connects everything without soldering. ~$8
9x 220Ohm resistors Protect LEDs from getting too much power (like a speed limit for electricity). ~$3

Total: ~$28 | Time: ~2-3 hours | Difficulty: ●●○○○


Step 1: Understand the Layout

A 3x3x3 cube has 3 layers of 9 LEDs each (27 total). Each layer shares a common ground (cathode), and each column shares a common positive (anode). This lets us control 27 LEDs with just 12 Arduino pins (9 columns + 3 layers).

Think of it like an apartment building: each floor is a layer, and each apartment is a column position. To turn on a specific apartment’s light, you need to know both the floor AND the apartment number.


Step 2: Prepare the LEDs

Bend the cathode (shorter leg) of each LED at 90 degrees. This will connect to the layer wire. Test each LED with a coin cell battery before building — finding a dead LED after assembly is painful.

Check: Every LED lights up when you touch the longer leg to the + side of a coin cell and the shorter leg to the - side. If one doesn’t glow, set it aside.


Step 3: Build Layer by Layer

  1. Create a 3x3 grid jig using cardboard with 9 holes
  2. Insert 9 LEDs into the jig
  3. Connect all cathodes in the layer together with wire
  4. Repeat for all 3 layers
  5. Stack the layers and connect the 9 column anodes

Check: Each layer should have 9 LEDs with all their short legs connected together. You should see 3 flat grids ready to stack.


Step 4: Wire to Arduino

Connect the 9 column wires to Arduino digital pins 2-10 (each through a 220Ohm resistor). Connect the 3 layer wires to pins 11, 12, and 13.

Check: Count your wires. You should have 12 connections total: 9 columns (pins 2-10) and 3 layers (pins 11-13). Each column wire has a resistor in series.


Step 5: Upload the Code

The big picture first. This program controls 27 LEDs using only 12 Arduino pins:

  • The cube has 3 layers (bottom, middle, top) and 9 columns (one per LED position in a layer).
  • To light a single LED: turn on its column AND its layer at the same time. Only the LED at the intersection glows.
  • By rapidly cycling through layers and columns, the eye sees all LEDs at once — even though only one is on at any moment. This trick is called multiplexing.
  • The code has three patterns: all LEDs per layer, a rain effect, and random sparkle.

A program is like a recipe. The Arduino reads it top to bottom and does exactly what’s written. Copy this entire recipe into Arduino IDE and upload it:

const int columns[] = {2, 3, 4, 5, 6, 7, 8, 9, 10};
const int layers[] = {11, 12, 13};

void setup() {
  for (int i = 0; i < 9; i++) pinMode(columns[i], OUTPUT);
  for (int i = 0; i < 3; i++) pinMode(layers[i], OUTPUT);
}

void allOff() {
  for (int i = 0; i < 9; i++) digitalWrite(columns[i], LOW);
  for (int i = 0; i < 3; i++) digitalWrite(layers[i], HIGH);
}

void layerOn(int layer) {
  digitalWrite(layers[layer], LOW);
}

void columnOn(int col) {
  digitalWrite(columns[col], HIGH);
}

void loop() {
  for (int l = 0; l < 3; l++) {
    allOff();
    layerOn(l);
    for (int c = 0; c < 9; c++) columnOn(c);
    delay(300);
  }

  for (int c = 0; c < 9; c++) {
    for (int l = 2; l >= 0; l--) {
      allOff();
      layerOn(l);
      columnOn(c);
      delay(100);
    }
  }

  for (int i = 0; i < 30; i++) {
    allOff();
    layerOn(random(3));
    columnOn(random(9));
    delay(80);
  }
}

Check: After uploading, your cube should immediately start cycling through three patterns: all LEDs per layer, rain drops falling, and random sparkles. If nothing happens, see Troubleshooting at the bottom.


Line-by-line: what every line does and why

Lines 1-2: The pin lists

const int columns[] = {2, 3, 4, 5, 6, 7, 8, 9, 10};
const int layers[] = {11, 12, 13};

const int means “a whole number that never changes.” columns[] is a shelf with 9 slots — one Arduino pin number per column. layers[] is a shelf with 3 slots — one pin per layer. The square brackets [] tell Arduino to make the shelf exactly as long as the list inside the curly braces.


Lines 4-7: setup() — the morning routine

void setup() {
  for (int i = 0; i < 9; i++) pinMode(columns[i], OUTPUT);
  for (int i = 0; i < 3; i++) pinMode(layers[i], OUTPUT);
}

setup() runs once when you plug in power. pinMode(pin, OUTPUT) sets a pin as an output — meaning the Arduino controls it (sends electricity out) rather than listens to it. The for loops do this for all 9 column pins and then all 3 layer pins. i starts at 0, increases by 1 each time (i++), and stops when it equals the limit (9 or 3).


Lines 9-13: allOff() — the blank slate

void allOff() {
  for (int i = 0; i < 9; i++) digitalWrite(columns[i], LOW);
  for (int i = 0; i < 3; i++) digitalWrite(layers[i], HIGH);
}

This function turns every LED off. digitalWrite(pin, LOW) makes a pin output 0 volts (off for columns). digitalWrite(pin, HIGH) makes a pin output 5 volts (but for layers, HIGH means off — because layers use common cathode wiring, so HIGH disconnects the ground path).

Think of it like a light switch that works backwards for layers: LOW = connected to ground = LEDs can light up. HIGH = disconnected = LEDs cannot light up.


Lines 15-17: layerOn() — enabling one floor

void layerOn(int layer) {
  digitalWrite(layers[layer], LOW);
}

This function receives a number (0, 1, or 2) and pulls that layer’s pin LOW — connecting the ground path for that entire layer. Now any column that goes HIGH will light up on this layer. layers[layer] uses the number as an index into the layers shelf — like picking the 2nd item from the list.


Lines 19-21: columnOn() — lighting one LED

void columnOn(int col) {
  digitalWrite(columns[col], HIGH);
}

This sends 5V to the specified column pin. If a layer is also active, the LED at that column + layer intersection glows. Only one LED per column + layer combination can be on at once.


Pattern 1: Layer by layer (lines 24-29)

for (int l = 0; l < 3; l++) {
  allOff();
  layerOn(l);
  for (int c = 0; c < 9; c++) columnOn(c);
  delay(300);
}

The outer for loop runs 3 times (l = 0, 1, 2 — one per layer). For each layer: wipe everything off, activate that layer, then turn on all 9 columns one by one. delay(300) holds this for 300 milliseconds before moving to the next layer. You see the cube light up one floor at a time.


Pattern 2: Rain effect (lines 32-38)

for (int c = 0; c < 9; c++) {
  for (int l = 2; l >= 0; l--) {
    allOff();
    layerOn(l);
    columnOn(c);
    delay(100);
  }
}

This is a nested loop — a loop inside a loop. The outer loop steps through each column (0 to 8). For each column, the inner loop starts at layer 2 (top) and counts DOWN to 0 (bottom): l = 2; l >= 0; l--. l-- means “subtract 1 each time.” This makes a single LED drop from top to bottom on each column, one after another — like a raindrop falling.


Pattern 3: Random sparkle (lines 40-44)

for (int i = 0; i < 30; i++) {
  allOff();
  layerOn(random(3));
  columnOn(random(9));
  delay(80);
}

random(3) picks a random whole number from 0 to 2. random(9) picks 0 to 8. This creates 30 random single-LED flashes, each lasting 80ms. The overall effect looks like stars sparkling.


The whole thing in one sentence

When powered on, setup() configures all pins as outputs. Then loop() repeats forever: three patterns in sequence — layer sweep, rain drop, random sparkle — each pattern made of nested for loops that rapidly switch individual LEDs on and off.

First thing to try: change the first delay(300) to delay(50) and upload. The layer sweep will become so fast you can’t see individual layers — they all appear lit simultaneously. This is how multiplexing creates the illusion of all LEDs being on at once. Then change it back to 300 to see each layer separately.


What just happened (what you learned)

You might not realize it, but you just used some real engineering concepts:

  • Multiplexing — you controlled 27 LEDs with only 12 pins. That’s the same trick used inside your TV screen, which has millions of pixels but doesn’t have millions of wires. Instead, it scans through rows and columns so fast your eyes think everything is on at once.

  • Arrays — columns[] and layers[] are arrays (shelves with numbered slots). Instead of creating 9 separate variables like column1, column2, column3… you put them all on one shelf and grab them by number. Every program that handles lists of things — playlists, contacts, game scores — uses arrays.

  • Functions — allOff(), layerOn(), and columnOn() are functions. A function is a recipe with a name. Instead of writing the same 5 lines every time you want to turn everything off, you write them once inside allOff() and then just call the name. It’s like saving a contact in your phone instead of dialing the number every time.

  • Nested loops — the rain effect uses a loop inside a loop. The outer loop picks a column, the inner loop drops through layers. This is how computers process grids — spreadsheets, images, game boards — anything with rows AND columns needs a nested loop.


Level Up

Slow-motion mode: Change all delay() values to 1000 (one second). Now you can watch each individual LED turn on and off. This is the best way to understand how multiplexing really works — you’ll see that only one LED is ever on at a time.

New pattern — spiral: Instead of lighting columns 0-8 in order, light them in a spiral: 0, 1, 2, 5, 8, 7, 6, 3, 4. Create a new array const int spiral[] = {0, 1, 2, 5, 8, 7, 6, 3, 4}; and use columnOn(spiral[c]) in a new pattern block.

Sound-reactive cube: Add a microphone module (~$2) to an analog pin. Read the sound level with analogRead() and use it to control how many LEDs light up — quiet room = few LEDs, loud music = full cube.

RGB upgrade: Replace the single-color LEDs with WS2812 RGB LEDs and use the FastLED library. Now each LED can be any color. The wiring is actually simpler — just one data wire chains through all 27 LEDs.


Troubleshooting

Problem Fix
No LEDs light up at all Check USB cable is connected and code uploaded (TX/RX LEDs flash during upload). Check that resistors are on column wires, not layer wires.
Only one layer works The layer pin (11, 12, or 13) for the dead layers might not be connected. Trace the wire from the layer’s common cathode back to the Arduino pin.
LEDs are very dim You might have the resistor values too high. 220Ohm is correct for 5V. If you used 1KOhm or 10KOhm by mistake, swap them out.
Wrong LEDs light up Your column wires are probably swapped. It won’t break anything — just rearrange the numbers in the columns[] array to match your actual wiring.
LEDs flicker or look uneven This is normal with multiplexing at slow speeds. Decrease the delay() values to make the scanning faster. Below 10ms, flickering disappears.
Code won’t upload Select the correct board (Tools > Board > Arduino UNO) and port (Tools > Port). On Mac/Linux, the port looks like /dev/ttyUSB0. On Windows, it’s COM3 or similar.

Affiliate disclosure: Some links on this page are affiliate links. If you buy through them, we may earn a small commission at no extra cost to you.