PracHub
QuestionsPremiumCoachesLearningGuidesInterview Prep
|Home/Coding & Algorithms/Figma

Implement Layer History and Grid Counting

Last updated: Jun 21, 2026

Quick Overview

This question evaluates a candidate's proficiency in mutable state management and reversible operations through implementing an undo/redo layer editor with atomic batching, and their ability to reason about connectivity and algorithmic complexity by counting connected land regions in a grid.

  • medium
  • Figma
  • Coding & Algorithms
  • Machine Learning Engineer

Implement Layer History and Grid Counting

Company: Figma

Role: Machine Learning Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Onsite

The coding portion of this loop covered two independent tasks. Implement each cleanly, handle the edge cases, and analyze the time and space complexity. ### Constraints & Assumptions - **Task 1 (layer editor):** `id`s are unique (strings or integers). The `index` for `addLayer` and the `newIndex` for `moveLayer` are valid positions in the current ordering ($0 \le \text{index} \le n$ for insert, $0 \le \text{newIndex} < n$ for move). Batches are **not nested** (no `beginBatch()` while a batch is already open). Edits outside a batch each form their own history entry; edits inside a batch collapse into **one** atomic entry. `undo()` / `redo()` on an empty stack are no-ops. The number of layers fits comfortably in memory; favor correctness and clear semantics over micro-optimization. - **Task 2 (grid counting):** The grid has $R$ rows and $C$ columns of characters, each `'L'` (land) or `'W'` (water). Adjacency is **4-directional** (up/down/left/right) only — diagonals do **not** connect. The grid may be empty. You may mutate the grid in place if you wish. ### Clarifying Questions to Ask - Task 1: Can `beginBatch()` be called twice without an intervening `endBatch()`, and what should happen to an **empty** batch (no sub-edits before `endBatch`)? - Task 1: For `moveLayer`, is `newIndex` interpreted against the list **before** or **after** the element is removed from its old slot? - Task 1: Should `addLayer` with a duplicate `id`, or `removeLayer` / `moveLayer` of a missing `id`, raise an error or be a silent no-op? - Task 2: Is adjacency 4-directional or 8-directional (do diagonals connect)? - Task 2: Roughly how large can the grid get, and am I allowed to mutate it in place rather than allocate a separate `visited` structure? ### Part 1 — Layer editor with undo/redo and atomic batches Implement a `LayerEditor` that maintains an **ordered** list of layer IDs and supports: - `addLayer(id, index)` — insert a new layer at position `index`. - `removeLayer(id)` — remove an existing layer. - `moveLayer(id, newIndex)` — move a layer to a new position. - `undo()` — revert the most recent committed edit. - `redo()` — re-apply the most recently undone edit. - `beginBatch()` / `endBatch()` — treat every edit between them as **one** atomic history entry. Required semantics: applying a **new edit after an `undo()` clears the redo history**; `undo()` and `redo()` must restore the **exact** previous ordering of the list. ```hint Model the unit of history The hard part is not the list ops — it's deciding *what one undo entry is*. Settle on a single "committed edit" representation that one `undo()` can reverse and one `redo()` can re-apply. Then ask what that representation should hold: enough information to *recompute* the prior state (an inverse-operation log), or the prior state itself (a snapshot)? Both are defensible — weigh the time/space trade-off out loud. ``` ```hint What makes a batch one entry? A batch must produce exactly one history entry no matter how many sub-edits it contains. Think about where a sub-edit's record should go *while a batch is open* versus when it closes. If a single "edit" can hold multiple sub-ops, what must be true about the **order** in which you reverse them so the list comes back exactly right? And handle the awkward cases: an empty batch, and an `endBatch()` with no batch open. ``` #### What This Part Should Cover - A clear, single definition of a "committed edit" and an argument that one batch yields exactly **one** history entry. - Correct redo-clear-on-new-edit behavior and **exact** ordering restored by `undo`/`redo` (the reverse-order subtlety inside a batch). - Sensible handling of edge cases: empty stacks, empty batch, stray `endBatch`, missing/duplicate ids. - A stated time/space cost per operation (including `undo`/`redo` of a $k$-op edit). ### Part 2 — Count connected land regions Given a 2D grid of `'L'` (land) and `'W'` (water), count how many **connected land regions** exist, where two land cells belong to the same region iff they share a **side**. Provide an algorithm and analyze its time and space complexity. ```hint Reframe the problem Translate "region" into graph terms: if each land cell is a node, what are its edges, and what graph-theory object does a single region correspond to? Once you have that mapping, does this resemble a problem you already know? Whatever traversal you choose, decide up front how you'll guarantee you count each region exactly **once** as you scan the grid. ``` ```hint Don't recount, and watch the worst case When you start exploring a component, you need a way to never revisit its cells (and never start a second count inside it). What state records "already seen," and could you reuse the grid itself instead of allocating extra memory? For time: each cell is touched a constant number of times — reason from that to the bound. For space: the dominant term is whatever your traversal holds at once (frontier / stack / structure); pin down its worst case (think about an all-land grid). ``` #### What This Part Should Cover - Correct connected-components reasoning under the stated **4-directional** adjacency. - A flood-fill (BFS / DFS / union-find) that visits each cell once, with explicit visited-marking so no cell or region is recounted. - Handling of the empty grid and the in-place vs. separate-`visited` trade-off. - A justified $O(R \cdot C)$ time bound and a justified worst-case space bound. ### What a Strong Answer Covers These dimensions span both parts: - Clean, well-named interfaces and a stated handling of each edge case — not just code that happens to pass the happy path. - Complexity reported precisely in $O(\cdot)$ terms for every operation/algorithm. - A spoken design rationale: which representation/traversal you chose and **why**, including the alternative you rejected. ### Follow-up Questions - Task 1: How would you support **nested** batches, or a `redo()` that survives across batch boundaries? How does the design change if the history must persist to disk? - Task 1: If the layer list were huge, would you switch from whole-list snapshots to inverse-operation logging (or vice versa)? What's the trade-off? - Task 2: How would the solution change for **8-directional** adjacency, or for a grid too large to fit in memory (rows streamed one at a time)? - Task 2: If you must also return the **size of the largest** region, how do you extend the flood-fill?

