How X counts characters
X publishes its counting rules in its developer documentation (docs.x.com, "Counting characters"), and this counter follows them. The 280 limit is a weighted count, not a count of letters.
Before counting, X normalizes the text, so an accented letter typed as two parts (e plus an accent mark) counts as one character. Then each character gets a weight. Characters in the range U+0000 to U+10FF count 1: that covers Latin letters with accents, Greek, Cyrillic, Hebrew, Arabic, Indian scripts and most punctuation. So do a few typographic marks: curly quotes, dashes and primes (U+2000 to U+200D, U+2010 to U+201F, U+2032 to U+2037). Everything else counts 2, which includes Chinese, Japanese and Korean characters, the ellipsis character and the Unicode letters used by font generators.
Two things are counted as a unit instead of character by character. A link counts 23 whatever its length. An emoji counts 2, even when it is built from several code points, such as a family emoji, a flag or a thumbs-up with a skin tone.
Plain English
Hello world = 11
Japanese, three characters
日本語 = 6
Emoji with a skin tone
👍🏽 = 2
Any link
https://example.com/a/very/long/page?ref=newsletter = 23
Why other counters disagree with X
Most generic character counters measure JavaScript string length. That counts an emoji as 2 or more (a family emoji can be 8), counts a long link at its full length, and counts a Japanese character as 1. On X, all three are wrong in different directions: the family emoji is really 2, the long link is 23, and the Japanese character is 2.
If you write in English with no links or emoji, any counter gets close. As soon as a tweet has a link, an emoji or a non-Latin script, use one that applies the weighting. This one also shows the plain string length under the result, so you can see the gap.
One limit of any counter outside X: X recognizes a link even without https:// in front, if it ends in a real domain. This counter recognizes the common endings (.com, .io, .co and others); a link on a rare domain ending may be counted at full length here and as 23 on X.
Writing a post that fits
Leave room for the link first. If the post includes a URL, you have 256 characters for the text plus a space.
Cut words, not meaning. Filler like "I just wanted to say that" costs 25 characters and says nothing. If the point needs more than one post, write a thread: number the posts or end each with a clear next step, so readers know there is more.
Scheduling the post ahead? Paste the final version here before you schedule it. A post that is one character over fails at publish time, and you may not be around to fix it.