Guide

Airport Codes: IATA vs ICAO Explained

Understand the differences between IATA and ICAO airport coding systems. Learn how to use airport codes for flight search, hotel booking, and airport navigation with practical examples and API integrations.

Pavel Volkov
Aug 30, 2025
12 min read

Introduction to Airport Codes: IATA vs ICAO

Airport codes are essential identifiers used in aviation, travel booking systems, and logistics. Understanding the differences between IATA and ICAO airport coding systems is crucial for developers working with travel APIs, flight search engines, and airport navigation applications.

Understanding the Two Systems

IATA Codes (3-Letter Codes)

The International Air Transport Association (IATA) codes are the most commonly used airport identifiers:

  • Format: 3 letters (e.g., JFK, LHR, CDG)
  • Usage: Flight booking, baggage handling, passenger displays
  • Scope: Commercial aviation, travel industry
  • Examples: LAX (Los Angeles), NRT (Tokyo Narita), DXB (Dubai)

ICAO Codes (4-Letter Codes)

The International Civil Aviation Organization (ICAO) codes are used for air traffic control:

  • Format: 4 letters (e.g., KJFK, EGLL, LFPG)
  • Usage: Air traffic control, flight planning, aviation navigation
  • Scope: Global aviation operations, all airports and airfields
  • Examples: KLAX (Los Angeles), RJAA (Tokyo Narita), OMDB (Dubai)

Key Differences Comparison

Aspect IATA ICAO
Code Length 3 letters 4 letters
Primary Use Commercial aviation Air traffic control
Coverage Major airports only All airports and airfields
Booking Systems Yes No
Flight Planning Limited Yes

Implementation in Travel Applications

Flight Search API Integration

Most travel APIs use IATA codes for user-facing features:

// JavaScript: Flight search with multiple APIs
class FlightSearchService {
    constructor() {
        this.apis = {
            amadeus: {
                baseUrl: 'https://api.amadeus.com',
                codeType: 'IATA'
            },
            skyscanner: {
                baseUrl: 'https://partners.api.skyscanner.net',
                codeType: 'IATA'
            }
        };
    }

    async searchFlights(origin, destination, departDate, returnDate = null) {
        // Validate IATA codes
        if (!this.isValidIATACode(origin) || !this.isValidIATACode(destination)) {
            throw new Error('Invalid airport codes. Please use 3-letter IATA codes.');
        }

        const searchParams = {
            originLocationCode: origin,
            destinationLocationCode: destination,
            departureDate: departDate,
            returnDate: returnDate,
            adults: 1,
            max: 250
        };

        try {
            const response = await this.callAmadeusAPI('/shopping/flight-offers', searchParams);
            return this.formatFlightResults(response.data);
        } catch (error) {
            console.error('Flight search failed:', error);
            throw error;
        }
    }

    isValidIATACode(code) {
        return /^[A-Z]{3}$/.test(code);
    }

    formatFlightResults(flights) {
        return flights.map(flight => ({
            id: flight.id,
            origin: flight.itineraries[0].segments[0].departure.iataCode,
            destination: flight.itineraries[0].segments.slice(-1)[0].arrival.iataCode,
            departureTime: flight.itineraries[0].segments[0].departure.at,
            arrivalTime: flight.itineraries[0].segments.slice(-1)[0].arrival.at,
            price: flight.price.total,
            currency: flight.price.currency,
            carrier: flight.validatingAirlineCodes[0]
        }));
    }
}

Airport Autocomplete Implementation

Building user-friendly airport search with both codes:

