C++ Stopwatch Project
Start, stop, lap, and reset — precise timing down to milliseconds.
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
- Measuring elapsed time with steady_clock
- Start/stop state management
- Recording and displaying lap splits
- Formatting minutes, seconds, and milliseconds
Feature checklist
- Start and pause timing
- Lap button records split times
- Reset clears time and laps
- Display in MM:SS.mmm format
- Elapsed time stays accurate while paused
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
- Using system_clock, so a daylight-saving or NTP adjustment mid-timing makes the elapsed time jump or go negative.
- Trying to keep a running total updated by adding sleep intervals — drift accumulates; always derive elapsed from two time_points.
- Printing raw milliseconds without the MM:SS.mmm split, or forgetting % 60 so 90 seconds prints as 01:90.000.
Compile and run
g++ -std=c++17 -o stopwatch stopwatch.cppSample 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
> QTry 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.