cswork / libraryOpen practice workspace

Articles · Figma

Figma Software Engineer Interview Guide 2026: Coding, Systems, and Product Sense

Prepare for Figma engineering interviews with a nested-layer editor, undo/redo traces, permission revocation, collaboration trade-offs and project drills.

Figma software engineer interview preparation becomes more useful when product behavior is part of the coding contract. “Support undo” is incomplete until you define what is restored, what happens after a new edit, and whether an actor who lost access can still change the document through history.

This guide works through an original nested-layer editor and a resolved design trade-off. It is a local teaching implementation, not Figma’s production code, a recalled private prompt or a published interview rubric.

Calibrate the process to the role

Figma’s careers page lists engineering roles across domains. The reviewed public careers and engineering sources do not establish one permanent technical interview loop. Ask your recruiter for the sessions, duration, permitted language, design domain and assessment-tool rules. An intern assessment and an experienced-engineer loop can require different preparation.

Prepare tested code, a failure-aware design, product decisions and a true project deep dive in the environment named in your invitation.

AreaUseful outputQuestion to answer
CodingSmall API, state trace and testsWhat must stay true after every operation?
SystemsAuthority, persistence and recovery pathsWhat happens after a lost acknowledgement?
Product judgmentUser goal and guardrailsCan an apparently fast answer lose another person’s work?
Project deep diveYour decision, alternatives and evidenceWhich contribution was yours?

The separate exercise below uses nested layers and permissions; it does not claim to implement every requirement of the linked question.

Original exercise: nested layers with reversible edits

Prompt. Build a small document store. A layer has a unique ID within the current document, a parent ID or no parent, and a name. Support adding a layer, deleting a layer and all descendants, undo, redo and changing a writer’s permission. Show the exact state after each action.

Agree on these choices before implementation:

  • An added layer’s parent must already exist, or be None for a root. Existing IDs cannot be added again. Reparenting and child ordering are outside this exercise.
  • A delete removes one entire subtree as one committed edit. Undo restores all its names and parent links together.
  • Each successful add or delete records one history unit. A new edit after undo clears the redo branch.
  • Invalid edits change neither document nor history. Empty undo/redo returns False and changes no revision.
  • Only current writers can add, delete, undo or redo. Only the owner can grant or revoke writer access, and the owner cannot revoke itself.
  • Permissions are current authority, not document history. Undo cannot restore the old writer set.
  • The document revision increases on every successful add, delete, undo or redo. Restoring old content does not move its revision backward.

This is a single-threaded, trusted in-memory store with global document history. Identity comes from the caller in the teaching harness, not a browser request. The snapshot method is a debugging read without read authorization; this example is not a secure public document service.

Walk the edit history before writing code

Start empty with owner owner. The owner grants alice write access. Permission changes do not add a document-history unit or increment its document revision.

StepActionVisible layersHistory behavior
1Alice adds frame as a rootframeRevision 1
2Adds label under frameframe → labelRevision 2
3Adds icon under labelframe → label → iconRevision 3
4Deletes labelframeLabel and icon disappear together; revision 4
5Undoframe → label → iconExact subtree restored; revision 5
6RedoframeSame subtree removed; revision 6
7Undoframe → label → iconRevision 7
8Adds note under frameframe with label/icon and noteNew branch clears redo; revision 8
9RedoSame four layersReturns False; revision stays 8

A common bug restores label but loses icon, leaving incomplete content. Another records the new note edit without clearing the old redo branch, allowing a later redo to erase the restored subtree unexpectedly. Name both behaviors before selecting a data structure.

Now undo the note edit, leaving a redo entry for it. Revoke Alice’s access. Her add, delete, undo and redo requests must all fail without consuming history or changing the revision. The owner can redo the note, but Alice must remain revoked. Document content and current authority are separate state domains.

A complete Python implementation

Use a dictionary from layer ID to (parent, name). Because tuple values contain only strings or None, a dictionary copy is sufficient for this model; arbitrary mutable properties would need a different copying or immutable-value policy.

A successful mutation is computed first, then committed. Delete builds parent-to-children adjacency and traverses the subtree, so it does not leave orphan descendants. There is no reparent operation: a new ID pointing to an existing parent cannot create a cycle.

from collections import defaultdict

