Kelick Tech Hub

Build, learn & keep up — tech, Linux & sports news included.

C++ Stopwatch Project

Beginner•2 hours•<chrono>, state, formatting

Start, stop, lap, and reset — precise timing down to milliseconds.

Share on X

What you will build

A stopwatch with start, stop, lap, and reset, accurate to milliseconds. The core trick — capturing a start timestamp and computing elapsed time on demand instead of counting ticks — is how every profiler and game timer works.

What you will learn

Feature checklist

Starter code

#include <iostream>
#include <chrono>

using Clock = std::chrono::steady_clock;

long long elapsedMs(Clock::time_point start, bool running,
                    long long banked) {
    if (!running) return banked;
    return banked + std::chrono::duration_cast<
        std::chrono::milliseconds>(Clock::now() - start).count();
}

void printMs(long long ms) {
    long long m = ms / 60000, s = (ms / 1000) % 60, r = ms % 1000;
    std::cout << m << ":" << (s < 10 ? "0" : "") << s << "."
              << (r < 100 ? (r < 10 ? "00" : "0") : "") << r;
}

int main() {
    bool running = false; long long banked = 0;
    Clock::time_point start;
    char c;
    std::cout << "(S)tart/stop, (L)ap, (R)eset, (Q)uit\n";
    while (std::cin >> c && c != 'Q' && c != 'q') {
        if (c == 'S' || c == 's') {
            if (running) banked = elapsedMs(start, true, banked);
            else start = Clock::now();
            running = !running;
        } else if (c == 'L' || c == 'l') {
            std::cout << "Lap: "; printMs(elapsedMs(start, running, banked));
            std::cout << "\n";
        } else if (c == 'R' || c == 'r') {
            running = false; banked = 0;
        }
    }
}

Full walkthrough

Step 1: Measure time the chrono way, never by counting loops

A stopwatch that counts loop iterations drifts with CPU speed. The starter uses std::chrono::steady_clock — steady matters: unlike system_clock it never jumps backwards when the machine syncs its clock, so an elapsed measurement can never go negative mid-race.

Step 2: Capture a start point and compute elapsed on demand

Store a steady_clock::time_point start when timing begins. elapsedMs() returns the duration between start and now, cast to milliseconds. You never UPDATE the elapsed time — you derive it whenever asked, which is why pausing works so cleanly.

long long elapsedMs() {
    return std::chrono::duration_cast<std::chrono::milliseconds>(
        std::chrono::steady_clock::now() - start).count();
}

Step 3: Format milliseconds as MM:SS.mmm

printMs(long long ms) does the divisions: minutes = ms / 60000, seconds = (ms / 1000) % 60, millis = ms % 1000, printed with leading zeros via std::setw and std::setfill from <iomanip>. Formatting is a pure function of the number, so you can test it on paper: 83410 ms must print 01:23.410.

Step 4: Build the command loop

The console version is keyboard-driven: print the commands once — (S)tart, (P)ause, (L)ap, (R)eset, (Q)uit — then loop reading a char. Because std::cin waits for Enter, this design checks the time at each command rather than redrawing live; the lecture's structure keeps timing exact regardless.

Step 5: Make pause honest: bank the elapsed time

On pause, add elapsedMs() into a banked total and stop the clock. On resume, reset start to now. Displayed time = banked + (running ? elapsedMs() : 0). Two variables — a bank and a running start point — model the entire pause feature; the checklist's 'elapsed stays accurate while paused' falls straight out of it.

Step 6: Record laps as splits AND totals

On (L)ap, store the current total in a vector and print both the total and the split (total minus the previous lap's total). Athletes read splits; totals alone force them to do subtraction, and a lap feature that needs mental arithmetic is a lap feature nobody uses.

Step 7: Test against a real clock

Start it beside your phone's stopwatch and stop both together after ~30 seconds — agreement within a few hundred milliseconds (your reaction time) proves the chrono maths. Then pause for 10 seconds, resume, and confirm those 10 seconds are NOT in the total: that is the banking logic passing its exam.

Common mistakes

Compile and run

g++ -std=c++17 -o stopwatch stopwatch.cpp

Sample run

$ ./stopwatch
Stopwatch — (S)tart (P)ause (L)ap (R)eset (Q)uit
> S
Started.
> L
Lap 1: total 00:32.410  (split 00:32.410)
> L
Lap 2: total 01:05.208  (split 00:32.798)
> P
Paused at 01:12.950
> S
Resumed.
> P
Paused at 01:40.372  (the 10 seconds you spent paused were not counted)
> R
Reset to 00:00.000 — laps cleared
> Q

Try it in your browser

This project has a browser demo in the classic site. The demo is a browser re-implementation of the same logic, not the compiled C++ program: Start, stop, and record laps, timed to hundredths of a second.

Open the browser demo in the classic site →

Related Beginner projects

← All 60 projects · Classic site (Learn & Sandbox)

Download Fake GPS