Skip to the notes
JSGroundwork
JSGroundwork handwritten · web dev
✎Playground→⌘Problems↻Review🔥Progress

Chapters

20 chapters
⌕
Beginner15›
00Scouting reportR1Screening callR2Machine codingR3JavaScript & TSR3·TSTypeScriptR4React & Next.jsR5Node & NestJSR6Databases & RedisR7DSA roundR8System designR9AWS, Docker, CI/CDR10Resume grillingR11BehaviouralR12HR & the number✓The week before
Advanced5›
50LWhat changes at ₹50L50LHard DSA50LDistributed systems50LRuntime internals50LStaff behavioural
/ search[ ] chaptert top

Ready to read

JSJavaScript⑂Git◎Interview prepΣDSA in JSSDSystem Design
More topics15›
</>HTML{ }CSS⚛ReactNNext.jsNeNest.jsTSTypeScriptNoNode.js🐳DockerDBSQL & Databases✓Testing🔒Web Security☁Cloud & DevOps◈GraphQL◆Redis☸Kubernetes
100%
R2

Machine coding

Length90–120 min
WhoSenior engineer, often silent
DecidesThe startup loop, almost entirely
Fail modeOne giant file, nothing running
productsaasagencyservice

You get a small product spec and a laptop. There is no algorithm in this round — the entire test is whether code you write under pressure looks like code from someone who has shipped. Your background should make this your strongest round, and it is where unprepared candidates with better resumes lose.

WeightWhat is scoredWhat it means in the room
30%It worksThe happy path runs end to end. A beautiful non-working submission scores below an ugly working one, every time.
25%Separation of concernsComponents, hooks, services, types in separate files with clear boundaries. No business logic inside a JSX return.
20%ExtensibilityThey will say "now add X" in the last ten minutes. If that takes thirty seconds, you pass.
15%States & edgesLoading, empty, error, disabled-while-submitting. Most candidates ship only the success state.
10%Naming & typesNo any. No data2. Types that describe the domain, not the shape of a fetch response.
The first ten minutes

Do not open the editor. Spend six to eight minutes out loud: restate the requirements in your own words, ask two clarifying questions, say what you will build and what you are explicitly leaving out, then sketch the file structure. Interviewers score this. Candidates who start typing at second thirty almost always finish with a mess.

2.1
Build a task board with drag-and-drop between three columns, persisted locally.
What they are really testingData modelling. The drag is theatre; the state shape is the exam.

Skip a drag library unless it is explicitly allowed — the HTML5 dragstart / dragover / drop trio is enough and shows you know the platform. The decision they are watching for is normalised state:

the shape that makes reordering trivial
type Task = { id: string; title: string; createdAt: number }

type Board = {
  tasks: Record<string, Task>          // id → task, one source of truth
  columns: Record<string, string[]>    // columnId → ordered ids
  order: string[]                      // column order
}

// moving a card is two array operations, not a tree walk
function move(b: Board, id: string, from: string, to: string, at: number): Board {
  const src = b.columns[from].filter(x => x !== id)
  const dst = from === to ? src : [...b.columns[to]]
  dst.splice(at, 0, id)
  return { ...b, columns: { ...b.columns, [from]: src, [to]: dst } }
}

Nested arrays of task objects force a deep clone on every drag and make "which column is this card in" an O(n·m) search. Say that out loud as you choose — the reasoning scores higher than the result.

They will push with
  • Now persist it and reload without losing order.
  • Add an undo.
  • What happens if two tabs are open?
2.2
Build a searchable, filterable, paginated data table over a mock API.
What they are really testingWhether three interacting features fight each other. Most candidates build them independently and produce bugs.

Derive the visible rows in one pipeline, memoised, and reset the page index whenever the filter or query changes. That reset is the bug they are looking for: filter down to three results while on page 4 and an unprepared implementation renders an empty table.

const rows = useMemo(() => {
  const q = query.trim().toLowerCase()
  return data
    .filter(r => !q || r.name.toLowerCase().includes(q))
    .filter(r => !status || r.status === status)
    .sort(BY[sortKey])
}, [data, query, status, sortKey])

const pageRows = rows.slice(page * SIZE, page * SIZE + SIZE)

useEffect(() => { setPage(0) }, [query, status])  // the bit everyone forgets

Debounce the search input at ~300ms and say why. Put the fetch in a custom hook (useTasks), never inline in the component body — that single move is most of the "separation of concerns" score.

