Skip to the solution
HardMachine Coding12 min read

Build a Date Picker Calendar with Range Selection

668 views

The problem

Question presented to candidate: "We are a hotel booking site. Build the check-in / check-out calendar: two months side by side, the guest clicks a start date and an end date, and while they move the mouse we preview the stay. Some nights are already booked, and a stay can never include one. Tell the guest how many nights they picked, and make it work from the keyboard."

Requirements:

  • Two consecutive months visible, with previous / next navigation and a min / max window.
  • First pick sets check-in, second pick sets check-out; a pick before check-in restarts the range; a completed range restarts on the next pick.
  • While choosing check-out, hovering or keyboard-focusing a day previews the range up to that day.
  • Booked dates are not selectable, and the preview and check-out are capped at the night before the next booked date.
  • A live status line: the next step, the latest allowed check-out, and finally "Tue, Mar 3 to Mon, Mar 9, 6 nights".
  • Keyboard: arrows, Home / End, PageUp / PageDown move focus across both months (the view scrolls when focus leaves it), Enter selects, Escape cancels a half-made range. Emit [start, end] when complete.

What a strong answer should cover:

  • A small selection state machine (no start, choosing end, complete) and a pure pick(range, day) function.
  • Deriving cell states (start, end, in range, preview, unavailable) during render instead of storing them.
  • Capping the range at the first blocked date after check-in (maxEnd), and disabling every later day while choosing.
  • Counting nights with UTC day arithmetic: local midnights across a DST change are 23 or 25 hours apart, so dividing by 86,400,000 and rounding down with Math.floor is wrong.
  • A roving tabindex that spans two grids, focus-driven preview for keyboard users, aria-selected on the range and a polite live region.

Clarifying questions expected:

  • "Is the minimum stay one night, and is there a maximum stay?"
  • "Can check-out fall on a booked date (the guest leaves that morning), or must every day of the range be free?"
  • "Should clicking a date earlier than check-in move check-in, or swap the two?"

Code / implementation expected: Yes. A working two-month range picker; it may reuse the single-date picker's date helpers.

  • React
  • Calendar
  • Date
  • Accessibility

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

12 min read ยท 9 sections

React solution

Every file of the working solution. Open it in the playground to run and change it.

ReactComplete solution
import { useState } from "react";
import RangePicker from "./src/components/RangePicker";

const css = `
  body { margin: 0; background: #0f1115; color: #e6e6e6; font-family: system-ui, sans-serif; }
  .page { max-width: 720px; margin: 0 auto; padding: 24px 16px; }
  .rp { position: relative; background: #171a21; border: 1px solid #2c3340; border-radius: 12px; padding: 12px; }
  .rp-nav { position: absolute; top: 10px; left: 8px; right: 8px; display: flex; justify-content: space-between; pointer-events: none; }
  .rp-nav button { pointer-events: auto; background: none; border: 0; color: #c9d1d9; font-size: 20px; width: 32px; height: 32px; border-radius: 6px; cursor: pointer; }
  .rp-nav button:disabled { opacity: .3; }
  .rp-months { display: flex; flex-wrap: wrap; gap: 16px; }
  .rp-month { flex: 1 1 260px; }
  .rp-month h3 { text-align: center; font-size: 15px; margin: 6px 0 10px; }
  table { width: 100%; border-collapse: collapse; }
  th { font-size: 12px; color: #8b949e; font-weight: 500; padding: 4px 0; }
  td.day { text-align: center; padding: 8px 0; font-size: 14px; cursor: pointer; }
  td.day:focus { outline: 2px solid #f9a825; outline-offset: -2px; }
  td.range { background: #3d2810; }
  td.preview { background: #2a2214; box-shadow: inset 0 0 0 1px #6b5320; }
  td.start, td.end { background: #f9a825; color: #1c140a; font-weight: 700; }
  td.start { border-radius: 8px 0 0 8px; } td.end { border-radius: 0 8px 8px 0; }
  td.off { color: #474e5a; text-decoration: line-through; cursor: not-allowed; }
  .rp-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; border-top: 1px solid #2c3340; margin-top: 8px; padding-top: 8px; }
  .rp-foot p { margin: 0; font-size: 14px; color: #c9d1d9; }
  .rp-foot button { background: #222834; color: #e6e6e6; border: 1px solid #2c3340; border-radius: 8px; padding: 6px 12px; cursor: pointer; }
  .out { color: #9aa4b2; font-size: 14px; }
`;

const BOOKED = ["2026-03-12", "2026-03-13", "2026-03-27"];

export default function App() {
  const [stay, setStay] = useState(null);
  return (
    <main className="page">
      <style>{css}</style>
      <h1 style={{ fontSize: 18 }}>Book your stay</h1>
      <RangePicker
        initialMonth="2026-03-05"
        min="2026-03-02"
        max="2026-12-31"
        blocked={BOOKED}
        onChange={setStay}
      />
      <p className="out" data-testid="out">
        onChange: {stay ? stay.join(" to ") : "nothing yet"}
      </p>
    </main>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 90 of 107 in Machine coding

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextHard

Build a Canvas Drawing Board

Continue