class LayerDocument:
    """Trusted, single-threaded store; global document undo, not multiplayer undo."""
    def __init__(self, owner):
        self.owner = owner
        self.writers = {owner}
        self.layers = {}  # id -> (parent id or None, immutable name)
        self.past, self.future = [], []
        self.revision = 0

    def allow(self, actor, target, enabled):
        if actor != self.owner:
            raise PermissionError('only the owner manages writers')
        if type(target) is not str or not target or type(enabled) is not bool:
            raise ValueError('invalid permission change')
        if target == self.owner:
            raise ValueError('owner permission cannot change')
        if enabled: self.writers.add(target)
        else: self.writers.discard(target)

    def _check(self, actor):
        if actor not in self.writers:
            raise PermissionError('current write permission required')

    def snapshot(self):
        return self.layers.copy()  # values are tuples of strings/None

    def _commit(self, changed):
        self.past.append(self.layers.copy())
        self.layers = changed
        self.future.clear()
        self.revision += 1

    def add(self, actor, layer_id, parent, name):
        self._check(actor)
        if any(type(s) is not str or not s or len(s) > 80
               for s in (layer_id, name)):
            raise ValueError('invalid layer')
        if parent is not None and type(parent) is not str:
            raise ValueError('invalid parent')
        if layer_id in self.layers or (parent is not None and parent not in self.layers):
            raise ValueError('duplicate ID or missing parent')
        changed = self.layers.copy()
        changed[layer_id] = (parent, name)
        self._commit(changed)

    def delete(self, actor, layer_id):
        self._check(actor)
        if type(layer_id) is not str or layer_id not in self.layers:
            raise ValueError('missing layer')
        children = defaultdict(list)
        for key, (parent, _) in self.layers.items():
            children[parent].append(key)
        removed, pending = set(), [layer_id]
        while pending:
            key = pending.pop()
            removed.add(key)
            pending.extend(children[key])
        self._commit({k: v for k, v in self.layers.items() if k not in removed})

    def undo(self, actor):
        self._check(actor)
        if not self.past: return False
        self.future.append(self.layers.copy())
        self.layers = self.past.pop()
        self.revision += 1
        return True

    def redo(self, actor):
        self._check(actor)
        if not self.future: return False
        self.past.append(self.layers.copy())
        self.layers = self.future.pop()
        self.revision += 1
        return True

past holds document states before committed changes. future holds states removed by undo. Undo pushes the current state into future before restoring the previous state; redo performs the reverse. Both check current write authority first.

Notice that _commit stores only the document dictionary. Capturing writers in the same snapshot would make a content undo capable of reversing an access decision. Permission checks on ordinary edits alone would also be insufficient: undo and redo are writes too.

The revision is a teaching counter for observed document transitions. It is not a durable sequence number or a compare-and-swap check. Changing access does not increment it, so a real service would need separate permission-version or authoritative access handling.

Run a small trace and test the rejection boundaries

# Place the class above in layers.py, then run this in the same folder.
from layers import LayerDocument

doc = LayerDocument("owner")
doc.allow("owner", "alice", True)
doc.add("alice", "frame", None, "Frame")
doc.add("alice", "label", "frame", "Label")
doc.add("alice", "icon", "label", "Icon")
doc.delete("alice", "label")
assert doc.snapshot() == {"frame": (None, "Frame")}
assert doc.undo("alice") and len(doc.snapshot()) == 3
assert doc.redo("alice") and len(doc.snapshot()) == 1
assert doc.undo("alice")
doc.add("alice", "note", "frame", "Note")
assert not doc.redo("alice") and len(doc.snapshot()) == 4
assert doc.undo("alice")
doc.allow("owner", "alice", False)
try:
    doc.redo("alice")
    raise AssertionError("revoked writer accepted")
except PermissionError:
    pass
assert doc.redo("owner")
assert "alice" not in doc.writers and "note" in doc.snapshot()

We executed 2,510 local checks. They include the displayed nested trace, rejected edits and permission changes, snapshot isolation, and 20 seeded sequences of 50 operations. A separate reference model stores a timeline and cursor instead of the implementation’s two stacks; each step compares content and revision, then checks parent validity. These checks exercise local Python only.

Do not test rejection by observing an exception alone. Capture the document, both history stacks, revision and writer set before the call, then compare them afterward. A denied redo that accidentally pops its entry still breaks the contract even if it raises a permission error.

Test a missing parent, duplicate ID, empty name, missing delete target, invalid permission change, nonowner permission change and the owner-revocation attempt. After undo, an invalid add must preserve redo. A returned snapshot must be detached: clearing that returned dictionary must not erase the store.

The tests do not demonstrate concurrency, crash recovery, authentication, network delivery, rendering or Figma compatibility. Adding those concerns changes the authority and commit boundaries; it is not simply a matter of placing this class behind an endpoint.

Resolve the design trade-off instead of naming alternatives

For this bounded local exercise, choose snapshots because they make subtree restoration straightforward and the state easy to inspect. With n layers, a successful edit copies up to O(n) state; delete also builds and traverses an O(n) adjacency structure. Undo and redo copy up to O(n) into the opposite history stack. With h saved states, worst-case retained history is O(hn).

That is an explicit correctness-first choice, not a claim of production efficiency. Cap history or document size if the exercise adds a memory budget. A command log with inverse operations can store only changed data, but deletion still needs every removed descendant’s properties and parent relationship. Compression must preserve the agreed unit of work and failure behavior.

