Implement tail N lines

Quick Overview

Confluent software-engineer technical-screen coding question: implement tail(path, N) to print the last N lines of a possibly multi-GB file or an unbounded stream without loading it into memory. It tests a backward block scan for seekable files versus an O(N) ring buffer for non-seekable streams, plus correct handling of \n/\r\n/\r newlines, UTF-8 multibyte characters, missing trailing newlines, and extremely long lines, with follow-ups on tail -f, log rotation/truncation, and the -c last-N-bytes option.

Implement tail N lines

Company: Confluent

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

##### Question Implement a utility `tail(path: string, N: int)` (and an equivalent stream variant) that prints the **last N lines** of input without loading the whole input into memory. Assume variable-length lines, an unknown total line count, and a file that may be many gigabytes. Your solution must address both input modes and the full set of correctness constraints below: 1. **Seekable / random-access file.** Use either a backward scan (seek to the end and read fixed-size blocks toward the front, counting newlines until you have collected N lines) or a fixed-size ring buffer over a forward read. Avoid re-reading the whole file. 2. **Non-seekable / unbounded stream (stdin or a pipe).** Seeking is impossible and the length is unknown, so maintain only **O(N)** memory — a ring buffer of the last N lines — while reading forward exactly once. 3. **Memory bound.** Never read the entire file into memory; memory must be bounded by N (and one line), not by file size. 4. **Newline conventions.** Correctly handle Unix `LF` (`\n`), Windows `CRLF` (`\r\n`), and classic Mac `CR` (`\r`) line endings, including a mix. 5. **UTF-8 correctness.** Split on line boundaries at the byte level but preserve UTF-8 multibyte characters — never cut a character in the middle when reading or buffering blocks. 6. **Missing trailing newline.** A final line not terminated by a newline must still count as a line and be emitted. 7. **Extremely long lines.** A single line may exceed your buffer/block size; handle lines larger than the read buffer without corruption. 8. State the **time and space complexity** of each approach and give the **key test cases** you would write. **Follow-ups the interviewer will push on:** - Extend to `tail -f`: follow the file as it grows, emitting new lines as they are appended. - Handle **log rotation / truncation** and **symlinks** (detect when the file is replaced or truncated and re-open / reset position). - Support the `-c N` option: print the last N **bytes** instead of the last N lines.

Overview: Confluent software-engineer technical-screen coding question: implement tail(path, N) to print the last N lines of a possibly multi-GB file or an unbounded stream without loading it into memory. It tests a backward block scan for seekable files versus an O(N) ring buffer for non-seekable streams, plus correct handling of \n/\r\n/\r newlines, UTF-8 multibyte characters, missing trailing newlines, and extremely long lines, with follow-ups on tail -f, log rotation/truncation, and the -c last-N-bytes option.

Confluent logo
Confluent
Sep 6, 2025
mediumSoftware EngineerTechnical ScreenCoding & Algorithms
80
0
Question

Implement a utility tail(path: string, N: int) (and an equivalent stream variant) that prints the last N lines of input without loading the whole input into memory. Assume variable-length lines, an unknown total line count, and a file that may be many gigabytes.

Your solution must address both input modes and the full set of correctness constraints below:

  1. Seekable / random-access file. Use either a backward scan (seek to the end and read fixed-size blocks toward the front, counting newlines until you have collected N lines) or a fixed-size ring buffer over a forward read. Avoid re-reading the whole file.
  2. Non-seekable / unbounded stream (stdin or a pipe). Seeking is impossible and the length is unknown, so maintain only O(N) memory — a ring buffer of the last N lines — while reading forward exactly once.
  3. Memory bound. Never read the entire file into memory; memory must be bounded by N (and one line), not by file size.
  4. Newline conventions. Correctly handle Unix LF ( \n ), Windows CRLF ( \r\n ), and classic Mac CR ( \r ) line endings, including a mix.
  5. UTF-8 correctness. Split on line boundaries at the byte level but preserve UTF-8 multibyte characters — never cut a character in the middle when reading or buffering blocks.
  6. Missing trailing newline. A final line not terminated by a newline must still count as a line and be emitted.
  7. Extremely long lines. A single line may exceed your buffer/block size; handle lines larger than the read buffer without corruption.
  8. State the time and space complexity of each approach and give the key test cases you would write.

Follow-ups the interviewer will push on:

  • Extend to tail -f : follow the file as it grows, emitting new lines as they are appended.
  • Handle log rotation / truncation and symlinks (detect when the file is replaced or truncated and re-open / reset position).
  • Support the -c N option: print the last N bytes instead of the last N lines.