字节跳动后端面经 · 2025

字节跳动后端开发(抖音直播)2025 校招完整面经:4 轮技术面 + HR 面,算法题涵盖最小覆盖子串、滑动窗口、拓扑排序、LCA 与并查集,系统设计通知系统,项目深挖与 Java 细节。

字节跳动后端开发面经 · 2025

岗位:后端开发工程师(抖音直播)
背景:211 硕士,Java 为主,1 段大厂实习 + 1 段开源贡献
结果:OC,总包 38w+(Base + 期权 + 房补)
时间线:9.1 投递 → 9.6 一面 → 9.20 四面 → 9.26 OC


字节面试风格(2025 版)

相比 2024 年的感受,2025 字节后端面试有几个明显变化:

  • 算法仍然硬核:但更偏向「模板 + 变体追问」,滑动窗口、图论成了新的高频区。
  • 项目深挖更体系化:面试官会把你实习的项目当生产系统来拷问,量化指标要求更高。
  • 系统设计比重上升:中后期几乎必出,且会追问可靠性、限流、去重等细节。
  • 语言细节抽查:Java 八股虽不如算法重要,但集合源码、并发工具是送分题,必须拿稳。

整体感觉:算法决定下限,项目与系统设计决定上限。


一面(9.6):算法 + Java 基础

面试官:抖音直播基础架构组
时长:60 分钟

算法题 1:最小覆盖子串

LeetCode 76。直接考察滑动窗口模板的熟练度。

我按「扩大 → 收缩 → 更新」三步写,need/window 双哈希 + valid 计数:

public String minWindow(String s, String t) {
    Map<Character, Integer> need = new HashMap<>();
    for (char c : t.toCharArray()) need.put(c, need.getOrDefault(c, 0) + 1);
    Map<Character, Integer> window = new HashMap<>();
    int left = 0, valid = 0, start = 0, len = Integer.MAX_VALUE;

    for (int right = 0; right < s.length(); right++) {
        char c = s.charAt(right);
        if (need.containsKey(c)) {
            window.put(c, window.getOrDefault(c, 0) + 1);
            if (window.get(c).equals(need.get(c))) valid++;
        }
        while (valid == need.size()) {
            if (right - left + 1 < len) { start = left; len = right - left + 1; }
            char d = s.charAt(left);
            if (need.containsKey(d)) {
                if (window.get(d).equals(need.get(d))) valid--;
                window.put(d, window.get(d) - 1);
            }
            left++;
        }
    }
    return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
}

追问:

  • “为什么 valid 用 need.size() 而不是 t.length()?” → 目标字符可能重复,need 的 key 数才是覆盖条件。
  • “如果字符集是英文小写,怎么优化?” → 用 int[128] 数组替代 HashMap,常数更快。
  • “给你一个全是正数的数组,求最短子数组和 ≥ target(LC 209)?” → 同样滑动窗口,while 收缩条件换成和。

算法题 2:无重复字符的最长子串

LeetCode 3。变长窗口,Set 判重。

public int lengthOfLongestSubstring(String s) {
    Set<Character> set = new HashSet<>();
    int left = 0, ans = 0;
    for (int right = 0; right < s.length(); right++) {
        while (set.contains(s.charAt(right))) set.remove(s.charAt(left++));
        set.add(s.charAt(right));
        ans = Math.max(ans, right - left + 1);
    }
    return ans;
}

Java 基础(15 分钟)

  • HashMap 底层:数组 + 链表 + 红黑树(≥8 转树),put 流程、负载因子 0.75、扩容机制。
  • ConcurrentHashMap:CAS + synchronized 锁桶,size 用 CounterCell 累加。
  • String 不可变的原因:安全、哈希缓存、线程安全、常量池复用。

二面(9.11):项目深挖 + 算法

面试官:抖音直播弹幕服务负责人
时长:75 分钟

项目深挖(35 分钟)

