I'm using a zip code listing, and I am curious to know how many (or which) zip codes map to more than one US state or US city?

For instance I know that Zip Code 42223 resolves to US Army, Fort Campbell which straddles the KY-TN state line. Oddly, the google API only returns TN for that state corresponding to that zip.

  • How are you defining "city", and "zip code"? – Evan Carroll Jan 5 '17 at 20:10

There are 13 multi-state US Census' ZIP Code Tabulation Areas (ZCTAs): 02861, 42223, 59221, 63673, 71749, 73949, 81137, 84536, 86044, 86515, 88063, 89439 & 97635.

As others have mentioned, there are a few different ways to figure out the area covered by a ZIP Code, but ZCTAs are the easiest, and the only official version that I know of.

So your example of 42223 does span a state border, but it looks like it is actually between Maryland and Virginia. that between Kentucky and Tennessee.

Here's the full list with states:

 02861  Massachusetts
 02861   Rhode Island
 42223       Kentucky
 42223      Tennessee
 59221        Montana
 59221   North Dakota
 63673       Illinois
 63673       Missouri
 71749       Arkansas
 71749      Louisiana
 73949       Oklahoma
 73949          Texas
 81137       Colorado
 81137     New Mexico
 84536        Arizona
 84536           Utah
 86044        Arizona
 86044           Utah
 86515        Arizona
 86515     New Mexico
 88063     New Mexico
 88063          Texas
 89439     California
 89439         Nevada
 97635     California
 97635         Oregon

Here's how I generated it (with Pandas in Python):

import pandas as pd

zcta_to_place_url = 'http://www2.census.gov/geo/docs/maps-data/data/rel/zcta_place_rel_10.txt'

# load relevant data
df = pd.read_csv(
  dtype={'ZCTA5': str},
  usecols=['ZCTA5', 'STATE'])

# the data often repeats the same (ZCTA, state) pair. Remove these
df = df.drop_duplicates()

# get number of times each ZCTA appears (most are only 1)
counts = df['ZCTA5'].value_counts()

# get those listed more than once
multi_state_zips = df[df.ZCTA5.isin(counts[counts > 1].index)]

# the census uses numeric state codes
# replace these with state names

census_codes_to_names_url = 'http://www2.census.gov/geo/docs/reference/state.txt'

states = pd.read_csv(census_codes_to_names_url, sep='|')
merged = pd.merge(
  multi_state_zips, states,
  )[['ZCTA5', 'STATE_NAME']]
print merged.sort(['ZCTA5', 'STATE_NAME']).to_string(index=False)

Edit: It seems the Census has two different two-digit codings for states. Both are numbers assigned based on the state's alphabetical ordering, but one seems to apply the numbers directly from 1-51 (50 states + DC), while the other skips some numbers. I was using the first, while I should have been using the second, so the state names I listed were wrong. I've updated the code and results with the correct list.

Edit: new state mapping confirmed by the OpenCongress API: https://gist.github.com/gabrielgrant/89f883d093e2abf129ad

  • 1
    Thanks a lot for catching this @JesseCrocker - It seems the Census (confusingly) has two different two-digit codings for states. Both are numbers assigned based on the state's alphabetical ordering, but one seems to apply the numbers directly from 1-51 (50 states + DC), while the other skips some numbers. I was using the first, but I should have been using the second, so the state names I listed were wrong (though the ZCTAs were good). I've fixed the code and results with the correct list. – Gabriel Grant Oct 22 '15 at 10:42
  • 2
    Regarding the gaps in the FIPS codes, the skipped numbers were reserved in the 1970s for outlying territories (American Samoa, Canal Zone, Guam, Puerto Rico, and Virgin Islands), but then didn't end up being used for them. en.wikipedia.org/wiki/… – neuhausr Jan 7 '16 at 14:41
  • 3
    Don't forget zipcode 57717 which spans three states six, counties, and multiple cities: 57717 Aurora, SD 57717 Butte, SD 57717 Carter, MT 57717 Crook, WY 57717 Harding, SD 57717 Lawrence, SD – Jeffrey Apr 22 '16 at 21:58
  • This listing isn't near complete. Check out my answer for a far better approximation. gis.stackexchange.com/a/223445/6052 – Evan Carroll Jan 5 '17 at 19:08
  • @Jeffrey interesting, I wonder why that's not listed in the ZCTA place list? – Gabriel Grant Jan 17 '17 at 5:29

There really isn't a way to tell this; since there is not a ZipCode boundary shape that is defined by the USPS. ZipCodes are defined by a bounding box of Streets delivered to by carriers from a particular distribution center.

