Lesson 80 of 80
The browser can compute a real SHA-256 hash without a library — it's just async, and it hands you bytes, not a string, which is where most people get stuck.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
crypto.subtle is the browser's built-in cryptography API — part of the Web Crypto API, available in every modern browser and in Node via globalThis.crypto. crypto.subtle.digest() computes a cryptographic hash (SHA-256, SHA-384, SHA-512) of some data. You reach for this to fingerprint a file before upload, verify a payload wasn't tampered with, or generate a cache key from content — not for hiding passwords, which needs a slower, salted algorithm this API doesn't provide.
async function sha256Hex(message) {
const bytes = new TextEncoder().encode(message); // string -> bytes
const hashBuffer = await crypto.subtle.digest("SHA-256", bytes); // bytes -> hash
const hashArray = Array.from(new Uint8Array(hashBuffer)); // buffer -> byte array
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
}
await sha256Hex("hello world");
// "b94d27b9934d3e08a52e52d7da7dacefe95a9c7f8de0e1a6ec2a7db2b48e5f5"
Three conversions are doing the work here. digest doesn't take a string — it takes an ArrayBuffer or a typed array, so TextEncoder().encode() turns the JS string into raw UTF-8 bytes first. digest is async because it can be genuinely slow for large inputs and the API is designed to never block the main thread — it always returns a Promise<ArrayBuffer>, even for a two-character input. And the result is a raw binary buffer, not a printable string, so the last two lines walk each byte and format it as two hex characters — that's the part that trips people up the first time, because they expect digest to just hand back a hex string the way most language stdlibs do.
crypto.subtle requires a secure context. It's only available on https:// or localhost — on plain http://, crypto.subtle is undefined, which reads like a browser support bug but is actually the platform enforcing that cryptographic operations don't happen on an unencrypted page.SubtleCrypto's digest provides.JSON.stringify(obj) inherits JSON.stringify's key-order sensitivity — two objects that are deeply equal but built with keys in a different order produce different strings and therefore different hashes, unless you sort keys before stringifying.The async-returns-a-buffer shape here is the same pattern as fetch returning a Response you still have to call .json() or .arrayBuffer() on — the platform hands you a low-level result and expects you to convert it into whatever shape you actually need. It also leans on the same string-to-bytes step every text-processing Web API needs, the same conversion TextEncoder/TextDecoder do for Web Workers passing data across the thread boundary.