我的实习项目是直播间弹幕去重与频控服务,面试官直接按生产系统标准连环追问:

  1. “弹幕高峰期 QPS 多少?怎么压测出来的?”

    • 峰值约 8 万 QPS,用 JMeter 分布式压测 + 线上真实流量回放验证。
  2. “同一用户短时间内连发相同弹幕,怎么去重?”

    • Redis SETNX + 时间窗口(5 秒内重复内容直接丢弃)。
    • 追问"窗口是滑动窗口还是固定窗口?" → 固定窗口简单但有边界问题,我用了 Redis Sorted Set 做滑动窗口计数。
  3. “弹幕要有序展示,怎么保证时序?”

    • 按服务端接收时间打单调递增 ID(雪花算法),客户端按 ID 排序。
    • 追问"同一秒内大量弹幕,雪花 ID 并发够吗?" → 位段分配 + 队列化消费。
  4. “如果有人刷屏(灌水弹幕)怎么办?”

    • 单用户频控(令牌桶,按用户维度)+ 全局内容黑名单 + 命中后降级静默丢弃。
  5. “如果 Redis 挂了,你的去重逻辑怎么兜底?”

    • 降级到本地内存窗口(单机限流)+ 数据库唯一索引兜底,Redis 恢复后补齐。

算法题:滑动窗口最大值

LeetCode 239。要求 O(n)。单调队列维护窗口内最大值。

public int[] maxSlidingWindow(int[] nums, int k) {
    Deque<Integer> q = new ArrayDeque<>();   // 存下标,队首是窗口最大值
    int[] ans = new int[nums.length - k + 1];
    for (int i = 0; i < nums.length; i++) {
        while (!q.isEmpty() && nums[q.peekLast()] <= nums[i]) q.pollLast();
        q.offerLast(i);
        if (q.peekFirst() <= i - k) q.pollFirst();
        if (i >= k - 1) ans[i - k + 1] = nums[q.peekFirst()];
    }
    return ans;
}

追问:“如果改为求滑动窗口中位数?” → 用两个堆(大顶堆存小半、小顶堆存大半)或有序集合,复杂度 O(n log k)。


三面(9.16):算法 + 系统设计

面试官:抖音直播中台架构师
时长:80 分钟

算法题 1:课程表 II(拓扑排序)

LeetCode 210。Kahn 算法按入度剥离,同时检测环。

public int[] findOrder(int numCourses, int[][] prerequisites) {
    List<List<Integer>> graph = new ArrayList<>();
    int[] in = new int[numCourses];
    for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>());
    for (int[] p : prerequisites) { graph.get(p[1]).add(p[0]); in[p[0]]++; }

    Queue<Integer> q = new ArrayDeque<>();
    for (int i = 0; i < numCourses; i++) if (in[i] == 0) q.offer(i);
    int[] order = new int[numCourses];
    int idx = 0;
    while (!q.isEmpty()) {
        int cur = q.poll();
        order[idx++] = cur;
        for (int nxt : graph.get(cur))
            if (--in[nxt] == 0) q.offer(nxt);
    }
    return idx == numCourses ? order : new int[0];   // 有环返回空
}

追问:“DFS 三色标记怎么写?判环的依据?” → 0 未访问 / 1 访问中 / 2 已完成,遇到访问中节点即有环。

算法题 2:二叉树的最近公共祖先

LeetCode 236。后序递归,左有右有则当前为 LCA。

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null || root == p || root == q) return root;
    TreeNode left = lowestCommonAncestor(root.left, p, q);
    TreeNode right = lowestCommonAncestor(root.right, p, q);
    if (left != null && right != null) return root;
    return left != null ? left : right;
}

系统设计:通知系统

要求设计一个支持亿级用户、多平台推送、防打扰的通知系统。这是 2025 年字节很喜欢考的一道题(App 弹窗/站内信/邮件都属于它)。

我的回答框架:

  1. 需求澄清:用户量 2 亿、峰值 50 万 QPS、渠道含推送/站内信/短信/邮件、分钟级延迟可接受、不能丢消息。
  2. 架构分层:API 网关 → 通知服务(落库 + 入队)→ Kafka 按渠道分区 → 渠道分发器 → 推送 worker / 站内信 / 邮件。
  3. 消息模型:模板 + 参数,多渠道独立渲染,业务方只管发模板 ID。
  4. 去重与限流:单用户频控(Redis 计数)+ 内容去重(SETNX + 时间窗)+ 客户端静默聚合。
  5. 可靠性:消息状态机 PENDING → SENT → FAILED,指数退避重试 + 幂等消费(全局消息 ID 去重)+ 死信队列告警。
  6. 扩展:渠道降级(推送失败补发短信)、token 失效清理(APNs 410)、营销错峰。