// PHP: Airport search service
class AirportSearchService
{
    public function searchAirports(string $query, int $limit = 10): array
    {
        $query = strtoupper(trim($query));
        
        // Search by IATA code, ICAO code, or name
        $results = Airport::where(function ($q) use ($query) {
            $q->where('iata_code', 'LIKE', $query . '%')
              ->orWhere('icao_code', 'LIKE', $query . '%')
              ->orWhere('name', 'LIKE', '%' . $query . '%')
              ->orWhere('city', 'LIKE', '%' . $query . '%');
        })
        ->where('type', 'large_airport') // Prioritize major airports
        ->orderByRaw('CASE 
            WHEN iata_code LIKE ? THEN 1
            WHEN icao_code LIKE ? THEN 2
            WHEN name LIKE ? THEN 3
            ELSE 4
        END', [$query . '%', $query . '%', $query . '%'])
        ->limit($limit)
        ->get();

        return $results->map(function ($airport) {
            return [
                'iata_code' => $airport->iata_code,
                'icao_code' => $airport->icao_code,
                'name' => $airport->name,
                'city' => $airport->city,
                'country' => $airport->country,
                'display_name' => $this->formatAirportDisplay($airport),
                'coordinates' => [
                    'lat' => $airport->latitude,
                    'lng' => $airport->longitude
                ]
            ];
        })->toArray();
    }

    private function formatAirportDisplay($airport): string
    {
        $parts = array_filter([
            $airport->iata_code,
            $airport->name,
            $airport->city,
            $airport->country
        ]);
        
        return implode(' - ', $parts);
    }

    public function getAirportDetails(string $code): ?array
    {
        $airport = null;
        
        if (strlen($code) === 3) {
            $airport = Airport::where('iata_code', strtoupper($code))->first();
        } elseif (strlen($code) === 4) {
            $airport = Airport::where('icao_code', strtoupper($code))->first();
        }
        
        if (!$airport) {
            return null;
        }
        
        return [
            'iata_code' => $airport->iata_code,
            'icao_code' => $airport->icao_code,
            'name' => $airport->name,
            'city' => $airport->city,
            'country' => $airport->country,
            'timezone' => $airport->timezone,
            'elevation' => $airport->elevation_ft,
            'runways' => $this->getRunwayInfo($airport),
            'services' => $this->getAirportServices($airport)
        ];
    }
}

React Airport Selector Component

Building a modern airport selection interface:

import React, { useState, useEffect, useRef } from 'react';

const AirportSelector = ({ onSelect, placeholder = "Search airports...", value = "" }) => {
    const [query, setQuery] = useState(value);
    const [suggestions, setSuggestions] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    const [showSuggestions, setShowSuggestions] = useState(false);
    const debounceRef = useRef(null);

    useEffect(() => {
        if (query.length < 2) {
            setSuggestions([]);
            setShowSuggestions(false);
            return;
        }

        // Debounce search requests
        if (debounceRef.current) {
            clearTimeout(debounceRef.current);
        }

        debounceRef.current = setTimeout(async () => {
            setIsLoading(true);
            try {
                const response = await fetch(`/api/airports/search?q=${encodeURIComponent(query)}`);
                const data = await response.json();
                setSuggestions(data.airports || []);
                setShowSuggestions(true);
            } catch (error) {
                console.error('Airport search failed:', error);
                setSuggestions([]);
            } finally {
                setIsLoading(false);
            }
        }, 300);
    }, [query]);

    const handleSelect = (airport) => {
        setQuery(airport.display_name);
        setShowSuggestions(false);
        onSelect(airport);
    };

    const highlightMatch = (text, query) => {
        if (!query) return text;
        const regex = new RegExp(`(${query})`, 'gi');
        return text.replace(regex, '$1');
    };

    return (
        
setQuery(e.target.value)} onFocus={() => query.length >= 2 && setShowSuggestions(true)} placeholder={placeholder} className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> {isLoading && (
)} {showSuggestions && suggestions.length > 0 && (
{suggestions.map((airport) => (
handleSelect(airport)} >
{airport.iata_code}
{airport.city}, {airport.country} {airport.icao_code && ( ICAO: {airport.icao_code} )}
{airport.coordinates && (
{airport.coordinates.lat.toFixed(2)}, {airport.coordinates.lng.toFixed(2)}
)}
))}
)} {showSuggestions && query.length >= 2 && suggestions.length === 0 && !isLoading && (
No airports found matching "{query}"
)}
); }; export default AirportSelector;

Hotel Booking Integration

Airport codes are essential for location-based hotel searches:

// Python: Hotel search with airport proximity
import requests
from geopy.distance import geodesic
from typing import List, Dict, Optional

