typescript
37 lines · 9 steps
Building a self-stopping countdown timer
Convert a future date into days, hours, and minutes, then tick every minute until it expires.
Explained by
highlit
1type Countdown = {
2 days: number;
3 hours: number;
4 minutes: number;
5 expired: boolean;
6};
7
8function getCountdown(target: Date, now: Date = new Date()): Countdown {
9 const diff = target.getTime() - now.getTime();
10
11 if (diff <= 0) {
12 return { days: 0, hours: 0, minutes: 0, expired: true };
13 }
14
15 const totalMinutes = Math.floor(diff / 60_000);
16 const days = Math.floor(totalMinutes / (60 * 24));
17 const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
18 const minutes = totalMinutes % 60;
19
20 return { days, hours, minutes, expired: false };
21}
22
23function startCountdown(
24 target: Date,
25 onTick: (c: Countdown) => void,
26): () => void {
27 const tick = () => {
28 const countdown = getCountdown(target);
29 onTick(countdown);
30 if (countdown.expired) clearInterval(timer);
31 };
32
33 tick();
34 const timer = setInterval(tick, 60_000);
35
36 return () => clearInterval(timer);
37}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Reduce a time difference to whole units by dividing down and taking the remainder at each level.
- 2Returning a cleanup function lets callers cancel side effects like intervals cleanly.
- 3Running the first tick immediately avoids a blank interval before the first update fires.
Related explainers
typescript
type Middleware<TIn, TOut> = (ctx: TIn) => Promise<TOut> | TOut; class Pipeline<TIn, TOut> { private constructor(private readonly run: Middleware<TIn, TOut>) {}
A type-safe async middleware pipeline
generics
type-safety
middleware
Advanced
9 steps
typescript
export function isValidCardNumber(input: string): boolean { const digits = input.replace(/[\s-]/g, ""); if (!/^\d{12,19}$/.test(digits)) {
Validating card numbers with the Luhn check
luhn-algorithm
checksum
input-validation
Intermediate
7 steps
typescript
interface UserAgentInfo { browser: { name: string; version: string }; os: { name: string; version: string }; device: 'mobile' | 'tablet' | 'desktop';
Parsing a user-agent string with ordered rules
regex
parsing
pattern-matching
Intermediate
9 steps
typescript
import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, timer, throwError } from 'rxjs'; import { switchMap, takeWhile, filter, take, catchError } from 'rxjs/operators';
Polling a job until it finishes in Angular
rxjs
polling
observables
Intermediate
7 steps
typescript
type Flatten = Record<string, unknown>; function isPlainObject(value: unknown): value is Record<string, unknown> { return (
Flattening nested objects into dotted keys
recursion
reduce
type-guards
Intermediate
7 steps
rust
use once_cell::sync::Lazy; use regex::Regex; static CREDIT_CARD: Lazy<Regex> = Lazy::new(|| {
Redacting sensitive data from logs in Rust
regex
lazy-initialization
checksum-validation
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-self-stopping-countdown-timer-explained-typescript-59cc/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.