Variables & Constants

Lesson 5 - Understanding unsigned long Data Type

Learn how unsigned long stores non-negative large whole numbers and why it is commonly used for millis() timing and long-running counters.

Progress indicator

Lesson 5 of 5

Learning Objectives

  • Understand that unsigned long stores only non-negative whole numbers and larger values than unsigned int.
  • Learn why unsigned long is commonly used with millis() timing in Arduino/ESP32 projects.
  • Compare long vs unsigned long and signed vs unsigned numbers in beginner-friendly language.
  • Explain overflow (wrap-around) and avoid common mistakes with long-running counters.
  • Read and explain a practical unsigned long timing example line by line.
  • Choose the right data type for timing, counters, and values that should never become negative.

Concept Explanation

What is unsigned long

unsigned long is a whole-number data type that stores only non-negative values (0 and positive numbers), but it can hold much larger values than unsigned int.

It is a common choice for values that keep increasing for a long time, such as elapsed milliseconds, uptime counters, and total event counts.

In beginner Arduino lessons, you will often see it when learning millis() because the board's running time grows continuously while the program keeps running.

unsigned long Syntax

Basic syntax:

unsigned long variableName;

You can also declare and initialize a value in the same line.

unsigned long currentTime;
unsigned long intervalMs = 1000;
unsigned long totalPulses = 0;

The words unsigned and long work together: long means a larger whole-number type, and unsigned means non-negative only.

Declaring unsigned long Variables

Declaring means creating a variable with a name and data type so your program can store a value later.

unsigned long currentTime;
unsigned long blinkCount;

Initializing unsigned long

Initializing means giving the variable a starting value. Counters often start at 0, and intervals may start at values like 1000 (1 second in milliseconds).

unsigned long blinkCount = 0;
unsigned long blinkInterval = 1000;

Clear initialization helps beginners debug faster because you always know the starting value before the loop begins.

unsigned long Range

unsigned long stores values from 0 to a very large positive value. On many Arduino/ESP32 examples, it is commonly treated as a 32-bit type with a range of 0 to 4,294,967,295.

That large range is one reason it is useful for long-running timing and counters that may grow for minutes, hours, or longer.

unsigned long Memory Size

In most beginner Arduino/ESP32 examples, unsigned long uses 4 bytes of memory. More bytes allow a larger range, which is why it is useful for time values from millis().

Beginner rule: if exact size matters for your board, check the board documentation. Do not assume every platform uses the same sizes for all data types.

When to Use unsigned long

Use unsigned long for timing values from millis(), large counters, uptime tracking, and values that should never be negative.

  • LED blink timing without blocking (millis-based timing)
  • How long a device has been running (uptime)
  • Total pulses/events counted over a long time
  • Intervals like 1000 ms, 5000 ms, 60000 ms

Real-Life Example

Think of a stopwatch display at a sports event. The elapsed time only moves forward from 0 and keeps increasing. It does not need negative values, so an unsigned timing variable is a natural fit.

Signed vs Unsigned Numbers

Signed numbers can represent positive and negative values. Unsigned numbers can represent only zero and positive values.

Signed

Examples: -5, 0, 25

Unsigned

Examples: 0, 1, 1000 (but not -1)

Real-life comparison: temperature may need signed values (below zero is possible), but elapsed time on a running device is typically unsigned because it starts at 0 and increases.

unsigned long vs long (Comparison)

Featurelongunsigned long
Negative valuesYesNo
Timing (millis)Can be taught conceptuallyCommon practical choice
Wrap-around behaviorDifferent signed behaviorWraps to low positive value

Beginner takeaway: if you are storing a large value that should not go below zero (especially timing from millis()), unsigned long is usually the better choice.

Why unsigned long Cannot Store Negative Values

Because unsigned long uses its bits to represent only non-negative numbers, it does not include a sign (minus) representation. That is why values like -1 cannot be stored correctly as a negative number.

If your logic needs to go below zero, use a signed type like long instead.

Beginner warning

If you subtract too much from an unsigned value, you may not get a negative number. You may get a very large positive number because of wrap-around.

Code Example

This example uses unsigned long for timing and a blink counter.

unsigned long lastBlinkTime = 0;
unsigned long blinkInterval = 1000;
unsigned long blinkCount = 0;

void setup() {
  pinMode(2, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  unsigned long currentTime = millis();

  if (currentTime - lastBlinkTime >= blinkInterval) {
    digitalWrite(2, HIGH);
    delay(100);
    digitalWrite(2, LOW);

    blinkCount = blinkCount + 1;
    lastBlinkTime = currentTime;
    Serial.println("Blink cycle complete");
  }
}

Why this example is useful

This example combines two common uses of unsigned long: timing (using lastBlinkTime and blinkInterval) and a long-running counter (using blinkCount).

Practical Example Explanation (Line by Line)

  1. unsigned long lastBlinkTime = 0; creates a non-negative timing variable to store the last blink timestamp.
  2. unsigned long blinkInterval = 1000; sets the blink interval to 1000 milliseconds (1 second).
  3. unsigned long blinkCount = 0; creates a long-running counter for completed blink cycles.
  4. pinMode(2, OUTPUT); prepares GPIO 2 as an output for the LED.
  5. Serial.begin(115200); starts serial output so you can observe program behavior.
  6. unsigned long currentTime = millis(); reads the current board runtime in milliseconds.
  7. if (currentTime - lastBlinkTime >= blinkInterval) checks if enough time has passed since the last blink event.
  8. Inside the if block, the LED blinks quickly, the counter increases, and the new timestamp is saved.

Real-life example

Think of a digital sign that updates every 1 second. It checks the current time, compares it to the last update time, and only updates when enough time has passed.

Beginner reading tip

When reading timing code, focus on 3 parts first: currentTime, lastBlinkTime, and blinkInterval. Most millis-based examples are built around this same pattern.

Overflow and Wrap-Around

If an unsigned long reaches its maximum value and increases again, it wraps back to 0 instead of becoming negative. This is called wrap-around.

unsigned long counter = 4294967295;
counter = counter + 1;  // Wraps to 0 on common 32-bit boards

This behavior is very important in long-running timing code and counters.

Overflow

Overflow means a value goes past the maximum value that the data type can store.

Wrap-around

Wrap-around is what happens next for unsigned types: the value returns to a low value (often 0) and continues from there.

Good timing logic compares elapsed time (current - previous) so it can continue working correctly even when wrap-around eventually happens.

Common Mistakes with unsigned long

  • Using int instead of unsigned long for millis() timing values.
  • Assuming unsigned long can store negative values.
  • Forgetting that wrap-around can happen in long-running programs.
  • Comparing times incorrectly without subtracting old time from current time.
  • Mixing delay()-style thinking with millis()-style timing and getting confused about when code runs.

Best Practices for unsigned long

  • Use unsigned long for millis()-based timing.
  • Initialize timing variables and counters clearly (often to 0).
  • Use clear names like currentTime, lastBlinkTime, and intervalMs.
  • Use elapsed-time comparison (current - previous) for timing checks.
  • Remember wrap-around and design timing logic that still works after long runtimes.

Practice Task

  1. Change blinkInterval from 1000 to 2000 and explain how the LED behavior changes.
  2. Rename lastBlinkTime to lastToggleTime and explain why the new name may be clearer.
  3. Add another unsigned long variable like totalOnTimeMs and describe how you would increase it.
  4. Explain why unsigned long is a better choice than int for millis()-based timing values.

Try it now

Open the simulator workspace to practice timing ideas and compare how variables change while stepping through examples.

Run in Simulator