class HotelSearchService:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.booking.com/v1"
        
    def search_hotels_near_airport(
        self, 
        airport_code: str, 
        check_in: str, 
        check_out: str,
        radius_km: int = 20,
        adults: int = 2
    ) -> List[Dict]:
        """Search hotels near airport using IATA or ICAO code."""
        
        # Get airport coordinates
        airport_info = self.get_airport_info(airport_code)
        if not airport_info:
            raise ValueError(f"Airport {airport_code} not found")
            
        # Search hotels within radius
        search_params = {
            'latitude': airport_info['latitude'],
            'longitude': airport_info['longitude'],
            'radius': radius_km,
            'checkin': check_in,
            'checkout': check_out,
            'adults': adults,
            'currency': 'USD'
        }
        
        hotels = self.call_booking_api('/hotels/search', search_params)
        
        # Enhance results with distance from airport
        for hotel in hotels:
            hotel_location = (hotel['latitude'], hotel['longitude'])
            airport_location = (airport_info['latitude'], airport_info['longitude'])
            
            distance = geodesic(airport_location, hotel_location).kilometers
            hotel['distance_from_airport_km'] = round(distance, 1)
            hotel['airport_code'] = airport_code
            hotel['airport_name'] = airport_info['name']
            
        # Sort by distance from airport
        return sorted(hotels, key=lambda x: x['distance_from_airport_km'])
    
    def get_airport_shuttle_hotels(self, airport_code: str) -> List[Dict]:
        """Find hotels with airport shuttle service."""
        hotels = self.search_hotels_near_airport(airport_code, radius_km=30)
        
        return [
            hotel for hotel in hotels 
            if 'airport_shuttle' in hotel.get('amenities', []) or
               'free_airport_shuttle' in hotel.get('amenities', [])
        ]
    
    def format_hotel_display(self, hotel: Dict, airport_code: str) -> Dict:
        """Format hotel data for display with airport context."""
        return {
            'id': hotel['id'],
            'name': hotel['name'],
            'rating': hotel['rating'],
            'price_per_night': hotel['price'],
            'currency': hotel['currency'],
            'distance_text': f"{hotel['distance_from_airport_km']} km from {airport_code}",
            'has_shuttle': 'airport_shuttle' in hotel.get('amenities', []),
            'shuttle_info': self.get_shuttle_info(hotel),
            'address': hotel['address'],
            'amenities': hotel.get('amenities', []),
            'booking_url': hotel['booking_url']
        }

Airport Navigation and Wayfinding

Using airport codes for terminal and gate information:

// JavaScript: Airport terminal navigation
class AirportNavigationService {
    constructor() {
        this.terminalMaps = new Map();
        this.realTimeData = new Map();
    }

    async loadTerminalMap(airportCode) {
        if (this.terminalMaps.has(airportCode)) {
            return this.terminalMaps.get(airportCode);
        }

        try {
            const response = await fetch(`/api/airports/${airportCode}/terminals`);
            const terminalData = await response.json();
            
            this.terminalMaps.set(airportCode, terminalData);
            return terminalData;
        } catch (error) {
            console.error(`Failed to load terminal map for ${airportCode}:`, error);
            return null;
        }
    }

    async getFlightGateInfo(airportCode, flightNumber, date = new Date()) {
        const dateStr = date.toISOString().split('T')[0];
        
        try {
            const response = await fetch(
                `/api/airports/${airportCode}/flights/${flightNumber}?date=${dateStr}`
            );
            const flightData = await response.json();
            
            return {
                flight_number: flightData.flight_number,
                gate: flightData.gate,
                terminal: flightData.terminal,
                status: flightData.status,
                scheduled_departure: flightData.scheduled_departure,
                estimated_departure: flightData.estimated_departure,
                aircraft_type: flightData.aircraft_type,
                destination: flightData.destination,
                navigation: await this.getNavigationToGate(
                    airportCode, 
                    flightData.terminal, 
                    flightData.gate
                )
            };
        } catch (error) {
            console.error(`Flight info not found:`, error);
            return null;
        }
    }

    async getNavigationToGate(airportCode, terminal, gate) {
        const terminalMap = await this.loadTerminalMap(airportCode);
        if (!terminalMap) return null;

        const gateLocation = this.findGateLocation(terminalMap, terminal, gate);
        if (!gateLocation) return null;

        return {
            terminal: terminal,
            gate: gate,
            walking_time_minutes: gateLocation.walking_time,
            directions: gateLocation.directions,
            amenities_nearby: gateLocation.nearby_amenities,
            security_checkpoint: gateLocation.security_checkpoint,
            distance_from_entrance: gateLocation.distance_meters
        };
    }

