skip to content

Show how to read a file line by line using bufferedReader().use, and explain what .use guarantees and why you'd choose it over readText.

level: middleimportance: should knowfreq 55%

answer

  1. bufferedReader() = buffered, fast per-line reads
  2. use = try-with-resources, always closes
  3. lineSequence() inside use, terminal before exit
  4. readLine() returns null at EOF
  5. Choose over readText for big files / control

basics

~20 s

Call file.bufferedReader().use { reader -> ... }. The use block runs your code and then always closes the reader, even if an error happens. You choose it over readText when the file is too big to load all at once.

solid answer

~40 s

`File.bufferedReader(charset = Charsets.UTF_8): BufferedReader` returns a buffered reader over the file. `.use { }` is the stdlib extension on `Closeable`/`AutoCloseable` that runs the lambda and then calls `close()` in a `finally`, even on exception — Kotlin's equivalent of Java try-with-resources. Inside, you typically iterate with `reader.lineSequence()` (lazy `Sequence<String>`), `reader.readLine()` in a loop, or `reader.forEachLine`. You'd choose this over `readText()` when you need bounded memory (huge files), fine-grained control (custom parsing, early break, partial reads), or a reader you pass to another API. `readText` is simpler but eager. The buffering matters: `BufferedReader` reads in chunks, so per-line reads don't each hit the disk, which is much faster than an unbuffered `FileReader`.

code

kotlin · 8 lines
kotlin
import java.io.File

fun firstMatch(path: String, needle: String): String? =
    File(path).bufferedReader().use { reader ->
        reader.lineSequence().firstOrNull { it.contains(needle) }
    }
// firstOrNull short-circuits: stops reading at the first match,
// and use() closes the reader even on early return.

go deeper

for a junior

Can call bufferedReader().use and knows use closes the reader.

for a middle

Uses lineSequence/readLine correctly, explains buffering speed and the eager-vs-streaming choice vs readText.

for a senior

Knows use's suppressed-exception semantics, when to drop below useLines to the raw reader, and short-circuiting with firstOrNull.

for a principal

Reasons about buffer sizing, charset decoding cost, and reader composition/back-pressure in high-throughput pipelines.

## The pieces - `File.bufferedReader(charset = Charsets.UTF_8, bufferSize = DEFAULT_BUFFER_SIZE): BufferedReader` — opens the file and wraps it in a `BufferedReader`. *Buffering* means bytes are pulled from disk in chunks (default ~8 KB) into memory, so each `readLine()` is served from RAM, not a fresh disk syscall — far faster than reading char-by-char. - `.use { }` — an extension on `Closeable`/`AutoCloseable`. Signature: `inline fun <T : Closeable?, R> T.use(block: (T) -> R): R`. It runs `block`, then **always** calls `close()` in a `finally`, even if `block` throws. If `close()` itself throws during exception unwinding, that secondary exception is suppressed and attached, so the original error surfaces. This is Kotlin's analog of Java's try-with-resources. ## Idiomatic line-by-line read ```kotlin import java.io.File val longLines: List<String> = File("data.txt").bufferedReader().use { reader -> reader.lineSequence() // lazy Sequence<String> .filter { it.length > 80 } .toList() // terminal: consume INSIDE use } ``` - `lineSequence()` yields lines lazily; combine with `filter`/`map`. Finish with a terminal operator **inside** the `use` block, because the reader closes on exit. - Alternatively, an explicit loop: ```kotlin File("data.txt").bufferedReader().use { reader -> var line = reader.readLine() // returns null at EOF while (line != null) { process(line) line = reader.readLine() } } ``` `readLine()` returns the next line or `null` at end-of-file. ## bufferedReader().use vs readText | Aspect | `bufferedReader().use` | `readText()` | |--------|------------------------|--------------| | Memory | O(one line/chunk) | O(whole file) | | Control | full (loop, break, custom parse) | none | | Code | more verbose | one line | | Best for | large files, custom parsing, passing reader on | small files, want one String | ## Relationship to useLines `useLines` is essentially `bufferedReader().use { it.lineSequence().let(block) }` — a convenience wrapper. Drop to `bufferedReader().use` when you need the raw reader (e.g. mark/reset, mixing `readLine` with `read(charArray)`, or handing the reader to a parser). ## Pitfalls - Don't let the `Sequence`/reader escape the `use` block — it's closed on exit. - Don't call `.use` twice on the same reader; after the first it's closed. - Always prefer `.use` over manual `try/finally { close() }` — it handles suppressed exceptions correctly.

  • What does .use do if the block throws an exception?
    It still calls close() in a finally; the original exception propagates, and any exception from close() during unwinding is suppressed and attached to it.
  • What does BufferedReader.readLine() return at end of file?
    null. That's the loop-termination signal when reading manually.

bufferedReader is a conveyor belt feeding you items in batches; .use is the supervisor who shuts the belt off the moment you're done, even if you trip.

saying these in an interview costs you the question

  • Using try/finally manually instead of use and mishandling suppressed exceptions
  • Letting the reader or lineSequence escape the use block
  • Thinking bufferedReader().use loads the whole file like readText
  • Forgetting readLine() returns null at EOF and looping forever
  • Calling use on an already-closed reader

context