So you would need to take the USPS AIS data and extract by ZipCodes the streets that are delivered by a given Post Office, then Join these a street grid. This is what all the commercial vendors do (Nokia/TomTom) to create the Psuedo shape that they use to show postal boundaries.

This inexact process is the reason why the USPS does not provide spatial data.


The US Census Bureau derives approximate boundaries for ZIP codes based on the addresses contained within them, called ZIP Code Tabulation Areas (ZCTAs).

They publish relationship files that describe how their ZCTAs map to various other geographies. If you examine the ZCTA to Place relationship file you can see how they map to cities and towns. You can infer how they map to states from the ZCTA to Counties relationship file.

The relationship files use Census geography IDs, so you'll want to grab a gazetteer file to help you convert the numeric IDs into the place or county names you're expecting.

As other answers have stated, any mapping of ZIP codes to places is likely to be approximate, but I've had good luck with the Census data files.


2016 TIGER Data with PostGIS

As a special caveat, ZCTA data isn't USPS Zip Codes. It's an approximation of it. USPS Zip Codes are really horrible and not useful except to approximate. Everyone, including every governmental entity other than USPS, and (the Census for making ZCTA) ignores them entirely. If USPS wanted to a grow up a bit, they'd just convert to the latest ZCTA and provide authoritative GIS polygons.

Then... Here we query for intersections between TIGER State and TIGER ZCTA datasets. Note, we qualify states by 1% of the total ZCTA area. If 1% of the ZCTA area isn't in the state, we assume it's a rounding error, or someone fat fingering something at the Census. Check out 56168 or even 83832 for a zip code that we're pruning with this added selectivity.