They will push with
  • Now make sorting server-side.
  • The dataset is 50,000 rows — what changes?
  • Make the filter state survive a page refresh.
2.3
Build a multi-step form with per-step validation and a review step.
What they are really testingExtensibility. The follow-up is always "add a fourth step" and they are timing you.

Drive the steps from a config array, never a switch. Adding a step then means adding one object.

const STEPS = [
  { id: 'account', title: 'Account', fields: ['email','password'], schema: accountSchema },
  { id: 'profile', title: 'Profile', fields: ['name','phone'],     schema: profileSchema },
  { id: 'review',  title: 'Review',  fields: [],                   schema: null },
]

const step = STEPS[i]
const errors = step.schema ? validate(step.schema, form) : {}
const canNext = Object.keys(errors).length === 0

Keep every step's data in one parent object rather than per-step state, so the review step is a read and going back does not lose input. Mention Zod and derive the TypeScript types from the schema with z.infer, so the runtime check and the compile-time type cannot drift apart.

They will push with
  • Add a fourth step.
  • Persist a half-finished form and resume it.
  • The last step submits — handle the failure.
2.4
Build a booking or calendar slot picker with conflict detection.
What they are really testingInterval logic and timezones. Directly on your resume, so expect it.

Say up front that you have shipped a booking marketplace — it buys you credibility for the whole round. Then model slots as half-open intervals [start, end), which makes back-to-back slots not overlap, and write the predicate as a named pure function:

// half-open: [aStart, aEnd) and [bStart, bEnd)
const overlaps = (a: Slot, b: Slot) =>
  a.start < b.end && b.start < a.end

// 10:00–11:00 and 11:00–12:00 → false. Correct.
// Using <= here is the classic off-by-one that breaks adjacency.

Keep everything in UTC internally and format only at the edge. If they ask about recurring slots, the answer is: store the rule, expand to concrete instances for the visible window, and store exceptions separately — never store a thousand rows for a weekly repeat.

They will push with
  • Two people book the last seat at the same moment.
  • Handle a user in a different timezone.
  • Now support recurring weekly slots.
2.5
Backend variant — build a small REST API in NestJS.
What they are really testingWhether the layering on your resume is real when you have to type it in ninety minutes.

Given your stack this may replace the frontend problem. Scaffold with the CLI, then make the layering visible: controller does HTTP only, service holds logic, repository touches data, DTOs validate at the boundary, one module per domain, a global exception filter.

src/
  bookings/
    bookings.controller.ts   // HTTP only — no logic
    bookings.service.ts      // the actual rules
    bookings.repository.ts   // data access
    dto/create-booking.dto.ts
    entities/booking.entity.ts
    bookings.module.ts
  common/
    filters/http-exception.filter.ts
    interceptors/logging.interceptor.ts
They will push with
  • Add pagination.
  • Make POST idempotent.
  • Where would you put a transaction?
2.6
What do you say out loud while you are coding?
What they are really testingNothing is being asked here — but the interviewer is scoring your narration, and silence scores zero.
  • "I am building the data model first, because everything else falls out of it."
  • "I am storing this normalised so a reorder is a splice rather than a deep clone."
  • "This goes in a hook so the component stays presentational and the logic is testable on its own."
  • "Debouncing here at 300ms — without it we fire a request per keystroke."
  • "I am doing the empty state now rather than at the end, because it is the state people actually see first."
  • "I would use Zod here in real code; for time I am hand-rolling it, but I would not ship this."
  • "This is the seam where optimistic updates would go."
  • "If this list went past a few thousand rows I would virtualise it. I am not doing that now because it is not the bottleneck at this size."
  • "That is the happy path working. With the remaining twenty minutes I will add the error state, then tidy naming."

The last one matters most. Announcing your plan for the time you have left is the single clearest signal of a senior candidate rather than a fast one.

2.7
The file skeleton to have in your fingers
What they are really testingSpeed. If you have to invent a structure under time pressure you will invent a bad one.

Rehearse this until you can produce it without thinking:

src/
  components/        // presentational only: props in, events out
  features/<name>/   // the feature: its hooks, components, types
  hooks/             // useDebounce, useLocalStorage, useFetch
  lib/               // api client, formatters, pure helpers
  types/             // domain types

Two rules that survive contact with a timer: a component that fetches is a feature, not a component; and anything you would unit-test goes in lib/ or a hook, never inside JSX.

←previousScreening call↑ CovernextJavaScript & TS→