追问:

  • “验证码这种高优消息和营销消息混在一起怎么办?” → 优先级队列,Kafka 分区 + 消费端高优先取;堆积时优先丢弃低优营销。
  • “同一用户在 10 秒内收到 20 条推送怎么处理?” → 单用户限流 + 客户端聚合为一条。
  • “推送 token 失效怎么发现?” → 推送返回 410 Gone 时从设备表删除,避免反复无效投递。

四面(9.20):交叉面 + 算法

面试官:抖音集团基础技术部
时长:70 分钟

算法题:冗余连接(并查集)

LeetCode 684。加入一条边时若两端已在同一连通分量,则该边成环、即为答案。

int[] parent;
int find(int x) { return parent[x] == x ? x : (parent[x] = find(parent[x])); }

public int[] findRedundantConnection(int[][] edges) {
    int n = edges.length;
    parent = new int[n + 1];
    for (int i = 1; i <= n; i++) parent[i] = i;
    for (int[] e : edges) {
        int a = find(e[0]), b = find(e[1]);
        if (a == b) return e;
        parent[a] = b;
    }
    return new int[0];
}

追问:“路径压缩和按秩合并的作用?” → 把查找均摊到 O(α(N));按秩合并防止树退化。

交叉提问(Java 并发 + 系统)

  • ReentrantLock 和 synchronized 区别:公平性、可中断、Condition 多条件、锁升级(偏向 → 轻量 → 重量)。
  • 线程池参数与拒绝策略:核心/最大/队列、AbortPolicy vs CallerRunsPolicy 的场景选择。
  • “你的弹幕服务如果 QPS 翻 10 倍,哪里先成为瓶颈?” → 先 Redis 频控的热点 key,再 Kafka 分区数,最后是存储写入。

五面(9.22):HR 面

时长:35 分钟

  • 为什么投字节?对抖音直播的了解?
  • 实习中最大的成长与最失败的决策?
  • 手上其他 offer?薪资期望?
  • 反问:直播团队技术栈(Java + Go 混合)、新人 6 个月期望产出。

HR 面套路稳定,重点是别暴露无逻辑的回答。遇到「其他 offer 怎么选」,我的策略是给出决策维度(业务赛道、技术成长、团队匹配),而不是直接表态。


复盘与建议

字节后端面试通关密码(2025 版)

维度权重准备要点
算法45%滑动窗口/双指针、图论、树模板三块高频,全部要会写模板
项目30%量化指标 + 兜底方案,把实习项目当生产系统准备
系统设计15%通知系统、弹幕/IM、Feed 流——可靠性、限流、去重必答
基础10%Java 集合源码、并发、MySQL 索引、Redis 原理

2025 算法趋势

  • 滑动窗口/双指针取代部分老 DP 题成为一面主力(LC 3/76/209/239 高频出现)。
  • 图论(拓扑、并查集)在三、四面出现频率上升,因为能顺带考察「数据建模」。
  • 模板要能默写:面试官会直接让你「写个单调队列」,背不下来会很被动。

高频追问准备清单

  1. 你的接口峰值 QPS?压测怎么做的?瓶颈在哪?
  2. 缓存/Redis 挂了怎么兜底?数据怎么恢复?
  3. 频控用固定窗口还是滑动窗口?为什么?
  4. 消息可能重复消费吗?幂等怎么保证?
  5. QPS 翻 10 倍,哪里先崩?怎么扩?
  6. 为什么选这个数据结构/算法?trade-off 是什么?

与其他公司对比

公司侧重与字节对比
字节算法 + 项目 + 系统设计并重—
阿里项目深度 + 架构思维算法难度略降,更考业务建模
腾讯基础 + 开放题算法相近,项目追问深度略低
美团工程实践 + 业务理解场景题多,算法中等

祝你面试顺利!2025 字节更看重「能把算法模板落到项目里」的工程感——刷题之余,多想想每个模板在你的实习项目里怎么用,面试官会眼前一亮。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页