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

Walkthrough

Space play step click any line
Three takeaways
  1. 1Reduce a time difference to whole units by dividing down and taking the remainder at each level.
  2. 2Returning a cleanup function lets callers cancel side effects like intervals cleanly.
  3. 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.

Building a self-stopping countdown timer — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code