Dimostra buone potenzialità in salita già da dilettante: nel 2015, correndo per il Team Colpack, vince la classifica degli scalatori (con un secondo posto di tappa) al Giro della Valle d'Aosta, si impone nella Bassano-Monte Grappa, arriva sesto nella classifica finale del Tour de l'Avenir e secondo al Piccolo Giro di Lombardia. Nella seconda parte di stagione, con in mano un contratto già firmato per il team Professional Bardiani-CSF, vince anche la Milano-Rapallo e la Coppa Città di San Daniele battendo il favorito Gianni Moscon. Questa vittoria gli permette di conquistare la vetta della classifica del Prestigio Bicisport 2015 e la conseguente vittoria del titolo.
2016-2018: gli esordi da professionista
Diventa così professionista nel 2016 con la Bardiani-CSF. Nella gara d'esordio, il Gran Premio Costa degli Etruschi, arriva con il gruppo di 11 corridori che si giocano la vittoria, piazzandosi quinto in volata. Successivamente si piazza sesto in classifica alla Settimana Internazionale di Coppi e Bartali e quarto nella seconda tappa del Giro del Trentino, con arrivo su una salita breve ad Anras, vestendo anche la maglia bianca di miglior giovane prima che un virus intestinale lo costringa ad abbandonare la corsa. Pochi giorni dopo prende il via al suo primo Giro d'Italia. Nella decima tappa, di media montagna, con arrivo a Sestola, entra nella fuga buona. Si impone con un allungo nel penultimo tratto di discesa prima della rampa finale, andando a vincere con 42" su Ivan Rovnyj e 1'20" su Darwin Atapuma.[1] Successivamente non prende il via della diciannovesima tappa a causa di problemi fisici e la molta stanchezza accumulata.
L'inizio della stagione 2017 è molto travagliato a causa di vari infortuni che lo penalizzano. Rientra in gara dopo due operazioni al cuore,[2] e partecipa al Giro d'Italia senza ottenere risultati di rilievo: conclude la corsa rosa con il 95º posto in classifica generale. A luglio partecipa al Giro d'Austria, convocato in maglia azzurra della nazionale italiana dal ct Davide Cassani, e nella quarta frazione con arrivo in salita a Kitzbühel ottiene un buon terzo posto dietro al vincitore Miguel Ángel López e a Stefan Denifl; conclude invece al quarto posto la successiva tappa di alta montagna, dopo aver distanziato anche il gruppo degli uomini di classifica. In luglio prende il via al Tour of Utah, concludendo sesto. Nella corsa statunitense dimostra subito una buona condizione: prima si mette in mostra nella terza frazione, la cronoscalata individuale, concludendo al quinto posto, e poi, nella quinta tappa, scollina in prima posizione sui due GPM di giornata. L'indomani, nella sesta tappa con arrivo al termine di una salita di 8 km, stacca in solitaria tutti gli avversari ai -3,7 km dal traguardo e giunge al traguardo vittorioso.[3] Chiude la stagione 2017 con un altro podio prestigioso, il terzo posto posto ottenuto alla Pro Ötztaler 5500, preceduto dal vincitore Roman Kreuziger e da Simon Špilak.[4]
A partire dalla stagione 2019 si trasferisce alla Trek-Segafredo, formazione World Tour.[6] Inizia la stagione con un decimo posto al Challenge de Mallorca.
Convocato in Nazionale per il Trofeo Laigueglia, si dimostra tra i migliori in salita e giunge in sesta posizione.
Il 23 febbraio centra la vittoria nella seconda tappa del Tour du Haut Var. In seguito partecipa alla Parigi-Nizza.
Successivamente, grazie agli ottimi risultati conseguiti al Giro, prende parte anche al Tour de France, in supporto al capitano Richie Porte. Al termine della sesta tappa, con arrivo a La Planche des Belles Filles, conclusa in seconda posizione (battuto dal belga Dylan Teuns) dopo una lunga fuga, conquista la maglia gialla per 6 secondi su Julian Alaphilippe; dopo due giorni è costretto a cedere il simbolo del primato allo stesso Alaphilippe, ma mantiene la maglia bianca fino al termine della decima tappa, quando a indossarla è Egan Bernal, che poi vincerà il Tour.
2020: la stagione della COVID-19
Inizia la stagione 2020 vincendo il Trofeo Laigueglia, prima gara stagionale: nell'occasione, correndo con la maglia della Selezione italiana, è autore di un forcing sulla salita di Colla Micheri con altri tre attaccanti; prima dello scollinamento allunga sui tre rivali e al termine della discesa giunge solitario al traguardo, vincitore con 32" di margine su Biniam Girmay e Diego Rosa.[8]
La prima parte della stagione 2023, vissuta in preparazione al Giro d'Italia, è molto promettente: oltre a due vittorie di tappa, una alla Volta a la Comunitat Valenciana (in cui si aggiudica anche la classifica a punti) e una alla Volta a Catalunya, conclude nei primi 10 tutte le brevi corse a tappe a cui prende parte. Partecipa inoltre alle classiche delle Ardenne, cogliendo un quinto posto alla Freccia Vallone e un tredicesimo alla Liegi-Bastogne-Liegi. Il giorno seguente, tuttavia, risulta positivo al COVID-19 e, dopo una settimana in cui continuano a persistere i sintomi, decide di comune accordo con la Trek-Segafredo di non partecipare alla Corsa Rosa.
Torna alle corse a inizio giugno, al Giro del Delfinato, aggiudicandosi l'ultima frazione e la maglia a pois riservata al miglior scalatore. Partecipa quindi al Tour de France con due obiettivi principali: una vittoria di tappa e la maglia a pois; giunge secondo, dopo essere entrato nella fuga di giornata, nella quinta frazione, e al termine della quindicesima, dopo aver fatto nuovamente parte dei battistrada, veste per la prima volta la maglia a pois. Nelle tappe successive riesce a mantenere la leadership e durante la ventesima tappa si assicura matematicamente la maglia, che mancava in Italia dall'edizione del 1992 con Claudio Chiappucci.