    findGateLocation(terminalMap, terminal, gate) {
        const terminalData = terminalMap.terminals.find(t => t.code === terminal);
        if (!terminalData) return null;

        const gateData = terminalData.gates.find(g => g.number === gate);
        if (!gateData) return null;

        return {
            walking_time: gateData.walking_time_from_entrance,
            directions: gateData.directions,
            nearby_amenities: gateData.nearby_amenities,
            security_checkpoint: gateData.security_checkpoint,
            distance_meters: gateData.distance_from_entrance
        };
    }

    // Real-time updates for gate changes
    subscribeToFlightUpdates(airportCode, flightNumber, callback) {
        const eventSource = new EventSource(
            `/api/airports/${airportCode}/flights/${flightNumber}/updates`
        );
        
        eventSource.onmessage = (event) => {
            const update = JSON.parse(event.data);
            callback(update);
        };
        
        eventSource.onerror = (error) => {
            console.error('Flight updates subscription failed:', error);
            eventSource.close();
        };
        
        return () => eventSource.close();
    }
}

Database Schema for Airport Data

-- Comprehensive airport database schema
CREATE TABLE airports (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    iata_code CHAR(3) UNIQUE,
    icao_code CHAR(4) UNIQUE,
    name VARCHAR(255) NOT NULL,
    city VARCHAR(100) NOT NULL,
    country_code CHAR(2) NOT NULL,
    region VARCHAR(100),
    continent VARCHAR(50),
    type ENUM('large_airport', 'medium_airport', 'small_airport', 'heliport', 'seaplane_base', 'closed') DEFAULT 'small_airport',
    elevation_ft INT,
    latitude DECIMAL(10, 8),
    longitude DECIMAL(11, 8),
    timezone VARCHAR(50),
    scheduled_service BOOLEAN DEFAULT false,
    home_link VARCHAR(255),
    wikipedia_link VARCHAR(255),
    keywords TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_iata (iata_code),
    INDEX idx_icao (icao_code),
    INDEX idx_country (country_code),
    INDEX idx_type (type),
    INDEX idx_coordinates (latitude, longitude),
    INDEX idx_scheduled (scheduled_service),
    FULLTEXT idx_search (name, city, iata_code, icao_code, keywords)
);

CREATE TABLE airport_runways (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    airport_id BIGINT NOT NULL,
    runway_id VARCHAR(10) NOT NULL,
    length_ft INT,
    width_ft INT,
    surface VARCHAR(50),
    lighted BOOLEAN DEFAULT false,
    closed BOOLEAN DEFAULT false,
    
    FOREIGN KEY (airport_id) REFERENCES airports(id) ON DELETE CASCADE,
    INDEX idx_airport (airport_id)
);

CREATE TABLE airport_terminals (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    airport_id BIGINT NOT NULL,
    terminal_code VARCHAR(10) NOT NULL,
    terminal_name VARCHAR(100),
    
    FOREIGN KEY (airport_id) REFERENCES airports(id) ON DELETE CASCADE,
    UNIQUE KEY unique_terminal (airport_id, terminal_code)
);

CREATE TABLE airport_gates (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    terminal_id BIGINT NOT NULL,
    gate_number VARCHAR(10) NOT NULL,
    gate_type ENUM('domestic', 'international', 'both') DEFAULT 'both',
    
    FOREIGN KEY (terminal_id) REFERENCES airport_terminals(id) ON DELETE CASCADE,
    UNIQUE KEY unique_gate (terminal_id, gate_number)
);

Working with Flight APIs

FlightAware API Integration

class FlightTrackingService
{
    private string $apiKey;
    private string $baseUrl = 'https://aeroapi.flightaware.com/aeroapi';
    
    public function __construct(string $apiKey)
    {
        $this->apiKey = $apiKey;
    }
    