Quick Answer: This question evaluates a candidate's proficiency in mutable state management and reversible operations through implementing an undo/redo layer editor with atomic batching, and their ability to reason about connectivity and algorithmic complexity by counting connected land regions in a grid.

Related Interview Questions

  • Write SQL for first share and closest collaborator - Figma (medium)
  • Validate an IPv4 address string - Figma (medium)
  • Design document layer with undo/redo - Figma (Medium)
  • Design document editor with undo/redo and batching - Figma (Medium)
Figma logo
Figma
Mar 25, 2026, 12:00 AM
Machine Learning Engineer
Onsite
Coding & Algorithms
19
0
Loading...

The coding portion of this loop covered two independent tasks. Implement each cleanly, handle the edge cases, and analyze the time and space complexity.

Constraints & Assumptions

  • Task 1 (layer editor): id s are unique (strings or integers). The index for addLayer and the newIndex for moveLayer are valid positions in the current ordering ( 0≤index≤n0 \le \text{index} \le n0≤index≤n for insert, 0≤newIndex<n0 \le \text{newIndex} < n0≤newIndex<n for move). Batches are not nested (no beginBatch() while a batch is already open). Edits outside a batch each form their own history entry; edits inside a batch collapse into one atomic entry. undo() / redo() on an empty stack are no-ops. The number of layers fits comfortably in memory; favor correctness and clear semantics over micro-optimization.
  • Task 2 (grid counting): The grid has RRR rows and CCC columns of characters, each 'L' (land) or 'W' (water). Adjacency is 4-directional (up/down/left/right) only — diagonals do not connect. The grid may be empty. You may mutate the grid in place if you wish.

Clarifying Questions to Ask

  • Task 1: Can beginBatch() be called twice without an intervening endBatch() , and what should happen to an empty batch (no sub-edits before endBatch )?
  • Task 1: For moveLayer , is newIndex interpreted against the list before or after the element is removed from its old slot?
  • Task 1: Should addLayer with a duplicate id , or removeLayer / moveLayer of a missing id , raise an error or be a silent no-op?
  • Task 2: Is adjacency 4-directional or 8-directional (do diagonals connect)?
  • Task 2: Roughly how large can the grid get, and am I allowed to mutate it in place rather than allocate a separate visited structure?

Part 1 — Layer editor with undo/redo and atomic batches

Implement a LayerEditor that maintains an ordered list of layer IDs and supports:

  • addLayer(id, index) — insert a new layer at position index .
  • removeLayer(id) — remove an existing layer.
  • moveLayer(id, newIndex) — move a layer to a new position.
  • undo() — revert the most recent committed edit.
  • redo() — re-apply the most recently undone edit.
  • beginBatch() / endBatch() — treat every edit between them as one atomic history entry.

Required semantics: applying a new edit after an undo() clears the redo history; undo() and redo() must restore the exact previous ordering of the list.

What This Part Should Cover

  • A clear, single definition of a "committed edit" and an argument that one batch yields exactly one history entry.
  • Correct redo-clear-on-new-edit behavior and exact ordering restored by undo / redo (the reverse-order subtlety inside a batch).
  • Sensible handling of edge cases: empty stacks, empty batch, stray endBatch , missing/duplicate ids.
  • A stated time/space cost per operation (including undo / redo of a kkk -op edit).

Part 2 — Count connected land regions

Given a 2D grid of 'L' (land) and 'W' (water), count how many connected land regions exist, where two land cells belong to the same region iff they share a side. Provide an algorithm and analyze its time and space complexity.

What This Part Should Cover

  • Correct connected-components reasoning under the stated 4-directional adjacency.
  • A flood-fill (BFS / DFS / union-find) that visits each cell once, with explicit visited-marking so no cell or region is recounted.
  • Handling of the empty grid and the in-place vs. separate- visited trade-off.
  • A justified O(R⋅C)O(R \cdot C)O(R⋅C) time bound and a justified worst-case space bound.

What a Strong Answer Covers

These dimensions span both parts:

  • Clean, well-named interfaces and a stated handling of each edge case — not just code that happens to pass the happy path.
  • Complexity reported precisely in O(⋅)O(\cdot)O(⋅) terms for every operation/algorithm.
  • A spoken design rationale: which representation/traversal you chose and why , including the alternative you rejected.

Follow-up Questions

  • Task 1: How would you support nested batches, or a redo() that survives across batch boundaries? How does the design change if the history must persist to disk?
  • Task 1: If the layer list were huge, would you switch from whole-list snapshots to inverse-operation logging (or vice versa)? What's the trade-off?
  • Task 2: How would the solution change for 8-directional adjacency, or for a grid too large to fit in memory (rows streamed one at a time)?
  • Task 2: If you must also return the size of the largest region, how do you extend the flood-fill?

Solution

Show

Submit Your Answer to Earn 20XP

Sign in to leave a comment

Loading comments...

Browse More Questions

More Coding & Algorithms•More Figma•More Machine Learning Engineer•Figma Machine Learning Engineer•Figma Coding & Algorithms•Machine Learning Engineer Coding & Algorithms
PracHub

Master your tech interviews with 8,000+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.