WebTypeScript strict mode: i 9 setting che non disattiviamo mai
strict: true accende 9 controlli e da TypeScript 6.0 è il default. Cosa intercetta ogni flag in produzione, più i 3 flag fuori dal bundle che aggiungiamo.

TypeScript strict mode è un singolo flag dentro tsconfig.json che attiva nove controlli separati del compilatore tutti insieme. I nove sono strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, alwaysStrict e strictBuiltinIteratorReturn, entrato nella famiglia con TypeScript 5.6. La reference TSConfig di Microsoft li elenca dentro la famiglia strict e da TypeScript 6.0, uscito nel marzo 2026, strict è attivo di default: la issue 62333 è stata chiusa sulla milestone 6.0.0, quindi oggi un progetto deve spegnere strict di proposito per tornare al comportamento permissivo.
La maggior parte dei codebase parte con strict: true alla radice e una manciata di file o moduli che in silenzio rinunciano a uno o due di questi controlli. Le rinunce quasi mai tornano indietro. Questo articolo è l'elenco dei nove, con il fallimento che ognuno previene e il prezzo che abbiamo pagato per tenerli accesi. Nessuno è gratis. Nessuno ci è mai costato più del bug che ha intercettato.
Perché il bundle conta
Il flag strict è l'unico contratto stabile. Se un team disattiva uno dei nove, ogni lettore del codice deve ora ricordare quale è stato spento. Ogni dipendenza tipizzata contro strict acquisisce un buco in più. Ogni sviluppatore junior che entra nel team riscopre lo stesso inghippo. Il bundle non è una checklist; è una dichiarazione pubblica su come un codebase ragiona sui tipi. Una volta che un flag è spento, gli altri seguono lentamente. Da 6.0 quella dichiarazione si fa per omissione: strict è il default, quindi ogni rinuncia è ormai una riga scritta di proposito nella configurazione.
I 9 controlli dentro strict
1. strictNullChecks
Cosa intercetta. null e undefined come tipi distinti da ogni altro tipo. A flag spento, una funzione che ritorna User ritorna implicitamente User | null | undefined, e ogni dereferenza è insicura.
Il costo. Ogni valore da una risposta API, ogni prop opzionale, ogni return di Array.prototype.find() richiede un narrowing.
Perché lo lasciamo acceso. Ogni codebase che abbiamo ereditato con strictNullChecks: false spedisce un bug Cannot read properties of undefined in produzione almeno una volta al trimestre. Il costo runtime di uno di questi crash supera il costo dev di un if di parecchi ordini di grandezza. È la feature di sistema dei tipi più importante in TypeScript, e il consiglio di migrazione della community è costante: attivalo per ultimo perché produce il maggior numero di errori, e non spegnerlo mai più.
2. noImplicitAny
Cosa intercetta. Parametri e valori di ritorno senza annotazione e non inferibili. A flag spento, quelle posizioni diventano in silenzio any.
Il costo. Annotazioni in più sugli helper veloci, soprattutto durante la migrazione da JavaScript.
Perché lo lasciamo acceso. any è un virus. Un solo any in un helper profondo cancella i tipi su tutti i chiamanti, spesso senza un avviso. Ogni team che ho visto disattivare noImplicitAny "per la migrazione" un anno dopo aveva la maggior parte del codice nuovo non tipizzato. Il flag è l'unico meccanismo che obbliga il team a una scelta cosciente quando un tipo è sconosciuto, invece di scivolare in any per inerzia.
3. strictFunctionTypes
Cosa intercetta. Violazioni di controvarianza sui parametri di funzione. Una funzione che riceve un Dog non può essere assegnata a una variabile che si aspetta una funzione che riceve un qualsiasi Animal, perché il tipo più largo la chiamerebbe con valori che la funzione più stretta non sa gestire.
Il costo. Attrito occasionale con tipi di libreria scritti prima dell'esistenza di questo flag. Il flag inoltre non si applica ai parametri in sintassi metodo, quindi i casi più rumorosi restano silenziosi.
Perché lo lasciamo acceso. I bug di callback sono silenziosi a runtime. Un handler che si aspetta il sottotipo sbagliato viene chiamato con valori che non sa gestire e o crasha in profondità nel render o esegue in silenzio la cosa sbagliata. Nel nostro codice non abbiamo mai avuto un errore strictFunctionTypes che si rivelasse un falso positivo.
4. strictBindCallApply
Cosa intercetta. Argomenti sbagliati passati a .bind(), .call() o .apply().
Il costo. Praticamente zero nei codebase moderni, perché .bind() nessuno lo scrive più.
Perché lo lasciamo acceso. Il flag è gratis nel codice che usa arrow function, metodi di classe e hook React. Spegnerlo conterebbe solo per codice legacy che andrebbe modernizzato comunque. Non c'è alcun vantaggio nel disattivarlo.
5. strictPropertyInitialization
Cosa intercetta. Campi di classe dichiarati con un tipo non-undefined ma mai assegnati nel costruttore. Senza questo controllo, il campo è in silenzio undefined alla prima lettura.
Il costo. I campi vanno inizializzati nel costruttore, marcati con l'operatore di assegnamento definito (!), oppure il tipo deve includere undefined.
Perché lo lasciamo acceso. L'alternativa sono campi che sembrano inizializzati nel tipo ma non lo sono a runtime, e che spuntano come bug nel setup dei test e nei container DI. L'operatore ! è la via di fuga per i casi in cui un framework garantisce l'inizializzazione, e l'obbligo di scriverlo rende visibile l'assunzione. Il controllo si ripaga in ogni progetto che usa controller a classe, entità ORM o oggetti di servizio.
6. noImplicitThis
Cosa intercetta. Espressioni this il cui tipo il compilatore non riesce a inferire, tipicamente nei callback passati a forEach o in funzioni stand-alone usate come event handler.
Il costo. Zero nel codice React e Node moderno che usa arrow function e classi ES. Reale nel codice legacy class-heavy, che è poi proprio dove intercetta più bug.
Perché lo lasciamo acceso. Un this legato male è una delle famiglie di bug più antiche di JavaScript. Se TypeScript la intercetta gratis, non c'è motivo di rinunciare.
7. useUnknownInCatchVariables
Cosa intercetta. L'err dentro un blocco catch (err). Con questo flag err è tipizzato come unknown invece che any, quindi non lo si può dereferenziare senza prima fare narrowing.
Il costo. Una riga if (err instanceof Error) per blocco catch.
Perché lo lasciamo acceso. any dentro un catch è uno dei modi più comuni in cui any si infiltra nel resto del codice. err.message finisce in una stringa UI, poi in un payload di log, poi in un breadcrumb Sentry, e a quel punto any si è diffuso su quattro file. Fare narrowing una volta al confine del catch tiene onesta tutta la superficie a valle.
8. alwaysStrict
Cosa intercetta. Garantisce che ogni file parsato sia in strict mode e emette "use strict" nel JavaScript generato.
Il costo. Zero in qualsiasi codice scritto in questo decennio.
Perché lo lasciamo acceso. È già acceso di default in qualsiasi codice a forma di modulo (ESM, moduli CommonJS, classi). Il flag conta soprattutto per gli script e per i blocchi inline. Spento è un odore che qualcuno sta spedendo script semplici, che è un problema a parte.
9. strictBuiltinIteratorReturn
Cosa intercetta. Il value che leggi dal next() di un iteratore built-in. TypeScript 5.6 ha introdotto il tipo intrinseco BuiltinIteratorReturn, che è any di default e undefined con questo flag: così const { value, done } = iter.next() su un MapIterator, SetIterator o ArrayIterator tipizza value come possibilmente undefined finché non controlli done (release notes di TypeScript 5.6).
Il costo. Un controllo su done in ogni ciclo scritto a mano su map.entries() o set.values(). Zero nel for...of, che il risultato finale lo gestisce già.
Perché lo lasciamo acceso. È la stessa famiglia di bug di strictNullChecks, nell'unico punto dove strictNullChecks non arrivava. Il codice che chiama next() direttamente e legge value senza controllare done riceve undefined alla fine dell'iterazione, e con il flag spento il tipo diceva any: nessuno protestava.
Tre flag fuori da strict che attiviamo lo stesso
Il bundle strict copre il pavimento. Tre flag stanno fuori e hanno un impatto reale sul conteggio bug in produzione. Li attiviamo dal giorno uno su ogni progetto nuovo.
noUncheckedIndexedAccess
Obbliga obj[key] e arr[i] a essere tipizzati come T | undefined. Il comportamento di default assume che l'accesso vada sempre a buon fine, cosa che è falsa per qualsiasi record o array la cui forma è dinamica. La doc ufficiale lo dichiara in la pagina noUncheckedIndexedAccess, Resta fuori dal bundle strict, ma tsc --init lo scrive ormai in una configurazione nuova insieme a exactOptionalPropertyTypes, quindi un progetto appena creato li ha entrambi senza chiederlo (TypeScript PR 61813). Dal nostro lavoro: è il flag che aggiunge la pressione di type-check più alta di ogni altro singolo setting, ed elimina la classe di crash runtime più frequente che vediamo ancora nel codice con solo strict: true.
exactOptionalPropertyTypes
Separa "la proprietà potrebbe non essere impostata" da "la proprietà è impostata a undefined". La maggior parte dei team le tratta come la stessa cosa. Non lo sono, soprattutto con prop React passate attraverso wrapper e con stato di form dove un campo svuotato dall'utente produce una stringa vuota mentre un campo mai toccato produce una chiave mancante. Il flag impone la distinzione a livello di tipi e previene la fusione silenziosa che rompe la persistenza dei form.
noPropertyAccessFromIndexSignature
Obbliga obj["key"] per l'accesso dinamico e obj.key per le proprietà dichiarate. L'attrito è reale, ma il flag previene il fall-through silenzioso in cui un refuso in obj.foo ritorna undefined perché il tipo ha un index signature e il refuso non corrisponde a nessuna proprietà dichiarata.
Come migriamo un codebase legacy a strict
La migrazione ha sempre la stessa forma. Si accende strict alla radice in un branch. Si guarda il numero di errori. Si aggiunge un // @ts-nocheck in cima a ogni file che esplode, si registra il conteggio, e si rimuovono quei commenti un file per pull request. Non si abbassa strict per tenere verde la CI; gli errori soppressi oggi sono gli errori che un collega debugga alle 2 di notte il mese prossimo. Se la cremagliera si blocca, si esegue tsc --noEmit -p tsconfig.strict.json solo sui file modificati, così il codice nuovo paga il prezzo pieno mentre il codice vecchio viene ancora ripulito.
L'eccezione che abbiamo fatto, una volta, è stata su strictPropertyInitialization in un codebase NestJS dove il container DI garantisce l'inizializzazione dei campi dopo la costruzione. Anche lì, la risposta giusta è stata l'operatore ! sui campi specifici, non disattivare il flag su tutto il progetto.
Il riassunto in una riga
Ogni flag del bundle strict previene una classe di bug che abbiamo visto in produzione più di una volta. Tre flag fuori dal bundle prevengono i bug più comuni che sopravvivono a strict: true. Nessuno dei dodici ci è mai costato più del bug che ha intercettato. Se un team vuole disattivarne uno, la domanda giusta è quale bug preferisce spedire.
Aggiornato 2026-09-13: la famiglia strict è di nove flag, non otto, da quando strictBuiltinIteratorReturn è entrato con TypeScript 5.6, e strict è attivo di default da TypeScript 6.0, marzo 2026.
Domande frequenti
The strict family contains nine flags: strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, alwaysStrict, and strictBuiltinIteratorReturn, which joined in TypeScript 5.6. Setting strict: true turns on all nine at once, and since TypeScript 6.0 strict is the default, so you get all nine unless you switch strict off. Each one targets a specific class of bug, so disabling any single flag while leaving strict on at the root creates a hidden hole the rest of the codebase no longer guards against.
We think yes for any new project, even though it is not inside the strict bundle. The default behavior types obj[key] and arr[i] as if the access always succeeds, which is wrong for any record or array with a dynamic shape. It is no longer a question you have to answer yourself: tsc --init now writes noUncheckedIndexedAccess and exactOptionalPropertyTypes into a fresh config. The cost is real, it produces the most new type errors of any single flag in our experience. The benefit is that it removes the most common runtime crash class that still survives in code with strict: true alone, particularly around API responses, Record lookups, and array indexing.
Yes, and the order matters. Turn on strict at the root in a branch, then add // @ts-nocheck at the top of any file that produces too many errors to fix in one pass. Strip those comments one file per pull request, prioritising files closest to the user (API handlers, form code, payment flows). Inside the strict bundle, leave strictNullChecks for last: it produces the largest single error count and the highest cleanup effort. Do not lower strict to make CI green. Suppression at the file level is reversible; suppression at the config level rarely is.
Not in any way that matters. The strict flags change how types are checked, not how much work the type-checker does on each file. Build time and tsc --noEmit time are essentially identical with strict on or off. The real cost is in developer time spent fixing the errors strict surfaces, which is a one-time cost on a clean migration and an ongoing tax on new code that pays itself back the first time the flag prevents a production bug.
Servizi correlati
Articoli correlati
Web
WebDipendenze npm fuori controllo: i 4 costi sui frontend enterprise
Nel 2025 Sonatype ha contato 454.600 nuovi pacchetti malevoli e il 99,8% del malware del quarto trimestre veniva da npm. Quanto costa e come lo teniamo a freno.
Web