In JavaScript, what happens when you assign to a character position of a string, as in `let s = 'hello'; s[0] = 'H';`, and what does that tell you about how string methods behave?
answer
- the value versus the variable
- index properties are read-only
- primitives get a throwaway wrapper
- sloppy ignores, strict throws
- methods produce, never edit
basics
~20 sJavaScript strings are immutable, so the assignment silently does nothing in sloppy mode and throws a TypeError in strict mode; s stays "hello". Every string method, such as toUpperCase or trim, returns a brand-new string instead of editing the original.
solid answer
~40 sStrings in JavaScript are immutable primitives, so there is no way to edit one in place. Index properties of a string are non-writable, and writing through a primitive first boxes it into a throwaway `String` wrapper, so the write is discarded — silently in sloppy mode, and with a `TypeError` in strict mode (which includes all ES module code and class bodies). The practical consequence is that every method that looks like an edit — `toUpperCase()`, `trim()`, `replace()`, `slice()`, `padStart()` — leaves the receiver untouched and hands back a new string. That is why `input.trim();` on its own line is a classic bug: you have to use or reassign the result. Note also that `s = s.toUpperCase()` is rebinding the variable, not mutating the value.
code
javascript · 15 lineslet s = 'hello';
s[0] = 'H';
console.log(s); // "hello" — the write was silently discarded
function strictWrite() {
'use strict';
const t = 'hello';
t[0] = 'H';
}
try {
strictWrite();
} catch (err) {
console.log(err.constructor.name); // "TypeError"
}go deeper
Be ready to say plainly that strings cannot be changed in place: assigning to an index does nothing useful, and trim, toUpperCase and replace hand back a new string you must assign or use.
Explain the mechanism rather than the slogan: index properties on strings are non-writable, and a write through a primitive goes via a temporary wrapper object, so it is discarded in sloppy mode and throws a TypeError in strict mode.
Show where the discarded-result bug appears in real code — a lone sanitising call whose value is never used — and how you catch it systematically with a lint rule for unused expressions plus a test at the boundary that was supposed to clean the input.
Own the tradeoff: immutable strings remove a whole class of aliasing bugs and make text safe to share as cache and map keys without defensive copies, while incremental string building is an engine optimisation rather than a language guarantee and deserves measurement, not folklore.
## What "immutable" means here A JavaScript string is a primitive value, like a number or a boolean. The character sequence it holds is fixed the moment the value exists, and nothing in the language can change it afterwards. Anything that looks like an edit — uppercasing, trimming, replacing, slicing — actually produces a *different* string value and leaves the original exactly as it was. This is a property of the *value*, not of the *variable*. `let s = 'hello'; s = 'world';` is perfectly legal: you rebound the name `s` to a different string. What you cannot do is turn the string `'hello'` itself into `'Hello'`. ## Why `s[0] = 'H'` does nothing Two separate mechanisms conspire here. First, indexed properties of a string are read-only. In spec terms a String exotic object exposes each index as a property with `writable: false`, `enumerable: true`, `configurable: false`. You can observe this directly: ```javascript console.log(Object.getOwnPropertyDescriptor(Object('hi'), '0')); // { value: 'h', writable: false, enumerable: true, configurable: false } ``` Second, `s` in the example is a *primitive*, not an object, and primitives have no properties of their own. When you write `s[0] = 'H'`, the engine temporarily wraps the primitive in a `String` object, attempts the assignment on that wrapper, and then throws the wrapper away. Even if the property were writable, the mutation would land on an object that is discarded on the next line. The outcome depends on the mode: ```javascript let s = 'hello'; s[0] = 'H'; console.log(s); // "hello" — the write was silently discarded function strictWrite() { 'use strict'; const t = 'hello'; t[0] = 'H'; // TypeError: Cannot assign to read only property '0' } ``` Sloppy mode ignores a failed assignment to a non-writable property; strict mode throws a `TypeError`. Since ES module code and class bodies are strict by default, in a modern codebase this usually throws rather than fails quietly — which is the friendlier outcome. ## Methods return new strings No method on `String.prototype` mutates its receiver. Every one of them is a producer: ```javascript const raw = ' Ada '; raw.trim(); // returns "Ada" — and the result is thrown away console.log(raw); // " Ada " — unchanged const clean = raw.trim(); // keep the result console.log(clean); // "Ada" ``` The forgotten-result bug is by far the most common way this bites people, and it is easy to miss in review because the line reads like it does something. A lint rule against unused expressions catches most instances. Contrast this with arrays, where some methods mutate (`push`, `sort`, `reverse`) and some copy. Strings have no such split: there is nothing to mutate. ## Reassignment is not mutation ```javascript let name = 'ada'; name = name.toUpperCase(); // rebinding the variable console.log(name); // "ADA" const fixed = 'ada'; // fixed = fixed.toUpperCase(); // TypeError: Assignment to constant variable ``` `const` prevents rebinding the *name*. It is not what makes the string immutable — the string was already immutable, and `let` does not make it any less so. Similarly, `Object.freeze('abc')` is pointless: since ES2015 it simply returns the primitive unchanged, because there was never anything mutable to freeze. ## Why this design is convenient Because a string can never change underneath you, passing one to a function is inherently safe: the callee cannot corrupt the caller's value, and you never need a defensive copy. The same property makes strings dependable keys in a `Map` or in a cache, and lets engines share and intern string data behind the scenes. The one cost people worry about is building a long string incrementally. The spec says each `+=` produces a new value, which sounds like quadratic work, but real engines represent concatenations with internal shared structures and flatten them lazily, so ordinary loop concatenation is usually fine. That is an engine optimisation, not a language guarantee — if it matters for your workload, measure it rather than repeating folklore. ## What an interviewer is really checking They want to see that you distinguish *value* from *variable*, that you know method calls are producers, and that you can name the strict-vs-sloppy difference rather than saying vaguely "it doesn't work".
- Why does the same assignment throw in one file and fail silently in another?Because of strict mode. A failed write to a non-writable property is ignored in sloppy mode but raises a `TypeError` in strict mode. ES modules and class bodies are strict automatically, and a classic script becomes strict only with the `'use strict'` directive, so identical code can behave differently depending on where it lives.
- If strings are immutable, why does `const` still matter for a string variable?`const` constrains the binding, not the value: it stops the name being reassigned later. The string itself needed no protection — it was already unchangeable. So `const` here is about preventing accidental rebinding and signalling intent, not about immutability of the text.
- How would you spot this bug in a code review?Look for a method call used as a standalone statement — `input.trim();`, `s.replace(/x/, 'y');` — with the result discarded. Since no string method mutates, such a line is always dead code. A lint rule for unused expressions flags them mechanically, and a test at the boundary that was supposed to sanitise the value catches the rest.
saying these in an interview costs you the question
- Claims s[0] = 'H' edits the string in place
- Says string methods modify the receiver like Array.prototype.push
- Thinks const is what makes a string immutable
- Believes the assignment always throws, regardless of mode
- Suggests Object.freeze to make a string immutable