    public function getAirportFlights(string $airportCode, string $type = 'departures'): array
    {
        // Convert IATA to ICAO if needed for FlightAware
        $icaoCode = $this->convertIATAToICAO($airportCode);
        
        $endpoint = $type === 'arrivals' ? 'arrivals' : 'departures';
        $url = "{$this->baseUrl}/airports/{$icaoCode}/{$endpoint}";
        
        $response = $this->makeRequest($url, [
            'max_pages' => 2,
            'start' => date('c'),
            'end' => date('c', strtotime('+24 hours'))
        ]);
        
        return array_map(function($flight) {
            return [
                'flight_number' => $flight['ident'],
                'airline' => $flight['operator'],
                'aircraft_type' => $flight['aircraft_type'],
                'origin' => $flight['origin']['code'] ?? null,
                'destination' => $flight['destination']['code'] ?? null,
                'scheduled_time' => $flight['scheduled_out'] ?? $flight['scheduled_in'],
                'estimated_time' => $flight['estimated_out'] ?? $flight['estimated_in'],
                'actual_time' => $flight['actual_out'] ?? $flight['actual_in'],
                'status' => $this->mapFlightStatus($flight),
                'gate' => $flight['gate_origin'] ?? $flight['gate_destination'] ?? null,
                'terminal' => $flight['terminal_origin'] ?? $flight['terminal_destination'] ?? null
            ];
        }, $response['departures'] ?? $response['arrivals'] ?? []);
    }
    
    private function convertIATAToICAO(string $code): string
    {
        if (strlen($code) === 4) {
            return $code; // Already ICAO
        }
        
        // Use your airport database to convert
        $airport = Airport::where('iata_code', $code)->first();
        return $airport?->icao_code ?? $code;
    }
    
    private function makeRequest(string $url, array $params = []): array
    {
        $headers = [
            'x-apikey: ' . $this->apiKey,
            'Accept: application/json'
        ];
        
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url . '?' . http_build_query($params),
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30
        ]);
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($httpCode !== 200) {
            throw new Exception("FlightAware API error: HTTP {$httpCode}");
        }
        
        return json_decode($response, true);
    }
}

Performance Optimization

Caching Airport Data

// Redis caching for airport searches
class AirportCacheService
{
    private $redis;
    private int $defaultTTL = 3600; // 1 hour
    
    public function __construct()
    {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    
    public function getCachedAirportSearch(string $query): ?array
    {
        $cacheKey = "airport_search:" . md5(strtolower($query));
        $cached = $this->redis->get($cacheKey);
        
        return $cached ? json_decode($cached, true) : null;
    }
    
    public function cacheAirportSearch(string $query, array $results): void
    {
        $cacheKey = "airport_search:" . md5(strtolower($query));
        $this->redis->setex($cacheKey, $this->defaultTTL, json_encode($results));
    }
    
    public function preloadPopularAirports(): void
    {
        $popularAirports = [
            'JFK', 'LAX', 'LHR', 'CDG', 'NRT', 'SIN', 'DXB', 'FRA',
            'AMS', 'MAD', 'BCN', 'FCO', 'MUC', 'VIE', 'ZUR', 'CPH'
        ];
        
        foreach ($popularAirports as $code) {
            $this->preloadAirportData($code);
        }
    }
    
    private function preloadAirportData(string $code): void
    {
        $airport = Airport::where('iata_code', $code)->first();
        if ($airport) {
            $cacheKey = "airport_details:" . $code;
            $data = [
                'iata_code' => $airport->iata_code,
                'icao_code' => $airport->icao_code,
                'name' => $airport->name,
                'city' => $airport->city,
                'country' => $airport->country,
                'coordinates' => [
                    'lat' => $airport->latitude,
                    'lng' => $airport->longitude
                ]
            ];
            
            $this->redis->setex($cacheKey, 86400, json_encode($data)); // 24 hour cache
        }
    }
}

Common Use Cases and Best Practices

Best Practices

  • Always validate airport codes - Check both format and existence
  • Use IATA for customer-facing features - More recognizable to travelers
  • Use ICAO for technical integrations - More comprehensive coverage
  • Implement proper error handling - Invalid codes should fail gracefully
  • Cache frequently accessed data - Airport info changes rarely
  • Consider timezone handling - Essential for flight schedules

Common Pitfalls

  • Not all airports have IATA codes - Small airfields may only have ICAO
  • Code conflicts can exist - Same code in different regions
  • Seasonal airports - Some codes are only active seasonally
  • Historical codes - Old airports may retain codes after closure

Understanding both IATA and ICAO airport coding systems is essential for building robust travel applications. By implementing proper validation, caching, and error handling, you can create seamless experiences for users while integrating with various aviation APIs and services.

Last updated: Sep 27, 2025