独家 Figma 面经深度解析:Document Layer 核心状态管理与算法复盘

目录

背景介绍

近年来,随着前端与协同设计领域的不断深耕,大厂对于工程师的代码底子和系统架构设计要求越来越高。最近,有不少同学向我们打听如何准备Figma面试。为了帮助大家精准狙击考点,今天我们来深度复盘一道非常经典的Figma高频题目:Document Layer 的状态管理与撤销重做机制(Undo/Redo)。

这篇硬核的Figma面经将带你从零推导这道算法题的最优解,不仅有详细的设计思路,还有面试级别的可运行代码。无论你是在冲刺 Offer,还是想精进数据结构的实战能力,都能从中获益。

核心考点拆解

题目要求我们实现一个文档图层(Document Layer)系统,包含基础的 apply(应用修改)操作,以及进阶的 commit_batch(批量提交)和 redo(重做)的 follow up 功能。

其中有三个极为关键的陷阱与细节:

  1. Apply 的即时性:在执行 commit_batch 之前,每次调用的 apply 必须已经生效(take effect)。这意味着文档的当前状态必须是实时更新的。
  2. Commit Batch 的语义commit_batch 的作用是打包最近的一批修改,它仅影响 undo(撤销)操作的逻辑。撤销时,需要将整个 batch 里的修改一次性回退。
  3. 状态合并与空间优化:绝对不能无脑记录所有的 apply 历史流水。系统需要进行状态合并。例如:连续执行了 apply(1, color, red)apply(1, color, blue),我们只需要记录对这个图层的最终改动逻辑,从而节省大量内存。

算法设计思路

为了完美避开上述陷阱并满足空间优化要求,我们可以采用双栈(Undo Stack / Redo Stack)加上一个暂存区(Staging Area)的架构设计:

  • 文档状态(Document State):用一个字典维护每个 Layer 的当前最新属性。每次调用 apply 时,直接更新这个字典,保证修改立即生效。
  • 暂存区(Staging Area):用来存放当前正在进行但还未被 commit_batch 打包的改动。当执行 apply 时,不仅更新当前状态,同时也要把“修改前”的旧值记录到暂存区中。关键点在于:如果暂存区中已经记录过该 Layer 的该属性,则不再覆盖旧值。这样就能完美实现题目要求的状态合并操作。
  • Undo 栈与 Redo 栈
  • 当调用 commit_batch 时,将整个暂存区打包成一个事务(Transaction)压入 Undo 栈,并清空暂存区。同时,必须清空 Redo 栈(因为新的操作打断了可重做的未来分支)。
  • 执行 undo 时,从 Undo 栈弹出一个事务,根据里面保存的旧值恢复文档状态,同时将恢复前的值(即撤销时的当前值)打包压入 Redo 栈。
  • 执行 redo 时,从 Redo 栈弹出一个事务,应用其中的新值,同时将其压回 Undo 栈。

Python 面试级代码实现

下面是满足所有细节要求的核心 Python 实现,代码经过严格排版,可直接用于面试白板或代码评测系统中:

class DocumentLayer:
    def __init__(self):
        # 存储当前文档层的真实状态: {layer_id: {property: value}}
        self.state = {}
        # 暂存区:记录当前未提交批次中,属性被修改【前】的原始状态
        # 格式: {layer_id: {property: old_value}}
        self.staging = {}
        
        # 撤销与重做栈
        self.undo_stack = []
        self.redo_stack = []

    def apply(self, layer_id, prop, new_value):
        if layer_id not in self.state:
            self.state[layer_id] = {}
            
        old_value = self.state[layer_id].get(prop, None)
        
        # 状态合并:如果这是该批次中第一次修改此属性,记录其最原始的值
        if layer_id not in self.staging:
            self.staging[layer_id] = {}
        if prop not in self.staging[layer_id]:
            self.staging[layer_id][prop] = old_value

        # 修改立即生效
        self.state[layer_id][prop] = new_value

    def commit_batch(self):
        if not self.staging:
            return
            
        # 打包暂存区的原始状态,压入 undo 栈
        self.undo_stack.append(self.staging)
        self.staging = {}
        # 发生新提交后,未来的重做分支失效
        self.redo_stack.clear()

    def undo(self):
        if not self.undo_stack:
            return
            
        batch_to_undo = self.undo_stack.pop()
        redo_batch = {}
        
        for layer_id, props in batch_to_undo.items():
            if layer_id not in redo_batch:
                redo_batch[layer_id] = {}
            for prop, old_value in props.items():
                # 记录当前状态,供 redo 使用
                redo_batch[layer_id][prop] = self.state[layer_id].get(prop, None)
                
                # 回滚到旧状态
                if old_value is None:
                    if prop in self.state.get(layer_id, {}):
                        del self.state[layer_id][prop]
                else:
                    self.state[layer_id][prop] = old_value
                    
        self.redo_stack.append(redo_batch)

    def redo(self):
        if not self.redo_stack:
            return
            
        batch_to_redo = self.redo_stack.pop()
        undo_batch = {}
        
        for layer_id, props in batch_to_redo.items():
            if layer_id not in undo_batch:
                undo_batch[layer_id] = {}
            for prop, new_value in props.items():
                # 记录重做前的状态,供再次 undo 使用
                undo_batch[layer_id][prop] = self.state[layer_id].get(prop, None)
                
                # 应用重做状态
                if new_value is None:
                    if prop in self.state.get(layer_id, {}):
                        del self.state[layer_id][prop]
                else:
                    if layer_id not in self.state:
                        self.state[layer_id] = {}
                    self.state[layer_id][prop] = new_value
                    
        self.undo_stack.append(undo_batch)

2026 真人上岸案例分享

就在 2026 年初,我们的学员 Alex 面临了同样的挑战。Alex 之前一直在做传统的后端业务,面对复杂的状态机管理和前端底层逻辑时总是心里没底。为了把握住这次难得的机遇,他预约了我们的高级面试辅导。

在辅导过程中,我们的导师一针见血地指出了他的薄弱环节,针对性地带他手撕了三遍这道状态管理题目,反复强调了 apply 的即时性原则以及对状态合并空间优化的深入理解。在真实面试中,当面试官抛出 commit_batchredo 的进阶问题时,Alex 从容不迫地在白板上画出双栈加 Staging 区的架构图,并流畅写出了 O(1) 状态合并的代码逻辑。最终,他不仅当场拿到了面试官的 Strong Hire 评价,更是凭借出色的表现顺利Figma上岸,斩获了令同龄人艳羡的顶配包裹!

面试救急与冲刺

技术面试越往深处走,越考校的是你对系统细节的把控力与解决极端场景的思维深度。刷题虽然重要,但在关键时刻如果有行业专家的精准点拨,往往能让你事半功倍。

如果你正在备战,或者已经收到了心仪公司的面试邀请,但面对像 Document Layer 这样复杂的算法与设计题仍然感到吃力,不要犹豫,点击下方链接联系我们!我们提供顶级名企专家的 1v1 指导,助你在竞争激烈的环境中脱颖而出。

👉 立即预约 1v1 面试辅导,定制专属冲刺计划!

遇到突发面试?拿捏不准高频题?点击联系我们的 面试救急通道 获取你的专属突击方案!

Next
Next

2026最新DRW面经深度解析:硬核算法与数据处理实战,带你一举DRW上岸!