This post explains a quiz originally shared as a LinkedIn poll.
๐น The Question
class MaskPII {
constructor(char) {
this.char = char;
}
[Symbol.replace](value) {
return value.replace(/\d/g, this.char);
}
}
const token = 'card=4111-1111-1111-1111';
const masked = token.replace(new MaskPII('#'));
console.log(masked);
Hint: String.replace looks for a Symbol.replace hook before treating the argument as a regular expression or string, so the custom mask object runs with the original string.
๐น Solution
Correct Answer: A) card=####-####-####-####
The logged string is: card=####-####-####-####
๐ง How this works
String.prototype.replace checks if the replacement argument exposes a [Symbol.replace] method. When it does, the engine delegates the entire substitution process to that method instead of doing a built-in replace. In our case, the MaskPII class implements the protocol and simply delegates to the original string with its own String.prototype.replace call, swapping every digit with the provided mask character.
This means the usual RegExp parsing logic never runsโthe string is passed as-is to our hook. Because the digits are replaced in one pass, each numeric character is turned into #, including digits inside the hyphenated credit card pattern, while the hyphens remain untouched.
๐ Line-by-line explanation
-
class MaskPIIdefines a helper with[Symbol.replace]soString.replacecalls it instead of interpreting the object as a RegExp. -
tokenis set to a hyphenated credit card string containing only digits and hyphens. -
token.replace(new MaskPII('#'))triggersString.replace, which immediately invokes the mask's[Symbol.replace]with the full string. - Inside the hook,
value.replace(/\d/g, this.char)iterates over every digit and substitutes#, leaving hyphens untouched. - The hook returns the fully masked string, which
String.replacelogs.
๐น Real-World Impact
Custom [Symbol.replace] implementations are a handy way to keep instrumentation, analytics, or logging pipelines in control of how sensitive strings are displayed. When teams try to mask tokens, session IDs, or credit card numbers directly via existing libraries, they often end up leaking digits because the library still applies its built-in regex. Implementing the mask as a Symbol.replace hook ensures the masking logic always runs before any downstream replace logic can mutate the string, preventing partial redactions that could fail compliance audits.
๐น Key Takeaways
-
String.replacefirst checks for a[Symbol.replace]hook and delegates entirely to it if present. - Masking sensitive data is safer when you control the replacement hook to avoid built-in RegExp behavior leaking digits.
- Because the hook receives the original string, you can reuse existing regex helpers while still overriding the final output.
- Customizing
[Symbol.replace]is useful for logging, auditing, or sanitizing middleware that crosses synchronous and asynchronous boundaries. - Always confirm that middleware relying on
.replacestill respects your masking hook rather than re-running its own regex after partial replacements.
Follow me for JavaScript puzzles and weekly curations of developer talks & insights at Talk::Overflow: https://talkoverflow.substack.com/
Top comments (0)