SELECT zcta5ce10, array_agg(state.name ORDER BY state.name) AS states
FROM census.state AS state
JOIN census.zcta AS zcta ON (
  ST_Intersects(state.geog::geometry, zcta.geog::geometry)
  AND NOT ST_Touches(state.geog::geometry, zcta.geog::geometry)
  AND ST_Area(ST_Intersection(state.geog, zcta.geog)) > (ST_Area(zcta.geog)*0.01)
GROUP BY zcta.zcta5ce10
HAVING count(*) > 1
ORDER BY zcta5ce10;

Here is the resulset

 zcta5ce10 |            states            
 03579     | {Maine,"New Hampshire"}
 20135     | {Virginia,"West Virginia"}
 24604     | {Virginia,"West Virginia"}
 31905     | {Alabama,Georgia}
 38079     | {Kentucky,Tennessee}
 38769     | {Arkansas,Mississippi}
 38852     | {Alabama,Mississippi}
 42223     | {Kentucky,Tennessee}
 51001     | {Iowa,"South Dakota"}
 51023     | {Iowa,"South Dakota"}
 51360     | {Iowa,Minnesota}
 51557     | {Iowa,Nebraska}
 51640     | {Iowa,Missouri}
 52542     | {Iowa,Missouri}
 52573     | {Iowa,Missouri}
 52626     | {Iowa,Missouri}
 54554     | {Michigan,Wisconsin}
 56027     | {Iowa,Minnesota}
 56144     | {Minnesota,"South Dakota"}
 56164     | {Minnesota,"South Dakota"}
 56219     | {Minnesota,"South Dakota"}
 56744     | {Minnesota,"North Dakota"}
 57026     | {Minnesota,"South Dakota"}
 57030     | {Minnesota,"South Dakota"}
 57068     | {Minnesota,"South Dakota"}
 57078     | {Nebraska,"South Dakota"}
 57638     | {"North Dakota","South Dakota"}
 57641     | {"North Dakota","South Dakota"}
 57642     | {"North Dakota","South Dakota"}
 57645     | {"North Dakota","South Dakota"}
 57648     | {"North Dakota","South Dakota"}
 57660     | {"North Dakota","South Dakota"}
 57717     | {"South Dakota",Wyoming}
 57724     | {Montana,"South Dakota"}
 58225     | {Minnesota,"North Dakota"}
 58439     | {"North Dakota","South Dakota"}
 58623     | {"North Dakota","South Dakota"}
 58649     | {"North Dakota","South Dakota"}
 58653     | {"North Dakota","South Dakota"}
 59221     | {Montana,"North Dakota"}
 59270     | {Montana,"North Dakota"}
 59275     | {Montana,"North Dakota"}
 59847     | {Idaho,Montana}
 63673     | {Illinois,Missouri}
 65729     | {Arkansas,Missouri}
 65733     | {Arkansas,Missouri}
 65761     | {Arkansas,Missouri}
 66541     | {Kansas,Nebraska}
 67950     | {Kansas,Oklahoma}
 68325     | {Kansas,Nebraska}
 68719     | {Nebraska,"South Dakota"}
 68978     | {Kansas,Nebraska}
 69201     | {Nebraska,"South Dakota"}
 69212     | {Nebraska,"South Dakota"}
 69216     | {Nebraska,"South Dakota"}
 71749     | {Arkansas,Louisiana}
 72338     | {Arkansas,Tennessee}
 72644     | {Arkansas,Missouri}
 73949     | {Oklahoma,Texas}
 75556     | {Arkansas,Texas}
 79837     | {"New Mexico",Texas}
 80758     | {Colorado,Nebraska}
 81137     | {Colorado,"New Mexico"}
 81324     | {Colorado,Utah}
 82063     | {Colorado,Wyoming}
 82082     | {Nebraska,Wyoming}
 82701     | {"South Dakota",Wyoming}
 82801     | {Montana,Wyoming}
 82930     | {Utah,Wyoming}
 83111     | {Idaho,Wyoming}
 83120     | {Idaho,Wyoming}
 83312     | {Idaho,Utah}
 83342     | {Idaho,Utah}
 84034     | {Nevada,Utah}
 84531     | {Arizona,Utah}
 84536     | {Arizona,Utah}
 86044     | {Arizona,Utah}
 86504     | {Arizona,"New Mexico"}
 86514     | {Arizona,Utah}
 86515     | {Arizona,"New Mexico"}
 87328     | {Arizona,"New Mexico"}
 88220     | {"New Mexico",Texas}
 88430     | {"New Mexico",Texas}
 89010     | {California,Nevada}
 89019     | {California,Nevada}
 89060     | {California,Nevada}
 89421     | {Nevada,Oregon}
 89439     | {California,Nevada}
 89832     | {Idaho,Nevada}
 97635     | {California,Oregon}
 97910     | {Idaho,Oregon}
 99128     | {Idaho,Washington}
 99362     | {Oregon,Washington}
(93 rows)

You should be able to spot check all of these in Google Maps. However, Google Maps is also not authoritative.


State Overlaps mentioned in 1994 Census Document

In June 1994, according to the following U.S. Census Bureau site there are 153 zip codes that cross state boundries.

As mentioned earlier, there are a few ZIP Codes that deliver across state lines, and there are a few ZIP/sectors that cross county lines. There are 153 ZIP Codes in more than one state. There are 9,000 ZIP Codes in more than one county. There were 11,331 (out of the total 857,400) ZIP/sectors that were split by county. All states had some split sectors, with Virginia, Michigan and Ohio having an especially larger dosage. The rural route sectors, as expected, contained (relatively) the lion's share of split sectors. Most of the other cases are in the lower sector range (reserved for post office boxes) and in Sector 99 (reserved for the postmaster and business mail return). There must be some non-standard county code assignment occurring for these selected cases. We will have to further investigate these at a later date.


With ArcGIS you can use the spatial join tool (or in a script) to find which zip code polygons intersect with more than one state polygons. In the output feature class, there will be a Join_Count field that will indicate multiple states. You could do a similar thing with zips and cities. There will likely be false positives where the zips unintentionally overlap more than one because of border inaccuracies/lack or resolution. You could possibly do a negative -100m buffer of the zips before the spatial join and see what that does.

import arcpy

target_features = "C:/data/usa.gdb/states"
join_features = "C:/data/usa.gdb/zips"
out_feature_class = "C:/data/usa.gdb/states_zips"

arcpy.SpatialJoin_analysis(target_features, join_features, out_feature_class, "JOIN_ONE_TO_MANY")

"Two new fields, Join_Count and TARGET_FID, are always added to the output feature class. Join_Count indicates how many join features match each target feature (TARGET_FID). Another new field, JOIN_FID, is added to the output when JOIN_ONE_TO_MANY is specified in the Join Operation parameter."


You can do a spatial intersect in PostGIS and get a list back of every State or City and the Zip Codes that they intersect, which would return multiple zip codes where multiple states intersect, and for each city that intersected the same zip, you would see that result as well.


In Pennsylvania the post office boundaries do no align with the municipal boundaries. Some townships may have several Post Offices delivering to them. When we were doing 911 addressing, some townships asked the PO to change their nae to the township name, the PO allowed them to do this with the condition they continued to use the old Post Office zip code. Many did this. You can see from these links the same zip code is in use for several towns. https://suburbanstats.org/zip-codes/pennsylvania/thornhurst https://suburbanstats.org/zip-codes/pennsylvania/scott-twp Basically using "Anytown" with the right zip code will work because of their sorting computers reading zip code first.

You may also run into PO that only have PO boxes and do not do local delivery so no polygon for your map. These PO are usually small.

protected by PolyGeo Sep 3 '16 at 22:40

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.