IDs are unique only in the current document here; a deleted ID can be reused on a new branch. Multiplayer or durable event streams need a stronger identity/tombstone contract to distinguish an old event from a new object. State that limitation before promising replay safety.

Multiplayer changes the meaning of undo

Imagine Alice edits a label and Bob later adds an icon. Restoring Alice’s earlier whole-document snapshot can remove Bob’s work. The local global-history policy is therefore unsuitable for an independent per-user undo experience.

A collaborative design must specify which operation is being reversed, how a reversal interacts with later changes, who orders accepted updates and how reconnecting clients recover. Decide whether an undo is a new authorized operation against current state rather than a restoration of an old global snapshot. Define what happens if its target was deleted or changed by another editor.

Figma’s October 2019 multiplayer article is historical engineering context. It describes client/server synchronization, object relationships and why multiplayer undo modifies the opposite history at the time of undo or redo. It does not make this exercise a reproduction of Figma’s current internals or establish a current interview format.

For a resolved service design, put write authorization at the authority that commits an operation. Include a document/version expectation and an operation identity if the contract requires retry receipts. Persist the accepted edit before promising durability. Recover missed updates using a documented snapshot or replay boundary, then reconcile client optimism with authoritative results.

If permission is revoked while a request is queued, check at the commit boundary; admission earlier in the queue is insufficient. A stale client-side permission cache can improve the interface but cannot authorize a write. Permission updates and document edits also need an ordering policy when they race. This local harness tests revocation before a call, not that distributed race.

Product sense: describe what the collaborator sees

For the nested-layer case, the user expects deletion and restoration to behave as one action. A denied mutation should leave the canvas stable and explain why editing is unavailable. A new edit after undo should make redo unavailable, rather than silently applying an incompatible old branch.

Measure failures that matter to that experience: missing descendants after restore, edits rejected after an optimistic preview, history memory growth and time until the interface reflects revoked access. Pair latency with correctness and access boundaries; a fast operation that discards another editor’s work is not a successful optimization.

Figma’s September 2025 WebGPU article illustrates another useful preparation theme: making rendering state explicit while preserving compatibility and rolling changes out carefully. For frontend roles, practice state transitions, accessibility and device performance alongside the document model. For backend roles, expand ordering, authority, persistence and recovery.

Prepare a truthful project deep dive

Choose one real project with a difficult decision. Explain the user problem, constraints, options, your contribution, verification and what remained unresolved. If you chose a simple implementation first, say why its limits were acceptable and what evidence would trigger a redesign.

Figma’s current careers page presents Build community, Run with it, Love your craft, Grow as you go and Play. Use true examples of collaboration, initiative, craft and learning; do not treat the wording as a secret scoring formula.

An honest practice summary for this exercise is: “The local model restores nested content exactly, clears redo on a new edit, and denies history writes after revocation. Snapshot cost is deliberate for the bounded fixture. Multiplayer ordering, secure reads and durable commits remain outside it.” For your own project, replace the practice result with evidence you personally observed.

A seven-day plan with reviewable outputs

Spend day one calibrating your invitation and choosing a role-relevant domain. On days two and three, implement the local trace, test rejection without mutation and inspect the independent reference comparison. On day four, replace global snapshots with a proposed collaborative-operation contract and work through two editors.

Use day five to connect the design to product behavior and measurement. On day six, rehearse a true project deep dive with a rejected alternative and an imperfect outcome. On day seven, run a mixed mock in the confirmed environment. Record the weakest category and repeat that exercise instead of adding random problems.

Frequently asked questions

Does Figma publish one software engineer interview loop?

The reviewed careers and engineering sources do not establish one fixed technical loop for every role. Confirm the sessions, duration, language, design domain and permitted tools with your recruiter.

Why practice undo and redo for a product-engineering interview?

They force you to define observable state, committed edits, branch behavior and failure invariants. This guide’s original nested-layer exercise makes those choices executable rather than leaving them as general advice.

Does undo restore a revoked permission?

It must not in this exercise. Document snapshots exclude the writer set, and add, delete, undo and redo all check current authority. A previously authorized actor cannot use history to regain edit access.

Can the sample be used as a multiplayer editor?

No. It has a trusted single-threaded store and one global document history. Restoring whole-document snapshots can overwrite other collaborators’ work, so multiplayer undo needs a separate operation and conflict contract.

How should I prepare for product sense and a project deep dive?

Connect a technical choice to a user outcome, show failure and permission boundaries, and use a true project to explain your contribution, alternatives, verification and remaining limits. Do not invent private interview criteria or experience.

Sources and scope

Checked October 8, 2026. The layer store, identities and traces are original practice material. Executed checks use a trusted local Python harness; no Figma account, API, renderer or multiplayer system is exercised. Your invitation controls assessment details and permitted tools.